diff --git a/.clang-format b/.clang-format index 42ac9f1..0e6ea30 100644 --- a/.clang-format +++ b/.clang-format @@ -3,11 +3,11 @@ # Language: Cpp AccessModifierOffset: -4 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: true -AlignConsecutiveDeclarations: true +AlignAfterOpenBracket: BlockIndent +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false AlignEscapedNewlines: DontAlign -AlignOperands: true +AlignOperands: false AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true @@ -25,7 +25,7 @@ BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum: false - AfterFunction: true + AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true @@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 140 +ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/.gitignore b/.gitignore index d17030c..a2b1bf0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ xs/MANIFEST.bak xs/assertlib* .init_bundle.ini .vs/* -local-lib /src/TAGS /.vscode/ build-linux/* diff --git a/Build.PL b/Build.PL deleted file mode 100644 index c165f83..0000000 --- a/Build.PL +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/perl - -print "This script is currently used for installing Perl dependenices for running\n"; -print "the libslic3r unit / integration tests through Perl prove.\n"; -print "If you don't plan to run the unit / integration tests, you don't need to\n"; -print "install these dependencies to build and run QIDISlicer.\n"; - -use strict; -use warnings; - -use Config; -use File::Spec; - -my %prereqs = qw( - Devel::CheckLib 0 - ExtUtils::MakeMaker 6.80 - ExtUtils::ParseXS 3.22 - ExtUtils::XSpp 0 - ExtUtils::XSpp::Cmd 0 - ExtUtils::CppGuess 0 - ExtUtils::Typemaps 0 - ExtUtils::Typemaps::Basic 0 - File::Basename 0 - File::Spec 0 - Getopt::Long 0 - Module::Build::WithXSpp 0.14 - Moo 1.003001 - POSIX 0 - Scalar::Util 0 - Test::More 0 - IO::Scalar 0 - Time::HiRes 0 -); -my %recommends = qw( - Class::XSAccessor 0 - Test::Harness 0 -); - -my $sudo = grep { $_ eq '--sudo' } @ARGV; -my $nolocal = grep { $_ eq '--nolocal' } @ARGV; - -my @missing_prereqs = (); -if ($ENV{SLIC3R_NO_AUTO}) { - foreach my $module (sort keys %prereqs) { - my $version = $prereqs{$module}; - next if eval "use $module $version; 1"; - push @missing_prereqs, $module if exists $prereqs{$module}; - print "Missing prerequisite $module $version\n"; - } - foreach my $module (sort keys %recommends) { - my $version = $recommends{$module}; - next if eval "use $module $version; 1"; - print "Missing optional $module $version\n"; - } -} else { - my @try = ( - $ENV{CPANM} // (), - File::Spec->catfile($Config{sitebin}, 'cpanm'), - File::Spec->catfile($Config{installscript}, 'cpanm'), - ); - - my $cpanm; - foreach my $path (@try) { - if (-e $path) { # don't use -x because it fails on Windows - $cpanm = $path; - last; - } - } - if (!$cpanm) { - if ($^O =~ /^(?:darwin|linux)$/ && system(qw(which cpanm)) == 0) { - $cpanm = 'cpanm'; - } - } - die <<'EOF' -cpanm was not found. Please install it before running this script. - -There are several ways to install cpanm, try one of these: - - apt-get install cpanminus - curl -L http://cpanmin.us | perl - --sudo App::cpanminus - cpan App::cpanminus - -If it is installed in a non-standard location you can do: - - CPANM=/path/to/cpanm perl Build.PL - -EOF - if !$cpanm; - my @cpanm_args = (); - push @cpanm_args, "--sudo" if $sudo; - - # install local::lib without --local-lib otherwise it's not usable afterwards - if (!eval "use local::lib qw(local-lib); 1") { - my $res = system $cpanm, @cpanm_args, 'local::lib'; - warn "Warning: local::lib is required. You might need to run the `cpanm --sudo local::lib` command in order to install it.\n" - if $res != 0; - } - - push @cpanm_args, ('--local-lib', 'local-lib') if ! $nolocal; - - # make sure our cpanm is updated (old ones don't support the ~ syntax) - system $cpanm, @cpanm_args, 'App::cpanminus'; - - my %modules = (%prereqs, %recommends); - foreach my $module (sort keys %modules) { - my $version = $modules{$module}; - my @cmd = ($cpanm, @cpanm_args); - - # temporary workaround for upstream bug in test - push @cmd, '--notest' - if $module =~ /^(?:OpenGL|Test::Harness)$/; - - push @cmd, "$module~$version"; - - my $res = system @cmd; - if ($res != 0) { - if (exists $prereqs{$module}) { - push @missing_prereqs, $module; - } else { - printf "Don't worry, this module is optional.\n"; - } - } - } -} - -print "\n"; -print "In the next step, you need to build the QIDISlicer C++ library.\n"; -print "1) Create a build directory and change to it\n"; -print "2) run cmake .. -DCMAKE_BUILD_TYPE=Release\n"; -print "3) run make\n"; -print "4) to execute the automatic tests, run ctest --verbose\n"; -__END__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 354e264..8b88ef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ option(SLIC3R_FHS "Assume QIDISlicer is to be installed in a FHS d option(SLIC3R_PCH "Use precompiled headers" 1) option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) -option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) option(SLIC3R_UBSAN "Enable UBSan on Clang and GCC" 0) option(SLIC3R_ENABLE_FORMAT_STEP "Enable compilation of STEP file support" ON) @@ -72,7 +71,6 @@ option(SLIC3R_BUILD_TESTS "Build unit tests" ON) if (IS_CROSS_COMPILE) message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!") - set(SLIC3R_PERL_XS OFF CACHE BOOL "" FORCE) set(SLIC3R_BUILD_TESTS OFF CACHE BOOL "" FORCE) endif () @@ -165,9 +163,6 @@ if(NOT WIN32) add_compile_options("$<$:-DDEBUG>") endif() -# To be able to link libslic3r with the Perl XS module. -# Once we get rid of Perl and libslic3r is linked statically, we can get rid of -fPIC -set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way @@ -608,12 +603,6 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Q add_dependencies(gettext_make_pot hintsToPot) -# Perl bindings, currently only used for the unit / integration tests of libslic3r. -# Also runs the unit / integration tests. -#FIXME Port the tests into C++ to finally get rid of the Perl! -if (SLIC3R_PERL_XS) - add_subdirectory(xs) -endif () if(SLIC3R_BUILD_SANDBOXES) add_subdirectory(sandboxes) diff --git a/QIDISlicer.sublime-project b/QIDISlicer.sublime-project deleted file mode 100644 index 2638f93..0000000 --- a/QIDISlicer.sublime-project +++ /dev/null @@ -1,103 +0,0 @@ -{ - "build_systems": - [ - { - "name": "List", - //"file_regex": " at ([^-\\s]*) line ([0-9]*)", -// "file_regex": " at (D\\:\\/src\\/Slic3r\\/.*?) line ([0-9]*)", - "shell_cmd": "ls -l" - }, - { - "name": "Run", - "working_dir": "$project_path", - "file_regex": " at (.*?) line ([0-9]*)", -// "shell_cmd": "chdir & perl slic3r.pl --DataDir \"C:\\Users\\Public\\Documents\\QIDI3D\\Slic3r settings MK2\" --gui \"..\\Slic3r-tests\\gap fill torture 20 -rt.stl\"" - "shell_cmd": "chdir & perl slic3r.pl" - }, - { - "name": "full", - "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "chdir & perl Build.pl" - }, - { - "name": "xs", - "working_dir": "$project_path/build", - // for Visual Studio: - "file_regex": "^(..[^:]*)\\(([0-9]+)\\)(.*)$", - // For GCC: -// "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "chdir & ninja -j 6 -v", - "env": { -// "PATH": "C:\\Program Files (x86)\\MSBuild\\12.0\\bin\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\BIN\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\Tools;%PATH%;c:\\wperl64d\\site\\bin;c:\\wperl64d\\bin", -// "PERL_CPANM_HOME": "c:\\wperl64d\\cpanm", -// "WXDIR": "D:\\src-perl\\wxWidgets-3.0.3-beta1", -// "BOOST_DIR": "D:\\src-perl\\boost_1_61_0", -// "BOOST_INCLUDEDIR": "D:\\src-perl\\boost_1_61_0", -// "BOOST_LIBRARYDIR": "D:\\src-perl\\boost_1_61_0\\stage\\x64\\lib", -// "SLIC3R_STATIC": "1" - } - }, - { - "name": "xs & run", - "working_dir": "$project_path/build", - "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "chdir & ninja -j 6 & cd .. & perl slic3r.pl --gui \"..\\Slic3r-tests\\star3-big2.stl\"" - }, - { - "name": "Slic3r - clean", - "working_dir": "$project_path/build", - "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", - "shell_cmd": ["chdir & ninja clean"] - }, - { - "name": "run tests", - "working_dir": "$project_path/build", - // for Visual Studio: - "file_regex": "^(..[^:]*)\\(([0-9]+)\\)(.*)$", - "shell_cmd": "chdir & ctest --verbose" - }, - { - "name": "Clean & Configure", - "working_dir": "$project_path", - // for Visual Studio: - "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", - "shell_cmd": "chdir & rmdir /S /Q build & mkdir build & cd build & cmake -G Ninja .. -DCMAKE_COLOR_MAKEFILE=OFF -DCMAKE_RULE_PROGRESS=OFF -DCMAKE_RULE_MESSAGES=OFF -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo" - }, - { - "name": "Configure", - "working_dir": "$project_path/build", - // for Visual Studio: - "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", - "shell_cmd": "cmake -G Ninja .. -DCMAKE_COLOR_MAKEFILE=OFF -DCMAKE_RULE_PROGRESS=OFF -DCMAKE_RULE_MESSAGES=OFF -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo" - } - ], - "folders": - [ - { - "path": ".", -// "folder_exclude_patterns": [".svn", "._d", ".metadata", ".settings"], - "file_exclude_patterns": ["XS.c", "*.pch", "*.ilk", "*.js" ] - } - ], - - "settings": - { - "sublimegdb_workingdir": "${folder:${project_path:run}}", - // NOTE: You MUST provide --interpreter=mi for the plugin to work -// "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -ex 'target localhost:2345'", -// "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl --args perl slic3r.pl", -// "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl --args slic3r.pl ", -// "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -e C:\\Strawberry\\perl\\bin\\perl.exe -s C:\\Strawberry\\perl\\site\\lib\\auto\\Slic3r\\XS\\XS.xs.dll --args perl slic3r.pl -j 1 --gui D:\\src\\Slic3r-tests\\star3-big.stl", - "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl.exe --args perl slic3r.pl -j 1 --gui", // D:\\src\\Slic3r-tests\\star3-big.stl", -// "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -x slic3r.gdb", -// "arguments": "slic3r -j 1 --gui ../Slic3r-tests/star3-big.stl", -// "arguments": "../slic3r.pl -j 1 --gui", -// "sublimegdb_exec_cmd": "-exec-continue", - - // Add "pending breakpoints" for symbols that are dynamically loaded from - // external shared libraries - "debug_ext" : true, - "run_after_init": false, - "close_views": false - } -} diff --git a/cmake/modules/FindAlienWx.cmake b/cmake/modules/FindAlienWx.cmake deleted file mode 100644 index 6522117..0000000 --- a/cmake/modules/FindAlienWx.cmake +++ /dev/null @@ -1,109 +0,0 @@ -# Find the wxWidgets module based on the information provided by the Perl Alien::wxWidgets module. - -# Check for the Perl & PerlLib modules -include(LibFindMacros) -libfind_package(AlienWx Perl) -libfind_package(AlienWx PerlLibs) - -if (AlienWx_DEBUG) - message(STATUS " AlienWx_FIND_COMPONENTS=${AlienWx_FIND_COMPONENTS}") -endif() - -# Execute an Alien::Wx module to find the relevant information regarding -# the wxWidgets used by the Perl interpreter. -# Perl specific stuff -set(AlienWx_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/AlienWx_TEMP_INCLUDE.txt) -execute_process( - COMMAND ${PERL_EXECUTABLE} -e " -# Import Perl modules. -use strict; -use warnings; -use Text::ParseWords; - -BEGIN { - # CMake sets the environment variables CC and CXX to the detected C compiler. - # There is an issue with the Perl ExtUtils::CBuilder, which does not handle whitespaces - # in the paths correctly on Windows, so we rather drop the CMake auto-detected paths. - delete \$ENV{CC}; - delete \$ENV{CXX}; -} - -use Alien::wxWidgets; -use ExtUtils::CppGuess; - -# Test for a Visual Studio compiler -my \$cpp_guess = ExtUtils::CppGuess->new; -my \$mswin = \$^O eq 'MSWin32'; -my \$msvc = \$cpp_guess->is_msvc; - -# List of wxWidgets components to be used. -my @components = split /;/, '${AlienWx_FIND_COMPONENTS}'; - -# Query the available data from Alien::wxWidgets. -my \$version = Alien::wxWidgets->version; -my \$config = Alien::wxWidgets->config; -my \$compiler = Alien::wxWidgets->compiler; -my \$linker = Alien::wxWidgets->linker; -my \$include_path = ' ' . Alien::wxWidgets->include_path; -my \$defines = ' ' . Alien::wxWidgets->defines; -my \$cflags = Alien::wxWidgets->c_flags; -my \$linkflags = Alien::wxWidgets->link_flags; -my \$libraries = ' ' . Alien::wxWidgets->libraries(@components); -my \$gui_toolkit = Alien::wxWidgets->config->{toolkit}; -#my @libraries = Alien::wxWidgets->link_libraries(@components); -#my @implib = Alien::wxWidgets->import_libraries(@components); -#my @shrlib = Alien::wxWidgets->shared_libraries(@components); -#my @keys = Alien::wxWidgets->library_keys; # 'gl', 'adv', ... -#my \$library_path = Alien::wxWidgets->shared_library_path; -#my \$key = Alien::wxWidgets->key; -#my \$prefix = Alien::wxWidgets->prefix; - -my \$filename = '${AlienWx_TEMP_INCLUDE}'; -open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\"; - -# Convert a space separated lists to CMake semicolon separated lists, -# escape the backslashes, -# export the resulting list to a temp file. -sub cmake_set_var { - my (\$varname, \$content) = @_; - # Remove line separators. - \$content =~ s/\\r|\\n//g; - # Escape the path separators. - \$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g; - my @words = shellwords(\$content); - print \$fh \"set(AlienWx_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\"; -} -cmake_set_var('VERSION', \$version); -\$include_path =~ s/ -I/ /g; -cmake_set_var('INCLUDE_DIRS', \$include_path); -\$libraries =~ s/ -L/ -LIBPATH:/g if \$msvc; -cmake_set_var('LIBRARIES', \$libraries); -#cmake_set_var('LIBRARY_DIRS', ); -#\$defines =~ s/ -D/ /g; -cmake_set_var('DEFINITIONS', \$defines); -#cmake_set_var('DEFINITIONS_DEBUG', ); -cmake_set_var('CXX_FLAGS', \$cflags); -cmake_set_var('GUI_TOOLKIT', \$gui_toolkit); -close \$fh; -") -include(${AlienWx_TEMP_INCLUDE}) -file(REMOVE ${AlienWx_TEMP_INCLUDE}) -unset(AlienWx_TEMP_INCLUDE) - -if (AlienWx_DEBUG) - message(STATUS " AlienWx_VERSION = ${AlienWx_VERSION}") - message(STATUS " AlienWx_INCLUDE_DIRS = ${AlienWx_INCLUDE_DIRS}") - message(STATUS " AlienWx_LIBRARIES = ${AlienWx_LIBRARIES}") - message(STATUS " AlienWx_LIBRARY_DIRS = ${AlienWx_LIBRARY_DIRS}") - message(STATUS " AlienWx_DEFINITIONS = ${AlienWx_DEFINITIONS}") - message(STATUS " AlienWx_DEFINITIONS_DEBUG = ${AlienWx_DEFINITIONS_DEBUG}") - message(STATUS " AlienWx_CXX_FLAGS = ${AlienWx_CXX_FLAGS}") - message(STATUS " AlienWx_GUI_TOOLKIT = ${AlienWx_GUI_TOOLKIT}") -endif() - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(AlienWx - REQUIRED_VARS AlienWx_INCLUDE_DIRS AlienWx_LIBRARIES -# HANDLE_COMPONENTS - VERSION_VAR AlienWx_VERSION) diff --git a/cmake/modules/FindPerlEmbed.cmake b/cmake/modules/FindPerlEmbed.cmake deleted file mode 100644 index b12fc40..0000000 --- a/cmake/modules/FindPerlEmbed.cmake +++ /dev/null @@ -1,88 +0,0 @@ -# Find the dependencies for linking with the Perl runtime library. - -# Check for the Perl & PerlLib modules -include(LibFindMacros) -libfind_package(PerlEmbed Perl) -libfind_package(PerlEmbed PerlLibs) - -# Execute an Alien::Wx module to find the relevant information regarding -# the wxWidgets used by the Perl interpreter. -# Perl specific stuff -set(PerlEmbed_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/PerlEmbed_TEMP_INCLUDE.txt) -execute_process( - COMMAND ${PERL_EXECUTABLE} -MExtUtils::Embed -e " -# Import Perl modules. -use strict; -use warnings; -use Config; -use Text::ParseWords; -use ExtUtils::CppGuess; - -# Test for a Visual Studio compiler -my \$cpp_guess = ExtUtils::CppGuess->new; -my \$mswin = \$^O eq 'MSWin32'; -my \$msvc = \$cpp_guess->is_msvc; - -# Query the available data from Alien::wxWidgets. -my \$ccflags; -my \$ldflags; -{ local *STDOUT; open STDOUT, '>', \\\$ccflags; ccflags; } -{ local *STDOUT; open STDOUT, '>', \\\$ldflags; ldopts; } -\$ccflags = ' ' . \$ccflags; -\$ldflags = ' ' . \$ldflags; - -my \$filename = '${PerlEmbed_TEMP_INCLUDE}'; -open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\"; - -# Convert a space separated lists to CMake semicolon separated lists, -# escape the backslashes, -# export the resulting list to a temp file. -sub cmake_set_var { - my (\$varname, \$content) = @_; - # Remove line separators. - \$content =~ s/\\r|\\n//g; - # Escape the path separators. - \$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g; - my @words = shellwords(\$content); - print \$fh \"set(PerlEmbed_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\"; -} -cmake_set_var('ARCHNAME', \$Config{archname}); -cmake_set_var('CCFLAGS', \$ccflags); -\$ldflags =~ s/ -L/ -LIBPATH:/g if \$msvc; -cmake_set_var('LD', \$Config{ld}); -cmake_set_var('LDFLAGS', \$ldflags); -cmake_set_var('CCCDLFLAGS', \$Config{cccdlflags}); -cmake_set_var('LDDLFLAGS', \$Config{lddlflags}); -cmake_set_var('DLEXT', \$Config{dlext}); -close \$fh; -") -include(${PerlEmbed_TEMP_INCLUDE}) -file(REMOVE ${PerlEmbed_TEMP_INCLUDE}) -unset(PerlEmbed_TEMP_INCLUDE) - -if (PerlEmbed_DEBUG) - # First show the configuration extracted by FindPerl & FindPerlLibs: - message(STATUS " PERL_INCLUDE_PATH = ${PERL_INCLUDE_PATH}") - message(STATUS " PERL_LIBRARY = ${PERL_LIBRARY}") - message(STATUS " PERL_EXECUTABLE = ${PERL_EXECUTABLE}") - message(STATUS " PERL_SITESEARCH = ${PERL_SITESEARCH}") - message(STATUS " PERL_SITELIB = ${PERL_SITELIB}") - message(STATUS " PERL_VENDORARCH = ${PERL_VENDORARCH}") - message(STATUS " PERL_VENDORLIB = ${PERL_VENDORLIB}") - message(STATUS " PERL_ARCHLIB = ${PERL_ARCHLIB}") - message(STATUS " PERL_PRIVLIB = ${PERL_PRIVLIB}") - message(STATUS " PERL_EXTRA_C_FLAGS = ${PERL_EXTRA_C_FLAGS}") - # Second show the configuration extracted by this module (FindPerlEmbed): - message(STATUS " PerlEmbed_ARCHNAME = ${PerlEmbed_ARCHNAME}") - message(STATUS " PerlEmbed_CCFLAGS = ${PerlEmbed_CCFLAGS}") - message(STATUS " PerlEmbed_CCCDLFLAGS = ${PerlEmbed_CCCDLFLAGS}") - message(STATUS " LD = ${PerlEmbed_LD}") - message(STATUS " PerlEmbed_LDFLAGS = ${PerlEmbed_LDFLAGS}") - message(STATUS " PerlEmbed_LDDLFLAGS = ${PerlEmbed_LDDLFLAGS}") -endif() - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(PerlEmbed - REQUIRED_VARS PerlEmbed_CCFLAGS PerlEmbed_LDFLAGS - VERSION_VAR PERL_VERSION) diff --git a/deps/+LibBGCode/LibBGCode.cmake b/deps/+LibBGCode/LibBGCode.cmake index ea528b2..059caed 100644 --- a/deps/+LibBGCode/LibBGCode.cmake +++ b/deps/+LibBGCode/LibBGCode.cmake @@ -1,9 +1,8 @@ set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory") set(_source_dir_line - URL https://github.com/prusa3d/libbgcode/archive/bc390aab4427589a6402b4c7f65cf4d0a8f987ec.zip - URL_HASH SHA256=0c86cb67232089728233014f937e2a07d133a61e31dd8811a9c905e563a49f24 -) + URL https://github.com/prusa3d/libbgcode/archive/6f43cb004ef3d3bda37dde49f6235e24d2717629.zip + URL_HASH SHA256=eb5198caecb6a693a294af6a56c37b0adb1eb159a34a9c3116970b80659ee9f9) if (LibBGCode_SOURCE_DIR) set(_source_dir_line "SOURCE_DIR;${LibBGCode_SOURCE_DIR};BUILD_ALWAYS;ON") diff --git a/deps/+OpenVDB/OpenVDB.cmake b/deps/+OpenVDB/OpenVDB.cmake index cfafa03..6211f34 100644 --- a/deps/+OpenVDB/OpenVDB.cmake +++ b/deps/+OpenVDB/OpenVDB.cmake @@ -15,7 +15,7 @@ endif () add_cmake_project(OpenVDB # 8.2 patched - URL https://github.com/tamasmeszaros/openvdb/archive/a68fd58d0e2b85f01adeb8b13d7555183ab10aa5.zip + URL https://github.com/prusa3d/openvdb/archive/a68fd58d0e2b85f01adeb8b13d7555183ab10aa5.zip URL_HASH SHA256=f353e7b99bd0cbfc27ac9082de51acf32a8bc0b3e21ff9661ecca6f205ec1d81 CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm deleted file mode 100644 index 3b01673..0000000 --- a/lib/Slic3r.pm +++ /dev/null @@ -1,51 +0,0 @@ -# This package loads all the non-GUI Slic3r perl packages. - -package Slic3r; - -# Copyright holder: Alessandro Ranellucci -# This application is licensed under the GNU Affero General Public License, version 3 - -use strict; -use warnings; -use Config; -require v5.10; - -our $VERSION = VERSION(); -our $BUILD = BUILD(); -our $FORK_NAME = FORK_NAME(); - -our $debug = 0; -sub debugf { - printf @_ if $debug; -} - -our $loglevel = 0; - -BEGIN { - $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); - print "Debugging output enabled\n" if $debug; -} - -use FindBin; - -use Moo 1.003001; - -use Slic3r::XS; # import all symbols (constants etc.) before they get parsed -use Slic3r::Config; -use Slic3r::GCode::Reader; -use Slic3r::Line; -use Slic3r::Model; -use Slic3r::Point; -use Slic3r::Polygon; -use Slic3r::Polyline; -our $build = eval "use Slic3r::Build; 1"; - -# Scaling between the float and integer coordinates. -# Floats are in mm. -use constant SCALING_FACTOR => 0.000001; - -# Set the logging level at the Slic3r XS module. -$Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; -set_logging_level($Slic3r::loglevel); - -1; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm deleted file mode 100644 index dadf76a..0000000 --- a/lib/Slic3r/Config.pm +++ /dev/null @@ -1,32 +0,0 @@ -# Extends C++ class Slic3r::DynamicPrintConfig -# This perl class does not keep any perl class variables, -# all the storage is handled by the underlying C++ code. -package Slic3r::Config; -use strict; -use warnings; -use utf8; - -use List::Util qw(first max); - -# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. -# The C++ counterpart is a constant singleton. -our $Options = print_config_def(); - -# Generate accessors. -{ - no strict 'refs'; - for my $opt_key (keys %$Options) { - *{$opt_key} = sub { - #print "Slic3r::Config::accessor $opt_key\n"; - $_[0]->get($opt_key) - }; - } -} - -package Slic3r::Config::Static; -use parent 'Slic3r::Config'; - -sub Slic3r::Config::GCode::new { Slic3r::Config::Static::new_GCodeConfig } -sub Slic3r::Config::Print::new { Slic3r::Config::Static::new_PrintConfig } - -1; diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm deleted file mode 100644 index 6f95f5e..0000000 --- a/lib/Slic3r/GCode/Reader.pm +++ /dev/null @@ -1,90 +0,0 @@ -# Helper module to parse and interpret a G-code file, -# invoking a callback for each move extracted from the G-code. -# Currently used by the automatic tests only. -package Slic3r::GCode::Reader; -use Moo; - -has 'config' => (is => 'ro', default => sub { Slic3r::Config::GCode->new }); -has 'X' => (is => 'rw', default => sub {0}); -has 'Y' => (is => 'rw', default => sub {0}); -has 'Z' => (is => 'rw', default => sub {0}); -has 'E' => (is => 'rw', default => sub {0}); -has 'F' => (is => 'rw', default => sub {0}); -has '_extrusion_axis' => (is => 'rw', default => sub {"E"}); - -our $Verbose = 0; -my @AXES = qw(X Y Z E); - -sub apply_print_config { - my ($self, $print_config) = @_; - - $self->config->apply_static($print_config); - $self->_extrusion_axis($self->config->get_extrusion_axis); -} - -sub clone { - my $self = shift; - return (ref $self)->new( - map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis', 'config'), - ); -} - -sub parse { - my $self = shift; - my ($gcode, $cb) = @_; - - foreach my $raw_line (split /\R+/, $gcode) { - print "$raw_line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE}; - my $line = $raw_line; - $line =~ s/\s*;(.*)//; # strip comment - my %info = (comment => $1, raw => $raw_line); - - # parse command - my ($command, @args) = split /\s+/, $line; - $command //= ''; - my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; - - # convert extrusion axis - if (exists $args{ $self->_extrusion_axis }) { - $args{E} = $args{ $self->_extrusion_axis }; - } - - # check motion - if ($command =~ /^G[01]$/) { - foreach my $axis (@AXES) { - if (exists $args{$axis}) { - $self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances; - $info{"dist_$axis"} = $args{$axis} - $self->$axis; - $info{"new_$axis"} = $args{$axis}; - } else { - $info{"dist_$axis"} = 0; - $info{"new_$axis"} = $self->$axis; - } - } - $info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2)); - if (exists $args{E}) { - if ($info{dist_E} > 0) { - $info{extruding} = 1; - } elsif ($info{dist_E} < 0) { - $info{retracting} = 1 - } - } else { - $info{travel} = 1; - } - } - - # run callback - $cb->($self, $command, \%args, \%info); - - # update coordinates - if ($command =~ /^(?:G[01]|G92)$/) { - for my $axis (@AXES, 'F') { - $self->$axis($args{$axis}) if exists $args{$axis}; - } - } - - # TODO: update temperatures - } -} - -1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm deleted file mode 100644 index ae0dc85..0000000 --- a/lib/Slic3r/Geometry.pm +++ /dev/null @@ -1,36 +0,0 @@ -package Slic3r::Geometry; -use strict; -use warnings; - -require Exporter; -our @ISA = qw(Exporter); - -# Exported by this module. The last section starting with convex_hull is exported by Geometry.xsp -our @EXPORT_OK = qw( - PI epsilon - - scale - unscale - scaled_epsilon - - X Y Z - convex_hull - deg2rad - rad2deg -); - -use constant PI => 4 * atan2(1, 1); -use constant A => 0; -use constant B => 1; -use constant X1 => 0; -use constant Y1 => 1; -use constant X2 => 2; -use constant Y2 => 3; - -sub epsilon () { 1E-4 } -sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } - -sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } -sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } - -1; diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm deleted file mode 100644 index 16b609c..0000000 --- a/lib/Slic3r/Line.pm +++ /dev/null @@ -1,8 +0,0 @@ -package Slic3r::Line; -use strict; -use warnings; - -# a line is a two-points line -use parent 'Slic3r::Polyline'; - -1; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm deleted file mode 100644 index d44e987..0000000 --- a/lib/Slic3r/Model.pm +++ /dev/null @@ -1,136 +0,0 @@ -# extends C++ class Slic3r::Model -package Slic3r::Model; - -use List::Util qw(first max any); - -sub merge { - my $class = shift; - my @models = @_; - - my $new_model = ref($class) - ? $class - : $class->new; - - $new_model->add_object($_) for map @{$_->objects}, @models; - return $new_model; -} - -sub add_object { - my $self = shift; - - if (@_ == 1) { - # we have a Model::Object - my ($object) = @_; - return $self->_add_object_clone($object); - } else { - my (%args) = @_; - - my $new_object = $self->_add_object; - - $new_object->set_name($args{name}) - if defined $args{name}; - $new_object->set_input_file($args{input_file}) - if defined $args{input_file}; - $new_object->config->apply($args{config}) - if defined $args{config}; - $new_object->set_layer_height_ranges($args{layer_height_ranges}) - if defined $args{layer_height_ranges}; - $new_object->set_origin_translation($args{origin_translation}) - if defined $args{origin_translation}; - - return $new_object; - } -} - -sub set_material { - my $self = shift; - my ($material_id, $attributes) = @_; - - my $material = $self->add_material($material_id); - $material->apply($attributes // {}); - return $material; -} - -# Extends C++ class Slic3r::ModelMaterial -package Slic3r::Model::Material; - -sub apply { - my ($self, $attributes) = @_; - $self->set_attribute($_, $attributes{$_}) for keys %$attributes; -} - -# Extends C++ class Slic3r::ModelObject -package Slic3r::Model::Object; - -use List::Util qw(first sum); - -sub add_volume { - my $self = shift; - - my $new_volume; - if (@_ == 1) { - # we have a Model::Volume - my ($volume) = @_; - - $new_volume = $self->_add_volume_clone($volume); - - if ($volume->material_id ne '') { - # merge material attributes and config (should we rename materials in case of duplicates?) - if (my $material = $volume->object->model->get_material($volume->material_id)) { - my %attributes = %{ $material->attributes }; - if ($self->model->has_material($volume->material_id)) { - %attributes = (%attributes, %{ $self->model->get_material($volume->material_id)->attributes }) - } - my $new_material = $self->model->set_material($volume->material_id, {%attributes}); - $new_material->config->apply($material->config); - } - } - } else { - my %args = @_; - - $new_volume = $self->_add_volume($args{mesh}); - - $new_volume->set_name($args{name}) - if defined $args{name}; - $new_volume->set_material_id($args{material_id}) - if defined $args{material_id}; - $new_volume->set_modifier($args{modifier}) - if defined $args{modifier}; - $new_volume->config->apply($args{config}) - if defined $args{config}; - } - - if ($new_volume->material_id ne '' && !defined $self->model->get_material($new_volume->material_id)) { - # TODO: this should be a trigger on Volume::material_id - $self->model->set_material($new_volume->material_id); - } - - $self->invalidate_bounding_box; - - return $new_volume; -} - -sub add_instance { - my $self = shift; - - if (@_ == 1) { - # we have a Model::Instance - my ($instance) = @_; - return $self->_add_instance_clone($instance); - } else { - my (%args) = @_; - - my $new_instance = $self->_add_instance; - - $new_instance->set_rotations($args{rotation}) - if defined $args{rotation}; - $new_instance->set_scaling_factors($args{scaling_factor}) - if defined $args{scaling_factor}; - $new_instance->set_offset($args{offset}) - if defined $args{offset}; - - return $new_instance; - } -} - -1; diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm deleted file mode 100644 index 1134138..0000000 --- a/lib/Slic3r/Point.pm +++ /dev/null @@ -1,28 +0,0 @@ -package Slic3r::Point; -use strict; -use warnings; - -sub new_scale { - my $class = shift; - return $class->new(map Slic3r::Geometry::scale($_), @_); -} - -package Slic3r::Pointf; -use strict; -use warnings; - -sub new_unscale { - my $class = shift; - return $class->new(map Slic3r::Geometry::unscale($_), @_); -} - -package Slic3r::Pointf3; -use strict; -use warnings; - -sub new_unscale { - my $class = shift; - return $class->new(map Slic3r::Geometry::unscale($_), @_); -} - -1; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm deleted file mode 100644 index d544504..0000000 --- a/lib/Slic3r/Polygon.pm +++ /dev/null @@ -1,8 +0,0 @@ -package Slic3r::Polygon; -use strict; -use warnings; - -# a polygon is a closed polyline. -use parent 'Slic3r::Polyline'; - -1; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm deleted file mode 100644 index a42b5d1..0000000 --- a/lib/Slic3r/Polyline.pm +++ /dev/null @@ -1,13 +0,0 @@ -package Slic3r::Polyline; -use strict; -use warnings; - -use Slic3r::Geometry qw(X Y); - -sub new_scale { - my $class = shift; - my @points = map { ref($_) eq 'Slic3r::Point' ? $_->pp : $_ } @_; - return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points); -} - -1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm deleted file mode 100644 index d0696aa..0000000 --- a/lib/Slic3r/Test.pm +++ /dev/null @@ -1,261 +0,0 @@ -package Slic3r::Test; -use strict; -use warnings; -use Cwd 'abs_path'; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw(_eq); - -use List::Util qw(first); -use Slic3r::Geometry qw(epsilon X Y Z); - -my %cuboids = ( - '20mm_cube' => [20,20,20], - '2x20x10' => [2, 20,10], -); - -sub mesh { - my ($name, %params) = @_; - - my ($vertices, $facets); - if ($cuboids{$name}) { - my ($x, $y, $z) = @{ $cuboids{$name} }; - $vertices = [ - [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z], - ]; - $facets = [ - [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], - ], - } elsif ($name eq 'box') { - my ($x, $y, $z) = @{ $params{"dim"} }; - $vertices = [ - [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z], - ]; - $facets = [ - [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], - ], - } elsif ($name eq 'cube_with_hole') { - $vertices = [ - [0,0,0],[0,0,10],[0,20,0],[0,20,10],[20,0,0],[20,0,10],[5,5,0],[15,5,0],[5,15,0],[20,20,0],[15,15,0],[20,20,10],[5,5,10],[5,15,10],[15,5,10],[15,15,10] - ]; - $facets = [ - [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6] - ], - } elsif ($name eq 'cube_with_concave_hole') { - $vertices = [ - [-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5] - ]; - $facets = [ - [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6] - ], - } elsif ($name eq 'V') { - $vertices = [ - [-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20] - ]; - $facets = [ - [0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10] - ], - } elsif ($name eq 'L') { - $vertices = [ - [0,10,0],[0,10,10],[0,20,0],[0,20,10],[10,10,0],[10,10,10],[20,20,0],[20,0,0],[10,0,0],[20,20,10],[10,0,10],[20,0,10] - ]; - $facets = [ - [0,1,2],[2,1,3],[4,5,1],[0,4,1],[0,2,4],[4,2,6],[4,6,7],[4,7,8],[2,3,6],[6,3,9],[3,1,5],[9,3,5],[10,11,5],[11,9,5],[5,4,10],[10,4,8],[10,8,7],[11,10,7],[11,7,6],[9,11,6] - ], - } elsif ($name eq 'overhang') { - $vertices = [ - [1364.68505859375,614.398010253906,20.002498626709],[1389.68505859375,614.398010253906,20.002498626709],[1377.18505859375,589.398986816406,20.002498626709],[1389.68505859375,589.398986816406,20.002498626709],[1389.68505859375,564.398986816406,20.0014991760254],[1364.68505859375,589.398986816406,20.002498626709],[1364.68505859375,564.398986816406,20.0014991760254],[1360.93505859375,589.398986816406,17.0014991760254],[1360.93505859375,585.64697265625,17.0014991760254],[1357.18505859375,564.398986816406,17.0014991760254],[1364.68505859375,589.398986816406,17.0014991760254],[1364.68505859375,571.899963378906,17.0014991760254],[1364.68505859375,564.398986816406,17.0014991760254],[1348.43603515625,564.398986816406,17.0014991760254],[1352.80908203125,589.398986816406,17.0014991760254],[1357.18408203125,589.398986816406,17.0014991760254],[1357.18310546875,614.398010253906,17.0014991760254],[1364.68505859375,606.89599609375,17.0014991760254],[1364.68505859375,614.398010253906,17.0014991760254],[1352.18603515625,564.398986816406,20.0014991760254],[1363.65405273438,589.398986816406,23.3004989624023],[1359.46704101562,589.398986816406,23.3004989624023],[1358.37109375,564.398986816406,23.3004989624023],[1385.56103515625,564.398986816406,23.3004989624023],[1373.06311035156,589.398986816406,23.3004989624023],[1368.80810546875,564.398986816406,23.3004989624023],[1387.623046875,589.398986816406,23.3004989624023],[1387.623046875,585.276000976562,23.3004989624023],[1389.68505859375,589.398986816406,23.3004989624023],[1389.68505859375,572.64599609375,23.3004989624023],[1389.68505859375,564.398986816406,23.3004989624023],[1367.77709960938,589.398986816406,23.3004989624023],[1366.7470703125,564.398986816406,23.3004989624023],[1354.31201171875,589.398986816406,23.3004989624023],[1352.18603515625,564.398986816406,23.3004989624023],[1389.68505859375,614.398010253906,23.3004989624023],[1377.31701660156,614.398010253906,23.3004989624023],[1381.43908691406,589.398986816406,23.3004989624023],[1368.80700683594,614.398010253906,23.3004989624023],[1368.80810546875,589.398986816406,23.3004989624023],[1356.43908691406,614.398010253906,23.3004989624023],[1357.40502929688,589.398986816406,23.3004989624023],[1360.56201171875,614.398010253906,23.3004989624023],[1348.705078125,614.398010253906,23.3004989624023],[1350.44506835938,589.398986816406,23.3004989624023],[1389.68505859375,606.153015136719,23.3004989624023],[1347.35205078125,589.398986816406,23.3004989624023],[1346.56005859375,589.398986816406,23.3004989624023],[1346.56005859375,594.159912109375,17.0014991760254],[1346.56005859375,589.398986816406,17.0014991760254],[1346.56005859375,605.250427246094,23.3004989624023],[1346.56005859375,614.398010253906,23.3004989624023],[1346.56005859375,614.398010253906,20.8258285522461],[1346.56005859375,614.398010253906,17.0014991760254],[1346.56005859375,564.398986816406,19.10133934021],[1346.56005859375,567.548583984375,23.3004989624023],[1346.56005859375,564.398986816406,17.0020332336426],[1346.56005859375,564.398986816406,23.0018501281738],[1346.56005859375,564.398986816406,23.3004989624023],[1346.56005859375,575.118957519531,17.0014991760254],[1346.56005859375,574.754028320312,23.3004989624023] - ]; - $facets = [ - [0,1,2],[2,3,4],[2,5,0],[4,6,2],[2,6,5],[2,1,3],[7,8,9],[10,9,8],[11,9,10],[12,9,11],[9,13,14],[7,15,16],[10,17,0],[10,0,5],[12,11,6],[18,16,0],[6,19,13],[6,13,9],[9,12,6],[17,18,0],[11,10,5],[11,5,6],[14,16,15],[17,7,18],[16,18,7],[14,15,9],[7,9,15],[7,17,8],[10,8,17],[20,21,22],[23,24,25],[26,23,27],[28,27,23],[29,28,23],[30,29,23],[25,31,32],[22,33,34],[35,36,37],[24,38,39],[21,40,41],[38,42,20],[33,43,44],[6,4,23],[6,23,25],[36,35,1],[1,0,38],[1,38,36],[29,30,4],[25,32,6],[40,42,0],[35,45,1],[4,3,28],[4,28,29],[3,1,45],[3,45,28],[22,34,19],[19,6,32],[19,32,22],[42,38,0],[30,23,4],[0,16,43],[0,43,40],[24,37,36],[38,24,36],[24,23,37],[37,23,26],[22,32,20],[20,32,31],[33,41,40],[43,33,40],[45,35,26],[37,26,35],[33,44,34],[44,43,46],[20,42,21],[40,21,42],[31,39,38],[20,31,38],[33,22,41],[21,41,22],[31,25,39],[24,39,25],[26,27,45],[28,45,27],[47,48,49],[47,50,48],[51,48,50],[52,48,51],[53,48,52],[54,55,56],[57,55,54],[58,55,57],[49,59,47],[60,56,55],[59,56,60],[60,47,59],[48,53,16],[56,13,19],[54,56,19],[56,59,13],[59,49,14],[59,14,13],[49,48,16],[49,16,14],[44,46,60],[44,60,55],[51,50,43],[19,34,58],[19,58,57],[53,52,16],[43,16,52],[43,52,51],[57,54,19],[47,60,46],[55,58,34],[55,34,44],[50,47,46],[50,46,43] - ], - } elsif ($name eq '40x10') { - $vertices = [ - [12.8680295944214,29.5799007415771,12],[11.7364797592163,29.8480796813965,12],[11.1571502685547,29.5300102233887,12],[10.5814504623413,29.9830799102783,12],[10,29.6000003814697,12],[9.41855144500732,29.9830799102783,12],[8.84284687042236,29.5300102233887,12],[8.26351833343506,29.8480796813965,12],[7.70256900787354,29.3210391998291,12],[7.13196802139282,29.5799007415771,12],[6.59579277038574,28.9761600494385,12],[6.03920221328735,29.1821594238281,12],[5.53865718841553,28.5003795623779,12],[5,28.6602592468262,12],[4.54657793045044,27.9006500244141,12],[4.02841377258301,28.0212306976318,12],[3.63402199745178,27.1856994628906,12],[3.13758301734924,27.2737407684326,12],[2.81429696083069,26.3659801483154,12],[2.33955597877502,26.4278793334961,12],[2.0993549823761,25.4534206390381,12],[1.64512205123901,25.4950904846191,12],[1.49962198734283,24.4613399505615,12],[1.0636739730835,24.4879894256592,12],[1.02384400367737,23.4042091369629,12],[0.603073298931122,23.4202003479004,12],[0.678958415985107,22.2974300384521,12],[0.269550800323486,22.3061599731445,12],[0.469994693994522,21.1571502685547,12],[0.067615881562233,21.1609306335449,12],[0.399999290704727,20,12],[0,20,12],[0.399999290704727,5,12],[0,5,12],[0.456633001565933,4.2804012298584,12],[0.0615576282143593,4.21782684326172,12],[0.625140011310577,3.5785219669342,12],[0.244717106223106,3.45491504669189,12],[0.901369392871857,2.91164398193359,12],[0.544967114925385,2.73004698753357,12],[1.27852201461792,2.29618692398071,12],[0.954914808273315,2.06107401847839,12],[1.74730801582336,1.74730801582336,12],[1.46446597576141,1.46446597576141,12],[2.29618692398071,1.27852201461792,12],[2.06107401847839,0.954914808273315,12],[2.91164398193359,0.901369392871857,12],[2.73004698753357,0.544967114925385,12],[3.5785219669342,0.625140011310577,12],[3.45491504669189,0.244717106223106,12],[4.2804012298584,0.456633001565933,12],[4.21782684326172,0.0615576282143593,12],[5,0.399999290704727,12],[5,0,12],[19.6000003814697,0.399999290704727,12],[20,0,12],[19.6000003814697,20,12],[20,20,12],[19.5300102233887,21.1571502685547,12],[19.9323806762695,21.1609306335449,12],[19.3210391998291,22.2974300384521,12],[19.7304496765137,22.3061599731445,12],[18.9761600494385,23.4042091369629,12],[19.3969306945801,23.4202003479004,12],[18.5003795623779,24.4613399505615,12],[18.9363307952881,24.4879894256592,12],[17.9006500244141,25.4534206390381,12],[18.3548793792725,25.4950904846191,12],[17.1856994628906,26.3659801483154,12],[17.6604404449463,26.4278793334961,12],[16.3659801483154,27.1856994628906,12],[16.862419128418,27.2737407684326,12],[15.4534196853638,27.9006500244141,12],[15.9715900421143,28.0212306976318,12],[14.4613399505615,28.5003795623779,12],[15,28.6602592468262,12],[13.4042100906372,28.9761600494385,12],[13.9608001708984,29.1821594238281,12],[12.2974300384521,29.3210391998291,12],[7.13196802139282,29.5799007415771,0],[8.26351833343506,29.8480796813965,0],[8.84284687042236,29.5300102233887,0],[9.41855144500732,29.9830799102783,0],[10,29.6000003814697,0],[10.5814504623413,29.9830799102783,0],[11.1571502685547,29.5300102233887,0],[11.7364797592163,29.8480796813965,0],[12.2974300384521,29.3210391998291,0],[12.8680295944214,29.5799007415771,0],[13.4042100906372,28.9761600494385,0],[13.9608001708984,29.1821594238281,0],[14.4613399505615,28.5003795623779,0],[15,28.6602592468262,0],[15.4534196853638,27.9006500244141,0],[15.9715900421143,28.0212306976318,0],[16.3659801483154,27.1856994628906,0],[16.862419128418,27.2737407684326,0],[17.1856994628906,26.3659801483154,0],[17.6604404449463,26.4278793334961,0],[17.9006500244141,25.4534206390381,0],[18.3548793792725,25.4950904846191,0],[18.5003795623779,24.4613399505615,0],[18.9363307952881,24.4879894256592,0],[18.9761600494385,23.4042091369629,0],[19.3969306945801,23.4202003479004,0],[19.3210391998291,22.2974300384521,0],[19.7304496765137,22.3061599731445,0],[19.5300102233887,21.1571502685547,0],[19.9323806762695,21.1609306335449,0],[19.6000003814697,20,0],[20,20,0],[19.6000003814697,0.399999290704727,0],[20,0,0],[5,0.399999290704727,0],[5,0,0],[4.2804012298584,0.456633001565933,0],[4.21782684326172,0.0615576282143593,0],[3.5785219669342,0.625140011310577,0],[3.45491504669189,0.244717106223106,0],[2.91164398193359,0.901369392871857,0],[2.73004698753357,0.544967114925385,0],[2.29618692398071,1.27852201461792,0],[2.06107401847839,0.954914808273315,0],[1.74730801582336,1.74730801582336,0],[1.46446597576141,1.46446597576141,0],[1.27852201461792,2.29618692398071,0],[0.954914808273315,2.06107401847839,0],[0.901369392871857,2.91164398193359,0],[0.544967114925385,2.73004698753357,0],[0.625140011310577,3.5785219669342,0],[0.244717106223106,3.45491504669189,0],[0.456633001565933,4.2804012298584,0],[0.0615576282143593,4.21782684326172,0],[0.399999290704727,5,0],[0,5,0],[0.399999290704727,20,0],[0,20,0],[0.469994693994522,21.1571502685547,0],[0.067615881562233,21.1609306335449,0],[0.678958415985107,22.2974300384521,0],[0.269550800323486,22.3061599731445,0],[1.02384400367737,23.4042091369629,0],[0.603073298931122,23.4202003479004,0],[1.49962198734283,24.4613399505615,0],[1.0636739730835,24.4879894256592,0],[2.0993549823761,25.4534206390381,0],[1.64512205123901,25.4950904846191,0],[2.81429696083069,26.3659801483154,0],[2.33955597877502,26.4278793334961,0],[3.63402199745178,27.1856994628906,0],[3.13758301734924,27.2737407684326,0],[4.54657793045044,27.9006500244141,0],[4.02841377258301,28.0212306976318,0],[5.53865718841553,28.5003795623779,0],[5,28.6602592468262,0],[6.59579277038574,28.9761600494385,0],[6.03920221328735,29.1821594238281,0],[7.70256900787354,29.3210391998291,0] - ]; - $facets = [ - [0,1,2],[2,1,3],[2,3,4],[4,3,5],[4,5,6],[6,5,7],[6,7,8],[8,7,9],[8,9,10],[10,9,11],[10,11,12],[12,11,13],[12,13,14],[14,13,15],[14,15,16],[16,15,17],[16,17,18],[18,17,19],[18,19,20],[20,19,21],[20,21,22],[22,21,23],[22,23,24],[24,23,25],[24,25,26],[26,25,27],[26,27,28],[28,27,29],[28,29,30],[30,29,31],[30,31,32],[32,31,33],[32,33,34],[34,33,35],[34,35,36],[36,35,37],[36,37,38],[38,37,39],[38,39,40],[40,39,41],[40,41,42],[42,41,43],[42,43,44],[44,43,45],[44,45,46],[46,45,47],[46,47,48],[48,47,49],[48,49,50],[50,49,51],[50,51,52],[52,51,53],[52,53,54],[54,53,55],[54,55,56],[56,55,57],[56,57,58],[58,57,59],[58,59,60],[60,59,61],[60,61,62],[62,61,63],[62,63,64],[64,63,65],[64,65,66],[66,65,67],[66,67,68],[68,67,69],[68,69,70],[70,69,71],[70,71,72],[72,71,73],[72,73,74],[74,73,75],[74,75,76],[76,75,77],[76,77,78],[78,77,0],[78,0,2],[79,80,81],[81,80,82],[81,82,83],[83,82,84],[83,84,85],[85,84,86],[85,86,87],[87,86,88],[87,88,89],[89,88,90],[89,90,91],[91,90,92],[91,92,93],[93,92,94],[93,94,95],[95,94,96],[95,96,97],[97,96,98],[97,98,99],[99,98,100],[99,100,101],[101,100,102],[101,102,103],[103,102,104],[103,104,105],[105,104,106],[105,106,107],[107,106,108],[107,108,109],[109,108,110],[109,110,111],[111,110,112],[111,112,113],[113,112,114],[113,114,115],[115,114,116],[115,116,117],[117,116,118],[117,118,119],[119,118,120],[119,120,121],[121,120,122],[121,122,123],[123,122,124],[123,124,125],[125,124,126],[125,126,127],[127,126,128],[127,128,129],[129,128,130],[129,130,131],[131,130,132],[131,132,133],[133,132,134],[133,134,135],[135,134,136],[135,136,137],[137,136,138],[137,138,139],[139,138,140],[139,140,141],[141,140,142],[141,142,143],[143,142,144],[143,144,145],[145,144,146],[145,146,147],[147,146,148],[147,148,149],[149,148,150],[149,150,151],[151,150,152],[151,152,153],[153,152,154],[153,154,155],[155,154,156],[155,156,157],[157,156,79],[157,79,81],[57,110,108],[57,108,59],[59,108,106],[59,106,61],[61,106,104],[61,104,63],[63,104,102],[63,102,65],[65,102,100],[65,100,67],[67,100,98],[67,98,69],[69,98,96],[69,96,71],[71,96,94],[71,94,73],[73,94,92],[73,92,75],[75,92,90],[75,90,77],[77,90,88],[77,88,0],[0,88,86],[0,86,1],[1,86,84],[1,84,3],[3,84,82],[3,82,5],[5,82,80],[5,80,7],[7,80,79],[7,79,9],[9,79,156],[9,156,11],[11,156,154],[11,154,13],[13,154,152],[13,152,15],[15,152,150],[15,150,17],[17,150,148],[17,148,19],[19,148,146],[19,146,21],[21,146,144],[21,144,23],[23,144,142],[23,142,25],[25,142,140],[25,140,27],[27,140,138],[27,138,29],[29,138,136],[29,136,31],[33,31,134],[134,31,136],[33,134,132],[33,132,35],[35,132,130],[35,130,37],[37,130,128],[37,128,39],[39,128,126],[39,126,41],[41,126,124],[41,124,43],[43,124,122],[43,122,45],[45,122,120],[45,120,47],[47,120,118],[47,118,49],[49,118,116],[49,116,51],[51,116,114],[51,114,53],[55,53,112],[112,53,114],[57,55,110],[110,55,112],[30,135,137],[30,137,28],[28,137,139],[28,139,26],[26,139,141],[26,141,24],[24,141,143],[24,143,22],[22,143,145],[22,145,20],[20,145,147],[20,147,18],[18,147,149],[18,149,16],[16,149,151],[16,151,14],[14,151,153],[14,153,12],[12,153,155],[12,155,10],[10,155,157],[10,157,8],[8,157,81],[8,81,6],[6,81,83],[6,83,4],[4,83,85],[4,85,2],[2,85,87],[2,87,78],[78,87,89],[78,89,76],[76,89,91],[76,91,74],[74,91,93],[74,93,72],[72,93,95],[72,95,70],[70,95,97],[70,97,68],[68,97,99],[68,99,66],[66,99,101],[66,101,64],[64,101,103],[64,103,62],[62,103,105],[62,105,60],[60,105,107],[60,107,58],[58,107,109],[58,109,56],[30,32,135],[135,32,133],[52,113,115],[52,115,50],[50,115,117],[50,117,48],[48,117,119],[48,119,46],[46,119,121],[46,121,44],[44,121,123],[44,123,42],[42,123,125],[42,125,40],[40,125,127],[40,127,38],[38,127,129],[38,129,36],[36,129,131],[36,131,34],[34,131,133],[34,133,32],[52,54,113],[113,54,111],[54,56,111],[111,56,109] - ], - } elsif ($name eq 'sloping_hole') { - $vertices = [ - [-20,-20,-5],[-20,-20,5],[-20,20,-5],[-20,20,5],[20,-20,-5],[20,-20,5],[4.46294021606445,7.43144989013672,-5],[20,20,-5],[-19.1420993804932,0,-5],[-18.8330993652344,-2.07911992073059,-5],[-17.9195003509521,-4.06736993789673,-5],[-16.4412002563477,-5.87785005569458,-5],[-14.4629001617432,-7.43144989013672,-5],[-12.0711002349854,-8.66024971008301,-5],[-9.37016010284424,-9.51056003570557,-5],[-3.5217399597168,-9.94521999359131,-5],[-6.4782600402832,-9.94521999359131,-5],[-0.629840016365051,-9.51056003570557,-5],[2.07106995582581,-8.66024971008301,-5],[6.44122982025146,-5.87785005569458,-5],[4.46294021606445,-7.43144989013672,-5],[-12.0711002349854,8.66024971008301,-5],[-9.37016010284424,9.51056003570557,-5],[7.91947984695435,-4.06736993789673,-5],[8.83310031890869,-2.07911992073059,-5],[-6.4782600402832,9.94521999359131,-5],[-0.629840016365051,9.51056003570557,-5],[2.07106995582581,8.66024971008301,-5],[9.14214038848877,0,-5],[8.83310031890869,2.07911992073059,-5],[-3.5217399597168,9.94521999359131,-5],[7.91947984695435,4.06736993789673,-5],[6.44122982025146,5.87785005569458,-5],[-14.4629001617432,7.43144989013672,-5],[-16.4412002563477,5.87785005569458,-5],[-17.9195003509521,4.06736993789673,-5],[-18.8330993652344,2.07911992073059,-5],[20,20,5],[3.5217399597168,-9.94521999359131,5],[-8.83310031890869,-2.07911992073059,5],[-9.14214038848877,0,5],[-8.83310031890869,2.07911992073059,5],[6.4782600402832,-9.94521999359131,5],[-7.91947984695435,4.06736993789673,5],[-6.44122982025146,5.87785005569458,5],[-4.46294021606445,7.43144989013672,5],[-2.07106995582581,8.66024971008301,5],[0.629840016365051,9.51056003570557,5],[12.0711002349854,-8.66024971008301,5],[9.37016010284424,-9.51056003570557,5],[3.5217399597168,9.94521999359131,5],[6.4782600402832,9.94521999359131,5],[9.37016010284424,9.51056003570557,5],[12.0711002349854,8.66024971008301,5],[14.4629001617432,7.43144989013672,5],[16.4412002563477,-5.87785005569458,5],[14.4629001617432,-7.43144989013672,5],[16.4412002563477,5.87785005569458,5],[17.9195003509521,4.06736993789673,5],[18.8330993652344,-2.07911992073059,5],[17.9195003509521,-4.06736993789673,5],[18.8330993652344,2.07911992073059,5],[19.1420993804932,0,5],[0.629840016365051,-9.51056003570557,5],[-2.07106995582581,-8.66024971008301,5],[-4.46294021606445,-7.43144989013672,5],[-6.44122982025146,-5.87785005569458,5],[-7.91947984695435,-4.06736993789673,5] - ]; - $facets = [ - [0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,2,7],[0,2,8],[0,8,9],[0,9,10],[0,10,11],[0,11,12],[0,12,13],[0,13,4],[13,14,4],[15,4,16],[17,4,15],[18,4,17],[19,4,20],[18,20,4],[21,2,22],[4,19,23],[4,23,7],[23,24,7],[22,2,25],[26,2,27],[28,29,7],[25,2,30],[29,31,7],[30,2,26],[31,32,7],[27,2,6],[32,6,7],[28,7,24],[33,2,21],[34,2,33],[35,2,34],[36,2,35],[8,2,36],[16,4,14],[2,3,7],[7,3,37],[38,1,5],[3,1,39],[3,39,40],[3,40,41],[42,38,5],[3,41,43],[3,43,44],[37,3,45],[37,45,46],[37,46,47],[48,49,5],[37,47,50],[49,42,5],[37,50,51],[37,51,52],[37,52,53],[37,53,54],[55,56,5],[37,54,57],[37,57,58],[59,60,5],[37,58,61],[37,62,5],[37,61,62],[62,59,5],[60,55,5],[63,1,38],[64,1,63],[65,1,64],[66,1,65],[67,1,66],[39,1,67],[44,45,3],[56,48,5],[5,4,7],[37,5,7],[41,40,36],[36,40,8],[39,9,40],[40,9,8],[43,41,35],[35,41,36],[44,43,34],[34,43,35],[33,45,44],[34,33,44],[21,46,45],[33,21,45],[22,47,46],[21,22,46],[25,50,47],[22,25,47],[30,51,50],[25,30,50],[26,52,51],[30,26,51],[27,53,52],[26,27,52],[6,54,53],[27,6,53],[32,57,54],[6,32,54],[31,58,57],[32,31,57],[29,61,58],[31,29,58],[28,62,61],[29,28,61],[59,62,28],[24,59,28],[60,59,24],[23,60,24],[55,60,23],[19,55,23],[55,19,56],[56,19,20],[56,20,48],[48,20,18],[48,18,49],[49,18,17],[49,17,42],[42,17,15],[42,15,38],[38,15,16],[38,16,63],[63,16,14],[63,14,64],[64,14,13],[64,13,65],[65,13,12],[65,12,66],[66,12,11],[66,11,67],[67,11,10],[67,10,39],[39,10,9] - ], - } elsif ($name eq 'ipadstand') { - $vertices = [ - [17.4344673156738,-2.69879599481136e-16,9.5],[14.2814798355103,10,9.5],[0,0,9.5],[31.7159481048584,10,9.5],[62.2344741821289,2.06667568800577e-16,20],[31.7159481048584,10,20],[17.4344673156738,-2.69879599481136e-16,20],[62.2344741821289,10,20],[98.2079696655273,10,0],[98.2079696655273,8.56525380796383e-16,10],[98.2079696655273,0,0],[98.2079696655273,10,20],[98.2079696655273,0,20],[81.6609649658203,-4.39753856997999e-16,10],[90.0549850463867,10,10],[78.5079803466797,10,10],[93.2079696655273,8.56525380796383e-16,10],[14.2814798355103,10,20],[0,0,20],[87.4344711303711,2.81343962782118e-15,20],[84.2814788818359,10,20],[0,10,20],[0,0,0],[0,10,0],[62.2344741821289,2.06667568800577e-16,30],[66.9609756469727,10,30],[62.2344741821289,10,30],[70.1139602661133,8.5525763717214e-16,30],[67.7053375244141,10,28.7107200622559],[71.6787109375,1.24046736339707e-15,27.2897701263428] - ]; - $facets = [ - [0,1,2],[1,0,3],[4,5,6],[5,4,7],[8,9,10],[9,11,12],[11,9,8],[13,14,15],[14,13,16],[17,2,1],[2,17,18],[19,11,20],[11,19,12],[17,21,18],[21,2,18],[2,21,22],[22,21,23],[8,22,23],[22,8,10],[24,25,26],[25,24,27],[23,1,8],[1,23,21],[1,21,17],[5,15,3],[15,5,7],[15,7,28],[28,7,26],[28,26,25],[8,14,11],[14,8,3],[3,8,1],[14,3,15],[11,14,20],[26,4,24],[4,26,7],[12,16,9],[16,12,19],[29,4,13],[4,29,24],[24,29,27],[9,22,10],[22,9,0],[0,9,16],[0,16,13],[0,13,6],[6,13,4],[2,22,0],[19,14,16],[14,19,20],[15,29,13],[29,25,27],[25,29,15],[25,15,28],[6,3,0],[3,6,5] - ]; - } elsif ($name eq 'A') { - $vertices = [ - [513.075988769531,51.6074333190918,36.0009002685547],[516.648803710938,51.7324333190918,36.0009002685547],[513.495178222656,51.7324333190918,36.0009002685547],[489.391204833984,51.4824333190918,24.0011005401611],[488.928588867188,51.7324333190918,24.0011005401611],[492.06201171875,51.7324333190918,24.0011005401611],[496.840393066406,51.2324333190918,24.0011005401611],[495.195404052734,51.7324333190918,24.0011005401611],[498.981994628906,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,24.0011005401611],[510.342010498047,51.7324333190918,24.0011005401611],[507.163818359375,51.6074333190918,24.0011005401611],[512.515380859375,54.7190322875977,36.0009002685547],[514.161987304688,54.5058326721191,36.0009002685547],[493.06201171875,54.7190322875977,36.0009002685547],[495.195404052734,51.7324333190918,36.0009002685547],[496.195404052734,54.7190322875977,36.0009002685547],[497.195404052734,57.7058334350586,36.0009002685547],[500.851989746094,60.2658309936523,36.0009002685547],[498.915405273438,62.8258323669434,36.0009002685547],[506.701995849609,62.8258323669434,36.0009002685547],[503.648590087891,60.2658309936523,36.0009002685547],[508.381805419922,57.7058334350586,36.0009002685547],[496.418792724609,60.052433013916,36.0009002685547],[506.515197753906,72.2124328613281,36.0009002685547],[502.808807373047,74.5324325561523,36.0009002685547],[503.781982421875,71.6058349609375,36.0009002685547],[515.358764648438,55.4658317565918,36.0009002685547],[499.375183105469,76.9058380126953,36.0009002685547],[501.168792724609,78.0658340454102,36.0009002685547],[504.568786621094,78.0658340454102,36.0009002685547],[506.32861328125,81.599235534668,36.0009002685547],[502.928588867188,81.599235534668,36.0009002685547],[499.528594970703,81.599235534668,36.0009002685547],[498.20361328125,77.8658294677734,36.0009002685547],[495.195404052734,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,27.0011005401611],[506.555206298828,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,36.0009002685547],[510.342010498047,51.7324333190918,36.0009002685547],[512.515380859375,54.7190322875977,24.0011005401611],[509.361999511719,54.7190322875977,24.0011005401611],[508.381805419922,57.7058334350586,24.0011005401611],[506.701995849609,62.8258323669434,24.0011005401611],[509.188812255859,60.052433013916,24.0011005401611],[493.06201171875,54.7190322875977,24.0011005401611],[503.648590087891,60.2658309936523,24.0011005401611],[500.851989746094,60.2658309936523,24.0011005401611],[498.915405273438,62.8258323669434,24.0011005401611],[502.808807373047,62.8258323669434,24.0011005401611],[491.425201416016,54.5058326721191,24.0011005401611],[506.421813964844,76.9058380126953,24.0011005401611],[502.808807373047,74.5324325561523,24.0011005401611],[504.568786621094,78.0658340454102,24.0011005401611],[506.32861328125,81.599235534668,24.0011005401611],[507.618804931641,77.8658294677734,24.0011005401611],[499.221801757812,72.2124328613281,24.0011005401611],[501.835388183594,71.6058349609375,24.0011005401611],[501.168792724609,78.0658340454102,24.0011005401611],[499.528594970703,81.599235534668,24.0011005401611],[502.048583984375,79.8324356079102,24.0011005401611],[490.253601074219,55.4658317565918,24.0011005401611],[488.928588867188,51.7324333190918,30.0011005401611],[488.928588867188,51.7324333190918,36.0009002685547],[490.253601074219,55.4658317565918,31.5009002685547],[498.20361328125,77.8658294677734,34.5009002685547],[508.381805419922,57.7058334350586,30.0011005401611],[505.585388183594,57.7058334350586,27.0011005401611],[502.788818359375,57.7058334350586,36.0009002685547],[499.992004394531,57.7058334350586,33.0009002685547],[509.851989746094,53.2258338928223,33.0009002685547],[509.361999511719,54.7190322875977,36.0009002685547],[508.871795654297,56.2124328613281,27.0011005401611],[496.695404052734,56.2124328613281,33.0009002685547],[495.695404052734,53.2258338928223,27.0011005401611],[506.32861328125,81.599235534668,30.0011005401611],[507.618804931641,77.8658294677734,25.5011005401611],[515.358764648438,55.4658317565918,34.5009002685547],[501.228607177734,81.599235534668,33.0009002685547],[504.628601074219,81.599235534668,27.0011005401611],[503.781982421875,71.6058349609375,33.0009002685547],[502.808807373047,74.5324325561523,30.0011005401611],[498.915405273438,62.8258323669434,30.0011005401611],[500.861999511719,62.8258323669434,27.0011005401611],[502.808807373047,62.8258323669434,36.0009002685547],[504.755187988281,62.8258323669434,33.0009002685547],[501.835388183594,71.6058349609375,33.0009002685547],[499.888793945312,65.7524337768555,33.0009002685547],[499.888793945312,65.7524337768555,36.0009002685547],[513.128601074219,51.4824333190918,36.0009002685547],[513.075988769531,51.6074333190918,24.0011005401611],[516.648803710938,51.7324333190918,24.0011005401611],[513.128601074219,51.4824333190918,24.0011005401611],[513.495178222656,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,36.0009002685547],[507.163818359375,51.6074333190918,36.0009002685547],[490.337799072266,51.4824333190918,24.0011005401611],[489.391204833984,51.4824333190918,36.0009002685547],[492.06201171875,51.7324333190918,36.0009002685547],[490.337799072266,51.4824333190918,36.0009002685547],[513.233764648438,51.2324333190918,24.0011005401611],[513.233764648438,51.2324333190918,36.0009002685547],[504.773803710938,51.4824333190918,36.0009002685547],[504.773803710938,51.4824333190918,24.0011005401611],[489.266998291016,51.2324333190918,24.0011005401611],[489.266998291016,51.2324333190918,36.0009002685547],[490.253601074219,55.4658317565918,25.5011005401611],[499.528594970703,81.599235534668,30.0011005401611],[498.20361328125,77.8658294677734,31.5009002685547],[515.358764648438,55.4658317565918,28.5011005401611],[515.358764648438,55.4658317565918,25.5011005401611],[495.246795654297,61.0124320983887,36.0009002685547],[490.253601074219,55.4658317565918,34.5009002685547],[490.253601074219,55.4658317565918,36.0009002685547],[494.228607177734,66.6658325195312,24.0011005401611],[499.068786621094,67.5192337036133,24.0011005401611],[498.20361328125,77.8658294677734,25.5011005401611],[498.20361328125,77.8658294677734,24.0011005401611],[506.608795166016,67.5192337036133,36.0009002685547],[509.09521484375,64.7458343505859,36.0009002685547],[507.618804931641,77.8658294677734,34.5009002685547],[507.618804931641,77.8658294677734,36.0009002685547],[510.385406494141,61.0124320983887,24.0011005401611],[515.358764648438,55.4658317565918,24.0011005401611],[489.32861328125,47.7324333190918,31.5009002685547],[492.95361328125,47.7324333190918,33.5634994506836],[489.32861328125,47.7324333190918,34.5009002685547],[489.32861328125,47.7324333190918,28.5011005401611],[489.32861328125,47.7324333190918,25.5011005401611],[492.95361328125,47.7324333190918,26.4385013580322],[492.95361328125,47.7324333190918,30.5635013580322],[492.95361328125,47.7324333190918,32.0634994506836],[492.95361328125,47.7324333190918,31.3135013580322],[492.95361328125,47.7324333190918,35.4384994506836],[489.32861328125,47.7324333190918,36.0009002685547],[492.95361328125,47.7324333190918,34.3134994506836],[492.95361328125,47.7324333190918,34.6884994506836],[492.95361328125,47.7324333190918,27.9385013580322],[492.95361328125,47.7324333190918,28.6885013580322],[492.95361328125,47.7324333190918,29.0635013580322],[489.32861328125,47.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,24.5635013580322],[492.95361328125,47.7324333190918,25.6885013580322],[492.95361328125,47.7324333190918,25.3135013580322],[492.95361328125,47.7324333190918,24.1885013580322],[492.95361328125,47.7324333190918,24.0011005401611],[513.443786621094,50.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,35.8134994506836],[492.95361328125,47.7324333190918,36.0009002685547],[513.443786621094,50.7324333190918,36.0009002685547],[506.350402832031,51.4824333190918,36.0009002685547],[506.350402832031,51.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,24.0011005401611],[492.638793945312,48.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,36.0009002685547],[492.638793945312,48.4824333190918,36.0009002685547],[490.089599609375,50.9824333190918,36.0009002685547],[490.089599609375,50.9824333190918,24.0011005401611],[510.342010498047,51.7324333190918,30.0011005401611],[499.068786621094,67.5192337036133,36.0009002685547],[494.228607177734,66.6658325195312,36.0009002685547],[499.375183105469,76.9058380126953,24.0011005401611],[506.421813964844,76.9058380126953,36.0009002685547],[506.608795166016,67.5192337036133,24.0011005401611],[505.728607177734,65.7524337768555,24.0011005401611],[509.09521484375,64.7458343505859,24.0011005401611],[506.701995849609,62.8258323669434,30.0011005401611],[505.728607177734,65.7524337768555,27.0011005401611],[501.835388183594,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,27.0011005401611],[494.228607177734,66.6658325195312,30.0011005401611],[495.553588867188,70.3992309570312,28.5011005401611],[492.903594970703,62.9324340820312,28.5011005401611],[495.553588867188,70.3992309570312,31.5009002685547],[492.903594970703,62.9324340820312,31.5009002685547],[511.488800048828,66.6658325195312,24.0011005401611],[511.488800048828,66.6658325195312,30.0011005401611],[512.778564453125,62.9324340820312,28.5011005401611],[515.358764648438,55.4658317565918,31.5009002685547],[507.618804931641,77.8658294677734,31.5009002685547],[510.198791503906,70.3992309570312,28.5011005401611],[511.488800048828,66.6658325195312,36.0009002685547],[512.778564453125,62.9324340820312,31.5009002685547],[510.198791503906,70.3992309570312,31.5009002685547],[502.788818359375,57.7058334350586,24.0011005401611],[497.195404052734,57.7058334350586,30.0011005401611],[492.903594970703,62.9324340820312,34.5009002685547],[492.903594970703,62.9324340820312,36.0009002685547],[495.553588867188,70.3992309570312,24.0011005401611],[496.725189208984,69.4392318725586,24.0011005401611],[495.553588867188,70.3992309570312,25.5011005401611],[495.246795654297,61.0124320983887,24.0011005401611],[492.903594970703,62.9324340820312,25.5011005401611],[492.903594970703,62.9324340820312,24.0011005401611],[495.553588867188,70.3992309570312,36.0009002685547],[496.725189208984,69.4392318725586,36.0009002685547],[495.553588867188,70.3992309570312,34.5009002685547],[510.198791503906,70.3992309570312,36.0009002685547],[509.002014160156,69.4392318725586,36.0009002685547],[510.198791503906,70.3992309570312,34.5009002685547],[512.778564453125,62.9324340820312,25.5011005401611],[512.778564453125,62.9324340820312,24.0011005401611],[510.198791503906,70.3992309570312,24.0011005401611],[509.002014160156,69.4392318725586,24.0011005401611],[510.198791503906,70.3992309570312,25.5011005401611],[510.385406494141,61.0124320983887,36.0009002685547],[512.778564453125,62.9324340820312,34.5009002685547],[512.778564453125,62.9324340820312,36.0009002685547],[496.840393066406,51.2324333190918,36.0009002685547],[498.981994628906,51.7324333190918,36.0009002685547],[498.981994628906,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,24.0011005401611],[506.555206298828,51.7324333190918,27.0011005401611],[503.82861328125,47.7324333190918,30.7509002685547],[507.45361328125,47.7324333190918,32.8134994506836],[503.82861328125,47.7324333190918,33.7509002685547],[503.82861328125,47.7324333190918,29.2511005401611],[503.82861328125,47.7324333190918,26.2511005401611],[507.45361328125,47.7324333190918,27.1885013580322],[493.921813964844,57.2792320251465,36.0009002685547],[491.425201416016,54.5058326721191,36.0009002685547],[497.195404052734,57.7058334350586,24.0011005401611],[496.418792724609,60.052433013916,24.0011005401611],[509.188812255859,60.052433013916,36.0009002685547],[511.675415039062,57.2792320251465,24.0011005401611],[514.161987304688,54.5058326721191,24.0011005401611],[507.45361328125,47.7324333190918,34.3134994506836],[503.82861328125,47.7324333190918,35.2509002685547],[507.45361328125,47.7324333190918,25.6885013580322],[503.82861328125,47.7324333190918,24.7511005401611],[500.20361328125,47.7324333190918,31.6885013580322],[500.20361328125,47.7324333190918,28.3135013580322],[500.20361328125,47.7324333190918,30.1885013580322],[507.45361328125,47.7324333190918,29.8135013580322],[507.45361328125,47.7324333190918,31.3135013580322],[507.45361328125,47.7324333190918,30.5635013580322],[503.82861328125,47.7324333190918,36.0009002685547],[507.45361328125,47.7324333190918,35.4384994506836],[507.45361328125,47.7324333190918,35.0634994506836],[507.45361328125,47.7324333190918,28.6885013580322],[507.45361328125,47.7324333190918,29.4385013580322],[503.82861328125,47.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,34.6884994506836],[500.20361328125,47.7324333190918,33.1884994506836],[500.20361328125,47.7324333190918,33.9384994506836],[500.20361328125,47.7324333190918,25.3135013580322],[500.20361328125,47.7324333190918,26.8135013580322],[500.20361328125,47.7324333190918,26.0635013580322],[500.20361328125,47.7324333190918,30.9385013580322],[500.20361328125,47.7324333190918,35.0634994506836],[500.20361328125,47.7324333190918,35.4384994506836],[500.20361328125,47.7324333190918,29.0635013580322],[500.20361328125,47.7324333190918,29.4385013580322],[500.20361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.1885013580322],[507.45361328125,47.7324333190918,24.0011005401611],[513.86376953125,49.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,35.8134994506836],[507.45361328125,47.7324333190918,36.0009002685547],[513.86376953125,49.7324333190918,36.0009002685547],[500.20361328125,47.7324333190918,24.1885013580322],[500.20361328125,47.7324333190918,24.0011005401611],[502.988800048828,49.7324333190918,24.0011005401611],[500.20361328125,47.7324333190918,35.8134994506836],[500.20361328125,47.7324333190918,36.0009002685547],[502.988800048828,49.7324333190918,36.0009002685547],[504.755187988281,62.8258323669434,27.0011005401611],[499.205383300781,51.2324333190918,36.0009002685547],[498.786193847656,51.1074333190918,36.0009002685547],[502.358795166016,51.2324333190918,36.0009002685547],[499.205383300781,51.2324333190918,24.0011005401611],[502.358795166016,51.2324333190918,24.0011005401611],[498.786193847656,51.1074333190918,24.0011005401611],[502.568786621094,50.7324333190918,24.0011005401611],[505.931213378906,51.3574333190918,24.0011005401611],[509.503601074219,51.4824333190918,24.0011005401611],[502.568786621094,50.7324333190918,36.0009002685547],[505.931213378906,51.3574333190918,36.0009002685547],[509.503601074219,51.4824333190918,36.0009002685547],[499.048583984375,50.4824333190918,36.0009002685547],[492.428588867188,48.9824333190918,36.0009002685547],[499.048583984375,50.4824333190918,24.0011005401611],[492.428588867188,48.9824333190918,24.0011005401611],[506.088806152344,50.9824333190918,24.0011005401611],[506.036010742188,51.1074333190918,24.0011005401611],[506.088806152344,50.9824333190918,36.0009002685547],[506.036010742188,51.1074333190918,36.0009002685547],[498.891204833984,50.8574333190918,36.0009002685547],[498.943786621094,50.7324333190918,36.0009002685547],[498.891204833984,50.8574333190918,24.0011005401611],[498.943786621094,50.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,24.0011005401611],[499.783813476562,48.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,36.0009002685547],[499.783813476562,48.7324333190918,36.0009002685547],[506.403594970703,50.2324333190918,24.0011005401611],[506.298797607422,50.4824333190918,24.0011005401611],[506.403594970703,50.2324333190918,36.0009002685547],[506.298797607422,50.4824333190918,36.0009002685547],[501.228607177734,81.599235534668,27.0011005401611],[502.928588867188,81.599235534668,24.0011005401611],[499.2587890625,49.9824333190918,36.0009002685547],[499.363800048828,49.7324333190918,36.0009002685547],[499.2587890625,49.9824333190918,24.0011005401611],[499.363800048828,49.7324333190918,24.0011005401611],[496.695404052734,56.2124328613281,27.0011005401611],[496.195404052734,54.7190322875977,24.0011005401611],[509.851989746094,53.2258338928223,27.0011005401611],[493.464782714844,51.1074333190918,36.0009002685547],[493.464782714844,51.1074333190918,24.0011005401611],[502.768798828125,51.7324333190918,24.0011005401611],[500.215789794922,51.3574333190918,24.0011005401611],[497.628601074219,51.2324333190918,24.0011005401611],[502.768798828125,51.7324333190918,36.0009002685547],[500.215789794922,51.3574333190918,36.0009002685547],[497.628601074219,51.2324333190918,36.0009002685547],[507.033813476562,48.7324333190918,24.0011005401611],[506.823791503906,49.2324333190918,24.0011005401611],[507.033813476562,48.7324333190918,36.0009002685547],[506.823791503906,49.2324333190918,36.0009002685547],[494.4501953125,51.1074333190918,24.0011005401611],[494.4501953125,51.1074333190918,36.0009002685547],[500.807006835938,51.3574333190918,36.0009002685547],[503.591186523438,51.4824333190918,36.0009002685547],[503.591186523438,51.4824333190918,24.0011005401611],[500.807006835938,51.3574333190918,24.0011005401611],[505.728607177734,65.7524337768555,36.0009002685547],[505.728607177734,65.7524337768555,33.0009002685547],[499.221801757812,72.2124328613281,36.0009002685547],[501.835388183594,71.6058349609375,36.0009002685547],[506.515197753906,72.2124328613281,24.0011005401611],[503.781982421875,71.6058349609375,24.0011005401611],[503.781982421875,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,24.0011005401611],[495.695404052734,53.2258338928223,33.0009002685547],[516.648803710938,51.7324333190918,30.0011005401611],[498.20361328125,77.8658294677734,28.5011005401611],[505.585388183594,57.7058334350586,33.0009002685547],[508.871795654297,56.2124328613281,33.0009002685547],[499.992004394531,57.7058334350586,27.0011005401611],[504.628601074219,81.599235534668,33.0009002685547],[500.861999511719,62.8258323669434,33.0009002685547],[496.878601074219,74.1324310302734,27.0011005401611],[496.878601074219,74.1324310302734,33.0009002685547],[491.57861328125,59.199031829834,27.0011005401611],[490.253601074219,55.4658317565918,28.5011005401611],[491.57861328125,59.199031829834,33.0009002685547],[514.068786621094,59.199031829834,27.0011005401611],[514.068786621094,59.199031829834,33.0009002685547],[508.908813476562,74.1324310302734,27.0011005401611],[507.618804931641,77.8658294677734,28.5011005401611],[508.908813476562,74.1324310302734,33.0009002685547],[491.271789550781,50.9824333190918,36.0009002685547],[490.877807617188,50.9824333190918,36.0009002685547],[491.271789550781,50.9824333190918,24.0011005401611],[490.877807617188,50.9824333190918,24.0011005401611],[495.213806152344,50.9824333190918,36.0009002685547],[493.636993408203,50.9824333190918,36.0009002685547],[495.213806152344,50.9824333190918,24.0011005401611],[493.636993408203,50.9824333190918,24.0011005401611],[503.985412597656,51.4824333190918,36.0009002685547],[503.985412597656,51.4824333190918,24.0011005401611],[511.675415039062,57.2792320251465,36.0009002685547],[493.921813964844,57.2792320251465,24.0011005401611],[502.768798828125,51.7324333190918,30.0011005401611],[506.555206298828,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,30.0011005401611],[492.848815917969,50.9824333190918,24.0011005401611],[492.848815917969,50.9824333190918,36.0009002685547],[500.861999511719,68.6792297363281,36.0009002685547],[500.861999511719,68.6792297363281,24.0011005401611],[496.878601074219,74.1324310302734,24.0011005401611],[496.878601074219,74.1324310302734,36.0009002685547],[504.755187988281,68.6792297363281,24.0011005401611],[504.755187988281,68.6792297363281,36.0009002685547],[508.908813476562,74.1324310302734,36.0009002685547],[508.908813476562,74.1324310302734,24.0011005401611],[505.728607177734,65.7524337768555,30.0011005401611],[504.755187988281,68.6792297363281,30.0011005401611],[503.781982421875,71.6058349609375,30.0011005401611],[500.861999511719,68.6792297363281,30.0011005401611],[499.888793945312,65.7524337768555,30.0011005401611],[501.835388183594,71.6058349609375,30.0011005401611],[491.57861328125,59.199031829834,24.0011005401611],[491.57861328125,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,24.0011005401611],[511.07861328125,47.7324333190918,34.8759002685547],[511.07861328125,47.7324333190918,31.8759002685547],[514.70361328125,47.7324333190918,33.9384994506836],[511.07861328125,47.7324333190918,25.1261005401611],[514.70361328125,47.7324333190918,26.0635013580322],[511.07861328125,47.7324333190918,28.1261005401611],[502.788818359375,57.7058334350586,30.0011005401611],[502.048583984375,79.8324356079102,36.0009002685547],[514.70361328125,47.7324333190918,30.9385013580322],[511.07861328125,47.7324333190918,30.3759002685547],[514.70361328125,47.7324333190918,29.0635013580322],[511.07861328125,47.7324333190918,29.6261005401611],[496.57861328125,47.7324333190918,31.1259002685547],[496.57861328125,47.7324333190918,32.6259002685547],[496.57861328125,47.7324333190918,34.1259002685547],[496.57861328125,47.7324333190918,28.8761005401611],[496.57861328125,47.7324333190918,27.3761005401611],[496.57861328125,47.7324333190918,25.8761005401611],[496.57861328125,47.7324333190918,29.6261005401611],[514.70361328125,47.7324333190918,35.4384994506836],[511.07861328125,47.7324333190918,35.6259002685547],[514.70361328125,47.7324333190918,24.5635013580322],[511.07861328125,47.7324333190918,24.3761005401611],[496.57861328125,47.7324333190918,34.8759002685547],[496.57861328125,47.7324333190918,25.1261005401611],[496.57861328125,47.7324333190918,35.6259002685547],[496.57861328125,47.7324333190918,24.3761005401611],[511.07861328125,47.7324333190918,36.0009002685547],[511.07861328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,30.1885013580322],[514.70361328125,47.7324333190918,35.8134994506836],[514.70361328125,47.7324333190918,29.8135013580322],[514.70361328125,47.7324333190918,24.1885013580322],[496.57861328125,47.7324333190918,36.0009002685547],[496.57861328125,47.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,36.0009002685547],[514.70361328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,24.0011005401611],[502.808807373047,62.8258323669434,30.0011005401611],[509.608795166016,51.2324333190918,24.0011005401611],[509.608795166016,51.2324333190918,36.0009002685547],[491.641204833984,50.8574333190918,24.0011005401611],[495.423797607422,50.4824333190918,36.0009002685547],[495.423797607422,50.4824333190918,24.0011005401611],[491.641204833984,50.8574333190918,36.0009002685547],[495.528594970703,50.2324333190918,24.0011005401611],[492.0087890625,49.9824333190918,24.0011005401611],[509.818786621094,50.7324333190918,24.0011005401611],[495.948608398438,49.2324333190918,36.0009002685547],[495.528594970703,50.2324333190918,36.0009002685547],[495.948608398438,49.2324333190918,24.0011005401611],[509.818786621094,50.7324333190918,36.0009002685547],[492.0087890625,49.9824333190918,36.0009002685547],[491.956207275391,50.1074333190918,24.0011005401611],[491.956207275391,50.1074333190918,36.0009002685547],[502.928588867188,81.599235534668,30.0011005401611],[491.851013183594,50.3574333190918,36.0009002685547],[491.851013183594,50.3574333190918,24.0011005401611],[496.195404052734,54.7190322875977,30.0011005401611],[509.361999511719,54.7190322875977,30.0011005401611],[488.632598876953,51.7256317138672,30.0011005401611],[488.632598876953,51.7256317138672,29.5091018676758],[488.632598876953,51.7188339233398,24.0011005401611],[488.632598876953,51.7256317138672,27.4929008483887],[488.632598876953,51.7324333190918,30.0011005401611],[488.632598876953,51.7324333190918,29.0175018310547],[488.632598876953,51.7324333190918,24.9847011566162],[488.632598876953,51.7324333190918,24.0011005401611],[488.632598876953,51.7188339233398,30.0011005401611],[488.632598876953,51.7176322937012,24.0011005401611],[488.632598876953,51.7182312011719,30.0011005401611],[488.632598876953,51.7176322937012,30.0011005401611],[488.632598876953,51.715030670166,24.0011005401611],[488.632598876953,51.7162322998047,30.0011005401611],[488.632598876953,50.761833190918,24.0011005401611],[488.632598876953,50.7578315734863,24.0011005401611],[488.632598876953,50.7598342895508,30.0011005401611],[488.632598876953,50.7522315979004,24.0011005401611],[488.632598876953,49.7838325500488,24.0011005401611],[488.632598876953,50.2680320739746,30.0011005401611],[488.632598876953,51.7046318054199,24.0011005401611],[488.632598876953,51.709831237793,30.0011005401611],[488.632598876953,50.9120330810547,24.0011005401611],[488.632598876953,50.8882331848145,24.0011005401611],[488.632598876953,50.9002304077148,30.0011005401611],[488.632598876953,47.7324333190918,24.0370998382568],[488.632598876953,48.5612335205078,30.0011005401611],[488.632598876953,47.7324333190918,24.0011005401611],[488.632598876953,47.7324333190918,24.1091003417969],[488.632598876953,48.5612335205078,30.0189018249512],[488.632598876953,47.7324333190918,25.3211002349854],[488.632598876953,48.5612335205078,30.0551013946533],[488.632598876953,47.7324333190918,25.4651012420654],[488.632598876953,48.5612335205078,30.6609001159668],[488.632598876953,47.7324333190918,25.5371017456055],[488.632598876953,48.5612335205078,30.7329006195068],[488.632598876953,47.7324333190918,25.6091003417969],[488.632598876953,48.5612335205078,30.7689018249512],[488.632598876953,47.7324333190918,25.8971004486084],[488.632598876953,48.5612335205078,30.8051013946533],[488.632598876953,47.7324333190918,28.321102142334],[488.632598876953,48.5612335205078,30.9491004943848],[488.632598876953,47.7324333190918,28.4651012420654],[488.632598876953,48.5612335205078,32.1609001159668],[488.632598876953,47.7324333190918,28.5371017456055],[488.632598876953,48.5612335205078,32.2329025268555],[488.632598876953,47.7324333190918,28.6811008453369],[488.632598876953,48.5612335205078,32.2689018249512],[488.632598876953,47.7324333190918,31.1049003601074],[488.632598876953,48.5612335205078,32.3411026000977],[488.632598876953,47.7324333190918,31.3929004669189],[488.632598876953,49.3900299072266,36.0009002685547],[488.632598876953,47.7324333190918,31.536901473999],[488.632598876953,47.7324333190918,31.6809005737305],[488.632598876953,47.7324333190918,34.1049003601074],[488.632598876953,47.7324333190918,34.3929023742676],[488.632598876953,47.7324333190918,34.464900970459],[488.632598876953,47.7324333190918,34.5369033813477],[488.632598876953,47.7324333190918,34.6809005737305],[488.632598876953,47.7324333190918,35.8929023742676],[488.632598876953,47.7324333190918,35.964900970459],[488.632598876953,47.7324333190918,36.0009002685547],[488.632598876953,50.8816299438477,24.0011005401611],[488.632598876953,50.8850326538086,30.0011005401611],[488.632598876953,49.7480316162109,24.0011005401611],[488.632598876953,49.7426300048828,24.0011005401611],[488.632598876953,49.745231628418,30.0011005401611],[488.632598876953,49.7592315673828,24.0011005401611],[488.632598876953,49.7536315917969,30.0011005401611],[488.632598876953,49.3900299072266,24.0011005401611],[488.632598876953,49.5664329528809,30.0011005401611],[488.632598876953,50.8786315917969,24.0011005401611],[488.632598876953,50.7764320373535,24.0011005401611],[488.632598876953,50.8274307250977,30.0011005401611],[488.632598876953,50.7550315856934,30.0011005401611],[488.632598876953,50.7692337036133,30.0011005401611],[488.632598876953,50.9284324645996,24.0011005401611],[488.632598876953,50.9202308654785,30.0011005401611],[488.632598876953,51.1788330078125,24.0011005401611],[488.632598876953,51.139232635498,24.0011005401611],[488.632598876953,51.1590309143066,30.0011005401611],[488.632598876953,51.2324333190918,24.0011005401611],[488.632598876953,51.2056312561035,30.0011005401611],[488.632598876953,51.4340324401855,24.0011005401611],[488.632598876953,51.3946304321289,24.0011005401611],[488.632598876953,51.4142303466797,30.0011005401611],[488.632598876953,51.4498329162598,24.0011005401611],[488.632598876953,51.5772323608398,30.0011005401611],[488.632598876953,51.4418334960938,30.0011005401611],[488.632598876953,51.3136329650879,30.0011005401611],[488.632598876953,49.7714309692383,30.0011005401611],[488.632598876953,51.0338325500488,30.0011005401611],[488.632598876953,50.8816299438477,30.0011005401611],[488.632598876953,50.8800315856934,30.0011005401611],[488.632598876953,51.7188339233398,36.0009002685547],[488.632598876953,51.7176322937012,36.0009002685547],[488.632598876953,49.3900299072266,30.0011005401611],[488.632598876953,50.7522315979004,30.0011005401611],[488.632598876953,50.7522315979004,36.0009002685547],[488.632598876953,49.7426300048828,30.0011005401611],[488.632598876953,49.7426300048828,36.0009002685547],[488.632598876953,49.7480316162109,30.0011005401611],[488.632598876953,49.7480316162109,36.0009002685547],[488.632598876953,51.715030670166,30.0011005401611],[488.632598876953,51.715030670166,36.0009002685547],[488.632598876953,50.7578315734863,30.0011005401611],[488.632598876953,50.7578315734863,36.0009002685547],[488.632598876953,50.761833190918,30.0011005401611],[488.632598876953,50.761833190918,36.0009002685547],[488.632598876953,50.8882331848145,30.0011005401611],[488.632598876953,50.8882331848145,36.0009002685547],[488.632598876953,49.7592315673828,30.0011005401611],[488.632598876953,49.7592315673828,36.0009002685547],[488.632598876953,51.1788330078125,30.0011005401611],[488.632598876953,51.1788330078125,36.0009002685547],[488.632598876953,50.9120330810547,30.0011005401611],[488.632598876953,50.9120330810547,36.0009002685547],[488.632598876953,51.4498329162598,30.0011005401611],[488.632598876953,51.4498329162598,36.0009002685547],[488.632598876953,51.7046318054199,30.0011005401611],[488.632598876953,51.7046318054199,36.0009002685547],[488.632598876953,51.2324333190918,30.0011005401611],[488.632598876953,51.2324333190918,36.0009002685547],[488.632598876953,51.3946304321289,30.0011005401611],[488.632598876953,51.3946304321289,36.0009002685547],[488.632598876953,51.4340324401855,30.0011005401611],[488.632598876953,51.4340324401855,36.0009002685547],[488.632598876953,49.7838325500488,30.0011005401611],[488.632598876953,49.7838325500488,36.0009002685547],[488.632598876953,50.7764320373535,30.0011005401611],[488.632598876953,50.7764320373535,36.0009002685547],[488.632598876953,51.139232635498,30.0011005401611],[488.632598876953,51.139232635498,36.0009002685547],[488.632598876953,50.9284324645996,30.0011005401611],[488.632598876953,50.9284324645996,36.0009002685547],[488.632598876953,50.8816299438477,36.0009002685547],[488.632598876953,50.8786315917969,30.0011005401611],[488.632598876953,50.8786315917969,36.0009002685547],[488.632598876953,51.7324333190918,35.0173034667969],[488.632598876953,51.7324333190918,36.0009002685547],[488.632598876953,51.7324333190918,30.9847011566162],[517.188415527344,51.7140884399414,24.0011005401611],[517.188415527344,51.7140884399414,36.0009002685547],[517.188415527344,50.4475173950195,24.0011005401611],[517.188415527344,51.7324333190918,35.3734130859375],[517.188415527344,51.7324333190918,36.0009002685547],[517.188415527344,51.7324333190918,34.1185760498047],[517.188415527344,51.7324333190918,31.88330078125],[517.188415527344,51.7324333190918,30.0011005401611],[517.188415527344,51.7324333190918,28.1187744140625],[517.188415527344,51.7324333190918,25.8834266662598],[517.188415527344,51.7324333190918,24.6285915374756],[517.188415527344,51.7324333190918,24.0011005401611],[517.188415527344,47.7324333190918,24.0600452423096],[517.188415527344,47.7324333190918,24.0011005401611],[517.188415527344,50.4475173950195,36.0009002685547],[517.188415527344,47.7324333190918,24.1779975891113],[517.188415527344,47.7324333190918,24.6498031616211],[517.188415527344,47.7324333190918,28.7625770568848],[517.188415527344,47.7324333190918,29.7061901092529],[517.188415527344,47.7324333190918,29.9420928955078],[517.188415527344,47.7324333190918,30.0600452423096],[517.188415527344,47.7324333190918,30.2959480285645],[517.188415527344,47.7324333190918,31.2395629882812],[517.188415527344,47.7324333190918,35.3521995544434],[517.188415527344,47.7324333190918,35.8240051269531],[517.188415527344,47.7324333190918,35.9419555664062],[517.188415527344,47.7324333190918,36.0009002685547] - ]; - $facets = [ - [0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,2,1],[12,1,13],[14,15,16],[17,18,19],[20,21,22],[17,19,23],[24,25,26],[27,13,1],[28,25,29],[30,31,32],[28,33,34],[35,36,7],[37,38,39],[40,10,41],[42,43,44],[45,5,4],[46,47,48],[46,48,49],[45,4,50],[51,52,53],[51,54,55],[56,52,57],[58,59,60],[61,50,4],[62,63,64],[65,34,33],[66,67,42],[68,17,69],[70,71,22],[66,42,72],[73,16,15],[35,7,74],[75,76,54],[77,27,1],[78,32,31],[75,54,79],[80,26,25],[81,80,25],[82,83,48],[84,20,85],[81,25,86],[87,88,19],[0,89,1],[90,91,92],[90,10,93],[38,94,39],[94,95,39],[3,7,96],[97,15,98],[97,99,15],[92,91,100],[89,101,1],[102,39,95],[103,11,10],[104,96,7],[105,15,99],[106,61,4],[107,108,33],[76,55,54],[109,91,110],[111,23,19],[112,63,113],[114,115,48],[116,59,117],[118,20,119],[120,31,121],[122,44,43],[110,91,123],[124,125,126],[127,128,129],[127,130,124],[131,124,132],[126,133,134],[135,136,126],[137,138,127],[139,127,138],[128,140,141],[142,128,143],[144,140,145],[100,91,146],[147,148,134],[101,149,1],[102,150,39],[103,10,151],[145,140,152],[152,140,153],[148,154,134],[154,155,134],[156,15,105],[157,104,7],[36,8,7],[158,37,39],[159,19,88],[160,19,159],[161,59,58],[161,117,59],[162,31,30],[162,121,31],[163,43,164],[163,165,43],[166,167,43],[167,164,43],[168,57,52],[82,48,169],[114,170,171],[108,65,33],[64,63,112],[114,172,170],[160,173,170],[171,170,173],[172,174,170],[160,170,174],[175,176,177],[178,77,1],[179,31,120],[175,180,176],[181,182,176],[177,176,182],[180,183,176],[181,176,183],[184,42,67],[185,69,17],[160,111,19],[186,187,160],[188,189,114],[190,188,114],[114,48,191],[192,114,193],[194,160,195],[196,160,194],[197,198,181],[199,197,181],[122,43,165],[200,201,175],[202,175,203],[204,175,202],[205,119,20],[206,181,207],[208,209,15],[210,15,209],[211,10,9],[212,10,211],[213,214,215],[216,217,218],[219,14,17],[113,63,220],[221,222,48],[191,48,222],[22,223,20],[205,20,223],[224,40,42],[123,91,225],[214,226,215],[227,215,226],[218,217,228],[229,228,217],[215,230,213],[125,135,126],[217,216,231],[129,128,142],[216,213,232],[130,132,124],[213,216,233],[234,213,235],[236,227,237],[238,237,227],[239,240,216],[233,216,240],[241,242,229],[243,229,242],[215,227,244],[245,215,246],[217,247,229],[248,249,217],[232,213,250],[230,250,213],[133,147,134],[244,227,251],[236,252,227],[251,227,252],[231,216,253],[254,253,216],[141,140,144],[247,255,229],[241,229,256],[255,256,229],[257,241,258],[259,146,91],[260,261,236],[262,1,149],[263,264,241],[265,241,264],[266,236,267],[268,267,236],[49,48,83],[166,43,269],[270,271,272],[273,274,275],[276,274,277],[278,151,10],[279,280,272],[281,39,150],[272,282,279],[155,283,134],[274,276,284],[153,140,285],[286,276,287],[265,276,286],[288,289,279],[268,288,279],[290,291,272],[271,290,272],[292,274,293],[275,274,292],[294,265,295],[276,265,294],[296,297,268],[279,296,268],[241,265,298],[298,265,299],[236,300,268],[300,301,268],[107,33,78],[302,303,59],[304,305,279],[282,304,279],[306,276,307],[284,276,306],[185,17,73],[308,309,221],[158,39,70],[310,41,10],[15,311,208],[7,6,312],[313,314,6],[315,6,314],[316,208,317],[318,317,208],[258,241,319],[319,241,320],[261,321,236],[321,322,236],[6,315,323],[208,324,318],[270,325,318],[326,318,325],[327,328,315],[273,315,328],[118,329,20],[330,20,329],[331,332,25],[86,25,332],[333,334,52],[335,52,334],[115,336,48],[169,48,336],[62,106,4],[35,15,210],[35,337,15],[158,10,212],[158,310,10],[338,178,1],[339,59,116],[107,302,59],[66,22,340],[66,341,22],[185,221,342],[185,308,221],[75,31,179],[75,343,31],[166,20,330],[166,85,20],[81,52,335],[81,168,52],[82,19,344],[82,87,19],[108,339,345],[346,108,345],[64,347,348],[349,347,64],[178,109,350],[351,178,350],[179,352,353],[354,352,179],[355,208,356],[356,208,311],[357,358,6],[358,312,6],[68,22,21],[68,340,22],[221,48,47],[184,342,221],[359,270,360],[318,360,270],[361,362,273],[315,273,362],[272,102,270],[363,270,102],[274,273,103],[364,103,273],[21,19,18],[21,20,84],[184,46,42],[43,42,46],[12,22,71],[365,22,12],[14,98,15],[14,220,63],[40,93,10],[40,225,91],[45,221,309],[366,221,45],[313,367,212],[212,367,368],[36,369,367],[313,36,367],[316,37,367],[37,368,367],[210,367,369],[316,367,210],[362,370,315],[370,323,315],[360,318,371],[371,318,324],[372,331,159],[159,195,160],[373,115,56],[115,114,189],[52,56,161],[374,161,56],[25,28,331],[375,331,28],[376,333,163],[163,203,175],[377,118,24],[118,181,198],[25,24,162],[378,162,24],[52,51,333],[379,333,51],[167,380,381],[376,167,381],[377,381,330],[330,381,380],[335,381,382],[376,381,335],[373,383,169],[169,383,384],[168,385,383],[373,168,383],[372,87,383],[87,384,383],[377,80,381],[80,382,381],[86,383,385],[372,383,86],[106,348,347],[386,106,347],[375,65,346],[108,346,65],[64,112,349],[387,349,112],[171,190,114],[346,345,171],[374,190,345],[171,345,190],[349,172,347],[172,114,192],[386,347,192],[172,192,347],[173,160,196],[171,173,346],[375,346,196],[173,196,346],[172,349,174],[174,186,160],[387,186,349],[174,349,186],[64,348,62],[106,62,348],[108,107,339],[59,339,107],[374,345,116],[339,116,345],[76,353,352],[379,76,352],[388,77,351],[178,351,77],[179,120,354],[378,354,120],[177,200,175],[351,350,177],[389,200,350],[177,350,200],[354,180,352],[180,175,204],[379,352,204],[180,204,352],[182,181,206],[177,182,351],[388,351,206],[182,206,351],[180,354,183],[183,199,181],[378,199,354],[183,354,199],[91,109,338],[178,338,109],[76,75,353],[179,353,75],[389,350,110],[109,110,350],[390,391,392],[393,394,395],[224,122,389],[122,175,201],[365,388,205],[205,207,181],[66,340,396],[68,396,340],[184,396,342],[185,342,396],[66,396,67],[184,67,396],[68,69,396],[185,396,69],[219,111,387],[111,160,187],[366,386,191],[191,193,114],[150,272,280],[102,272,150],[151,277,274],[103,151,274],[161,374,117],[116,117,374],[366,61,386],[106,386,61],[111,187,387],[186,387,187],[56,188,374],[190,374,188],[191,386,193],[192,193,386],[331,375,194],[196,194,375],[28,34,375],[65,375,34],[219,387,113],[112,113,387],[224,389,123],[110,123,389],[51,55,379],[76,379,55],[24,197,378],[199,378,197],[122,201,389],[200,389,201],[333,379,202],[204,202,379],[205,388,207],[206,207,388],[365,27,388],[77,388,27],[162,378,121],[120,121,378],[162,30,25],[30,29,25],[51,53,54],[303,60,59],[28,29,33],[29,397,33],[161,58,52],[53,52,58],[21,84,19],[84,344,19],[46,49,43],[49,269,43],[208,316,209],[210,209,316],[327,313,211],[212,211,313],[36,35,369],[210,369,35],[37,158,368],[212,368,158],[6,8,313],[36,313,8],[326,38,316],[37,316,38],[392,391,398],[399,398,391],[394,400,395],[401,395,400],[390,214,391],[214,213,234],[393,395,218],[218,239,216],[402,230,403],[230,215,245],[125,124,131],[404,125,403],[405,406,231],[231,248,217],[129,137,127],[407,406,129],[130,127,139],[402,130,408],[194,195,331],[159,331,195],[115,189,56],[188,56,189],[14,219,220],[113,220,219],[45,50,366],[61,366,50],[221,366,222],[191,222,366],[17,23,219],[111,219,23],[118,198,24],[197,24,198],[202,203,333],[163,333,203],[40,224,225],[123,225,224],[12,13,365],[27,365,13],[22,365,223],[205,223,365],[42,44,224],[122,224,44],[399,391,234],[214,234,391],[401,239,395],[218,395,239],[214,390,226],[226,238,227],[218,228,393],[228,229,243],[401,399,233],[233,235,213],[392,409,390],[410,390,409],[394,393,411],[412,411,393],[402,403,131],[125,131,403],[405,137,406],[129,406,137],[405,408,139],[130,139,408],[230,245,403],[404,403,245],[231,406,248],[407,248,406],[232,254,216],[402,408,232],[413,404,244],[244,246,215],[414,247,407],[247,217,249],[133,126,136],[415,133,413],[141,143,128],[416,414,141],[410,238,390],[226,390,238],[412,393,243],[228,243,393],[233,399,235],[234,235,399],[237,260,236],[238,410,237],[417,260,410],[237,410,260],[239,401,240],[233,240,401],[242,241,257],[243,242,412],[418,412,257],[242,257,412],[401,419,399],[398,399,419],[417,410,420],[409,420,410],[400,421,401],[419,401,421],[418,422,412],[411,412,422],[413,135,404],[125,404,135],[414,407,142],[129,142,407],[130,402,132],[131,132,402],[133,136,413],[135,413,136],[423,147,415],[133,415,147],[137,405,138],[139,138,405],[141,414,143],[142,143,414],[424,416,144],[141,144,416],[405,254,408],[232,408,254],[244,404,246],[245,246,404],[247,249,407],[248,407,249],[232,250,402],[230,402,250],[415,413,251],[244,251,413],[252,236,266],[251,252,415],[423,415,266],[252,266,415],[231,253,405],[254,405,253],[416,255,414],[247,414,255],[256,263,241],[255,416,256],[424,263,416],[256,416,263],[257,258,418],[425,418,258],[260,417,261],[426,261,417],[422,418,427],[427,259,91],[420,428,417],[428,1,262],[147,423,148],[429,148,423],[263,424,264],[264,295,265],[266,267,423],[267,268,297],[144,145,424],[430,424,145],[49,431,269],[166,269,431],[82,431,83],[49,83,431],[84,85,431],[166,431,85],[82,344,431],[84,431,344],[432,278,90],[10,90,278],[433,0,281],[39,281,0],[362,361,434],[435,271,359],[270,359,271],[436,361,275],[273,275,361],[360,437,359],[277,287,276],[151,278,277],[280,279,289],[150,280,281],[436,438,439],[439,285,140],[90,92,432],[440,432,92],[282,272,291],[441,282,442],[284,293,274],[443,438,284],[278,432,286],[286,299,265],[281,288,433],[288,268,301],[0,433,89],[444,89,433],[435,445,442],[445,134,283],[439,446,436],[361,436,446],[442,290,435],[271,435,290],[438,436,292],[275,292,436],[445,435,447],[359,447,435],[286,287,278],[277,278,287],[288,281,289],[280,289,281],[145,152,430],[443,430,152],[148,429,154],[441,154,429],[424,430,294],[294,307,276],[423,296,429],[296,279,305],[425,440,100],[92,100,440],[290,442,291],[282,291,442],[292,293,438],[284,438,293],[298,320,241],[432,440,298],[300,236,322],[433,300,444],[426,101,444],[89,444,101],[107,448,302],[302,79,54],[78,31,343],[107,78,448],[75,79,448],[302,448,79],[78,343,448],[75,448,343],[427,418,259],[425,259,418],[428,262,417],[426,417,262],[437,449,359],[447,359,449],[434,361,450],[446,450,361],[32,33,397],[78,33,32],[53,303,54],[302,54,303],[152,153,443],[438,443,153],[429,304,441],[282,441,304],[430,443,306],[284,306,443],[154,441,155],[442,155,441],[298,299,432],[286,432,299],[300,433,301],[288,301,433],[185,451,308],[308,74,7],[73,15,337],[185,73,451],[35,74,451],[308,451,74],[73,337,451],[35,451,337],[158,452,310],[310,72,42],[70,22,341],[158,70,452],[66,72,452],[310,452,72],[70,341,452],[66,452,341],[313,327,314],[315,314,327],[316,317,326],[318,326,317],[15,156,311],[356,311,156],[7,312,157],[358,157,312],[211,9,327],[364,327,9],[38,326,94],[363,94,326],[294,295,424],[264,424,295],[296,423,297],[267,297,423],[262,149,426],[101,426,149],[258,319,425],[440,425,319],[261,426,321],[444,321,426],[259,425,146],[100,146,425],[306,307,430],[294,430,307],[304,429,305],[296,305,429],[319,320,440],[298,440,320],[321,444,322],[300,322,444],[445,283,442],[155,442,283],[439,438,285],[153,285,438],[17,68,18],[21,18,68],[46,184,47],[221,47,184],[102,95,363],[94,363,95],[9,11,364],[103,364,11],[6,323,357],[370,357,323],[371,324,355],[208,355,324],[270,363,325],[326,325,363],[327,364,328],[273,328,364],[0,2,39],[12,39,2],[90,93,91],[40,91,93],[14,16,17],[73,17,16],[45,309,7],[308,7,309],[12,71,39],[70,39,71],[40,41,42],[310,42,41],[97,98,63],[14,63,98],[3,5,7],[45,7,5],[118,377,329],[330,329,377],[331,372,332],[86,332,372],[333,376,334],[335,334,376],[115,373,336],[169,336,373],[167,166,380],[330,380,166],[80,81,382],[335,382,81],[86,385,81],[168,81,385],[169,384,82],[87,82,384],[159,88,372],[87,372,88],[163,164,376],[167,376,164],[24,26,377],[80,377,26],[56,57,373],[168,373,57],[32,397,30],[29,30,397],[58,60,53],[303,53,60],[205,181,119],[118,119,181],[163,175,165],[122,165,175],[453,454,455],[454,456,455],[457,455,456],[458,455,457],[459,455,458],[460,455,459],[461,462,463],[464,465,466],[467,468,469],[470,471,472],[465,473,474],[475,476,477],[478,479,480],[481,482,478],[483,484,481],[485,486,483],[487,488,485],[489,490,487],[491,492,489],[493,494,491],[495,496,493],[497,498,495],[499,500,497],[501,502,499],[503,504,501],[505,504,503],[506,504,505],[507,504,506],[508,504,507],[509,504,508],[510,504,509],[511,504,510],[512,504,511],[513,504,512],[514,504,513],[476,515,516],[517,518,519],[520,517,521],[518,522,523],[522,480,479],[524,525,526],[468,470,527],[525,467,528],[529,475,530],[531,532,533],[534,531,535],[536,537,538],[473,539,540],[539,536,541],[537,534,542],[471,520,543],[532,529,544],[545,524,546],[453,461,547],[463,464,548],[523,549,504],[527,550,551],[519,552,553],[521,554,555],[466,556,557],[469,558,559],[528,560,561],[477,562,563],[543,564,565],[535,566,567],[530,568,569],[540,570,571],[474,572,573],[542,574,575],[538,576,577],[541,578,579],[472,580,581],[526,582,583],[533,584,585],[544,586,587],[516,545,588],[588,589,590],[455,460,4],[591,592,63],[462,455,4],[592,547,63],[547,548,63],[465,462,4],[548,557,63],[127,124,501],[127,501,499],[505,503,124],[124,126,507],[124,507,506],[509,508,126],[126,134,512],[126,512,511],[510,509,126],[128,127,493],[128,493,491],[497,495,127],[489,487,128],[140,128,483],[140,483,481],[487,485,128],[478,480,140],[480,522,140],[514,513,134],[504,514,134],[551,581,437],[471,470,434],[445,447,555],[445,555,553],[134,445,553],[134,553,504],[446,439,518],[446,518,517],[439,140,522],[439,522,518],[515,476,358],[563,588,356],[557,573,63],[473,465,4],[437,360,559],[437,559,551],[360,371,561],[360,561,559],[362,434,470],[362,470,468],[370,362,468],[370,468,467],[499,497,127],[506,505,124],[495,493,127],[513,512,134],[481,478,140],[447,449,565],[447,565,555],[450,446,517],[450,517,520],[356,156,569],[356,569,563],[157,358,476],[157,476,475],[357,370,467],[357,467,525],[371,355,583],[371,583,561],[460,459,4],[63,62,593],[63,593,591],[62,4,459],[62,459,458],[532,531,104],[531,534,104],[567,585,105],[575,567,105],[4,3,539],[4,539,473],[536,539,3],[97,63,573],[97,573,571],[571,579,97],[99,97,579],[99,579,577],[105,99,577],[105,577,575],[96,104,534],[96,534,537],[3,96,537],[3,537,536],[503,501,124],[508,507,126],[491,489,128],[511,510,126],[485,483,128],[434,450,520],[434,520,471],[449,437,581],[449,581,565],[156,105,585],[156,585,587],[587,569,156],[104,157,529],[104,529,532],[475,529,157],[590,583,355],[355,356,588],[355,588,590],[358,357,524],[358,524,515],[525,524,357],[458,457,62],[457,593,62],[479,478,482],[479,504,549],[479,482,504],[482,481,484],[472,551,550],[581,551,472],[482,484,504],[484,483,486],[523,553,552],[504,553,523],[540,573,572],[571,573,540],[544,585,584],[587,585,544],[542,577,576],[575,577,542],[526,590,589],[583,590,526],[535,575,574],[567,575,535],[533,567,566],[585,567,533],[538,579,578],[577,579,538],[543,581,580],[565,581,543],[477,569,568],[563,569,477],[530,587,586],[569,587,530],[541,571,570],[579,571,541],[528,583,582],[561,583,528],[591,453,592],[547,592,453],[521,565,564],[555,565,521],[474,557,556],[573,557,474],[516,563,562],[588,563,516],[519,555,554],[553,555,519],[527,559,558],[551,559,527],[469,561,560],[559,561,469],[462,461,455],[453,455,461],[461,463,547],[548,547,463],[465,464,462],[463,462,464],[464,466,548],[557,548,466],[469,560,467],[528,467,560],[472,550,470],[527,470,550],[474,556,465],[466,465,556],[477,568,475],[530,475,568],[516,562,476],[477,476,562],[519,554,517],[521,517,554],[521,564,520],[543,520,564],[523,552,518],[519,518,552],[479,549,522],[523,522,549],[526,589,524],[589,546,524],[527,558,468],[469,468,558],[528,582,525],[526,525,582],[530,586,529],[544,529,586],[533,566,531],[535,531,566],[535,574,534],[542,534,574],[538,578,536],[541,536,578],[540,572,473],[474,473,572],[541,570,539],[540,539,570],[542,576,537],[538,537,576],[543,580,471],[472,471,580],[544,584,532],[533,532,584],[524,545,515],[516,515,545],[545,546,588],[589,588,546],[453,591,454],[593,454,591],[484,486,504],[486,485,488],[486,488,504],[488,487,490],[488,490,504],[490,489,492],[490,492,504],[492,491,494],[492,494,504],[494,493,496],[494,496,504],[496,495,498],[496,498,504],[498,497,500],[498,500,504],[500,499,502],[500,502,504],[501,504,502],[454,593,456],[457,456,593],[594,595,596],[597,598,594],[599,597,594],[600,599,594],[601,600,594],[602,601,594],[603,602,594],[604,603,594],[605,604,594],[606,607,608],[609,606,608],[610,609,608],[611,610,608],[612,611,608],[613,612,608],[614,613,608],[615,614,608],[616,615,608],[617,616,608],[618,617,608],[619,618,608],[620,619,608],[596,608,607],[595,594,598],[608,596,595],[605,594,91],[91,338,602],[91,602,603],[598,597,1],[594,596,91],[608,595,1],[595,598,1],[616,617,392],[610,611,394],[419,421,613],[419,613,614],[422,427,607],[422,607,606],[427,91,596],[427,596,607],[428,420,619],[428,619,620],[1,428,620],[1,620,608],[420,409,618],[420,618,619],[411,422,606],[411,606,609],[398,419,614],[398,614,615],[421,400,612],[421,612,613],[409,392,617],[409,617,618],[394,411,609],[394,609,610],[604,605,91],[338,1,599],[338,599,600],[392,398,615],[392,615,616],[400,394,611],[400,611,612],[603,604,91],[601,602,338],[597,599,1],[600,601,338] - ]; - } elsif ($name eq 'gt2_teeth') { - $vertices = [ - [15.8899993896484,19.444055557251,2.67489433288574],[15.9129991531372,19.1590557098389,2.67489433288574],[15.9039993286133,19.1500549316406,2.67489433288574],[15.9489994049072,19.2490558624268,2.67489433288574],[15.9579992294312,19.3570556640625,2.67489433288574],[15.8819999694824,18.690055847168,2.67489433288574],[15.8319997787476,17.7460556030273,2.67489433288574],[15.8489999771118,18.819055557251,2.67489433288574],[15.8589992523193,17.7190551757812,2.67489433288574],[15.8769998550415,19.0490550994873,2.67489433288574],[15.7529993057251,17.8080558776855,2.67489433288574],[15.7869997024536,19.5010547637939,2.67489433288574],[14.0329990386963,18.7170543670654,2.67489433288574],[13.9599990844727,18.7460556030273,2.67489433288574],[13.9869995117188,20.2840557098389,2.67489433288574],[14.2029991149902,20.149055480957,2.67489433288574],[14.1939992904663,19.9560546875,2.67489433288574],[14.1939992904663,20.1670551300049,2.67489433288574],[14.2119998931885,20.0590553283691,2.67489433288574],[12.1899995803833,19.1840553283691,2.67489433288574],[12.096999168396,19.1950550079346,2.67489433288574],[12.1099996566772,20.6690559387207,2.67489433288574],[11.382999420166,19.9750556945801,2.67489433288574],[11.2599992752075,19.2490558624268,2.67489433288574],[11.2369995117188,19.9320545196533,2.67489433288574],[11.5349998474121,20.0640544891357,2.67489433288574],[11.6259994506836,20.1550559997559,2.67489433288574],[11.6829986572266,20.2390556335449,2.67489433288574],[11.7369995117188,20.3570556640625,2.67489433288574],[11.8449993133545,20.645055770874,2.67489433288574],[11.7729988098145,20.4640560150146,2.67489433288574],[11.7799987792969,20.5370559692383,9.41389465332031],[11.7639999389648,20.4470558166504,2.67489433288574],[11.9559993743896,20.6810550689697,2.67489433288574],[12.3079996109009,20.6020545959473,2.67489433288574],[12.1959991455078,19.1860542297363,2.67489433288574],[12.2059993743896,20.6540546417236,2.67489433288574],[12.3489990234375,20.3740558624268,2.67489433288574],[12.3579998016357,20.2750549316406,2.67489433288574],[12.3669996261597,20.266056060791,2.67489433288574],[12.3849992752075,20.1670551300049,2.67489433288574],[12.4269990921021,20.0680541992188,2.67489433288574],[12.5029993057251,19.9540557861328,2.67489433288574],[12.6169996261597,19.8550548553467,2.67489433288574],[12.7449989318848,19.7800559997559,2.67489433288574],[12.7629995346069,19.7800559997559,2.67489433288574],[12.8799991607666,19.7350559234619,2.67489433288574],[13.0369997024536,19.7250556945801,2.67489433288574],[13.0149993896484,19.0340557098389,2.67489433288574],[11.1699991226196,19.2580547332764,2.67489433288574],[11.0959987640381,19.2580547332764,2.67489433288574],[11.1209993362427,19.9230556488037,2.67489433288574],[13.0599994659424,19.024055480957,2.67489433288574],[14.9049997329712,18.3170547485352,2.67489433288574],[14.8779993057251,18.3400554656982,2.67489433288574],[14.8779993057251,19.149055480957,2.67489433288574],[13.3039989471436,19.77805519104,2.67489433288574],[13.1589994430542,18.9890556335449,2.67489433288574],[13.1559991836548,19.7350559234619,2.67489433288574],[13.4269990921021,19.8600559234619,2.67489433288574],[13.5339994430542,19.9700546264648,2.67389440536499],[13.6359996795654,20.1220550537109,2.67489433288574],[13.6359996795654,20.1400547027588,2.67489433288574],[13.6719989776611,20.2210559844971,2.67489433288574],[13.6899995803833,20.2300548553467,2.67489433288574],[13.7509994506836,20.3010559082031,2.67489433288574],[13.8539991378784,20.3180541992188,2.67489433288574],[14.8329992294312,18.3580551147461,2.67489433288574],[14.1849994659424,19.8530559539795,2.67489433288574],[14.0769996643066,18.7000541687012,2.67489433288574],[14.1099996566772,20.2400550842285,2.67489433288574],[14.2009992599487,19.6230545043945,2.67489433288574],[14.2729997634888,19.4670543670654,2.67489433288574],[14.3379993438721,19.3790550231934,2.67489433288574],[14.4549999237061,19.2770557403564,2.67489433288574],[14.5899991989136,19.2040557861328,2.67489433288574],[14.6079998016357,19.2040557861328,2.67489433288574],[14.7209997177124,19.1600551605225,2.67489433288574],[15.1379995346069,19.210054397583,2.67489433288574],[14.9949998855591,18.2680549621582,2.67489433288574],[15.0029993057251,19.1580543518066,2.67489433288574],[15.2369995117188,19.2760543823242,2.67489433288574],[15.3779993057251,19.4060554504395,2.67489433288574],[15.4539995193481,19.520055770874,2.67489433288574],[15.471999168396,19.52805519104,2.67489433288574],[15.5449991226196,19.5830554962158,2.67489433288574],[15.6529998779297,19.573055267334,2.67489433288574],[15.7059993743896,17.8360557556152,2.67489433288574],[15.9449996948242,18.5560550689697,2.67489433288574],[15.8589992523193,18.9380550384521,2.67489433288574],[14.9589996337891,18.2950553894043,2.67489433288574],[15.7779998779297,19.5100555419922,2.67489433288574],[14.0049991607666,20.2750549316406,2.67489433288574],[12.3489990234375,20.5000553131104,2.67489433288574],[13.0689992904663,19.0150547027588,2.67489433288574],[13.0999994277954,19.0100555419922,2.67489433288574],[15.9489994049072,19.3670558929443,9.41489505767822],[15.9489994049072,19.2490558624268,9.41489505767822],[15.75,17.8080558776855,9.41489505767822],[15.6639995574951,19.5710544586182,9.41489505767822],[15.5709991455078,17.9260559082031,9.41489505767822],[15.8769998550415,18.690055847168,9.41489505767822],[15.8499994277954,18.8170547485352,9.41489505767822],[15.9459991455078,18.5520553588867,9.41489505767822],[15.914999961853,17.6890544891357,9.41489505767822],[15.3999996185303,19.4290542602539,9.41489505767822],[15.3099994659424,19.339054107666,9.41489505767822],[15.3729991912842,18.0440559387207,9.41489505767822],[15.4579992294312,19.5170555114746,9.41489505767822],[15.5469999313354,19.5820541381836,9.41489505767822],[13.2309989929199,19.7610549926758,9.41489505767822],[13.168999671936,19.7360553741455,9.41489505767822],[13.096999168396,19.0140552520752,9.41489505767822],[13.1999988555908,18.9870548248291,9.41489505767822],[15.1399993896484,19.2080554962158,9.41489505767822],[15.0159997940063,19.1600551605225,9.41489505767822],[14.9859991073608,18.2770557403564,9.41489505767822],[15.1749992370605,18.1690559387207,9.41489505767822],[15.9039993286133,19.1320552825928,9.41489505767822],[15.8949995040894,19.4460544586182,9.41489505767822],[15.8769998550415,19.0420551300049,9.41489505767822],[12.2169990539551,20.6500549316406,9.41489505767822],[11.9379997253418,20.6810550689697,9.41489505767822],[11.8629989624023,19.2130546569824,9.41489505767822],[12.096999168396,19.1950550079346,9.41489505767822],[14.1669998168945,18.6640548706055,9.41489505767822],[14.1039991378784,20.2460556030273,9.41489505767822],[13.9849996566772,18.7360553741455,9.41489505767822],[14.7349996566772,19.1590557098389,9.41489505767822],[14.5849990844727,19.2050552368164,9.41489505767822],[14.5719995498657,18.4850559234619,9.41489505767822],[14.1939992904663,19.6760559082031,9.41489505767822],[14.1849994659424,19.9330558776855,9.41489505767822],[14.1759996414185,18.6640548706055,9.41489505767822],[14.261999130249,19.4890556335449,9.41489505767822],[14.3539991378784,19.3610553741455,9.41489505767822],[14.3559989929199,18.5830554962158,9.41489505767822],[11.6039991378784,20.1250553131104,9.41489505767822],[11.5209999084473,20.0520553588867,9.41489505767822],[11.4209995269775,19.2480545043945,9.41489505767822],[11.6989994049072,20.2690544128418,9.41389465332031],[11.7609996795654,20.4310550689697,9.41489505767822],[11.8359994888306,19.2130546569824,9.41489505767822],[14.1889991760254,20.1710548400879,9.41489505767822],[13.9689998626709,20.2840557098389,9.41489505767822],[13.8739995956421,20.315055847168,9.41489505767822],[13.7799997329712,18.8080558776855,9.41489505767822],[13.9869995117188,20.2750549316406,9.41489505767822],[12.3129997253418,20.5980548858643,9.41489505767822],[12.3399991989136,20.5090560913086,9.41489505767822],[12.3489990234375,20.3830547332764,9.41489505767822],[12.3599996566772,20.2680549621582,9.41489505767822],[12.3849992752075,20.1850547790527,9.41489505767822],[12.3849992752075,20.1670551300049,9.41489505767822],[12.4249992370605,20.065055847168,9.41489505767822],[12.4729995727539,19.1350555419922,9.41489505767822],[14.4399995803833,19.2900543212891,9.41489505767822],[14.3649997711182,18.5740547180176,9.41489505767822],[13.5729999542236,20.0310554504395,9.41489505767822],[13.4889993667603,19.9140548706055,9.41489505767822],[13.5639991760254,18.8710556030273,9.41489505767822],[13.6389999389648,20.1310558319092,9.41489505767822],[13.6719989776611,20.2130546569824,9.41489505767822],[13.75,20.3020553588867,9.41489505767822],[12.7399997711182,19.7810554504395,9.41489505767822],[12.6189994812012,19.8520545959473,9.41489505767822],[12.5799999237061,19.1200542449951,9.41489505767822],[12.8349990844727,19.069055557251,9.41489505767822],[11.2669992446899,19.9350547790527,9.41489505767822],[11.1029987335205,19.9230556488037,9.41489505767822],[11.0209999084473,19.2600555419922,9.41489505767822],[11.3819999694824,19.9710559844971,9.41489505767822],[13.418999671936,19.8530559539795,9.41489505767822],[13.4329996109009,18.9160556793213,9.41489505767822],[11.8399991989136,20.6430549621582,9.41489505767822],[13.3119993209839,19.7800559997559,9.41489505767822],[15.2189998626709,19.2600555419922,9.41489505767822],[15.1839990615845,18.1600551605225,9.41489505767822],[15.3639993667603,18.0520553588867,9.41489505767822],[13.0189990997314,19.7250556945801,9.41489505767822],[12.8949995040894,19.7350559234619,9.41489505767822],[15.9039993286133,19.1500549316406,9.41489505767822],[15.7699995040894,19.5140552520752,9.41489505767822],[15.8589992523193,18.9340553283691,9.41489505767822],[14.1939992904663,19.9510555267334,9.41489505767822],[14.2119998931885,20.0630550384521,9.41489505767822],[14.8589992523193,19.149055480957,9.41489505767822],[14.8159999847412,18.3670558929443,9.41489505767822],[14.8959999084473,18.3220558166504,9.41489505767822],[12.5189990997314,19.9360542297363,9.41489505767822],[11.0209999084473,19.9290542602539,9.41489505767822],[11.0209999084473,19.2530555725098,2.67489433288574],[11.0209999084473,19.9300556182861,2.67489433288574],[15.9799995422363,18.505931854248,5.58724021911621],[15.9799995422363,18.5044555664062,9.41489505767822],[15.9799995422363,18.5041732788086,2.67489433288574],[15.9799995422363,18.1684837341309,2.67489433288574],[15.9799995422363,18.1288299560547,9.41489505767822],[15.9799995422363,17.9876575469971,2.67489433288574],[15.9799995422363,17.6247596740723,3.91620373725891],[15.9799995422363,17.6247596740723,2.67489433288574],[15.9799995422363,17.6254329681396,4.32245063781738],[15.9799995422363,17.8920269012451,9.41489505767822],[15.9799995422363,17.8795108795166,2.67489433288574],[15.9799995422363,17.629810333252,4.58585262298584],[15.9799995422363,17.6336059570312,5.27938556671143],[15.9799995422363,17.8311748504639,2.67489433288574],[15.9799995422363,17.638355255127,9.41489505767822],[15.9799995422363,17.6346111297607,5.98653984069824],[15.9799995422363,17.8728256225586,2.67489433288574],[15.9799995422363,18.2221603393555,2.67489433288574] - ]; - $facets = [ - [0,1,2],[0,3,1],[0,4,3],[5,6,7],[8,6,5],[2,9,0],[6,10,11],[12,13,14],[15,16,17],[18,16,15],[19,20,21],[22,23,24],[25,23,22],[26,23,25],[27,23,26],[28,23,27],[29,30,31],[29,32,30],[29,28,32],[33,28,29],[33,23,28],[21,23,33],[20,23,21],[34,35,36],[37,35,34],[38,35,37],[39,35,38],[40,35,39],[41,35,40],[42,35,41],[43,35,42],[44,35,43],[45,35,44],[46,35,45],[47,35,46],[48,35,47],[49,50,51],[52,48,47],[23,49,24],[53,54,55],[56,57,58],[59,57,56],[60,57,59],[61,57,60],[62,57,61],[63,57,62],[64,57,63],[65,57,64],[66,57,65],[13,57,66],[54,67,55],[68,69,70],[71,69,68],[72,69,71],[73,69,72],[74,69,73],[75,69,74],[76,69,75],[77,69,76],[67,69,77],[70,16,68],[70,17,16],[78,79,80],[81,79,78],[82,79,81],[83,79,82],[84,79,83],[85,79,84],[86,79,85],[87,79,86],[88,8,5],[11,7,6],[11,89,7],[11,9,89],[11,0,9],[55,90,53],[55,79,90],[55,80,79],[91,11,10],[92,69,12],[92,70,69],[34,93,37],[47,94,52],[47,95,94],[47,57,95],[47,58,57],[51,24,49],[21,35,19],[21,36,35],[14,92,12],[86,10,87],[86,91,10],[77,55,67],[66,14,13],[96,97,4],[98,99,100],[101,102,98],[103,101,98],[104,103,98],[105,106,107],[108,105,107],[109,108,107],[100,109,107],[110,111,112],[113,110,112],[114,115,116],[117,114,116],[118,119,120],[121,122,123],[124,121,123],[125,126,127],[128,129,130],[131,132,133],[71,131,133],[134,71,133],[135,134,133],[136,135,133],[137,138,139],[140,137,139],[141,140,139],[142,31,141],[142,141,139],[143,126,132],[144,145,146],[147,144,146],[127,147,146],[148,121,124],[149,148,124],[150,149,124],[151,150,124],[152,151,124],[153,152,124],[154,153,124],[155,154,124],[129,156,157],[130,129,157],[158,159,160],[161,158,160],[162,161,160],[163,162,160],[146,163,160],[164,165,166],[167,164,166],[168,169,170],[171,168,170],[139,171,170],[159,172,173],[123,174,142],[175,110,113],[173,175,113],[106,176,177],[178,106,177],[179,180,167],[112,179,167],[175,173,172],[119,118,181],[119,181,97],[119,97,96],[182,98,102],[182,102,183],[182,183,120],[182,120,119],[143,132,184],[184,185,143],[147,127,126],[174,123,122],[159,173,160],[126,125,133],[126,133,132],[186,187,188],[186,188,116],[186,116,115],[99,98,182],[109,100,99],[106,178,107],[114,117,177],[114,177,176],[128,130,187],[128,187,186],[135,136,157],[135,157,156],[163,146,145],[164,167,180],[179,112,111],[171,139,138],[189,155,166],[189,166,165],[149,150,93],[154,155,189],[31,142,174],[114,176,78],[81,78,176],[7,89,183],[89,9,120],[89,120,183],[78,80,114],[176,106,81],[88,5,103],[183,102,7],[118,120,9],[9,2,181],[9,181,118],[115,114,80],[82,81,106],[101,103,5],[102,101,5],[5,7,102],[97,181,2],[2,1,97],[1,3,97],[80,55,115],[172,159,59],[59,56,172],[3,4,97],[4,0,96],[105,108,82],[186,115,55],[82,106,105],[83,82,108],[60,59,159],[175,172,56],[119,96,0],[0,11,119],[108,109,84],[84,83,108],[55,77,186],[56,58,110],[56,110,175],[60,159,158],[11,91,182],[182,119,11],[91,86,182],[85,84,109],[86,85,99],[128,186,77],[58,111,110],[158,161,60],[26,25,137],[138,137,25],[99,182,86],[109,99,85],[77,76,128],[58,47,111],[61,60,161],[137,140,26],[27,26,140],[25,22,138],[129,128,76],[76,75,129],[75,74,129],[74,73,156],[73,72,135],[68,16,184],[68,184,132],[16,18,185],[161,162,62],[62,61,161],[179,111,47],[171,138,22],[156,129,74],[135,156,73],[134,135,72],[72,71,134],[68,132,131],[185,184,16],[18,15,185],[63,62,162],[28,27,140],[22,24,171],[71,68,131],[15,17,143],[15,143,185],[17,70,143],[70,92,126],[162,163,64],[64,63,162],[180,179,47],[47,46,180],[140,141,28],[168,171,24],[126,143,70],[92,14,147],[147,126,92],[14,66,144],[14,144,147],[65,64,163],[66,65,145],[46,45,180],[32,28,141],[24,51,168],[145,144,66],[163,145,65],[164,180,45],[45,44,164],[44,43,164],[43,42,165],[38,37,151],[150,151,37],[37,93,150],[141,31,30],[30,32,141],[169,168,51],[165,164,43],[189,165,42],[42,41,189],[40,39,152],[40,152,153],[151,152,39],[39,38,151],[93,34,149],[154,189,41],[153,154,41],[41,40,153],[148,149,34],[34,36,148],[36,21,121],[31,174,29],[121,148,36],[21,33,122],[21,122,121],[33,29,122],[174,122,29],[116,188,53],[104,98,10],[87,10,98],[98,100,87],[79,87,100],[79,100,107],[90,79,107],[90,107,178],[178,177,90],[53,90,177],[53,177,117],[117,116,53],[54,53,188],[54,188,187],[67,54,187],[67,187,130],[69,67,130],[69,130,157],[12,69,157],[12,157,136],[136,133,12],[12,133,125],[125,127,12],[13,12,127],[127,146,13],[57,13,146],[57,146,160],[95,57,160],[95,160,173],[173,113,95],[94,95,113],[113,112,94],[52,94,112],[48,52,112],[112,167,48],[35,48,167],[35,167,166],[19,35,166],[139,170,50],[50,49,139],[166,155,19],[20,19,155],[155,124,20],[23,20,124],[23,124,123],[49,23,123],[49,123,142],[142,139,49],[190,191,170],[192,191,190],[191,192,51],[191,51,50],[170,169,190],[169,51,192],[169,192,190],[170,191,50],[193,194,195],[196,197,198],[199,200,201],[198,202,203],[204,201,200],[205,204,200],[206,207,208],[206,208,205],[206,205,200],[207,206,209],[207,209,203],[207,203,202],[202,198,197],[197,196,210],[197,210,195],[197,195,194],[8,88,195],[8,195,210],[210,196,8],[196,198,8],[198,203,8],[203,209,8],[209,206,8],[206,200,8],[202,197,104],[207,202,104],[103,104,197],[103,197,194],[193,195,88],[88,103,194],[88,194,193],[200,199,8],[199,201,8],[204,205,6],[6,8,201],[6,201,204],[10,6,205],[10,205,208],[104,10,208],[104,208,207] - ]; - } elsif ($name eq 'pyramid') { - $vertices = [ - [10,10,40],[0,0,0],[20,0,0],[20,20,0],[0,20,0], - ]; - $facets = [ - [0,1,2],[0,3,4],[3,1,4],[1,3,2],[3,0,2],[4,1,0], - ]; - } elsif ($name eq 'two_hollow_squares') { - $vertices = [ - [66.7133483886719,104.286666870117,0],[66.7133483886719,95.7133331298828,0],[65.6666870117188,94.6666717529297,0],[75.2866821289062,95.7133331298828,0],[76.3333435058594,105.333335876465,0],[76.3333435058594,94.6666717529297,0],[65.6666870117188,105.33332824707,0],[75.2866821289062,104.286666870117,0],[71.1066818237305,104.58666229248,2.79999995231628],[66.4133529663086,104.58666229248,2.79999995231628],[75.5866851806641,104.58666229248,2.79999995231628],[66.4133529663086,99.8933334350586,2.79999995231628],[66.4133529663086,95.4133377075195,2.79999995231628],[71.1066818237305,95.4133377075195,2.79999995231628],[75.5866851806641,95.4133377075195,2.79999995231628],[75.5866851806641,100.106666564941,2.79999995231628],[74.5400161743164,103.540000915527,2.79999995231628],[70.0320129394531,103.540000915527,2.79999995231628],[67.4600067138672,103.540000915527,2.79999995231628],[67.4600067138672,100.968002319336,2.79999995231628],[67.4600067138672,96.4599990844727,2.79999995231628],[74.5400161743164,99.0319976806641,2.79999995231628],[74.5400161743164,96.4599990844727,2.79999995231628],[70.0320129394531,96.4599990844727,2.79999995231628],[123.666717529297,94.6666717529297,0],[134.333312988281,94.6666717529297,0],[124.413360595703,95.4133377075195,2.79999995231628],[129.106674194336,95.4133377075195,2.79999995231628],[133.586669921875,95.4133377075195,2.79999995231628],[123.666717529297,105.33332824707,0],[124.413360595703,104.58666229248,2.79999995231628],[124.413360595703,99.8933334350586,2.79999995231628],[134.333312988281,105.33332824707,0],[129.106674194336,104.58666229248,2.79999995231628],[133.586669921875,104.58666229248,2.79999995231628],[133.586669921875,100.106666564941,2.79999995231628],[124.713317871094,104.286666870117,0],[124.713317871094,95.7133331298828,0],[133.286712646484,95.7133331298828,0],[133.286712646484,104.286666870117,0],[132.540023803711,103.540000915527,2.79999995231628],[128.032028198242,103.540008544922,2.79999995231628],[125.460006713867,103.540000915527,2.79999995231628],[125.460006713867,100.968002319336,2.79999995231628],[125.460006713867,96.4599990844727,2.79999995231628],[132.540023803711,99.0319976806641,2.79999995231628],[132.540023803711,96.4599990844727,2.79999995231628],[128.032028198242,96.4599990844727,2.79999995231628], - ]; - $facets = [ - [0,1,2],[3,4,5],[6,4,0],[6,0,2],[2,1,5],[7,4,3],[1,3,5],[0,4,7],[4,6,8],[6,9,8],[4,8,10],[6,2,9],[2,11,9],[2,12,11],[2,5,12],[5,13,12],[5,14,13],[4,10,15],[5,4,14],[4,15,14],[7,16,17],[0,7,18],[7,17,18],[1,19,20],[1,0,19],[0,18,19],[7,3,21],[3,22,21],[7,21,16],[3,23,22],[3,1,23],[1,20,23],[24,25,26],[25,27,26],[25,28,27],[29,24,30],[24,31,30],[24,26,31],[32,29,33],[29,30,33],[32,33,34],[32,34,35],[25,32,28],[32,35,28],[36,37,24],[38,32,25],[29,32,36],[29,36,24],[24,37,25],[39,32,38],[37,38,25],[36,32,39],[39,40,41],[36,39,42],[39,41,42],[37,43,44],[37,36,43],[36,42,43],[39,38,45],[38,46,45],[39,45,40],[38,47,46],[38,37,47],[37,44,47],[16,8,9],[16,10,8],[10,16,15],[15,16,21],[22,15,21],[15,22,14],[22,23,14],[23,20,14],[17,16,9],[18,17,9],[19,18,9],[19,9,11],[19,11,20],[13,14,20],[20,11,12],[13,20,12],[41,40,30],[42,41,30],[43,42,30],[43,30,31],[43,31,44],[27,28,44],[44,31,26],[27,44,26],[40,33,30],[40,34,33],[34,40,35],[35,40,45],[46,35,45],[35,46,28],[46,47,28],[47,44,28], - ]; - } elsif ($name eq 'small_dorito') { - $vertices = [ - [6.00058937072754,-22.9982089996338,0],[22.0010242462158,-49.9998741149902,0],[-9.99957847595215,-49.999870300293,0],[6.00071382522583,-32.2371635437012,28.0019245147705],[11.1670551300049,-37.9727020263672,18.9601669311523],[6.00060224533081,-26.5392456054688,10.7321853637695] - ]; - $facets = [ - [0,1,2],[3,4,5],[2,1,4],[2,4,3],[2,3,5],[2,5,0],[5,4,1],[5,1,0] - ]; - } elsif ($name eq 'bridge') { - $vertices = [ - [75,84.5,8],[125,84.5,8],[75,94.5,8],[120,84.5,5],[125,94.5,8],[75,84.5,0],[80,84.5,5],[125,84.5,0],[125,94.5,0],[80,94.5,5],[75,94.5,0],[120,94.5,5],[120,84.5,0],[80,94.5,0],[80,84.5,0],[120,94.5,0] - ]; - $facets = [ - [0,1,2],[1,0,3],[2,1,4],[2,5,0],[0,6,3],[1,3,7],[1,8,4],[4,9,2],[10,5,2],[5,6,0],[6,11,3],[3,12,7],[7,8,1],[4,8,11],[4,11,9],[9,10,2],[10,13,5],[14,6,5],[9,11,6],[11,12,3],[12,8,7],[11,8,15],[13,10,9],[5,13,14],[14,13,6],[6,13,9],[15,12,11],[15,8,12] - ]; - } else { - return undef; - } - - my $mesh = Slic3r::TriangleMesh->new; - $mesh->ReadFromPerl($vertices, $facets); - $mesh->scale_xyz(Slic3r::Pointf3->new(@{$params{scale_xyz}})) if $params{scale_xyz}; - $mesh->translate(@{$params{translate}}) if $params{translate}; - return $mesh; -} - -sub model { - my ($model_names, %params) = @_; - $model_names = [ $model_names ] if ! ref($model_names); - - my $model = Slic3r::Model->new; - - for my $model_name (@$model_names) { - my $input_file = "${model_name}.stl"; - my $mesh = mesh($model_name, %params); - # $mesh->write_ascii("out/$input_file"); - - my $object = $model->add_object(input_file => $input_file); - $model->set_material($model_name); - $object->add_volume(mesh => $mesh, material_id => $model_name); - $object->add_instance( - offset => Slic3r::Pointf->new(0,0), - # 3D full transform - rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), - scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), - # old transform - # rotation => $params{rotation} // 0, - # scaling_factor => $params{scale} // 1, - ); - } - return $model; -} - -sub init_print { - my ($models, %params) = @_; - my $model; - if (ref($models) eq 'ARRAY') { - $model = model($models, %params); - } elsif (ref($models)) { - $model = $models; - } else { - $model = model([$models], %params); - } - - my $config = Slic3r::Config->new; - $config->apply($params{config}) if $params{config}; - $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; - - my $print = Slic3r::Print->new; - die "Unknown model in test" if !defined $model; - if (defined $params{duplicate} && $params{duplicate} > 1) { - $model->duplicate($params{duplicate} // 1, $config->min_object_distance); - } - foreach my $model_object (@{$model->objects}) { - $model_object->ensure_on_bed; - $print->auto_assign_extruders($model_object); - } - $model->arrange_objects($config->min_object_distance); - $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); - - $print->apply($model, $config); - $print->validate; - - # We return a proxy object in order to keep $models alive as required by the Print API. - return Slic3r::Test::Print->new( - print => $print, - model => $model, - ); -} - -sub gcode { - my ($print) = @_; - - $print = $print->print if $print->isa('Slic3r::Test::Print'); - - # Write the resulting G-code into a temporary file. - my $gcode_temp_path = abs_path($0) . '.gcode.temp'; - # Remove the existing temp file. - unlink $gcode_temp_path; - $print->set_status_silent; - $print->process; - $print->export_gcode($gcode_temp_path); - # Read the temoprary G-code file. - my $gcode; - { - local $/; - open my $fh, '<', $gcode_temp_path or die "Test.pm: can't open $gcode_temp_path: $!"; - $gcode = <$fh>; - } - # Remove the temp file. - unlink $gcode_temp_path; - - return $gcode; -} - -sub _eq { - my ($a, $b) = @_; - return abs($a - $b) < epsilon; -} - -sub add_facet { - my ($facet, $vertices, $facets) = @_; - - push @$facets, []; - for my $i (0..2) { - my $v = first { $vertices->[$_][X] == $facet->[$i][X] && $vertices->[$_][Y] == $facet->[$i][Y] && $vertices->[$_][Z] == $facet->[$i][Z] } 0..$#$vertices; - if (!defined $v) { - push @$vertices, [ @{$facet->[$i]}[X,Y,Z] ]; - $v = $#$vertices; - } - $facets->[-1][$i] = $v; - } -} - -package Slic3r::Test::Print; -use Moo; - -has 'print' => (is => 'ro', required => 1, handles => [qw(process apply)]); -has 'model' => (is => 'ro', required => 1); - -1; diff --git a/src/QIDISlicer.cpp b/src/QIDISlicer.cpp index 1c6c231..29b04a4 100644 --- a/src/QIDISlicer.cpp +++ b/src/QIDISlicer.cpp @@ -130,12 +130,6 @@ int CLI::run(int argc, char **argv) // On Unix systems, the qidi-slicer binary may be symlinked to give the application a different meaning. boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); #endif // _WIN32 -#if ENABLE_GL_CORE_PROFILE - std::pair opengl_version = { 0, 0 }; -#if ENABLE_OPENGL_DEBUG_OPTION - bool opengl_debug = false; -#endif // ENABLE_OPENGL_DEBUG_OPTION -#endif // ENABLE_GL_CORE_PROFILE const std::vector &load_configs = m_config.option("load", true)->values; const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option>("config_compatibility", true)->value; @@ -174,7 +168,11 @@ int CLI::run(int argc, char **argv) m_print_config.apply(config); } +#ifdef SLIC3R_GUI #if ENABLE_GL_CORE_PROFILE + std::pair opengl_version = { 0, 0 }; + bool opengl_debug = false; + bool opengl_compatibility_profile = false; // search for special keys into command line parameters auto it = std::find(m_actions.begin(), m_actions.end(), "gcodeviewer"); if (it != m_actions.end()) { @@ -185,30 +183,37 @@ int CLI::run(int argc, char **argv) it = std::find(m_actions.begin(), m_actions.end(), "opengl-version"); if (it != m_actions.end()) { - std::string opengl_version_str = m_config.opt_string("opengl-version"); - if (std::find(Slic3r::GUI::OpenGLVersions::core_str.begin(), Slic3r::GUI::OpenGLVersions::core_str.end(), opengl_version_str) == Slic3r::GUI::OpenGLVersions::core_str.end()) { - if (std::find(Slic3r::GUI::OpenGLVersions::precore_str.begin(), Slic3r::GUI::OpenGLVersions::precore_str.end(), opengl_version_str) == Slic3r::GUI::OpenGLVersions::precore_str.end()) { - boost::nowide::cerr << "Found invalid OpenGL version: " << opengl_version_str << std::endl; - opengl_version_str.clear(); + const Semver opengl_minimum = Semver(3,2,0); + const std::string opengl_version_str = m_config.opt_string("opengl-version"); + boost::optional semver = Semver::parse(opengl_version_str); + if (semver.has_value() && (*semver) >= opengl_minimum ) { + opengl_version.first = semver->maj(); + opengl_version.second = semver->min(); + if (std::find(Slic3r::GUI::OpenGLVersions::core.begin(), Slic3r::GUI::OpenGLVersions::core.end(), std::make_pair(opengl_version.first, opengl_version.second)) == Slic3r::GUI::OpenGLVersions::core.end()) { + opengl_version = { 0, 0 }; + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " not recognized.\n Option 'opengl-version' ignored." << std::endl; } + } else + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << + opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; + start_gui = true; + m_actions.erase(it); } - if (!opengl_version_str.empty()) { - std::vector tokens; - boost::split(tokens, opengl_version_str, boost::is_any_of("."), boost::token_compress_on); - opengl_version.first = std::stoi(tokens[0].c_str()); - opengl_version.second = std::stoi(tokens[1].c_str()); - } + it = std::find(m_actions.begin(), m_actions.end(), "opengl-compatibility"); + if (it != m_actions.end()) { start_gui = true; + opengl_compatibility_profile = true; + // reset version as compatibility profile always take the highest version + // supported by the graphic card + opengl_version = std::make_pair(0, 0); m_actions.erase(it); } it = std::find(m_actions.begin(), m_actions.end(), "opengl-debug"); if (it != m_actions.end()) { start_gui = true; -#if ENABLE_OPENGL_DEBUG_OPTION opengl_debug = true; -#endif // ENABLE_OPENGL_DEBUG_OPTION m_actions.erase(it); } #else @@ -222,6 +227,16 @@ int CLI::run(int argc, char **argv) } } #endif // ENABLE_GL_CORE_PROFILE +#else // SLIC3R_GUI + // If there is no GUI, we shall ignore the parameters. Remove them from the list. + for (const std::string& s : { "opengl-version", "opengl-compatibility", "opengl-debug", "gcodeviewer" }) { + auto it = std::find(m_actions.cbegin(), m_actions.cend(), s); + if (it != m_actions.end()) { + boost::nowide::cerr << "Parameter '" << s << "' is ignored, this PrusaSlicer build is CLI only." << std::endl; + m_actions.erase(it); + } + } +#endif // Read input file(s) if any. for (const std::string& file : m_input_files) @@ -702,10 +717,9 @@ int CLI::run(int argc, char **argv) params.download_url = download_url; params.delete_after_load = delete_after_load; #if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION params.opengl_version = opengl_version; params.opengl_debug = opengl_debug; -#endif // ENABLE_OPENGL_DEBUG_OPTION + params.opengl_compatibiity_profile = opengl_compatibility_profile; #endif // ENABLE_GL_CORE_PROFILE return Slic3r::GUI::GUI_Run(params); #else // SLIC3R_GUI @@ -781,6 +795,7 @@ bool CLI::setup(int argc, char **argv) set_var_dir((path_resources / "icons").string()); set_local_dir((path_resources / "localization").string()); set_sys_shapes_dir((path_resources / "shapes").string()); + set_custom_gcodes_dir((path_resources / "custom_gcodes").string()); // Parse all command line options into a DynamicConfig. // If any option is unsupported, print usage and abort immediately. @@ -805,6 +820,11 @@ bool CLI::setup(int argc, char **argv) set_logging_level(opt_loglevel->value); } + { + const ConfigOptionInt *opt_threads = m_config.opt("threads"); + if (opt_threads != nullptr) + thread_count = opt_threads->value; + } //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. std::string validity = m_config.validate(); diff --git a/src/QIDISlicer_app_msvc.cpp b/src/QIDISlicer_app_msvc.cpp index a142ddf..e943d26 100644 --- a/src/QIDISlicer_app_msvc.cpp +++ b/src/QIDISlicer_app_msvc.cpp @@ -268,7 +268,7 @@ int wmain(int argc, wchar_t **argv) // In that case, use Mesa. (::GetSystemMetrics(SM_REMOTESESSION) && !force_hw) || // Try to load the default OpenGL driver and test its context version. - ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(3, 2); #endif /* SLIC3R_GUI */ wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 26d2dbe..5352681 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -10,7 +10,6 @@ #include #include -#include "utils/VoronoiUtils.hpp" #include "utils/linearAlg2D.hpp" #include "Utils.hpp" @@ -19,26 +18,9 @@ #include "Geometry/VoronoiUtilsCgal.hpp" #include "../EdgeGrid.hpp" +#include "Geometry/VoronoiUtils.hpp" #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). -namespace boost::polygon { - -template<> struct geometry_concept -{ - typedef segment_concept type; -}; - -template<> struct segment_traits -{ - typedef coord_t coordinate_type; - typedef Slic3r::Point point_type; - static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir) - { - return dir.to_int() ? CSegment.p() : CSegment.next().p(); - } -}; - -} // namespace boost::polygon namespace Slic3r::Arachne { @@ -108,8 +90,7 @@ static void export_graph_to_svg(const std::string } #endif -SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p) -{ +SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) { auto he_node_it = vd_node_to_he_node.find(&vd_node); if (he_node_it == vd_node_to_he_node.end()) { @@ -124,8 +105,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments) -{ +void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments) { auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); if (he_edge_it != vd_edge_to_he_edge.end()) { // Twin segment(s) have already been made @@ -235,22 +215,18 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& } } -Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector& segments) +Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector& segments) { + assert(Geometry::VoronoiUtils::is_in_range(vd_edge)); /*Terminology in this function assumes that the edge moves horizontally from left to right. This is not necessarily the case; the edge can go in any direction, but it helps to picture it in a certain direction in your head.*/ - const vd_t::cell_type* left_cell = vd_edge.cell(); - const vd_t::cell_type* right_cell = vd_edge.twin()->cell(); + const VD::cell_type *left_cell = vd_edge.cell(); + const VD::cell_type *right_cell = vd_edge.twin()->cell(); - assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits::lowest()); - - Point start = VoronoiUtils::p(vd_edge.vertex0()).cast(); - Point end = VoronoiUtils::p(vd_edge.vertex1()).cast(); + Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast(); + Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast(); bool point_left = left_cell->contains_point(); bool point_right = right_cell->contains_point(); @@ -260,17 +236,17 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const } else if (point_left != point_right) //This is a parabolic edge between a point and a line. { - Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); - const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); - return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); + Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end()); + const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end()); + return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle); } else //This is a straight edge between two points. { /*While the edge is straight, it is still discretized since the part becomes narrower between the two points. As such it may need different beadings along the way.*/ - Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); - Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); + Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end()); + Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end()); coord_t d = (right_point - left_point).cast().norm(); Point middle = (left_point + right_point) / 2; Point x_axis_dir = perp(Point(right_point - left_point)); @@ -350,8 +326,7 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const } } -bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) -{ +bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments) { if (cell.incident_edge()->is_infinite()) return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. @@ -359,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& // Copy whole cell into graph or not at all // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. - if (const vd_t::vertex_type &vert = *cell.incident_edge()->vertex0(); + if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0(); vert.x() >= double(std::numeric_limits::max()) || vert.x() <= double(std::numeric_limits::lowest()) || vert.y() >= double(std::numeric_limits::max()) || vert.y() <= double(std::numeric_limits::lowest())) return false; // Don't copy any part of this cell - const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); - const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); - Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); + const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end()); + const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end()); + Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0()); if (some_point == source_point.cast()) - some_point = VoronoiUtils::p(cell.incident_edge()->vertex1()); + some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1()); //Test if the some_point is even inside the polygon. //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. @@ -377,16 +352,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) return false; // Don't copy any part of this cell - vd_t::edge_type* vd_edge = cell.incident_edge(); + const VD::edge_type* vd_edge = cell.incident_edge(); do { assert(vd_edge->is_finite()); - if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast()) { + if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast()) { start_source_point = source_point; end_source_point = source_point; starting_vd_edge = vd_edge->next(); ending_vd_edge = vd_edge; } else { - assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); + assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); } } while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); @@ -395,46 +370,6 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& return true; } -void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) -{ - const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); - const Point from = source_segment.from(); - const Point to = source_segment.to(); - - // Find starting edge - // Find end edge - bool seen_possible_start = false; - bool after_start = false; - bool ending_edge_is_set_before_start = false; - vd_t::edge_type* edge = cell.incident_edge(); - do { - if (edge->is_infinite()) - continue; - - Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); - Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); - - assert(!(v0 == to.cast() && v1 == from.cast() )); - if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to - starting_vd_edge = edge; - seen_possible_start = true; - } - else if (seen_possible_start) { - after_start = true; - } - - if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { - ending_edge_is_set_before_start = !after_start; - ending_vd_edge = edge; - } - } while (edge = edge->next(), edge != cell.incident_edge()); - - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_edge); - - start_source_point = source_segment.to(); - end_source_point = source_segment.from(); -} SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, double transitioning_angle, coord_t discretization_step_size, @@ -450,194 +385,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead constructFromPolygons(polys); } -static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram) -{ - for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) { - if (edge.is_finite()) { - assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); - if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || - !VoronoiUtils::is_finite(*edge.vertex1())) - return true; - } - } - return false; -} - -static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) { - if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) - return true; - - for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { - if (!cell.incident_edge()) - continue; // There is no spoon - - if (cell.contains_segment()) { - const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); - const Point from = source_segment.from(); - const Point to = source_segment.to(); - - // Find starting edge - // Find end edge - bool seen_possible_start = false; - bool after_start = false; - bool ending_edge_is_set_before_start = false; - VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr; - VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr; - VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge(); - do { - if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1())) - continue; - - Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); - Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); - - assert(!(v0 == to.cast() && v1 == from.cast())); - if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to - starting_vd_edge = edge; - seen_possible_start = true; - } else if (seen_possible_start) { - after_start = true; - } - - if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { - ending_edge_is_set_before_start = !after_start; - ending_vd_edge = edge; - } - } while (edge = edge->next(), edge != cell.incident_edge()); - - if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge) - return true; - } - } - - return false; -} - -static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph) -{ - for (const auto &edge : graph.edges) - if (edge.twin == nullptr) - return true; - return false; -} - -using PointMap = SkeletalTrapezoidation::PointMap; - -inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph, - const double fix_angle, - const PointMap &vertex_mapping) -{ - for (STHalfEdgeNode &node : graph.nodes) { - // If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction. - if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end()) - node.p = node_it->second; - else - node.p.rotate(-fix_angle); - } -} - -bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) -{ - for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { - if (!cell.incident_edge()) - continue; // Degenerated cell, there is no spoon - - if (!cell.contains_segment()) - continue; // Skip cells that don't contain segments. - - const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); - const Vec2d source_segment_from = source_segment.from().cast(); - const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; - - Point start_source_point, end_source_point; - VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr; - SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments); - // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. - // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. - // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). - if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr) - for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) - if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) - return true; - } - - return false; -} - -enum class VoronoiDiagramStatus { - NO_ISSUE_DETECTED, - MISSING_VORONOI_VERTEX, - NON_PLANAR_VORONOI_DIAGRAM, - VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, - OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION -}; - -// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram -// is not planar or some Voronoi edge is intersecting input segment. -VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram, - const std::vector &segments) -{ - if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) { - return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX; - } else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) { - // Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446. - return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; - } else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) { - // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. - return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM; - } - return VoronoiDiagramStatus::NO_ISSUE_DETECTED; -} - -inline static std::pair try_to_fix_degenerated_voronoi_diagram_by_rotation( - Geometry::VoronoiDiagram &voronoi_diagram, - const Polygons &polys, - Polygons &polys_rotated, - std::vector &segments, - const std::vector &fix_angles) -{ - const Polygons polys_rotated_original = polys_rotated; - double fixed_by_angle = fix_angles.front(); - PointMap vertex_mapping; - - for (const double &fix_angle : fix_angles) { - vertex_mapping.clear(); - polys_rotated = polys_rotated_original; - fixed_by_angle = fix_angle; - - for (Polygon &poly : polys_rotated) - poly.rotate(fix_angle); - - assert(polys_rotated.size() == polys.size()); - for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) { - assert(polys_rotated[poly_idx].size() == polys[poly_idx].size()); - for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx) - vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]}); - } - - segments.clear(); - for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++) - for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++) - segments.emplace_back(&polys_rotated, poly_idx, point_idx); - - voronoi_diagram.clear(); - construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); - -#ifdef ARACHNE_DEBUG_VORONOI - { - static int iRun = 0; - dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys)); - } -#endif - - if (detect_voronoi_diagram_known_issues(voronoi_diagram, segments) == VoronoiDiagramStatus::NO_ISSUE_DETECTED) - break; - } - - assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); - - return {vertex_mapping, fixed_by_angle}; -} void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) { @@ -670,8 +417,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - Geometry::VoronoiDiagram voronoi_diagram; - construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); + VD voronoi_diagram; + voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend()); #ifdef ARACHNE_DEBUG_VORONOI { @@ -680,45 +427,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - // When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is - // intersecting input segment, rotate the input polygon and try again. - VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments); - const std::vector fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; - double fixed_by_angle = fix_angles.front(); - - PointMap vertex_mapping; - // polys_copy is referenced through items stored in the std::vector segments. - Polygons polys_copy = polys; - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { - if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) - BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; - else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM) - BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; - else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) - BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; - - std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles); - - VoronoiDiagramStatus status_after_fix = detect_voronoi_diagram_known_issues(voronoi_diagram, segments); - assert(status_after_fix == VoronoiDiagramStatus::NO_ISSUE_DETECTED); - if (status_after_fix == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) - BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; - else if (status_after_fix == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM) - BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; - else if (status_after_fix == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) - BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; - } - -process_voronoi_diagram: assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); - for (vd_t::cell_type cell : voronoi_diagram.cells()) { + for (const VD::cell_type &cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) continue; // There is no spoon Point start_source_point; Point end_source_point; - vd_t::edge_type* starting_voronoi_edge = nullptr; - vd_t::edge_type* ending_voronoi_edge = nullptr; + const VD::edge_type *starting_voronoi_edge = nullptr; + const VD::edge_type *ending_voronoi_edge = nullptr; // Compute and store result in above variables if (cell.contains_point()) { @@ -727,7 +444,12 @@ process_voronoi_diagram: continue; } else { assert(cell.contains_segment()); - computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); + Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend()); + assert(cell_range.is_valid()); + start_source_point = cell_range.segment_start_point; + end_source_point = cell_range.segment_end_point; + starting_voronoi_edge = cell_range.edge_begin; + ending_voronoi_edge = cell_range.edge_end; } if (!starting_voronoi_edge || !ending_voronoi_edge) { @@ -736,68 +458,30 @@ process_voronoi_diagram: } // Copy start to end edge to graph + assert(Geometry::VoronoiUtils::is_in_range(*starting_voronoi_edge)); edge_t* prev_edge = nullptr; - assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits::lowest()); - transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; starting_node->data.distance_to_boundary = 0; constexpr bool is_next_to_start_or_end = true; graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); - for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { + for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); - assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits::lowest()); + assert(Geometry::VoronoiUtils::is_in_range(*vd_edge)); - Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast(); - Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast(); + Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast(); + Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast(); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); } - assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits::lowest()); - transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); prev_edge->to->data.distance_to_boundary = 0; } - // For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar. - // When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have - // a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi - // diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram. - if (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) { - BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth."; - status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION; - std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles); - - assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); - if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) - BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input."; - - assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); - - this->graph.edges.clear(); - this->graph.nodes.clear(); - this->vd_edge_to_he_edge.clear(); - this->vd_node_to_he_node.clear(); - - goto process_voronoi_diagram; - } - - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { - assert(!has_missing_twin_edge(this->graph)); - - if (has_missing_twin_edge(this->graph)) - BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input."; - } - - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) - rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping); #ifdef ARACHNE_DEBUG assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index e2a013b..176f2a0 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -11,7 +11,6 @@ #include -#include #include "utils/HalfEdgeGraph.hpp" #include "utils/PolygonsSegmentIndex.hpp" @@ -26,8 +25,9 @@ //#define ARACHNE_DEBUG //#define ARACHNE_DEBUG_VORONOI -namespace Slic3r::Arachne -{ +namespace Slic3r::Arachne { + +using VD = Slic3r::Geometry::VoronoiDiagram; /*! * Main class of the dynamic beading strategies. @@ -50,8 +50,6 @@ deposition modeling" by Kuipers et al. */ class SkeletalTrapezoidation { - using pos_t = double; - using vd_t = boost::polygon::voronoi_diagram; using graph_t = SkeletalTrapezoidationGraph; using edge_t = STHalfEdge; using node_t = STHalfEdgeNode; @@ -83,7 +81,6 @@ class SkeletalTrapezoidation public: using Segment = PolygonsSegmentIndex; - using PointMap = ankerl::unordered_dense::map; using NodeSet = ankerl::unordered_dense::set; /*! @@ -168,9 +165,9 @@ protected: * mapping each voronoi VD edge to the corresponding halfedge HE edge * In case the result segment is discretized, we map the VD edge to the *last* HE edge */ - ankerl::unordered_dense::map vd_edge_to_he_edge; - ankerl::unordered_dense::map vd_node_to_he_node; - node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. + ankerl::unordered_dense::map vd_edge_to_he_edge; + ankerl::unordered_dense::map vd_node_to_he_node; + node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. /*! * (Eventual) returned 'polylines per index' result (from generateToolpaths): @@ -181,7 +178,7 @@ protected: * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * \p prev_edge serves as input and output. May be null as input. */ - void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments); + void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- @@ -208,7 +205,7 @@ protected: * \return A number of coordinates along the edge where the edge is broken * up into discrete pieces. */ - Points discretize(const vd_t::edge_type& segment, const std::vector& segments); + Points discretize(const VD::edge_type& segment, const std::vector& segments); /*! * Compute the range of line segments that surround a cell of the skeletal @@ -234,33 +231,7 @@ protected: * /return Whether the cell is inside of the polygon. If it's outside of the * polygon we should skip processing it altogether. */ - static bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); - - /*! - * Compute the range of line segments that surround a cell of the skeletal - * graph that belongs to a line segment of the medial axis. - * - * This should only be used on cells that belong to a central line segment - * of the skeletal graph, e.g. trapezoid cells, not triangular cells. - * - * The resulting line segments is just the first and the last segment. They - * are linked to the neighboring segments, so you can iterate over the - * segments until you reach the last segment. - * \param cell The cell to compute the range of line segments for. - * \param[out] start_source_point The start point of the source segment of - * this cell. - * \param[out] end_source_point The end point of the source segment of this - * cell. - * \param[out] starting_vd_edge The edge of the Voronoi diagram where the - * loop around the cell starts. - * \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop - * around the cell ends. - * \param points All vertices of the input Polygons. - * \param segments All edges of the input Polygons. - * /return Whether the cell is inside of the polygon. If it's outside of the - * polygon we should skip processing it altogether. - */ - static void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments); /*! * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two @@ -603,7 +574,7 @@ protected: */ void generateLocalMaximaSingleBeads(); - friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments); + friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp index 125b3ef..0dadc53 100644 --- a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp +++ b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp @@ -156,7 +156,6 @@ struct PathsPointIndexLocator } }; -using PolygonsPointIndexLocator = PathsPointIndexLocator; }//namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp index 6eff3d6..3258b41 100644 --- a/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp +++ b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp @@ -27,5 +27,24 @@ public: } // namespace Slic3r::Arachne +namespace boost::polygon { + +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + + static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir) + { + return dir.to_int() ? CSegment.to() : CSegment.from(); + } +}; + +} // namespace boost::polygon #endif//UTILS_POLYGONS_SEGMENT_INDEX_H diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp deleted file mode 100644 index 675a0eb..0000000 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ /dev/null @@ -1,251 +0,0 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#include -#include -#include - -#include "linearAlg2D.hpp" -#include "VoronoiUtils.hpp" - -namespace Slic3r::Arachne -{ - -Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node) -{ - const double x = node->x(); - const double y = node->y(); - assert(std::isfinite(x) && std::isfinite(y)); - assert(x <= double(std::numeric_limits::max()) && x >= std::numeric_limits::lowest()); - assert(y <= double(std::numeric_limits::max()) && y >= std::numeric_limits::lowest()); - return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates. -} - -Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector& segments) -{ - assert(cell.contains_point()); - if(!cell.contains_point()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; - - switch (cell.source_category()) { - case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT: - assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n"); - BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"; - break; - case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: - assert(cell.source_index() < segments.size()); - return segments[cell.source_index()].to(); - break; - case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: - assert(cell.source_index() < segments.size()); - return segments[cell.source_index()].from(); - break; - default: - assert(false && "getSourcePoint should only be called on point cells!\n"); - break; - } - - assert(false && "cell.source_category() is equal to an invalid value!\n"); - BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!"; - return {}; -} - -PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector& segments) -{ - assert(cell.contains_point()); - if(!cell.contains_point()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; - - assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); - switch (cell.source_category()) { - case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: { - assert(cell.source_index() < segments.size()); - PolygonsPointIndex ret = segments[cell.source_index()]; - ++ret; - return ret; - break; - } - case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: { - assert(cell.source_index() < segments.size()); - return segments[cell.source_index()]; - break; - } - default: - assert(false && "getSourcePoint should only be called on point cells!\n"); - break; - } - PolygonsPointIndex ret = segments[cell.source_index()]; - return ++ret; -} - -const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments) -{ - assert(cell.contains_segment()); - if (!cell.contains_segment()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!"; - - return segments[cell.source_index()]; -} - -class PointMatrix -{ -public: - double matrix[4]; - - PointMatrix() - { - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 1; - } - - PointMatrix(double rotation) - { - rotation = rotation / 180 * M_PI; - matrix[0] = cos(rotation); - matrix[1] = -sin(rotation); - matrix[2] = -matrix[1]; - matrix[3] = matrix[0]; - } - - PointMatrix(const Point p) - { - matrix[0] = p.x(); - matrix[1] = p.y(); - double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1])); - matrix[0] /= f; - matrix[1] /= f; - matrix[2] = -matrix[1]; - matrix[3] = matrix[0]; - } - - static PointMatrix scale(double s) - { - PointMatrix ret; - ret.matrix[0] = s; - ret.matrix[3] = s; - return ret; - } - - Point apply(const Point p) const - { - return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3])); - } - - Point unapply(const Point p) const - { - return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3])); - } -}; -Points VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle) -{ - Points discretized; - // x is distance of point projected on the segment ab - // xx is point projected on the segment ab - const Point a = segment.from(); - const Point b = segment.to(); - const Point ab = b - a; - const Point as = s - a; - const Point ae = e - a; - const coord_t ab_size = ab.cast().norm(); - const coord_t sx = as.cast().dot(ab.cast()) / ab_size; - const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; - const coord_t sxex = ex - sx; - - assert((as.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - assert((ae.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - - const Point ap = p - a; - const coord_t px = ap.cast().dot(ab.cast()) / ab_size; - - assert((ap.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - - Point pxx; - Line(a, b).distance_to_infinite_squared(p, &pxx); - const Point ppxx = pxx - p; - const coord_t d = ppxx.cast().norm(); - const PointMatrix rot = PointMatrix(perp(ppxx)); - - if (d == 0) - { - discretized.emplace_back(s); - discretized.emplace_back(e); - return discretized; - } - - const float marking_bound = atan(transitioning_angle * 0.5); - int64_t msx = - marking_bound * int64_t(d); // projected marking_start - int64_t mex = marking_bound * int64_t(d); // projected marking_end - - assert(msx <= std::numeric_limits::max()); - assert(double(msx) * double(msx) <= double(std::numeric_limits::max())); - assert(mex <= std::numeric_limits::max()); - assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits::max()); - - const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; - Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx; - Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx; - const int dir = (sx > ex) ? -1 : 1; - if (dir < 0) - { - std::swap(marking_start, marking_end); - std::swap(msx, mex); - } - - bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - - const Point apex = rot.unapply(Point(0, d / 2)) + pxx; - bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; - - assert(!(add_marking_start && add_marking_end) || add_apex); - if(add_marking_start && add_marking_end && !add_apex) - { - BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; - } - - const coord_t step_count = static_cast(static_cast(std::abs(ex - sx)) / approximate_step_size + 0.5); - - discretized.emplace_back(s); - for (coord_t step = 1; step < step_count; step++) - { - assert(double(sxex) * double(step) <= double(std::numeric_limits::max())); - const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); - assert(double(x) * double(x) <= double(std::numeric_limits::max())); - assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits::max())); - const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); - - if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) - { - discretized.emplace_back(marking_start); - add_marking_start = false; - } - if (add_apex && int64_t(x) * int64_t(dir) > 0) - { - discretized.emplace_back(apex); - add_apex = false; // only add the apex just before the - } - if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) - { - discretized.emplace_back(marking_end); - add_marking_end = false; - } - assert(x <= std::numeric_limits::max() && x >= std::numeric_limits::lowest()); - assert(y <= std::numeric_limits::max() && y >= std::numeric_limits::lowest()); - const Point result = rot.unapply(Point(x, y)) + pxx; - discretized.emplace_back(result); - } - if (add_apex) - { - discretized.emplace_back(apex); - } - if (add_marking_end) - { - discretized.emplace_back(marking_end); - } - discretized.emplace_back(e); - return discretized; -} - -}//namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp deleted file mode 100644 index ea6a849..0000000 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp +++ /dev/null @@ -1,47 +0,0 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - - -#ifndef UTILS_VORONOI_UTILS_H -#define UTILS_VORONOI_UTILS_H - -#include - - -#include - -#include "PolygonsSegmentIndex.hpp" - -namespace Slic3r::Arachne -{ - -/*! - */ -class VoronoiUtils -{ -public: - using Segment = PolygonsSegmentIndex; - using voronoi_data_t = double; - using vd_t = boost::polygon::voronoi_diagram; - - static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector &segments); - static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments); - static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector &segments); - - static Vec2i64 p(const vd_t::vertex_type *node); - - /*! - * Discretize a parabola based on (approximate) step size. - * The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola. - */ - static Points discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle); - - static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex) - { - return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); - } -}; - -} // namespace Slic3r::Arachne - -#endif // UTILS_VORONOI_UTILS_H diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp index 2df96b6..bd5c921 100644 --- a/src/libslic3r/Arrange/Arrange.hpp +++ b/src/libslic3r/Arrange/Arrange.hpp @@ -58,7 +58,7 @@ class DefaultArrangerCtl : public Arranger::Ctl { public: DefaultArrangerCtl() = default; - explicit DefaultArrangerCtl(ArrangeTaskBase::Ctl &ctl) : taskctl{&ctl} {} + explicit DefaultArrangerCtl(ArrangeTaskCtl &ctl) : taskctl{&ctl} {} void update_status(int st) override { diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 434bdd4..6b49496 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -325,7 +325,7 @@ class DefaultArranger: public Arranger { // a pure RectangleBed with inner-fit polygon calculation. if (!with_wipe_tower && m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto && - std::is_convertible_v) { + IsRectangular) { PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond}; RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)}; diff --git a/src/libslic3r/Arrange/Core/Beds.hpp b/src/libslic3r/Arrange/Core/Beds.hpp index d269e6a..1bcd2ac 100644 --- a/src/libslic3r/Arrange/Core/Beds.hpp +++ b/src/libslic3r/Arrange/Core/Beds.hpp @@ -181,6 +181,11 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed) ArrangeBed to_arrange_bed(const Points &bedpts); +template struct IsRectangular_ : public std::false_type {}; +template<> struct IsRectangular_: public std::true_type {}; +template<> struct IsRectangular_: public std::true_type {}; + +template static constexpr bool IsRectangular = IsRectangular_::value; } // namespace arr2 inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; } diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp index 120434c..379aebc 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp @@ -51,9 +51,9 @@ protected: public: TMArrangeKernel() = default; TMArrangeKernel(Vec2crd gravity_center, size_t itm_cnt, double bedarea = NaNd) - : sink{gravity_center} - , m_bin_area(bedarea) + : m_bin_area(bedarea) , m_item_cnt{itm_cnt} + , sink{gravity_center} {} TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd) @@ -87,8 +87,6 @@ public: // Will hold the resulting score double score = 0; - // Density is the pack density: how big is the arranged pile - double density = 0; // Distinction of cases for the arrangement scene enum e_cases { @@ -96,8 +94,6 @@ public: // OR for all items in a small-only scene. BIG_ITEM, - // This branch is for the last big item in a mixed scene - LAST_BIG_ITEM, // For small items in a mixed scene. SMALL_ITEM, @@ -109,10 +105,8 @@ public: bool bigitems = is_big(envelope_area(item)) || m_rtree.empty(); if (is_wt) compute_case = WIPE_TOWER; - else if (bigitems && m_rem_cnt > 0) + else if (bigitems) compute_case = BIG_ITEM; - else if (bigitems && m_rem_cnt == 0) - compute_case = LAST_BIG_ITEM; else compute_case = SMALL_ITEM; @@ -129,20 +123,8 @@ public: Point top_left{minc.x(), maxc.y()}; Point bottom_right{maxc.x(), minc.y()}; - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = (minc - cc).cast().norm(); - dists[1] = (maxc - cc).cast().norm(); - dists[2] = (itmcntr - cc).template cast().norm(); - dists[3] = (top_left - cc).cast().norm(); - dists[4] = (bottom_right - cc).cast().norm(); - - // The smalles distance from the arranged pile center: - double dist = norm(*(std::min_element(dists.begin(), dists.end()))); - double bindist = norm((ibb.center() - active_sink).template cast().norm()); - dist = 0.8 * dist + 0.2 * bindist; + // The smallest distance from the arranged pile center: + double dist = norm((itmcntr - m_pilebb.center()).template cast().norm()); // Prepare a variable for the alignment score. // This will indicate: how well is the candidate item @@ -150,7 +132,7 @@ public: // with all neighbors and return the score for the best // alignment. So it is enough for the candidate to be // aligned with only one item. - auto alignment_score = 1.0; + auto alignment_score = 1.; auto query = bgi::intersects(ibb); auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree; @@ -170,33 +152,24 @@ public: auto bb = p.bb; bb.merge(ibb); auto bbarea = area(bb); - auto ascore = 1.0 - (fixed_area(item) + parea) / bbarea; + auto ascore = 1.0 - (area(fixed_bounding_box(item)) + area(p.bb)) / bbarea; if(ascore < alignment_score) alignment_score = ascore; } } - auto fullbbsz = fullbb.size(); - density = std::sqrt(norm(fullbbsz.x()) * norm(fullbbsz.y())); double R = double(m_rem_cnt) / (m_item_cnt); + R = std::pow(R, 1./3.); // The final mix of the score is the balance between the // distance from the full pile center, the pack density and // the alignment with the neighbors - if (result.empty()) - score = 0.50 * dist + 0.50 * density; - else // Let the density matter more when fewer objects remain - score = 0.50 * dist + (1.0 - R) * 0.20 * density + - 0.30 * alignment_score; + score = 0.6 * dist + 0.1 * alignment_score + (1.0 - R) * (0.3 * dist) + R * 0.3 * alignment_score; break; } - case LAST_BIG_ITEM: { - score = norm((itmcntr - m_pilebb.center()).template cast().norm()); - break; - } case SMALL_ITEM: { // Here there are the small items that should be placed around the // already processed bigger items. @@ -236,8 +209,11 @@ public: if (m_item_cnt == 0) m_item_cnt = m_rem_cnt + fixed.size() + 1; - if (std::isnan(m_bin_area)) - m_bin_area = area(bed); + if (std::isnan(m_bin_area)) { + auto sz = bounding_box(bed).size(); + + m_bin_area = scaled(unscaled(sz.x()) * unscaled(sz.y())); + } m_norm = std::sqrt(m_bin_area); @@ -245,7 +221,7 @@ public: m_itemstats.reserve(fixed.size()); m_rtree.clear(); m_smallsrtree.clear(); - m_pilebb = {}; + m_pilebb = {active_sink, active_sink}; unsigned idx = 0; for (auto &fixitem : fixed) { auto fixitmbb = fixed_bounding_box(fixitem); diff --git a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp index 92d27f6..afbac63 100644 --- a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp +++ b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp @@ -101,6 +101,9 @@ ExPolygons to_expolygons(const SegmentedRectangleBed &bed) return to_expolygons(RectangleBed{bed.bb}); } +template +struct IsRectangular_, void>> : public std::true_type +{}; }} // namespace Slic3r::arr2 #endif // SEGMENTEDRECTANGLEBED_HPP diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp index 33fb3ef..1657634 100644 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp @@ -115,21 +115,13 @@ ArrangeTask::process_native(Ctl &ctl) } subctl{ctl, *this}; - auto fixed_items = printable.unselected; - - // static (unselected) unprintable objects should not be overlapped by - // movable and printable objects - std::copy(unprintable.unselected.begin(), - unprintable.unselected.end(), - std::back_inserter(fixed_items)); - - arranger->arrange(printable.selected, fixed_items, bed, subctl); + arranger->arrange(printable.selected, printable.unselected, bed, subctl); std::vector printable_bed_indices = get_bed_indices(crange(printable.selected), crange(printable.unselected)); // If there are no printables, leave the physical bed empty - constexpr int SearchFrom = 1; + static constexpr int SearchFrom = 1; // Unprintable items should go to the first logical (!) bed not containing // any printable items diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index f8da75a..db5c6fe 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -142,6 +142,15 @@ public: return this->min.x() < other.max.x() && this->max.x() > other.min.x() && this->min.y() < other.max.y() && this->max.y() > other.min.y() && this->min.z() < other.max.z() && this->max.z() > other.min.z(); } + // Shares some boundary. + bool shares_boundary(const BoundingBox3Base& other) const { + return is_approx(this->min.x(), other.max.x()) || + is_approx(this->max.x(), other.min.x()) || + is_approx(this->min.y(), other.max.y()) || + is_approx(this->max.y(), other.min.y()) || + is_approx(this->min.z(), other.max.z()) || + is_approx(this->max.z(), other.min.z()); + } }; // Will prevent warnings caused by non existing definition of template in hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6cc97cc..7fb70e7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -80,7 +80,6 @@ set(SLIC3R_SOURCES ExtrusionSimulator.hpp FileParserError.hpp Fill/Fill.cpp - Fill/Fill.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp Fill/FillAdaptive.cpp @@ -184,6 +183,8 @@ set(SLIC3R_SOURCES GCode/GCodeProcessor.hpp GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.hpp + GCode/Travels.cpp + GCode/Travels.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -205,6 +206,8 @@ set(SLIC3R_SOURCES Geometry/Voronoi.hpp Geometry/VoronoiOffset.cpp Geometry/VoronoiOffset.hpp + Geometry/VoronoiUtils.hpp + Geometry/VoronoiUtils.cpp Geometry/VoronoiVisualUtils.hpp Int128.hpp JumpPointSearch.cpp @@ -486,8 +489,9 @@ set(SLIC3R_SOURCES Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.cpp - Arachne/utils/VoronoiUtils.hpp - Arachne/utils/VoronoiUtils.cpp + Geometry/Voronoi.cpp + Geometry/VoronoiUtils.hpp + Geometry/VoronoiUtils.cpp Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidationEdge.hpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 99a1c9d..3bc2b54 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -245,6 +245,8 @@ ConfigOption* ConfigOptionDef::create_empty_option() const { if (this->nullable) { switch (this->type) { + case coFloat: return new ConfigOptionFloatNullable(); + case coInt: return new ConfigOptionIntNullable(); case coFloats: return new ConfigOptionFloatsNullable(); case coInts: return new ConfigOptionIntsNullable(); case coPercents: return new ConfigOptionPercentsNullable(); @@ -883,6 +885,8 @@ size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase& config, const char* end = start; } + // Do legacy conversion on a completely loaded dictionary. + // Perform composite conversions, for example merging multiple keys into one key. config.handle_legacy_composite(); return num_key_value_pairs; } @@ -1410,6 +1414,9 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) @@ -1417,9 +1424,11 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) @@ -1446,6 +1455,9 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionS CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) @@ -1453,9 +1465,11 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::Con CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable, Slic3r::ConfigOptionFloatNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloats) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionInt) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable, Slic3r::ConfigOptionIntNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionInts) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionString) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 52b5438..ed9a9a0 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -39,9 +40,9 @@ namespace Slic3r { template void serialize(Archive& ar) { ar(this->value); ar(this->percent); } }; - inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; } - inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); } - inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); } + inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value == r.value && l.percent == r.percent; } + inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return !(l == r); } + inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); } } namespace std { @@ -298,8 +299,55 @@ public: typedef ConfigOption* ConfigOptionPtr; typedef const ConfigOption* ConfigOptionConstPtr; +// Nill value will be defined in specializations +template struct NilValueTempl +{ + using NilType = T; + static_assert(always_false::value, "Type has no well defined nil value"); +}; + +template struct NilValueTempl, void>> { + using NilType = T; + static constexpr auto value = std::numeric_limits::max(); +}; + +template<> struct NilValueTempl : public NilValueTempl{}; + +// For enums the nil is the max value of the underlying type. +template +struct NilValueTempl, void>> +{ + using NilType = T; + static constexpr auto value = static_cast(std::numeric_limits>::max()); +}; + +template struct NilValueTempl, void>> { + using NilType = T; + static constexpr auto value = std::numeric_limits::quiet_NaN(); +}; + +template<> +struct NilValueTempl : public NilValueTempl {}; + +template<> struct NilValueTempl { + using NilType = const char *; + + static constexpr const char* value = ""; +}; + +template struct NilValueTempl> { + using NilType = Vec; + // No constexpr for Vec + static inline const Vec value = Vec::Ones() * NilValueTempl>::value; +}; + +template using NilType = typename NilValueTempl>::NilType; + +// Define shortcut as a function instead of a static const var so that it can be constexpr +// even if the NilValueTempl::value is not constexpr. +template static constexpr NilType NilValue() noexcept { return NilValueTempl>::value; } // Value of a single valued option (bool, int, float, string, point, enum) -template +template class ConfigOptionSingle : public ConfigOption { public: T value; @@ -310,16 +358,18 @@ public: { if (rhs->type() != this->type()) throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type"); - assert(dynamic_cast*>(rhs)); - this->value = static_cast*>(rhs)->value; + assert(dynamic_cast(rhs)); + this->value = static_cast(rhs)->value; } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types"); - assert(dynamic_cast*>(&rhs)); - return this->value == static_cast*>(&rhs)->value; + assert(dynamic_cast(&rhs)); + if (this->is_nil() && rhs.is_nil()) + return true; + return this->value == static_cast(&rhs)->value; } bool operator==(const T &rhs) const throw() { return this->value == rhs; } @@ -328,11 +378,65 @@ public: size_t hash() const throw() override { return std::hash{}(this->value); } + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + bool overriden_by(const ConfigOption *rhs) const override { + if (this->nullable()) + throw ConfigurationError("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types."); + auto rhs_co = static_cast(rhs); + if (! rhs->nullable()) + // Overridding a non-nullable object with another non-nullable object. + return this->value != rhs_co->value; + + return !rhs_co->is_nil() && rhs_co->value != this->value; + } + // Apply an override option, possibly a nullable one. + bool apply_override(const ConfigOption *rhs) override { + if (this->nullable()) + throw ConfigurationError("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types."); + auto rhs_co = static_cast(rhs); + if (! rhs->nullable()) { + // Overridding a non-nullable object with another non-nullable object. + if (this->value != rhs_co->value) { + this->value = rhs_co->value; + return true; + } + return false; + } + + if (!rhs_co->is_nil() && rhs_co->value != this->value) { + this->value = rhs_co->value; + return true; + } + + return false; + } + + bool nullable() const override { return NULLABLE; } + + static constexpr NilType nil_value() { return NilValue(); } + + // A scalar is nil, or all values of a vector are nil. + bool is_nil() const override + { + bool ret = false; + + if constexpr (NULLABLE) + ret = this->value == nil_value(); + + return ret; + } private: friend class cereal::access; template void serialize(Archive & ar) { ar(this->value); } }; +template +using ConfigOptionSingleNullable = ConfigOptionSingle; // Value of a vector valued option (bools, ints, floats, strings, points) class ConfigOptionVectorBase : public ConfigOption { public: @@ -551,23 +655,34 @@ private: template void serialize(Archive & ar) { ar(this->values); } }; -class ConfigOptionFloat : public ConfigOptionSingle +template +class ConfigOptionFloatTempl : public ConfigOptionSingle { public: - ConfigOptionFloat() : ConfigOptionSingle(0) {} - explicit ConfigOptionFloat(double _value) : ConfigOptionSingle(_value) {} + ConfigOptionFloatTempl() : ConfigOptionSingle(0) {} + explicit ConfigOptionFloatTempl(double _value) : ConfigOptionSingle(_value) {} static ConfigOptionType static_type() { return coFloat; } ConfigOptionType type() const override { return static_type(); } double getFloat() const override { return this->value; } - ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } - bool operator==(const ConfigOptionFloat &rhs) const throw() { return this->value == rhs.value; } - bool operator< (const ConfigOptionFloat &rhs) const throw() { return this->value < rhs.value; } + ConfigOption* clone() const override { return new ConfigOptionFloatTempl(*this); } + bool operator==(const ConfigOptionFloatTempl &rhs) const throw() { return this->value == rhs.value; } + bool operator< (const ConfigOptionFloatTempl &rhs) const throw() { return this->value < rhs.value; } std::string serialize() const override { std::ostringstream ss; - ss << this->value; + double v = this->value; + + if (std::isfinite(v)) + ss << v; + else if (std::isnan(v)) { + if (NULLABLE) + ss << "nil"; + else + throw ConfigurationError("Serializing NaN"); + } else + throw ConfigurationError("Serializing invalid number"); return ss.str(); } @@ -575,19 +690,30 @@ public: { UNUSED(append); std::istringstream iss(str); + if (str == "nil") { + if (NULLABLE) + this->value = this->nil_value(); + else + throw ConfigurationError("Deserializing nil into a non-nullable object"); + } else { iss >> this->value; + } return !iss.fail(); } - ConfigOptionFloat& operator=(const ConfigOption *opt) + ConfigOptionFloatTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool is_nil() const override + { + return std::isnan(this->value); + } private: friend class cereal::access; - template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; template @@ -713,26 +839,35 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionFloat = ConfigOptionFloatTempl; +using ConfigOptionFloatNullable = ConfigOptionFloatTempl; using ConfigOptionFloats = ConfigOptionFloatsTempl; using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl; -class ConfigOptionInt : public ConfigOptionSingle +template +class ConfigOptionIntTempl : public ConfigOptionSingle { public: - ConfigOptionInt() : ConfigOptionSingle(0) {} - explicit ConfigOptionInt(int value) : ConfigOptionSingle(value) {} - explicit ConfigOptionInt(double _value) : ConfigOptionSingle(int(floor(_value + 0.5))) {} + ConfigOptionIntTempl() : ConfigOptionSingle(0) {} + explicit ConfigOptionIntTempl(int value) : ConfigOptionSingle(value) {} + explicit ConfigOptionIntTempl(double _value) : ConfigOptionSingle(int(floor(_value + 0.5))) {} static ConfigOptionType static_type() { return coInt; } ConfigOptionType type() const override { return static_type(); } int getInt() const override { return this->value; } void setInt(int val) override { this->value = val; } - ConfigOption* clone() const override { return new ConfigOptionInt(*this); } - bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; } + ConfigOption* clone() const override { return new ConfigOptionIntTempl(*this); } + bool operator==(const ConfigOptionIntTempl &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override { std::ostringstream ss; + if (this->value == this->nil_value()) { + if (NULLABLE) + ss << "nil"; + else + throw ConfigurationError("Serializing NaN"); + } else ss << this->value; return ss.str(); } @@ -741,11 +876,18 @@ public: { UNUSED(append); std::istringstream iss(str); + if (str == "nil") { + if (NULLABLE) + this->value = this->nil_value(); + else + throw ConfigurationError("Deserializing nil into a non-nullable object"); + } else { iss >> this->value; + } return !iss.fail(); } - ConfigOptionInt& operator=(const ConfigOption *opt) + ConfigOptionIntTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; @@ -753,9 +895,11 @@ public: private: friend class cereal::access; - template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionInt = ConfigOptionIntTempl; +using ConfigOptionIntNullable = ConfigOptionIntTempl; template class ConfigOptionIntsTempl : public ConfigOptionVector { @@ -1838,6 +1982,8 @@ public: template ConfigOption* load_option_from_archive(Archive &archive) const { if (this->nullable) { switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } @@ -1870,6 +2016,8 @@ public: template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { if (this->nullable) { switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; case coFloats: archive(*static_cast(opt)); break; case coInts: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt));break; diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 5aebf8e..0c233e4 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -68,6 +68,21 @@ std::vector> custom_tool_changes(const Info& cus return custom_tool_changes; } +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// Where print_z corresponds to the layer on which we perform a color change for the specified extruder. +std::vector> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders) +{ + std::vector> custom_color_changes; + for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes) + if (custom_gcode.type == ColorChange) { + // If extruder count in PrinterSettings was changed, ignore custom g-codes for extruder ids bigger than num_extruders. + assert(custom_gcode.extruder >= 0); + if (size_t(custom_gcode.extruder) <= num_extruders) { + custom_color_changes.emplace_back(custom_gcode.print_z, static_cast(custom_gcode.extruder)); + } + } + return custom_color_changes; +} } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index c4a47b9..568963b 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -87,6 +87,9 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info); // print_z corresponds to the first layer printed with the new extruder. std::vector> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders); +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// Where print_z corresponds to the layer on which we perform a color change for the specified extruder. +std::vector> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders); } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 9094d2a..3e4a950 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -105,7 +105,7 @@ struct EmbossShape // Note: image is only cache it is not neccessary to store // Store file data as plain string - assert(file_data != nullptr); + // For Embossed text file_data are nullptr ar(path, path_in_3mf, (file_data != nullptr) ? *file_data : std::string("")); } template void load(Archive &ar) { diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 04e6bc1..1f8490a 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -434,7 +434,7 @@ inline void expolygons_rotate(ExPolygons &expolys, double angle) expoly.rotate(angle); } -inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true) +inline bool expolygons_contain(const ExPolygons &expolys, const Point &pt, bool border_result = true) { for (const ExPolygon &expoly : expolys) if (expoly.contains(pt, border_result)) @@ -465,6 +465,7 @@ std::vector get_extents_vector(const ExPolygons &polygons); bool has_duplicate_points(const ExPolygon &expoly); bool has_duplicate_points(const ExPolygons &expolys); +// Return True when erase some otherwise False. bool remove_same_neighbor(ExPolygons &expolys); bool remove_sticks(ExPolygon &poly); void keep_largest_contour_only(ExPolygons &polygons); diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index a08b4d8..fd3d925 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -697,11 +697,12 @@ void gcode_spread_points( */ float area_total = 0; float volume_total = 0; + size_t n_cells = 0; + +#if 0 float volume_excess = 0; float volume_deficit = 0; - size_t n_cells = 0; float area_circle_total = 0; -#if 0 // The intermediate lines. for (int j = row_first; j < row_last; ++ j) { const std::pair &span1 = spans[j]; @@ -755,7 +756,9 @@ void gcode_spread_points( cell.volume = acc[j][i]; cell.area = mask[j][i]; assert(cell.area >= 0.f && cell.area <= 1.000001f); +#if 0 area_circle_total += area; +#endif if (cell.area < area) cell.area = area; cell.fraction_covered = std::clamp((cell.area > 0) ? (area / cell.area) : 0, 0.f, 1.f); @@ -765,10 +768,13 @@ void gcode_spread_points( } float cell_height = cell.volume / cell.area; cell.excess_height = cell_height - height_target; +#if 0 + area_circle_total += area; if (cell.excess_height > 0.f) volume_excess += cell.excess_height * cell.area * cell.fraction_covered; else volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered; +#endif volume_total += cell.volume * cell.fraction_covered; area_total += cell.area * cell.fraction_covered; } diff --git a/src/libslic3r/Fill/Fill.hpp b/src/libslic3r/Fill/Fill.hpp deleted file mode 100644 index e92ab2d..0000000 --- a/src/libslic3r/Fill/Fill.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef slic3r_Fill_hpp_ -#define slic3r_Fill_hpp_ - -#include -#include -#include - -#include "../libslic3r.h" -#include "../PrintConfig.hpp" - -#include "FillBase.hpp" - -namespace Slic3r { - -class ExtrusionEntityCollection; -class LayerRegion; - -// An interface class to Perl, aggregating an instance of a Fill and a FillData. -class Filler -{ -public: - Filler() : fill(nullptr) {} - ~Filler() { - delete fill; - fill = nullptr; - } - Fill *fill; - FillParams params; -}; - -} // namespace Slic3r - -#endif // slic3r_Fill_hpp_ diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 6600573..61dc5a7 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -119,7 +119,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; -static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; +static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; static constexpr const char* KEY_ATTR = "key"; static constexpr const char* VALUE_ATTR = "value"; @@ -173,6 +173,7 @@ static constexpr const char *FONT_FACE_NAME_ATTR = "face_name"; static constexpr const char *FONT_STYLE_ATTR = "style"; static constexpr const char *FONT_WEIGHT_ATTR = "weight"; +// Store / load of EmbossShape static constexpr const char *SHAPE_TAG = "slic3rpe:shape"; static constexpr const char *SHAPE_SCALE_ATTR = "scale"; static constexpr const char *UNHEALED_ATTR = "unhealed"; @@ -182,6 +183,7 @@ static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf"; // EmbossProjection static constexpr const char *DEPTH_ATTR = "depth"; static constexpr const char *USE_SURFACE_ATTR = "use_surface"; +// static constexpr const char *FIX_TRANSFORMATION_ATTR = "transform"; const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const char* VALID_OBJECT_TYPES[] = { @@ -353,7 +355,7 @@ namespace Slic3r { std::vector triangles; std::vector custom_supports; std::vector custom_seam; - std::vector mmu_segmentation; + std::vector mm_segmentation; bool empty() { return vertices.empty() || triangles.empty(); } @@ -362,7 +364,7 @@ namespace Slic3r { triangles.clear(); custom_supports.clear(); custom_seam.clear(); - mmu_segmentation.clear(); + mm_segmentation.clear(); } }; @@ -698,12 +700,19 @@ namespace Slic3r { m_name = boost::filesystem::path(filename).stem().string(); // we first loop the entries to read from the archive the .model file only, in order to extract the version from it + bool found_model = false; for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) { std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); - if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { + if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) { + if(found_model){ + close_zip_reader(&archive); + add_error("3mf contain multiple .model files and it is not supported yet."); + return false; + } + found_model = true; try { // valid model name -> extract model @@ -722,6 +731,11 @@ namespace Slic3r { } } } + if (!found_model) { + close_zip_reader(&archive); + add_error("Not valid 3mf. There is missing .model file."); + return false; + } // we then loop again the entries to read other files stored in the archive for (mz_uint i = 0; i < num_entries; ++i) { @@ -1820,7 +1834,7 @@ namespace Slic3r { m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); - m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); + m_curr_object.geometry.mm_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR)); return true; } @@ -2310,41 +2324,30 @@ namespace Slic3r { if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - // recreate custom supports, seam and mmu segmentation from previously loaded attribute + // recreate custom supports, seam and mm segmentation from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); - volume->mmu_segmentation_facets.reserve(triangles_count); + volume->mm_segmentation_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); if (! geometry.custom_seam[index].empty()) volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); - if (! geometry.mmu_segmentation[index].empty()) - volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); + if (! geometry.mm_segmentation[index].empty()) + volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); - volume->mmu_segmentation_facets.shrink_to_fit(); + volume->mm_segmentation_facets.shrink_to_fit(); if (auto &es = volume_data.shape_configuration; es.has_value()) volume->emboss_shape = std::move(es); if (auto &tc = volume_data.text_configuration; tc.has_value()) volume->text_configuration = std::move(tc); - //// Transformation before store to 3mf - //const Transform3d &pre_trmat = *tc->fix_3mf_tr; - //// Cannot use source tranformation - //// When store transformed againg to 3mf it is not modified !!! - //// const Transform3d &pre_trmat = volume->source.transform.get_matrix(); - - //// create fix transformation - //assert(tc->fix_3mf_tr.has_value()); - //volume->text_configuration->fix_3mf_tr = - // pre_trmat.inverse() * - // volume->get_transformation().get_matrix(); // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -3003,12 +3006,12 @@ namespace Slic3r { output_buffer += "\""; } - std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); - if (! mmu_painting_data_string.empty()) { + std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i); + if (! mm_painting_data_string.empty()) { output_buffer += " "; - output_buffer += MMU_SEGMENTATION_ATTR; + output_buffer += MM_SEGMENTATION_ATTR; output_buffer += "=\""; - output_buffer += mmu_painting_data_string; + output_buffer += mm_painting_data_string; output_buffer += "\""; } @@ -3424,7 +3427,6 @@ namespace Slic3r { if (const std::optional &es = volume->emboss_shape; es.has_value()) to_xml(stream, *es, *volume, archive); - // stores volume's text data if (const std::optional &tc = volume->text_configuration; tc.has_value()) TextConfigurationSerialization::to_xml(stream, *tc); @@ -3575,11 +3577,11 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo // All import should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; _3MF_Importer importer; - importer.load_model_from_file(path, *model, config, config_substitutions, check_version); + bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); handle_legacy_project_loaded(importer.version(), config, importer.qidislicer_generator_version()); - return !model->objects.empty() || !config.empty(); + return res; } bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) @@ -3698,10 +3700,13 @@ namespace { FontProp::HorizontalAlign read_horizontal_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::HorizontalAlignToName& horizontal_align_to_name){ std::string horizontal_align_str = get_attribute_value_string(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR); - // FIX of baked transformation + // Back compatibility + // PS 2.6.0 do not have align if (horizontal_align_str.empty()) return FontProp::HorizontalAlign::center; + // Back compatibility + // PS 2.6.1 store indices(0|1|2) instead of text for align if (horizontal_align_str.length() == 1) { int horizontal_align_int = 0; if(boost::spirit::qi::parse(horizontal_align_str.c_str(), horizontal_align_str.c_str() + 1, boost::spirit::qi::int_, horizontal_align_int)) @@ -3711,12 +3716,11 @@ FontProp::HorizontalAlign read_horizontal_align(const char **attributes, unsigne return bimap_cvt(horizontal_align_to_name, std::string_view(horizontal_align_str), FontProp::HorizontalAlign::center); } - // IMPROVE: check if volume was modified (translated, rotated OR scaled) FontProp::VerticalAlign read_vertical_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::VerticalAlignToName& vertical_align_to_name){ std::string vertical_align_str = get_attribute_value_string(attributes, num_attributes, VERTICAL_ALIGN_ATTR); - // when no change do not calculate transformation only store original fix matrix - // Create transformation used after load actual stored volume + // Back compatibility + // PS 2.6.0 do not have align if (vertical_align_str.empty()) return FontProp::VerticalAlign::center; @@ -3730,7 +3734,7 @@ FontProp::VerticalAlign read_vertical_align(const char **attributes, unsigned in return bimap_cvt(vertical_align_to_name, std::string_view(vertical_align_str), FontProp::VerticalAlign::center); } -} +} // namespace std::optional TextConfigurationSerialization::read(const char **attributes, unsigned int num_attributes) { diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index cc41cd5..ebba27e 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -41,7 +41,8 @@ namespace { std::string to_ini(const ConfMap &m) { std::string ret; - for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; + for (auto ¶m : m) + ret += param.first + " = " + param.second + "\n"; return ret; } diff --git a/src/libslic3r/Format/ZipperArchiveImport.cpp b/src/libslic3r/Format/ZipperArchiveImport.cpp index 657e420..97195d1 100644 --- a/src/libslic3r/Format/ZipperArchiveImport.cpp +++ b/src/libslic3r/Format/ZipperArchiveImport.cpp @@ -135,7 +135,7 @@ std::pair extract_profile( // as output argument then replace it with the readed profile to report // that it was empty. profile_use = profile_in.empty() ? profile_out : profile_in; - profile_out = profile_in; + profile_out += std::move(profile_in); return {profile_use, std::move(config_substitutions)}; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6ecaadf..8acce2b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -12,6 +12,7 @@ #include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" #include "GCode/WipeTowerIntegration.hpp" +#include "GCode/Travels.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -212,7 +213,7 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &write this->parser.set("zhop", this->opt_zhop); } -void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer) +void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer, const WipeTowerData& wipe_tower_data) { memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3); this->opt_position->values = this->position; @@ -229,7 +230,18 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const for (const Extruder &e : extruders) { this->e_retracted[e.id()] = e.retracted(); this->e_restart_extra[e.id()] = e.restart_extra(); - double v = e.extruded_volume(); + // Wipe tower filament consumption has to be added separately, because that gcode is not generated by GCodeWriter. + double wt_vol = 0.; + const std::vector>>& wtuf = wipe_tower_data.used_filament_until_layer; + if (!wtuf.empty()) { + auto it = std::lower_bound(wtuf.begin(), wtuf.end(), writer.get_position().z(), + [](const auto& a, const float& val) { return a.first < val; }); + if (it == wtuf.end()) + it = wtuf.end() - 1; + wt_vol = it->second[e.id()] * e.filament_crossection(); + } + + double v = e.extruded_volume() + wt_vol; double w = v * e.filament_density() * 0.001; this->opt_extruded_volume->values[e.id()] = v; this->opt_extruded_weight->values[e.id()] = w; @@ -416,7 +428,7 @@ std::vector> GCodeGener return layers_to_print; } -// free functions called by GCode::do_export() +// free functions called by GCodeGenerator::do_export() namespace DoExport { // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) // { @@ -521,6 +533,28 @@ namespace DoExport { } } // namespace DoExport +GCodeGenerator::GCodeGenerator(const Print* print) : + m_origin(Vec2d::Zero()), + m_enable_loop_clipping(true), + m_enable_cooling_markers(false), + m_enable_extrusion_role_markers(false), + m_last_processor_extrusion_role(GCodeExtrusionRole::None), + m_layer_count(0), + m_layer_index(-1), + m_layer(nullptr), + m_object_layer_over_raft(false), + m_volumetric_speed(0), + m_last_extrusion_role(GCodeExtrusionRole::None), + m_last_width(0.0f), +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_last_mm3_per_mm(0.0), +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_brim_done(false), + m_second_layer_things_done(false), + m_silent_time_estimator_enabled(false), + m_current_instance({nullptr, -1}), + m_print(print) + {} void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) { CNumericLocalesSetter locales_setter; @@ -617,7 +651,7 @@ void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorRes print->set_done(psGCodeExport); } -// free functions called by GCode::_do_export() +// free functions called by GCodeGenerator::_do_export() namespace DoExport { static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { @@ -718,8 +752,8 @@ namespace DoExport { print_statistics.printing_extruders.emplace_back(extruder.id()); filament_types.emplace_back(config.filament_type.get_at(extruder.id())); - double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); - double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament_until_layer.back().second[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament_until_layer.back().second[extruder.id()] * extruder.filament_crossection() : 0.f); // assumes 1.75mm filament diameter double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001; auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { @@ -871,71 +905,23 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION)); // config data - encode_full_config(print, binary_data.slicer_metadata.raw_data); + encode_full_config(*m_print, binary_data.slicer_metadata.raw_data); - // printer data - binary_data.printer_metadata.raw_data.emplace_back("printer_model", print.config().printer_model.value); // duplicated into config data - std::string filament_types_str; - for (size_t i = 0; i < print.config().filament_type.values.size(); ++i) { - filament_types_str += print.config().filament_type.values[i]; - if (i < print.config().filament_type.values.size() - 1) - filament_types_str += ";"; + // printer data - this section contains duplicates from the slicer metadata + // that we just created. Find and copy the entries that we want to duplicate. + const auto& slicer_metadata = binary_data.slicer_metadata.raw_data; + const std::vector keys_to_duplicate = { "printer_model", "filament_type", "nozzle_diameter", "bed_temperature", + "brim_width", "fill_density", "layer_height", "temperature", "ironing", "support_material", "extruder_colour" }; + assert(std::is_sorted(slicer_metadata.begin(), slicer_metadata.end(), + [](const auto& a, const auto& b) { return a.first < b.first; })); + for (const std::string& key : keys_to_duplicate) { + auto it = std::lower_bound(slicer_metadata.begin(), slicer_metadata.end(), std::make_pair(key, 0), + [](const auto& a, const auto& b) { return a.first < b.first; }); + if (it != slicer_metadata.end() && it->first == key) + binary_data.printer_metadata.raw_data.emplace_back(*it); } - binary_data.printer_metadata.raw_data.emplace_back("filament_type", filament_types_str); // duplicated into config data - char buf[1024]; - std::string nozzle_diameters_str; - for (size_t i = 0; i < print.config().nozzle_diameter.values.size(); ++i) { - sprintf(buf, i < print.config().nozzle_diameter.values.size() - 1 ? "%.2g," : "%.2g", print.config().nozzle_diameter.values[i]); - nozzle_diameters_str += buf; } - binary_data.printer_metadata.raw_data.emplace_back("nozzle_diameter", nozzle_diameters_str); // duplicated into config data - std::string bed_temperatures_str; - for (size_t i = 0; i < print.config().bed_temperature.values.size(); ++i) { - sprintf(buf, i < print.config().bed_temperature.values.size() - 1 ? "%d," : "%d", print.config().bed_temperature.values[i]); - bed_temperatures_str += buf; - } - binary_data.printer_metadata.raw_data.emplace_back("bed_temperature", bed_temperatures_str); // duplicated into config data - const DynamicPrintConfig& cfg = print.full_print_config(); - if (auto opt = cfg.option("brim_width"); opt != nullptr) { - sprintf(buf, "%.2g", dynamic_cast(opt)->value); - binary_data.printer_metadata.raw_data.emplace_back("brim_width", buf); // duplicated into config data - } - if (auto opt = cfg.option("fill_density"); opt != nullptr) { - sprintf(buf, "%.2g%%", dynamic_cast(opt)->value); - binary_data.printer_metadata.raw_data.emplace_back("fill_density", buf); // duplicated into config data - } - if (auto opt = cfg.option("layer_height"); opt != nullptr) { - sprintf(buf, "%.2g", dynamic_cast(opt)->value); - binary_data.printer_metadata.raw_data.emplace_back("layer_height", buf); // duplicated into config data - } - if (auto opt = cfg.option("temperature"); opt != nullptr) { - auto values = dynamic_cast(opt)->values; - std::string temperatures_str; - for (size_t i = 0; i < values.size(); ++i) { - sprintf(buf, i < values.size() - 1 ? "%d," : "%d", values[i]); - temperatures_str += buf; - } - binary_data.printer_metadata.raw_data.emplace_back("temperature", temperatures_str); // duplicated into config data - } - if (auto opt = cfg.option("ironing"); opt != nullptr) - binary_data.printer_metadata.raw_data.emplace_back("ironing", dynamic_cast(opt)->value ? "1" : "0"); // duplicated into config data - if (auto opt = cfg.option("support_material"); opt != nullptr) - binary_data.printer_metadata.raw_data.emplace_back("support_material", dynamic_cast(opt)->value ? "1" : "0"); // duplicated into config data - if (auto opt = cfg.option("extruder_colour"); opt != nullptr) { - auto values = dynamic_cast(opt)->values; - std::string extruder_colours_str; - if (values.size() == 1 && values.front().empty()) - extruder_colours_str = "\"\""; - else { - for (size_t i = 0; i < values.size(); ++i) { - sprintf(buf, i < values.size() - 1 ? "%s;" : "%s", values[i].c_str()); - extruder_colours_str += buf; - } - } - binary_data.printer_metadata.raw_data.emplace_back("extruder_colour", extruder_colours_str); // duplicated into config data - } - } // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); @@ -953,7 +939,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_last_mm3_per_mm = 0.; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - // How many times will be change_layer() called? + // How many times will be change_layer() called?gcode.cpp // change_layer() in turn increments the progress bar status. m_layer_count = 0; if (print.config().complete_objects.value) { @@ -1244,7 +1230,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); file.write(this->retract_and_wipe()); - file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); + file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1280,7 +1266,10 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // QIDI Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower = std::make_unique(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); - file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); + // Set position for wipe tower generation. + Vec3d new_position = this->writer().get_position(); + new_position.z() = first_layer_height; + this->writer().update_position(new_position); if (print.config().single_extruder_multi_material_priming) { file.write(m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. @@ -1434,7 +1423,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail { file.write("\n; qidislicer_config = begin\n"); std::string full_config; - append_full_config(print, full_config); + append_full_config(*m_print, full_config); if (!full_config.empty()) file.write(full_config); file.write("; qidislicer_config = end\n"); @@ -1683,14 +1672,14 @@ std::string GCodeGenerator::placeholder_parser_process( #endif PlaceholderParserIntegration &ppi = m_placeholder_parser_integration; try { - ppi.update_from_gcodewriter(m_writer); + ppi.update_from_gcodewriter(m_writer, m_print->wipe_tower_data()); std::string output = ppi.parser.process(templ, current_extruder_id, config_override, &ppi.output_config, &ppi.context); ppi.validate_output_vector_variables(); if (const std::vector &pos = ppi.opt_position->values; ppi.position != pos) { // Update G-code writer. m_writer.update_position({ pos[0], pos[1], pos[2] }); - this->set_last_pos(this->gcode_to_point({ pos[0], pos[1] })); + this->last_position = this->gcode_to_point({ pos[0], pos[1] }); } for (const Extruder &e : m_writer.extruders()) { @@ -1789,7 +1778,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &print) +void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, const Print &print) { const GCodeFlavor flavor = print.config().gcode_flavor.value; if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware) @@ -1850,7 +1839,7 @@ void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &prin // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait -void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { bool autoemit = print.config().autoemit_temperature_commands; // Initial bed temperature based on the first extruder. @@ -1872,7 +1861,7 @@ void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait // RepRapFirmware: G10 Sxx -void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { bool autoemit = print.config().autoemit_temperature_commands; // Is the bed temperature set by the provided custom G-code? @@ -1950,85 +1939,90 @@ std::vector GCodeGenerator::sort_print_object_i namespace ProcessLayer { - static std::string emit_custom_gcode_per_print_z( +static std::string emit_custom_color_change_gcode_per_print_z( GCodeGenerator &gcodegen, - const CustomGCode::Item *custom_gcode, + const CustomGCode::Item &custom_gcode, unsigned int current_extruder_id, - // ID of the first extruder printing this layer. - unsigned int first_extruder_id, - const PrintConfig &config) - { - std::string gcode; - bool single_extruder_printer = config.nozzle_diameter.size() == 1; + unsigned int first_extruder_id, // ID of the first extruder printing this layer. + const PrintConfig &config +) { + const bool single_extruder_multi_material = config.single_extruder_multi_material; + const bool single_extruder_printer = config.nozzle_diameter.size() == 1; + const bool color_change = custom_gcode.type == CustomGCode::ColorChange; - if (custom_gcode != nullptr) { - // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->type != CustomGCode::ToolChange); + std::string gcode; - CustomGCode::Type gcode_type = custom_gcode->type; - bool color_change = gcode_type == CustomGCode::ColorChange; - bool tool_change = gcode_type == CustomGCode::ToolChange; - // Tool Change is applied as Color Change for a single extruder printer only. - assert(! tool_change || single_extruder_printer); + int color_change_extruder = -1; + if (color_change && custom_gcode.extruder > 0) + color_change_extruder = custom_gcode.extruder - 1; - std::string pause_print_msg; - int m600_extruder_before_layer = -1; - if (color_change && custom_gcode->extruder > 0) - m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (gcode_type == CustomGCode::PausePrint) - pause_print_msg = custom_gcode->extra; - - // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count - if (color_change || tool_change) - { - assert(m600_extruder_before_layer >= 0); + assert(color_change_extruder >= 0); // Color Change or Tool Change as Color Change. // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(m600_extruder_before_layer) + "," + custom_gcode->color + "\n"; + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(color_change_extruder) + "," + custom_gcode.color + "\n"; - if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer - // && !MMU1 - ) { + DynamicConfig cfg; + cfg.set_key_value("color_change_extruder", new ConfigOptionInt(color_change_extruder)); + if (single_extruder_multi_material && !single_extruder_printer && color_change_extruder >= 0 && first_extruder_id != unsigned(color_change_extruder)) { //! FIXME_in_fw show message during print pause - // FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? Why is that not - // passed to color_change_gcode below? - DynamicConfig cfg; - cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer)); + // FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg); gcode += "\n"; - gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; - } - else { - gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id); + gcode += "M117 Change filament for Extruder " + std::to_string(color_change_extruder) + "\n"; + } else { + gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id, &cfg); gcode += "\n"; //FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after // return from M600. Thus the G-code generated by the following line is ignored. // see GH issue #6362 gcodegen.writer().unretract(); } + return gcode; } - else { - if (gcode_type == CustomGCode::PausePrint) // Pause print + static std::string emit_custom_gcode_per_print_z( + GCodeGenerator &gcodegen, + const CustomGCode::Item &custom_gcode, + unsigned int current_extruder_id, + // ID of the first extruder printing this layer. + unsigned int first_extruder_id, + const PrintConfig &config) { + std::string gcode; + + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode.type != CustomGCode::ToolChange); + + CustomGCode::Type gcode_type = custom_gcode.type; + const bool color_change = gcode_type == CustomGCode::ColorChange; + const bool tool_change = gcode_type == CustomGCode::ToolChange; + // Tool Change is applied as Color Change for a single extruder printer only. + assert(!tool_change || config.nozzle_diameter.size() == 1); + + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (color_change || tool_change) { + gcode += emit_custom_color_change_gcode_per_print_z(gcodegen, custom_gcode, current_extruder_id, first_extruder_id, config); + } else { + if (gcode_type == CustomGCode::PausePrint) { // Pause print + const std::string pause_print_msg = custom_gcode.extra; // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n"; //! FIXME_in_fw show message during print pause if (!pause_print_msg.empty()) gcode += "M117 " + pause_print_msg + "\n"; - gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id); - } - else { + DynamicConfig cfg; + cfg.set_key_value("color_change_extruder", new ConfigOptionInt(int(current_extruder_id))); + gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg); + } else { // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n"; if (gcode_type == CustomGCode::Template) // Template Custom Gcode gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); else // custom Gcode - gcode += custom_gcode->extra; + gcode += custom_gcode.extra; } gcode += "\n"; } - } return gcode; } @@ -2113,6 +2107,51 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector& } return false; } +std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) { + const Polyline xy_path{ + this->gcode_to_point(from.head<2>()), + this->gcode_to_point(to.head<2>()) + }; + + using namespace GCode::Impl::Travels; + + ElevatedTravelParams elevation_params{ + get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)}; + + const double initial_elevation = from.z(); + const double z_change = to.z() - from.z(); + elevation_params.lift_height = std::max(z_change, elevation_params.lift_height); + + const double path_length = unscaled(xy_path.length()); + const double lift_at_travel_end = std::min( + elevation_params.lift_height, + elevation_params.lift_height / elevation_params.slope_end * path_length + ); + if (lift_at_travel_end < z_change) { + elevation_params.lift_height = z_change; + elevation_params.slope_end = path_length; + } + + const std::vector ensure_points_at_distances = linspace( + elevation_params.slope_end - elevation_params.blend_width / 2.0, + elevation_params.slope_end + elevation_params.blend_width / 2.0, + elevation_params.parabola_points_count + ); + + Points3 travel{generate_elevated_travel( + xy_path.points, ensure_points_at_distances, initial_elevation, + ElevatedTravelFormula{elevation_params} + )}; + + std::string travel_gcode; + Vec3d previous_point{this->point_to_gcode(travel.front())}; + for (const Vec3crd& point : travel) { + const Vec3d gcode_point{this->point_to_gcode(point)}; + travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change"); + previous_point = gcode_point; + } + return travel_gcode; +} // In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. @@ -2156,7 +2195,7 @@ LayerResult GCodeGenerator::process_layer( return result; // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z; + coordf_t print_z = layer.print_z + m_config.z_offset.value; bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); @@ -2203,9 +2242,11 @@ LayerResult GCodeGenerator::process_layer( m_last_layer_z = static_cast(print_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_last_height = height; + m_current_layer_first_position = std::nullopt; + m_already_unretracted = false; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. - if (! print.config().before_layer_gcode.value.empty()) { + if (!first_layer && ! print.config().before_layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); @@ -2216,11 +2257,10 @@ LayerResult GCodeGenerator::process_layer( } gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index m_layer = &layer; - if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) { - this->m_previous_layer_distancer = GCode::Impl::get_expolygons_distancer(m_layer->lower_layer->lslices); - } + if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) + m_travel_obstacle_tracker.init_layer(layer, layers); m_object_layer_over_raft = false; - if (! print.config().layer_gcode.value.empty()) { + if (!first_layer && ! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); @@ -2254,10 +2294,6 @@ LayerResult GCodeGenerator::process_layer( // Map from extruder ID to index of skirt loops to be extruded with that extruder. std::map> skirt_loops_per_extruder; - if (single_object_instance_idx == size_t(-1)) { - // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); - } // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. skirt_loops_per_extruder = first_layer ? @@ -2276,6 +2312,22 @@ LayerResult GCodeGenerator::process_layer( } } + const bool has_custom_gcode_to_emit = single_object_instance_idx == size_t(-1) && layer_tools.custom_gcode != nullptr; + const int extruder_id_for_custom_gcode = int(layer_tools.extruder_needed_for_color_changer) - 1; + + if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == -1) { + // Normal (non-sequential) print with some custom code without picking a specific extruder before it. + // If we don't need to pick a specific extruder before the color change, we can just emit a custom g-code. + // Otherwise, we will emit the g-code after picking the specific extruder. + + std::string custom_gcode = ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + if (layer_tools.custom_gcode->type == CustomGCode::ColorChange) { + // We have a color change to do on this layer, but we want to do it immediately before the first extrusion instead of now, in order to fix GH #2672. + m_pending_pre_extrusion_gcode = custom_gcode; + } else { + gcode += custom_gcode; + } + } // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) @@ -2288,6 +2340,12 @@ LayerResult GCodeGenerator::process_layer( if (layer_tools.has_wipe_tower && m_wipe_tower) m_last_processor_extrusion_role = GCodeExtrusionRole::WipeTower; + if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_id)) { + assert(m_writer.extruder()->id() == extruder_id_for_custom_gcode); + assert(m_pending_pre_extrusion_gcode.empty()); + // Now we have picked the right extruder, so we can emit the custom g-code. + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + } if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; this->set_origin(0., 0.); @@ -2343,6 +2401,71 @@ LayerResult GCodeGenerator::process_layer( is_anything_overridden, false /* print_wipe_extrusions */); } + // During layer change the starting position of next layer is now known. + // The solution is thus to emplace a temporary tag to the gcode, cache the postion and + // replace the tag later. The tag is Layer_Change_Travel, the cached position is + // m_current_layer_first_position and it is replaced here. + const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); + std::string layer_change_gcode; + const bool do_ramping_layer_change = ( + m_previous_layer_last_position + && m_current_layer_first_position + && m_layer_change_extruder_id + && !result.spiral_vase_enable + && print_z > previous_layer_z + && this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) + && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) > 0 + && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) < 90 + ); + if (first_layer) { + layer_change_gcode = ""; // Explicit for readability. + } else if (do_ramping_layer_change) { + layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id); + } else { + layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); + } + + const auto keep_retraciton{[&](){ + if (!do_ramping_layer_change) { + return true; + } + const double travel_length{(*m_current_layer_first_position - *m_previous_layer_last_position_before_wipe).norm()}; + if (this->m_config.retract_before_travel.get_at(*m_layer_change_extruder_id) < travel_length) { + // Travel is long, keep retraction. + return true; + } + return false; + }}; + + bool removed_retraction{false}; + if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) && !result.spiral_vase_enable) { + const std::string retraction_start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); + const std::string retraction_end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); + + if (keep_retraciton()) { + boost::algorithm::replace_first(gcode, retraction_start_tag, ""); + boost::algorithm::replace_first(gcode, retraction_end_tag, ""); + } else { + const std::size_t start{gcode.find(retraction_start_tag)}; + const std::size_t end_tag_start{gcode.find(retraction_end_tag)}; + const std::size_t end{end_tag_start + retraction_end_tag.size()}; + gcode.replace(start, end - start, ""); + + layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id); + + removed_retraction = true; + } + } + + if (removed_retraction) { + const std::size_t start{gcode.find("FIRST_UNRETRACT")}; + const std::size_t end{gcode.find("\n", start)}; + gcode.replace(start, end - start, ""); + } else { + boost::algorithm::replace_first(gcode,"FIRST_UNRETRACT", ""); + } + + boost::algorithm::replace_first(gcode, tag, layer_change_gcode); BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2376,9 +2499,8 @@ void GCodeGenerator::process_layer_single_object( const bool print_wipe_extrusions) { bool first = true; - int object_id = 0; // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first,&object_id, & gcode]() { + auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() { if (first) { first = false; const PrintObject &print_object = print_instance.print_object; @@ -2389,32 +2511,12 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.init_layer(*m_layer); // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; - std::pair this_object_copy(&print_object, offset); - if (m_last_obj_copy != this_object_copy) + GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; + if (m_current_instance != next_instance) m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; + m_current_instance = next_instance; this->set_origin(unscale(offset)); - // gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], - // GCode::LabelObjects::IncludeName::No); - if ((this->config().gcode_label_objects) != LabelObjectsStyle::Disabled ) { - for (const PrintObject *po : print_object.print()->objects()) - if (po == &print_object) - break; - else - ++ object_id; - //B38 //B41 //B46 - const LabelData &label = m_label_data.at(&print_instance.print_object.instances()[print_instance.instance_id]); - if ((this->config().gcode_label_objects) == LabelObjectsStyle::Octoprint) - gcode += std::string("; printing object ") + label.name + "\n"; - else if ((this->config().gcode_label_objects) == LabelObjectsStyle::Firmware) { - if (this->config().gcode_flavor == gcfKlipper) { - m_writer.set_object_start_str(std::string("EXCLUDE_OBJECT_START NAME=") + label.name + "\n"); - } else if (this->config().gcode_flavor == gcfMarlinFirmware || this->config().gcode_flavor == gcfMarlinLegacy || - this->config().gcode_flavor == gcfRepRapFirmware) { - gcode += std::string("M486 S") + std::to_string(label.unique_id) + "\n"; - } - } - } + gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); } }; @@ -2522,9 +2624,10 @@ void GCodeGenerator::process_layer_single_object( init_layer_delayed(); m_config.apply(region.config()); const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, &m_last_pos)) + const Point* start_near = this->last_position ? &(*(this->last_position)) : nullptr; + for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, start_near)) if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { - for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, &m_last_pos, fill.flipped())) + for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped())) gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name); } else gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name); @@ -2553,9 +2656,11 @@ void GCodeGenerator::process_layer_single_object( init_layer_delayed(); m_config.apply(region.config()); } - for (const ExtrusionEntity *ee : *eec) + for (const ExtrusionEntity *ee : *eec) { // Don't reorder, don't flip. gcode += this->extrude_entity({ *ee, false }, smooth_path_cache, comment_perimeter, -1.); + m_travel_obstacle_tracker.mark_extruded(ee, print_instance.object_layer_to_print_id, print_instance.instance_id); + } } } }; @@ -2593,25 +2698,8 @@ void GCodeGenerator::process_layer_single_object( } } - if (!first && ((this->config().gcode_label_objects) != LabelObjectsStyle::Disabled)) { - //B38 //B41 //B46 - const LabelData &label = m_label_data.at(&print_instance.print_object.instances()[print_instance.instance_id]); - - if ((this->config().gcode_label_objects) == LabelObjectsStyle::Octoprint) - gcode += std::string("; stop printing object ") + label.name + "\n"; - else if ((this->config().gcode_label_objects) == LabelObjectsStyle::Firmware) { - if (this->config().gcode_flavor == gcfKlipper) { - if (!m_writer.is_object_start_str_empty()) { - m_writer.set_object_start_str(""); - } else { - m_writer.set_object_end_str(std::string("EXCLUDE_OBJECT_END NAME=") + label.name + "\n"); - } - } else if (this->config().gcode_flavor == gcfMarlinFirmware || this->config().gcode_flavor == gcfMarlinLegacy || - this->config().gcode_flavor == gcfRepRapFirmware) { - gcode += std::string("M486 S-1\n"); - } - } - } + if (! first) + gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]); } @@ -2667,7 +2755,8 @@ void GCodeGenerator::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left const auto offset = Point::new_scale(m_origin - pointf); - m_last_pos += offset; + if (last_position.has_value()) + *(this->last_position) += offset; m_wipe.offset_path(offset); m_origin = pointf; } @@ -2685,146 +2774,41 @@ std::string GCodeGenerator::preamble() return gcode; } -namespace GCode::Impl { -Polygon generate_regular_polygon( - const Point& centroid, - const Point& start_point, - const unsigned points_count -) { - Points points; - points.reserve(points_count); - const double part_angle{2*M_PI / points_count}; - for (unsigned i = 0; i < points_count; ++i) { - const double current_angle{i * part_angle}; - points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle))); - } - - Polygon regular_polygon{points}; - const Vec2d current_vector{unscaled(regular_polygon.points.front())}; - const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)}; - - const double current_scale = current_vector.norm(); - const double expected_scale = expected_vector.norm(); - regular_polygon.scale(expected_scale / current_scale); - - regular_polygon.rotate(angle(current_vector, expected_vector)); - - regular_polygon.translate(centroid); - - return regular_polygon; -} -Bed::Bed(const std::vector& shape, const double padding): - inner_offset(get_inner_offset(shape, padding)), - centroid(unscaled(inner_offset.centroid())) -{} - -bool Bed::contains_within_padding(const Vec2d& point) const { - return inner_offset.contains(scaled(point)); -} -Polygon Bed::get_inner_offset(const std::vector& shape, const double padding) { - Points shape_scaled; - shape_scaled.reserve(shape.size()); - using std::begin, std::end, std::back_inserter, std::transform; - transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d& point){ - return scaled(point); - }); - Polygons inner_offset{shrink({Polygon{shape_scaled}}, scaled(padding))}; - if (inner_offset.empty()) { - return Polygon{}; - } - return inner_offset.front(); -} - } - -std::optional GCodeGenerator::get_helical_layer_change_gcode( - const coordf_t previous_layer_z, - const coordf_t print_z, - const std::string& comment -) { - - if (!this->last_pos_defined()) { - return std::nullopt; - } - const double circle_radius{2}; - const unsigned n_gon_points_count{16}; - - const Point n_gon_start_point{this->last_pos()}; - - GCode::Impl::Bed bed{ - this->m_config.bed_shape.values, - circle_radius * 2 - }; - if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) { - return std::nullopt; - } - - const Vec2crd n_gon_vector{scaled(Vec2d{ - (bed.centroid - this->point_to_gcode(n_gon_start_point)).normalized() * circle_radius - })}; - const Point n_gon_centeroid{n_gon_start_point + n_gon_vector}; - - const Polygon n_gon{GCode::Impl::generate_regular_polygon( - n_gon_centeroid, - n_gon_start_point, - n_gon_points_count - )}; - - const double n_gon_circumference = unscaled(n_gon.length()); - - const double z_change{print_z - previous_layer_z}; - Points3 helix{GCode::Impl::generate_elevated_travel( - n_gon.points, - {}, - previous_layer_z, - [&](const double distance){ - return distance / n_gon_circumference * z_change; - } - )}; - helix.emplace_back(to_3d(this->last_pos(), scaled(print_z))); - - return this->generate_travel_gcode(helix, comment); -} // called by GCodeGenerator::process_layer() std::string GCodeGenerator::change_layer( coordf_t previous_layer_z, coordf_t print_z, - const bool spiral_vase_enabled + bool vase_mode ) { std::string gcode; if (m_layer_count > 0) // Increment a progress bar indicator. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); - if (EXTRUDER_CONFIG(retract_layer_change)) + if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) { gcode += this->retract_and_wipe(); + } else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){ + m_previous_layer_last_position_before_wipe = this->last_position ? + std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : + std::nullopt; + gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); + gcode += this->retract_and_wipe(); + gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); + } - const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"}; + Vec3d new_position = this->writer().get_position(); + new_position.z() = print_z; + this->writer().update_position(new_position); //B38 //B46 m_writer.add_object_change_labels(gcode); - bool do_helical_layer_change{ - !spiral_vase_enabled - && print_z > previous_layer_z - && EXTRUDER_CONFIG(retract_layer_change) - && EXTRUDER_CONFIG(retract_length) > 0 - && EXTRUDER_CONFIG(travel_ramping_lift) - && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90 - }; - const std::optional helix_gcode{ - do_helical_layer_change ? - this->get_helical_layer_change_gcode( - m_config.z_offset.value + previous_layer_z, - m_config.z_offset.value + print_z, - comment - ) : - std::nullopt - }; - gcode += ( - helix_gcode ? - *helix_gcode : - m_writer.travel_to_z(m_config.z_offset.value + print_z, comment) - ); + m_previous_layer_last_position = this->last_position ? + std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : + std::nullopt; + + gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); + this->m_layer_change_extruder_id = m_writer.extruder()->id(); // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); @@ -2848,10 +2832,10 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC // Extrude all loops CCW. bool is_hole = loop_src.is_clockwise(); - Point seam_point = this->last_pos(); + Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); if (! m_config.spiral_vase && comment_is_perimeter(description)) { assert(m_layer != nullptr); - seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, this->last_pos()); + seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, seam_point); } // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, // thus empty path segments will not be produced by G-code export. @@ -2893,7 +2877,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC // Generate the seam hiding travel move. gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); - this->set_last_pos(*pt); + this->last_position = *pt; } } @@ -2904,8 +2888,9 @@ std::string GCodeGenerator::extrude_skirt( const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) { assert(loop_src.is_counter_clockwise()); + Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( - loop_src, false, m_scaled_resolution, this->last_pos(), scaled(0.0015)); + loop_src, false, m_scaled_resolution, seam_point, scaled(0.0015)); // Clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case. @@ -3079,6 +3064,51 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } +std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z) { + std::string gcode; + + const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); + + double lift{ + EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) : + EXTRUDER_CONFIG(retract_lift)}; + const double upper_limit = EXTRUDER_CONFIG(retract_lift_below); + const double lower_limit = EXTRUDER_CONFIG(retract_lift_above); + if ((lower_limit > 0 && gcode_point.z() < lower_limit) || + (upper_limit > 0 && gcode_point.z() > upper_limit)) { + lift = 0.0; + } + + if (EXTRUDER_CONFIG(retract_length) > 0 && (!this->last_position || (!EXTRUDER_CONFIG(travel_ramping_lift)))) { + if (!this->last_position || EXTRUDER_CONFIG(retract_before_travel) < (this->point_to_gcode(*this->last_position) - gcode_point.head<2>()).norm()) { + gcode += this->writer().retract(); + gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift"); + } + } + this->last_position = point.head<2>(); + this->writer().update_position(gcode_point); + + std::string comment{"move to first layer point"}; + gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment); + gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), comment); + + m_current_layer_first_position = gcode_point; + return gcode; +} + +double cap_speed( + double speed, const double mm3_per_mm, const FullPrintConfig &config, int extruder_id +) { + const double general_cap{config.max_volumetric_speed.value}; + if (general_cap > 0) { + speed = std::min(speed, general_cap / mm3_per_mm); + } + const double filament_cap{config.filament_max_volumetric_speed.get_at(extruder_id)}; + if (filament_cap > 0) { + speed = std::min(speed, filament_cap / mm3_per_mm); + } + return speed; +} std::string GCodeGenerator::_extrude( const ExtrusionAttributes &path_attr, const Geometry::ArcWelder::Path &path, @@ -3088,26 +3118,43 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; + if (!m_current_layer_first_position) { + const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); + gcode += this->travel_to_first_position(point, unscaled(point.z())); + } else { // go to first point of extrusion path - if (!m_last_pos_defined) { - const double z = this->m_last_layer_z + this->m_config.z_offset.value; + if (!this->last_position) { + const double z = this->m_last_layer_z; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); - } else if ( m_last_pos != path.front().point) { + } else if ( this->last_position != path.front().point) { std::string comment = "move to first "; comment += description; comment += description_bridge; comment += " point"; - gcode += this->travel_to(path.front().point, path_attr.role, comment); + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)}; + gcode += travel_gcode; + } } //B38 //B46 m_writer.add_object_change_labels(gcode); // compensate retraction + if (this->m_already_unretracted) { gcode += this->unretract(); + } else { + this->m_already_unretracted = true; + gcode += "FIRST_UNRETRACT" + this->unretract(); + } + + if (!m_pending_pre_extrusion_gcode.empty()) { + // There is G-Code that is due to be inserted before an extrusion starts. Insert it. + gcode += m_pending_pre_extrusion_gcode; + m_pending_pre_extrusion_gcode.clear(); + } // adjust acceleration if (m_config.default_acceleration.value > 0) { @@ -3174,20 +3221,6 @@ std::string GCodeGenerator::_extrude( m_config.get_abs_value("first_layer_speed", speed); else if (this->object_layer_over_raft()) speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); - if (m_config.max_volumetric_speed.value > 0) { - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min( - speed, - m_config.max_volumetric_speed.value / path_attr.mm3_per_mm - ); - } - if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min( - speed, - EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm - ); - } std::pair dynamic_speed_and_fan_speed{-1, -1}; if (path_attr.overhang_attributes.has_value()) { @@ -3195,13 +3228,9 @@ std::string GCodeGenerator::_extrude( double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed"); if (external_perim_reference_speed == 0) external_perim_reference_speed = m_volumetric_speed / path_attr.mm3_per_mm; - if (m_config.max_volumetric_speed.value > 0) - external_perim_reference_speed = std::min(external_perim_reference_speed, - m_config.max_volumetric_speed.value / path_attr.mm3_per_mm); - if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { - external_perim_reference_speed = std::min(external_perim_reference_speed, - EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm); - } + external_perim_reference_speed = cap_speed( + external_perim_reference_speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id() + ); dynamic_speed_and_fan_speed = ExtrusionProcessor::calculate_overhang_speed(path_attr, this->m_config, m_writer.extruder()->id(), external_perim_reference_speed, speed); @@ -3211,6 +3240,8 @@ std::string GCodeGenerator::_extrude( speed = dynamic_speed_and_fan_speed.first; } + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id()); double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line @@ -3276,7 +3307,6 @@ std::string GCodeGenerator::_extrude( gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); if (dynamic_speed_and_fan_speed.second >= 0) gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_speed_and_fan_speed.second)) + "\n"; - double path_length = 0.; std::string comment; if (m_config.gcode_comments) { comment = description; @@ -3310,15 +3340,12 @@ std::string GCodeGenerator::_extrude( } if (radius == 0) { // Extrude line segment. - if (const double line_length = (p - prev).norm(); line_length > 0) { - path_length += line_length; + if (const double line_length = (p - prev).norm(); line_length > 0) gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); - } } else { double angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); assert(angle > 0); const double line_length = angle * std::abs(radius); - path_length += line_length; const double dE = e_per_mm * line_length; assert(dE > 0); gcode += m_writer.extrude_to_xy_G2G3IJ(p, ij, it->ccw(), dE, comment); @@ -3334,210 +3361,10 @@ std::string GCodeGenerator::_extrude( if (dynamic_speed_and_fan_speed.second >= 0) gcode += ";_RESET_FAN_SPEED\n"; - this->set_last_pos(path.back().point); + this->last_position = path.back().point; return gcode; } -Points3 generate_flat_travel(tcb::span xy_path, const float elevation) { - Points3 result; - result.reserve(xy_path.size() - 1); - for (const Point& point : xy_path.subspan(1)) { - result.emplace_back(point.x(), point.y(), scaled(elevation)); - } - return result; -} -Vec2d place_at_segment(const Vec2d& current_point, const Vec2d& previous_point, const double distance) { - Vec2d direction = (current_point - previous_point).normalized(); - return previous_point + direction * distance; -} - -namespace GCode::Impl { -std::vector slice_xy_path(tcb::span xy_path, tcb::span sorted_distances) { - assert(xy_path.size() >= 2); - std::vector result; - result.reserve(xy_path.size() + sorted_distances.size()); - double total_distance{0}; - result.emplace_back(DistancedPoint{xy_path.front(), 0}); - Point previous_point = result.front().point; - std::size_t offset{0}; - for (const Point& point : xy_path.subspan(1)) { - Vec2d unscaled_point{unscaled(point)}; - Vec2d unscaled_previous_point{unscaled(previous_point)}; - const double current_segment_length = (unscaled_point - unscaled_previous_point).norm(); - for (const double distance_to_add : sorted_distances.subspan(offset)) { - if (distance_to_add <= total_distance + current_segment_length) { - Point to_place = scaled(place_at_segment( - unscaled_point, - unscaled_previous_point, - distance_to_add - total_distance - )); - if (to_place != previous_point && to_place != point) { - result.emplace_back(DistancedPoint{to_place, distance_to_add}); - } - ++offset; - } else { - break; - } - } - total_distance += current_segment_length; - result.emplace_back(DistancedPoint{point, total_distance}); - previous_point = point; - } - return result; -} - -struct ElevatedTravelParams { - double lift_height{}; - double slope_end{}; -}; - -struct ElevatedTravelFormula { - double operator()(double distance_from_start) const { - if (distance_from_start < this->params.slope_end) { - const double lift_percent = distance_from_start / this->params.slope_end; - return lift_percent * this->params.lift_height; - } else { - return this->params.lift_height; - } - } - - ElevatedTravelParams params{}; -}; - -Points3 generate_elevated_travel( - const tcb::span xy_path, - const std::vector& ensure_points_at_distances, - const double initial_elevation, - const std::function& elevation -) { - Points3 result{}; - - std::vector extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances); - result.reserve(extended_xy_path.size()); - - for (const DistancedPoint& point : extended_xy_path) { - result.emplace_back(point.point.x(), point.point.y(), scaled(initial_elevation + elevation(point.distance_from_start))); - } - - return result; -} -AABBTreeLines::LinesDistancer get_expolygons_distancer(const ExPolygons& polygons) { - std::vector lines; - for (const ExPolygon& polygon : polygons) { - for (const Line& line : polygon.lines()) { - lines.emplace_back(unscaled(line.a), unscaled(line.b)); - } - } - return AABBTreeLines::LinesDistancer{std::move(lines)}; - } - -std::optional get_first_crossed_line_distance( - tcb::span xy_path, - const AABBTreeLines::LinesDistancer& distancer -) { - assert(!xy_path.empty()); - if (xy_path.empty()) { - return {}; - } - double traversed_distance = 0; - for (const Line& line : xy_path) { - const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)}; - auto intersections = distancer.intersections_with_line(unscaled_line); - if (!intersections.empty()) { - const Vec2d intersection = intersections.front().first; - const double distance = traversed_distance + (unscaled_line.a - intersection).norm(); - if (distance > EPSILON) { - return distance; - } else if (intersections.size() >= 2) { // Edge case - const Vec2d second_intersection = intersections[1].first; - return traversed_distance + (unscaled_line.a - second_intersection).norm(); - } - } - traversed_distance += (unscaled_line.a - unscaled_line.b).norm(); - } - - return {}; -} -std::optional get_obstacle_adjusted_slope_end( - const Lines& xy_path, - const std::optional>& previous_layer_distancer -) { - if (!previous_layer_distancer) { - return std::nullopt; - } - std::optional first_obstacle_distance = get_first_crossed_line_distance( - xy_path, *previous_layer_distancer - ); - if (!first_obstacle_distance) { - return std::nullopt; - } - return *first_obstacle_distance; -} - -ElevatedTravelParams get_elevated_traval_params( - const Lines& xy_path, - const FullPrintConfig& config, - const unsigned extruder_id, - const std::optional>& previous_layer_distancer -) { - ElevatedTravelParams elevation_params{}; - if (!config.travel_ramping_lift.get_at(extruder_id)) { - elevation_params.slope_end = 0; - elevation_params.lift_height = config.retract_lift.get_at(extruder_id); - return elevation_params; - } - elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id); - const double slope_deg = config.travel_slope.get_at(extruder_id); - if (slope_deg >= 90 || slope_deg <= 0) { - elevation_params.slope_end = 0; - } else { - const double slope_rad = slope_deg * (M_PI / 180); // rad - elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); - } - std::optional obstacle_adjusted_slope_end{get_obstacle_adjusted_slope_end( - xy_path, - previous_layer_distancer - )}; - if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { - elevation_params.slope_end = *obstacle_adjusted_slope_end; - } - - return elevation_params; -} -Points3 generate_travel_to_extrusion( - const Polyline& xy_path, - const FullPrintConfig& config, - const unsigned extruder_id, - const double initial_elevation, - const std::optional>& previous_layer_distancer -) { - const double upper_limit = config.retract_lift_below.get_at(extruder_id); - const double lower_limit = config.retract_lift_above.get_at(extruder_id); - if ( - (lower_limit > 0 && initial_elevation < lower_limit) - || (upper_limit > 0 && initial_elevation > upper_limit) - ) { - return generate_flat_travel(xy_path.points, initial_elevation); - } - ElevatedTravelParams elevation_params{get_elevated_traval_params( - xy_path.lines(), - config, - extruder_id, - previous_layer_distancer - )}; - - const std::vector ensure_points_at_distances{elevation_params.slope_end}; - - Points3 result{generate_elevated_travel( - xy_path.points, - ensure_points_at_distances, - initial_elevation, - ElevatedTravelFormula{elevation_params} - )}; - result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation)); - return result; -} -} std::string GCodeGenerator::generate_travel_gcode( const Points3& travel, @@ -3555,9 +3382,13 @@ std::string GCodeGenerator::generate_travel_gcode( gcode += this->m_writer.set_travel_acceleration(acceleration); + Vec3d previous_point{this->point_to_gcode(travel.front())}; for (const Vec3crd& point : travel) { - gcode += this->m_writer.travel_to_xyz(to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())), comment); - this->set_last_pos(point.head<2>()); + const Vec3d gcode_point{this->point_to_gcode(point)}; + + gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment); + this->last_position = point.head<2>(); + previous_point = gcode_point; } if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { @@ -3649,20 +3480,17 @@ Polyline GCodeGenerator::generate_travel_xy_path( } // This method accepts &point in print coordinates. -std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment) -{ - - const Point start_point = this->last_pos(); - - using namespace GCode::Impl; +std::string GCodeGenerator::travel_to( + const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment +) { // check whether a straight travel move would need retraction bool could_be_wipe_disabled {false}; - bool needs_retraction = this->needs_retraction(Polyline{start_point, point}, role); + bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role); Polyline xy_path{generate_travel_xy_path( - start_point, point, needs_retraction, could_be_wipe_disabled + start_point, end_point, needs_retraction, could_be_wipe_disabled )}; needs_retraction = this->needs_retraction(xy_path, role); @@ -3673,12 +3501,12 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st m_wipe.reset_path(); } - Point position_before_wipe{this->last_pos()}; + Point position_before_wipe{*this->last_position}; wipe_retract_gcode = this->retract_and_wipe(); - if (this->last_pos() != position_before_wipe) { + if (*this->last_position != position_before_wipe) { xy_path = generate_travel_xy_path( - this->last_pos(), point, needs_retraction, could_be_wipe_disabled + *this->last_position, end_point, needs_retraction, could_be_wipe_disabled ); } } else { @@ -3692,16 +3520,24 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st const unsigned extruder_id = this->m_writer.extruder()->id(); const double retract_length = this->m_config.retract_length.get_at(extruder_id); bool can_be_flat{!needs_retraction || retract_length == 0}; - const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value; + const double initial_elevation = this->m_last_layer_z; + + const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id); + const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id); + if ((lower_limit > 0 && initial_elevation < lower_limit) || + (upper_limit > 0 && initial_elevation > upper_limit)) { + can_be_flat = true; + } const Points3 travel = ( can_be_flat ? - generate_flat_travel(xy_path.points, initial_elevation) : - GCode::Impl::generate_travel_to_extrusion( + GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : + GCode::Impl::Travels::generate_travel_to_extrusion( xy_path, - this->m_config, + m_config, extruder_id, initial_elevation, - this->m_previous_layer_distancer + m_travel_obstacle_tracker, + scaled(m_origin) ) ); @@ -3834,7 +3670,10 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); - this->m_last_pos_defined = false; + + // The position is now known after the tool change. + this->last_position = std::nullopt; + return gcode; } //B41 diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 28cdc7b..8cdec7d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -24,8 +24,9 @@ #include "GCode/WipeTowerIntegration.hpp" #include "GCode/SeamPlacer.hpp" #include "GCode/GCodeProcessor.hpp" -#include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" +#include "GCode/Travels.hpp" +#include "EdgeGrid.hpp" #include "tcbspan/span.hpp" #include @@ -38,6 +39,7 @@ namespace Slic3r { // Forward declarations. class GCodeGenerator; +struct WipeTowerData; namespace { struct Item; } struct PrintInstance; @@ -75,136 +77,33 @@ struct LayerResult { static LayerResult make_nop_layer_result() { return {"", std::numeric_limits::max(), false, false, true}; } }; -namespace GCode::Impl { -struct DistancedPoint { - Point point; - double distance_from_start; +namespace GCode { +// Object and support extrusions of the same PrintObject at the same print_z. +// public, so that it could be accessed by free helper functions from GCode.cpp +struct ObjectLayerToPrint +{ + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + const Layer* object_layer; + const SupportLayer* support_layer; + const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } + const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } + coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; -/** - * @brief Takes a path described as a list of points and adds points to it. - * - * @param xy_path A list of points describing a path in xy. - * @param sorted_distances A sorted list of distances along the path. - * @return Sliced path. - * - * The algorithm travels along the path segments and adds points to - * the segments in such a way that the points have specified distances - * from the xy_path start. **Any distances over the xy_path end will - * be simply ignored.** - * - * Example usage - simplified for clarity: - * @code - * std::vector distances{0.5, 1.5}; - * std::vector xy_path{{0, 0}, {1, 0}}; - * // produces - * {{0, 0}, {0, 0.5}, {1, 0}} - * // notice that 1.5 is omitted - * @endcode - */ -std::vector slice_xy_path(tcb::span xy_path, tcb::span sorted_distances); +struct PrintObjectInstance +{ + const PrintObject *print_object = nullptr; + int instance_idx = -1; -/** - * @brief Take xy_path and genrate a travel acording to elevation. - * - * @param xy_path A list of points describing a path in xy. - * @param ensure_points_at_distances See slice_xy_path sorted_distances. - * @param elevation A function taking current distance in mm as input and returning elevation in mm as output. - * - * **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are in - * scaled coordinates. - */ -Points3 generate_elevated_travel( - const tcb::span xy_path, - const std::vector& ensure_points_at_distances, - const double initial_elevation, - const std::function& elevation -); - -/** - * @brief Takes a list o polygons and builds a AABBTree over all unscaled lines. - * - * @param polygons A list of polygons. - * @return AABB Tree over all lines of the polygons. - * - * Unscales the lines in the process! - */ -AABBTreeLines::LinesDistancer get_expolygons_distancer(const ExPolygons& polygons); - -/** - * @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start. - * - * @param xy_path A path in 2D. - * @param distancer AABB Tree over lines. - * @return Distance to the first intersection if there is one. - * - * **Ignores intersection with xy_path starting point.** - */ -std::optional get_first_crossed_line_distance( - tcb::span xy_path, - const AABBTreeLines::LinesDistancer& distancer -); - - -/** - * Generates a regular polygon - all angles are the same (e.g. typical hexagon). - * - * @param centroid Central point. - * @param start_point The polygon point are ordered. This is the first point. - * @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon). - * - * Distance between centroid and start point sets the scale of the polygon. - */ -Polygon generate_regular_polygon( - const Point& centroid, - const Point& start_point, - const unsigned points_count -); - -class Bed { - private: - Polygon inner_offset; - static Polygon get_inner_offset(const std::vector& shape, const double padding); - - public: - /** - * Bed shape with inner padding. - */ - Bed(const std::vector& shape, const double padding); - - Vec2d centroid; - - /** - * Returns true if the point is within the bed shape including inner padding. - */ - bool contains_within_padding(const Vec2d& point) const; + bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; } + bool operator!=(const PrintObjectInstance &other) const { return *this == other; } }; -} + +} // namespace GCode class GCodeGenerator { public: - GCodeGenerator() : - m_origin(Vec2d::Zero()), - m_enable_loop_clipping(true), - m_enable_cooling_markers(false), - m_enable_extrusion_role_markers(false), - m_last_processor_extrusion_role(GCodeExtrusionRole::None), - m_layer_count(0), - m_layer_index(-1), - m_layer(nullptr), - m_object_layer_over_raft(false), - m_volumetric_speed(0), - m_last_pos_defined(false), - m_last_extrusion_role(GCodeExtrusionRole::None), - m_last_width(0.0f), -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_last_mm3_per_mm(0.0), -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - m_brim_done(false), - m_second_layer_things_done(false), - m_silent_time_estimator_enabled(false), - m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) - {} + GCodeGenerator(const Print* print = nullptr); // The default value is only used in unit tests. ~GCodeGenerator() = default; // throws std::runtime_exception on error, @@ -215,13 +114,25 @@ public: const Vec2d& origin() const { return m_origin; } void set_origin(const Vec2d &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } - const Point& last_pos() const { return m_last_pos; } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. template - Vec2d point_to_gcode(const Eigen::MatrixBase &point) const { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector"); + Eigen::Matrix point_to_gcode(const Eigen::MatrixBase &point) const { + static_assert( + Derived::IsVectorAtCompileTime, + "GCodeGenerator::point_to_gcode(): first parameter is not a vector" + ); + static_assert( + int(Derived::SizeAtCompileTime) == 2 || int(Derived::SizeAtCompileTime) == 3, + "GCodeGenerator::point_to_gcode(): first parameter is not a 2D or 3D vector" + ); + + if constexpr (Derived::SizeAtCompileTime == 2) { return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin - m_config.extruder_offset.get_at(m_writer.extruder()->id()); + } else { + const Vec2d gcode_point_xy{this->point_to_gcode(point.template head<2>())}; + return to_3d(gcode_point_xy, unscaled(point.z())); + } } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. template @@ -242,8 +153,6 @@ public: std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr); bool enable_cooling_markers() const { return m_enable_cooling_markers; } - // For Perl bindings, to be used exclusively by unit tests. - unsigned int layer_count() const { return m_layer_count; } void set_layer_count(unsigned int value) { m_layer_count = value; } void apply_print_config(const PrintConfig &print_config); @@ -252,18 +161,10 @@ public: // translate full config into a list of items static void encode_full_config(const Print& print, std::vector>& config); - // Object and support extrusions of the same PrintObject at the same print_z. - // public, so that it could be accessed by free helper functions from GCode.cpp - struct ObjectLayerToPrint - { - ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} - const Layer* object_layer; - const SupportLayer* support_layer; - const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } - const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } - coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } - }; - using ObjectsLayerToPrint = std::vector; + using ObjectLayerToPrint = GCode::ObjectLayerToPrint; + using ObjectsLayerToPrint = std::vector; + + std::optional last_position; private: class GCodeOutputStream { @@ -309,6 +210,8 @@ private: static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); static std::vector> collect_layers_to_print(const Print &print); + /** @brief Generates ramping travel gcode for layer change. */ + std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id); LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -342,19 +245,12 @@ private: const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream); - void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } - bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); - std::optional get_helical_layer_change_gcode( - const coordf_t previous_layer_z, - const coordf_t print_z, - const std::string& comment - ); std::string change_layer( coordf_t previous_layer_z, coordf_t print_z, - const bool spiral_vase_enabled + bool vase_mode ); std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); @@ -414,8 +310,14 @@ private: const bool needs_retraction, bool& could_be_wipe_disabled ); + std::string travel_to( + const Point &start_point, + const Point &end_point, + ExtrusionRole role, + const std::string &comment + ); - std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); + std::string travel_to_first_position(const Vec3crd& point, const double from_z); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); //B41 @@ -446,7 +348,7 @@ private: struct PlaceholderParserIntegration { void reset(); void init(const GCodeWriter &config); - void update_from_gcodewriter(const GCodeWriter &writer); + void update_from_gcodewriter(const GCodeWriter &writer, const WipeTowerData& wipe_tower_data); void validate_output_vector_variables(); PlaceholderParser parser; @@ -479,6 +381,7 @@ private: AvoidCrossingPerimeters m_avoid_crossing_perimeters; JPSPathFinder m_avoid_crossing_curled_overhangs; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; + GCode::TravelObstacleTracker m_travel_obstacle_tracker; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END @@ -498,7 +401,6 @@ private: // In non-sequential mode, all its copies will be printed. const Layer* m_layer; // m_layer is an object layer and it is being printed over raft surface. - std::optional> m_previous_layer_distancer; bool m_object_layer_over_raft; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? @@ -512,8 +414,12 @@ private: double m_last_mm3_per_mm; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - Point m_last_pos; - bool m_last_pos_defined; + std::optional m_previous_layer_last_position; + std::optional m_previous_layer_last_position_before_wipe; + // This needs to be populated during the layer processing! + std::optional m_current_layer_first_position; + std::optional m_layer_change_extruder_id; + bool m_already_unretracted{false}; std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; @@ -527,19 +433,23 @@ private: bool m_brim_done; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. bool m_second_layer_things_done; - // Index of a last object copy extruded. - std::pair m_last_obj_copy; + // G-code that is due to be written before the next extrusion + std::string m_pending_pre_extrusion_gcode; + // Pointer to currently exporting PrintObject and instance index. + GCode::PrintObjectInstance m_current_instance; bool m_silent_time_estimator_enabled; // Processor GCodeProcessor m_processor; + // Back-pointer to Print (const). + const Print* m_print; std::string _extrude( const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1); - void print_machine_envelope(GCodeOutputStream &file, Print &print); - void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void print_machine_envelope(GCodeOutputStream &file, const Print &print); + void _print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 72e717c..9f26105 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1173,7 +1173,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons // Otherwise perform the path planning in the coordinate system of the active object. bool use_external = m_use_external_mp || m_use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - const Point start = gcodegen.last_pos() + scaled_origin; + const Point start = *gcodegen.last_position + scaled_origin; const Point end = point + scaled_origin; const Line travel(start, end); @@ -1470,7 +1470,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index cc86fa9..e223850 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -54,6 +54,9 @@ const std::vector GCodeProcessor::Reserved_Tags = { "HEIGHT:", "WIDTH:", "LAYER_CHANGE", + "LAYER_CHANGE_TRAVEL", + "LAYER_CHANGE_RETRACTION_START", + "LAYER_CHANGE_RETRACTION_END", "COLOR_CHANGE", "PAUSE_PRINT", "CUSTOM_GCODE", diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a0ed388..3664437 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -185,6 +185,9 @@ namespace Slic3r { Height, Width, Layer_Change, + Layer_Change_Travel, + Layer_Change_Retraction_Start, + Layer_Change_Retraction_End, Color_Change, Pause_Print, Custom_Code, diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index 18f81a2..5fc08a3 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -316,9 +316,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con return w.string(); } -std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) +std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const { - m_pos.head<2>() = point.head<2>(); GCodeG1Formatter w; w.emit_xy(point); @@ -330,6 +329,11 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view return w.string(); } +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) +{ + m_pos.head<2>() = point.head<2>(); + return this->get_travel_to_xy_gcode(point, comment); +} std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment) { assert(std::abs(point.x()) < 1200.); @@ -347,33 +351,53 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij return w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment) { - if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) { - return this->travel_to_z(point.z(), comment); - } else if (std::abs(point.z() - m_pos.z()) < EPSILON) { - return this->travel_to_xy(point.head<2>(), comment); + if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) { + return this->travel_to_z(to.z(), comment); + } else if (std::abs(to.z() - m_pos.z()) < EPSILON) { + return this->travel_to_xy(to.head<2>(), comment); } else { - m_pos = point; + m_pos = to; + return this->get_travel_to_xyz_gcode(from, to, comment); + } +} +std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const { GCodeG1Formatter w; - w.emit_xyz(point); - Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value}; - w.emit_f(speed.norm() * 60.0); + w.emit_xyz(to); + + double speed_z = this->config.travel_speed_z.value; + if (speed_z == 0.) + speed_z = this->config.travel_speed.value; + + const double distance_xy{(to.head<2>() - from.head<2>()).norm()}; + const double distnace_z{std::abs(to.z() - from.z())}; + const double time_z = distnace_z / speed_z; + const double time_xy = distance_xy / this->config.travel_speed.value; + const double factor = time_z > 0 ? time_xy / time_z : 1; + if (factor < 1) { + w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0); + } else { + w.emit_f(this->config.travel_speed.value * 60.0); + } w.emit_comment(this->config.gcode_comments, comment); return w.string(); } - } std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) { - return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment); + if (std::abs(m_pos.z() - z) < EPSILON) { + return ""; + } else { + m_pos.z() = z; + return this->get_travel_to_z_gcode(z, comment); + } } -std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) +std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const { - m_pos.z() = z; double speed = this->config.travel_speed_z.value; if (speed == 0.) diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index e846d0b..ed0e826 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -66,10 +66,21 @@ public: std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const; + std::string get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const; std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {}); std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {}); - std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {}); - std::string get_travel_to_z_gcode(double z, const std::string_view comment); + /** + * @brief Return gcode with all three axis defined. Optionally adds feedrate. + * + * Feedrate is added the starting point "from" is specified. + * + * @param from Optional starting point of the travel. + * @param to Where to travel to. + * @param comment Description of the travel purpose. + */ + std::string get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const; + std::string travel_to_xyz(const Vec3d &from, const Vec3d &to, const std::string_view comment = {}); + std::string get_travel_to_z_gcode(double z, const std::string_view comment) const; std::string travel_to_z(double z, const std::string_view comment = {}); std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); @@ -224,7 +235,8 @@ public: } void emit_string(const std::string_view s) { - strncpy(ptr_err.ptr, s.data(), s.size()); + // Be aware that std::string_view::data() returns a pointer to a buffer that is not necessarily null-terminated. + memcpy(ptr_err.ptr, s.data(), s.size()); ptr_err.ptr += s.size(); } diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 1e5953e..0692ee9 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -81,7 +81,8 @@ void LabelObjects::init(const Print& print) if (object_has_more_instances) name += " (Instance " + std::to_string(instance_id) + ")"; if (m_flavor == gcfKlipper) { - const std::string banned = "-. \r\n\v\t\f"; + // Disallow Klipper special chars, common illegal filename chars, etc. + const std::string banned = "\b\t\n\v\f\r \"#%&\'*-./:;<>\\"; std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_'); } } @@ -111,7 +112,7 @@ std::string LabelObjects::all_objects_header() const for (const auto& [print_instance, label] : label_data_sorted) { if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) { char buffer[64]; - out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name; + out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'"; Polygon outline = instance_outline(print_instance); assert(! outline.empty()); outline.douglas_peucker(50000.f); @@ -154,7 +155,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl } out += "\n"; } else if (m_flavor == gcfKlipper) - out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n"; + out += "EXCLUDE_OBJECT_START NAME='" + label.name + "'\n"; else { // Not supported by / implemented for the other firmware flavors. } @@ -178,7 +179,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) out += std::string("M486 S-1\n"); else if (m_flavor ==gcfKlipper) - out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n"; + out += "EXCLUDE_OBJECT_END NAME='" + label.name + "'\n"; else { // Not supported by / implemented for the other firmware flavors. } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index f13e8bf..461b346 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -105,7 +105,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); // Collect extruders required to print the layers. - this->collect_extruders(object, std::vector>()); + this->collect_extruders(object, std::vector>(), std::vector>()); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -151,17 +151,24 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool // Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object. // Do it only if all the objects were configured to be printed with a single extruder. std::vector> per_layer_extruder_switches; - if (auto num_extruders = unsigned(print.config().nozzle_diameter.size()); - num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle + auto num_extruders = unsigned(print.config().nozzle_diameter.size()); + if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) { // Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material). // There may be custom per-layer tool changes available at the model. per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders); } - // Collect extruders reuqired to print the layers. + // Color changes for each layer to determine which extruder needs to be picked before color change. + // This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers). + std::vector> per_layer_color_changes; + if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) { + per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders); + } + + // Collect extruders required to print the layers. for (auto object : print.objects()) - this->collect_extruders(*object, per_layer_extruder_switches); + this->collect_extruders(*object, per_layer_extruder_switches, per_layer_color_changes); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -214,8 +221,11 @@ void ToolOrdering::initialize_layers(std::vector &zs) } // Collect extruders reuqired to print layers. -void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) -{ +void ToolOrdering::collect_extruders( + const PrintObject &object, + const std::vector> &per_layer_extruder_switches, + const std::vector> &per_layer_color_changes +) { // Collect the support extruders. for (auto support_layer : object.support_layers()) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); @@ -233,10 +243,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } // Extruder overrides are ordered by print_z. - std::vector>::const_iterator it_per_layer_extruder_override; - it_per_layer_extruder_override = per_layer_extruder_switches.begin(); + std::vector>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin(); unsigned int extruder_override = 0; + std::vector>::const_iterator it_per_layer_color_changes = per_layer_color_changes.begin(); // Collect the object extruders. for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); @@ -248,6 +258,14 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it. layer_tools.extruder_override = extruder_override; + // Append the extruder needed to be picked before performing the color change. + for (; it_per_layer_color_changes != per_layer_color_changes.end() && it_per_layer_color_changes->first < layer->print_z + EPSILON; ++it_per_layer_color_changes) { + if (std::abs(it_per_layer_color_changes->first - layer->print_z) < EPSILON) { + assert(layer_tools.extruder_needed_for_color_changer == 0); // Just on color change per layer is allowed. + layer_tools.extruder_needed_for_color_changer = it_per_layer_color_changes->second; + layer_tools.extruders.emplace_back(it_per_layer_color_changes->second); + } + } // What extruders are required to print this object layer? for (const LayerRegion *layerm : layer->regions()) { const PrintRegion ®ion = layerm->region(); @@ -365,6 +383,11 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) std::swap(lt.extruders[i], lt.extruders.front()); break; } + } else if (lt.extruder_needed_for_color_changer != 0) { + // Put the extruder needed for performing the color change at the beginning. + auto it = std::find(lt.extruders.begin(), lt.extruders.end(), lt.extruder_needed_for_color_changer); + assert(it != lt.extruders.end()); + std::rotate(lt.extruders.begin(), it, it + 1); } } last_extruder_id = lt.extruders.back(); diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 4cc62b6..d0e483b 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -94,6 +94,9 @@ public: // If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with. // If not overriden, it is set to 0. unsigned int extruder_override = 0; + // For multi-extruder printers, when there is a color change, this contains an extruder (1 based) on which the color change will be performed. + // Otherwise, it is set to 0. + unsigned int extruder_needed_for_color_changer = 0; // Should a skirt be printed at this layer? // Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed. bool has_skirt = false; @@ -165,7 +168,7 @@ public: private: void initialize_layers(std::vector &zs); - void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); + void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches, const std::vector> &per_layer_color_changes); void reorder_extruders(unsigned int last_extruder_id); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height); bool insert_wipe_tower_extruder(); diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp new file mode 100644 index 0000000..6795444 --- /dev/null +++ b/src/libslic3r/GCode/Travels.cpp @@ -0,0 +1,444 @@ +#include "Travels.hpp" + +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Print.hpp" + +#include "../GCode.hpp" + +namespace Slic3r::GCode { + +static Lines extrusion_entity_to_lines(const ExtrusionEntity &e_entity) +{ + if (const auto *path = dynamic_cast(&e_entity)) { + return to_lines(path->as_polyline()); + } else if (const auto *multipath = dynamic_cast(&e_entity)) { + return to_lines(multipath->as_polyline()); + } else if (const auto *loop = dynamic_cast(&e_entity)) { + return to_lines(loop->polygon()); + } else { + throw Slic3r::InvalidArgument("Invalid argument supplied to TODO()"); + } + + return {}; +} + +AABBTreeLines::LinesDistancer get_previous_layer_distancer( + const GCodeGenerator::ObjectsLayerToPrint &objects_to_print, const ExPolygons &slices +) { + std::vector lines; + for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) { + if (const PrintObject *object = object_to_print.object(); object) { + const size_t object_layer_idx = &object_to_print - &objects_to_print.front(); + for (const PrintInstance &instance : object->instances()) { + const size_t instance_idx = &instance - &object->instances().front(); + for (const ExPolygon &polygon : slices) + for (const Line &line : polygon.lines()) + lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx); + } + } + } + + return AABBTreeLines::LinesDistancer{std::move(lines)}; +} + +std::pair, size_t> get_current_layer_distancer(const ObjectsLayerToPrint &objects_to_print) +{ + std::vector lines; + size_t extrusion_entity_cnt = 0; + for (const ObjectLayerToPrint &object_to_print : objects_to_print) { + const size_t object_layer_idx = &object_to_print - &objects_to_print.front(); + if (const Layer *layer = object_to_print.object_layer; layer) { + for (const PrintInstance &instance : layer->object()->instances()) { + const size_t instance_idx = &instance - &layer->object()->instances().front(); + for (const LayerSlice &lslice : layer->lslices_ex) { + for (const LayerIsland &island : lslice.islands) { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + for (uint32_t perimeter_id : island.perimeters) { + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); + const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + for (const ExtrusionEntity *ee : *eec) { + if (ee->role().is_external_perimeter()) { + for (const Line &line : extrusion_entity_to_lines(*ee)) + lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx, ee); + } + + ++extrusion_entity_cnt; + } + } + } + } + } + } + } + + return {AABBTreeLines::LinesDistancer{std::move(lines)}, extrusion_entity_cnt}; +} + +void TravelObstacleTracker::init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print) +{ + size_t extrusion_entity_cnt = 0; + m_extruded_extrusion.clear(); + + m_objects_to_print = objects_to_print; + m_previous_layer_distancer = get_previous_layer_distancer(m_objects_to_print, layer.lower_layer->lslices); + + std::tie(m_current_layer_distancer, extrusion_entity_cnt) = get_current_layer_distancer(m_objects_to_print); + m_extruded_extrusion.reserve(extrusion_entity_cnt); +} + +void TravelObstacleTracker::mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx) +{ + if (extrusion_entity->role().is_external_perimeter()) + this->m_extruded_extrusion.insert({int(object_layer_idx), int(instance_idx), extrusion_entity}); +} + +bool TravelObstacleTracker::is_extruded(const ObjectOrExtrusionLinef &line) const +{ + return m_extruded_extrusion.find({line.object_layer_idx, line.instance_idx, line.extrusion_entity}) != m_extruded_extrusion.end(); +} + +} // namespace Slic3r::GCode + +namespace Slic3r::GCode::Impl::Travels { + +ElevatedTravelFormula::ElevatedTravelFormula(const ElevatedTravelParams ¶ms) + : smoothing_from(params.slope_end - params.blend_width / 2.0) + , smoothing_to(params.slope_end + params.blend_width / 2.0) + , blend_width(params.blend_width) + , lift_height(params.lift_height) + , slope_end(params.slope_end) { + if (smoothing_from < 0) { + smoothing_from = params.slope_end; + smoothing_to = params.slope_end; + } +} + +double parabola(const double x, const double a, const double b, const double c) { + return a * x * x + b * x + c; +} + +double ElevatedTravelFormula::slope_function(double distance_from_start) const { + if (distance_from_start < this->slope_end) { + const double lift_percent = distance_from_start / this->slope_end; + return lift_percent * this->lift_height; + } else { + return this->lift_height; + } +} + +double ElevatedTravelFormula::operator()(const double distance_from_start) const { + if (distance_from_start > this->smoothing_from && distance_from_start < this->smoothing_to) { + const double slope = this->lift_height / this->slope_end; + + // This is a part of a parabola going over a specific + // range and with specific end slopes. + const double a = -slope / 2.0 / this->blend_width; + const double b = slope * this->smoothing_to / this->blend_width; + const double c = this->lift_height + a * boost::math::pow<2>(this->smoothing_to); + return parabola(distance_from_start, a, b, c); + } + return slope_function(distance_from_start); +} + +Points3 generate_flat_travel(tcb::span xy_path, const float elevation) { + Points3 result; + result.reserve(xy_path.size()); + for (const Point &point : xy_path) { + result.emplace_back(point.x(), point.y(), scaled(elevation)); + } + return result; +} + +Vec2d place_at_segment( + const Vec2d ¤t_point, const Vec2d &previous_point, const double distance +) { + Vec2d direction = (current_point - previous_point).normalized(); + return previous_point + direction * distance; +} + +std::vector slice_xy_path( + tcb::span xy_path, tcb::span sorted_distances +) { + assert(xy_path.size() >= 2); + std::vector result; + result.reserve(xy_path.size() + sorted_distances.size()); + double total_distance{0}; + result.emplace_back(DistancedPoint{xy_path.front(), 0}); + Point previous_point = result.front().point; + std::size_t offset{0}; + for (const Point &point : xy_path.subspan(1)) { + Vec2d unscaled_point{unscaled(point)}; + Vec2d unscaled_previous_point{unscaled(previous_point)}; + const double current_segment_length = (unscaled_point - unscaled_previous_point).norm(); + for (const double distance_to_add : sorted_distances.subspan(offset)) { + if (distance_to_add <= total_distance + current_segment_length) { + Point to_place = scaled(place_at_segment( + unscaled_point, unscaled_previous_point, distance_to_add - total_distance + )); + if (to_place != previous_point && to_place != point) { + result.emplace_back(DistancedPoint{to_place, distance_to_add}); + } + ++offset; + } else { + break; + } + } + total_distance += current_segment_length; + result.emplace_back(DistancedPoint{point, total_distance}); + previous_point = point; + } + return result; +} + +Points3 generate_elevated_travel( + const tcb::span xy_path, + const std::vector &ensure_points_at_distances, + const double initial_elevation, + const std::function &elevation +) { + Points3 result{}; + + std::vector extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances); + result.reserve(extended_xy_path.size()); + + for (const DistancedPoint &point : extended_xy_path) { + result.emplace_back( + point.point.x(), point.point.y(), + scaled(initial_elevation + elevation(point.distance_from_start)) + ); + } + + return result; +} + +struct Intersection +{ + int object_layer_idx = -1; + int instance_idx = -1; + bool is_inside = false; + + bool is_print_instance_equal(const ObjectOrExtrusionLinef &print_istance) { + return this->object_layer_idx == print_istance.object_layer_idx && this->instance_idx == print_istance.instance_idx; + } +}; + +double get_first_crossed_line_distance( + tcb::span xy_path, + const AABBTreeLines::LinesDistancer &distancer, + const ObjectsLayerToPrint &objects_to_print, + const std::function &predicate, + const bool ignore_starting_object_intersection +) { + assert(!xy_path.empty()); + if (xy_path.empty()) + return std::numeric_limits::max(); + + const Point path_first_point = xy_path.front().a; + double traversed_distance = 0; + bool skip_intersection = ignore_starting_object_intersection; + Intersection first_intersection; + + for (const Line &line : xy_path) { + const ObjectOrExtrusionLinef unscaled_line = {unscaled(line.a), unscaled(line.b)}; + const std::vector> intersections = distancer.intersections_with_line(unscaled_line); + + if (intersections.empty()) + continue; + + if (!objects_to_print.empty() && ignore_starting_object_intersection && first_intersection.object_layer_idx == -1) { + const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersections.front().second); + const Point shift = objects_to_print[intersection_line.object_layer_idx].layer()->object()->instances()[intersection_line.instance_idx].shift; + const Point shifted_first_point = path_first_point - shift; + const bool contain_first_point = expolygons_contain(objects_to_print[intersection_line.object_layer_idx].layer()->lslices, shifted_first_point); + + first_intersection = {intersection_line.object_layer_idx, intersection_line.instance_idx, contain_first_point}; + } + + for (const auto &intersection : intersections) { + const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersection.second); + const double distance = traversed_distance + (unscaled_line.a - intersection.first).norm(); + if (distance <= EPSILON) + continue; + + // There is only one external border for each object, so when we cross this border, + // we definitely know that we are outside the object. + if (skip_intersection && first_intersection.is_print_instance_equal(intersection_line) && first_intersection.is_inside) { + skip_intersection = false; + continue; + } + + if (!predicate(intersection_line)) + continue; + + return distance; + } + + traversed_distance += (unscaled_line.a - unscaled_line.b).norm(); + } + + return std::numeric_limits::max(); +} + +double get_obstacle_adjusted_slope_end(const Lines &xy_path, const GCode::TravelObstacleTracker &obstacle_tracker) { + const double previous_layer_crossed_line = get_first_crossed_line_distance( + xy_path, obstacle_tracker.previous_layer_distancer(), obstacle_tracker.objects_to_print() + ); + const double current_layer_crossed_line = get_first_crossed_line_distance( + xy_path, obstacle_tracker.current_layer_distancer(), obstacle_tracker.objects_to_print(), + [&obstacle_tracker](const ObjectOrExtrusionLinef &line) { return obstacle_tracker.is_extruded(line); } + ); + + return std::min(previous_layer_crossed_line, current_layer_crossed_line); +} + +struct SmoothingParams +{ + double blend_width{}; + unsigned points_count{1}; +}; + +SmoothingParams get_smoothing_params( + const double lift_height, + const double slope_end, + unsigned extruder_id, + const double travel_length, + const FullPrintConfig &config +) { + if (config.gcode_flavor != gcfMarlinFirmware) + // Smoothing is supported only on Marlin. + return {0, 1}; + + const double slope = lift_height / slope_end; + const double max_machine_z_velocity = config.machine_max_feedrate_z.get_at(extruder_id); + const double max_xy_velocity = + Vec2d{ + config.machine_max_feedrate_x.get_at(extruder_id), + config.machine_max_feedrate_y.get_at(extruder_id)} + .norm(); + + const double xy_acceleration = config.machine_max_acceleration_travel.get_at(extruder_id); + + const double xy_acceleration_time = max_xy_velocity / xy_acceleration; + const double xy_acceleration_distance = 1.0 / 2.0 * xy_acceleration * + boost::math::pow<2>(xy_acceleration_time); + + if (travel_length < xy_acceleration_distance) { + return {0, 1}; + } + + const double max_z_velocity = std::min(max_xy_velocity * slope, max_machine_z_velocity); + const double deceleration_time = max_z_velocity / + config.machine_max_acceleration_z.get_at(extruder_id); + const double deceleration_xy_distance = deceleration_time * max_xy_velocity; + + const double blend_width = slope_end > deceleration_xy_distance / 2.0 ? deceleration_xy_distance : + slope_end * 2.0; + + const unsigned points_count = blend_width > 0 ? + std::ceil(max_z_velocity / config.machine_max_jerk_z.get_at(extruder_id)) : + 1; + + if (blend_width <= 0 // When there is no blend with, there is no need for smoothing. + || points_count > 6 // That would be way to many points. Do not do it at all. + || points_count <= 0 // Always return at least one point. + ) + return {0, 1}; + + return {blend_width, points_count}; +} + +ElevatedTravelParams get_elevated_traval_params( + const Polyline& xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const GCode::TravelObstacleTracker &obstacle_tracker +) { + ElevatedTravelParams elevation_params{}; + if (!config.travel_ramping_lift.get_at(extruder_id)) { + elevation_params.slope_end = 0; + elevation_params.lift_height = config.retract_lift.get_at(extruder_id); + elevation_params.blend_width = 0; + return elevation_params; + } + elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id); + + const double slope_deg = config.travel_slope.get_at(extruder_id); + + if (slope_deg >= 90 || slope_deg <= 0) { + elevation_params.slope_end = 0; + } else { + const double slope_rad = slope_deg * (M_PI / 180); // rad + elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); + } + + const double obstacle_adjusted_slope_end = get_obstacle_adjusted_slope_end(xy_path.lines(), obstacle_tracker); + if (obstacle_adjusted_slope_end < elevation_params.slope_end) + elevation_params.slope_end = obstacle_adjusted_slope_end; + + SmoothingParams smoothing_params{get_smoothing_params( + elevation_params.lift_height, elevation_params.slope_end, extruder_id, + unscaled(xy_path.length()), config + )}; + + elevation_params.blend_width = smoothing_params.blend_width; + elevation_params.parabola_points_count = smoothing_params.points_count; + return elevation_params; +} + +std::vector linspace(const double from, const double to, const unsigned count) { + if (count == 0) { + return {}; + } + std::vector result; + result.reserve(count); + if (count == 1) { + result.emplace_back((from + to) / 2.0); + return result; + } + const double step = (to - from) / count; + for (unsigned i = 0; i < count - 1; ++i) { + result.emplace_back(from + i * step); + } + result.emplace_back(to); // Make sure the last value is exactly equal to the value of "to". + return result; +} + +Points3 generate_travel_to_extrusion( + const Polyline &xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const double initial_elevation, + const GCode::TravelObstacleTracker &obstacle_tracker, + const Point &xy_path_coord_origin +) { + const double upper_limit = config.retract_lift_below.get_at(extruder_id); + const double lower_limit = config.retract_lift_above.get_at(extruder_id); + if ((lower_limit > 0 && initial_elevation < lower_limit) || + (upper_limit > 0 && initial_elevation > upper_limit)) { + return generate_flat_travel(xy_path.points, initial_elevation); + } + + Points global_xy_path; + for (const Point &point : xy_path.points) { + global_xy_path.emplace_back(point + xy_path_coord_origin); + } + + ElevatedTravelParams elevation_params{get_elevated_traval_params( + Polyline{std::move(global_xy_path)}, config, extruder_id, obstacle_tracker + )}; + + const std::vector ensure_points_at_distances = linspace( + elevation_params.slope_end - elevation_params.blend_width / 2.0, + elevation_params.slope_end + elevation_params.blend_width / 2.0, + elevation_params.parabola_points_count + ); + Points3 result{generate_elevated_travel( + xy_path.points, ensure_points_at_distances, initial_elevation, + ElevatedTravelFormula{elevation_params} + )}; + + result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation)); + return result; +} +} // namespace Slic3r::GCode::Impl::Travels diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp new file mode 100644 index 0000000..7f7c017 --- /dev/null +++ b/src/libslic3r/GCode/Travels.hpp @@ -0,0 +1,240 @@ +/** + * @file + * @brief Utility functions for travel gcode generation. + */ + +#ifndef slic3r_GCode_Travels_hpp_ +#define slic3r_GCode_Travels_hpp_ + +#include +#include +#include +#include + +#include +#include + +#include "libslic3r/AABBTreeLines.hpp" + +// Forward declarations. +namespace Slic3r { +class Layer; +class Point; +class Linef; +class Polyline; +class FullPrintConfig; +class ExtrusionEntity; + +} // namespace Slic3r + +namespace Slic3r::GCode { +struct ObjectLayerToPrint; +using ObjectsLayerToPrint = std::vector; + +class ObjectOrExtrusionLinef : public Linef +{ +public: + ObjectOrExtrusionLinef() = delete; + ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b) : Linef(a, b) {} + explicit ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx) + : Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)) {} + ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx, const ExtrusionEntity *extrusion_entity) + : Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)), extrusion_entity(extrusion_entity) {} + + virtual ~ObjectOrExtrusionLinef() = default; + + const int object_layer_idx = -1; + const int instance_idx = -1; + const ExtrusionEntity *extrusion_entity = nullptr; +}; + +struct ExtrudedExtrusionEntity +{ + const int object_layer_idx = -1; + const int instance_idx = -1; + const ExtrusionEntity *extrusion_entity = nullptr; + + bool operator==(const ExtrudedExtrusionEntity &other) const + { + return extrusion_entity == other.extrusion_entity && object_layer_idx == other.object_layer_idx && + instance_idx == other.instance_idx; + } +}; + +struct ExtrudedExtrusionEntityHash +{ + size_t operator()(const ExtrudedExtrusionEntity &eee) const noexcept + { + std::size_t seed = std::hash{}(eee.extrusion_entity); + boost::hash_combine(seed, std::hash{}(eee.object_layer_idx)); + boost::hash_combine(seed, std::hash{}(eee.instance_idx)); + return seed; + } +}; + +class TravelObstacleTracker +{ +public: + void init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print); + + void mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx); + + bool is_extruded(const ObjectOrExtrusionLinef &line) const; + + const AABBTreeLines::LinesDistancer &previous_layer_distancer() const { return m_previous_layer_distancer; } + + const AABBTreeLines::LinesDistancer ¤t_layer_distancer() const { return m_current_layer_distancer; } + + const ObjectsLayerToPrint &objects_to_print() const { return m_objects_to_print; } + +private: + ObjectsLayerToPrint m_objects_to_print; + AABBTreeLines::LinesDistancer m_previous_layer_distancer; + + AABBTreeLines::LinesDistancer m_current_layer_distancer; + std::unordered_set m_extruded_extrusion; +}; +} // namespace Slic3r::GCode + +namespace Slic3r::GCode::Impl::Travels { +/** + * @brief A point on a curve with a distance from start. + */ +struct DistancedPoint +{ + Point point; + double distance_from_start; +}; + +struct ElevatedTravelParams +{ + /** Maximal value of nozzle lift. */ + double lift_height{}; + + /** Distance from travel to the middle of the smoothing parabola. */ + double slope_end{}; + + /** Width of the smoothing parabola */ + double blend_width{}; + + /** How many points should be used to approximate the parabola */ + unsigned parabola_points_count{}; +}; + +/** + * @brief A mathematical formula for a smooth function. + * + * It starts lineary increasing than there is a parabola part and + * at the end it is flat. + */ +struct ElevatedTravelFormula +{ + ElevatedTravelFormula(const ElevatedTravelParams ¶ms); + double operator()(const double distance_from_start) const; + +private: + double slope_function(double distance_from_start) const; + + double smoothing_from; + double smoothing_to; + double blend_width; + double lift_height; + double slope_end; +}; + +/** + * @brief Takes a path described as a list of points and adds points to it. + * + * @param xy_path A list of points describing a path in xy. + * @param sorted_distances A sorted list of distances along the path. + * @return Sliced path. + * + * The algorithm travels along the path segments and adds points to + * the segments in such a way that the points have specified distances + * from the xy_path start. **Any distances over the xy_path end will + * be simply ignored.** + * + * Example usage - simplified for clarity: + * @code + * std::vector distances{0.5, 1.5}; + * std::vector xy_path{{0, 0}, {1, 0}}; + * // produces + * {{0, 0}, {0, 0.5}, {1, 0}} + * // notice that 1.5 is omitted + * @endcode + */ +std::vector slice_xy_path( + tcb::span xy_path, tcb::span sorted_distances +); + +/** + * @brief Generate regulary spaced points on 1 axis. Includes both from and to. + * + * If count is 1, the point is in the middle of the range. + */ +std::vector linspace(const double from, const double to, const unsigned count); + +ElevatedTravelParams get_elevated_traval_params( + const Polyline& xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const GCode::TravelObstacleTracker &obstacle_tracker +); + +/** + * @brief Simply return the xy_path with z coord set to elevation. + */ +Points3 generate_flat_travel(tcb::span xy_path, const float elevation); + +/** + * @brief Take xy_path and genrate a travel acording to elevation. + * + * @param xy_path A list of points describing a path in xy. + * @param ensure_points_at_distances See slice_xy_path sorted_distances. + * @param elevation A function taking current distance in mm as input and returning elevation in mm + * as output. + * + * **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are + * in scaled coordinates. + */ +Points3 generate_elevated_travel( + const tcb::span xy_path, + const std::vector &ensure_points_at_distances, + const double initial_elevation, + const std::function &elevation +); + +/** + * @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start. + * + * @param xy_path A path in 2D. + * @param distancer AABB Tree over lines. + * @param objects_to_print Objects to print are used to determine in which object xy_path starts. + + * @param ignore_starting_object_intersection When it is true, then the first intersection during traveling from the object out is ignored. + * @return Distance to the first intersection if there is one. + * + * **Ignores intersection with xy_path starting point.** + */ +double get_first_crossed_line_distance( + tcb::span xy_path, + const AABBTreeLines::LinesDistancer &distancer, + const ObjectsLayerToPrint &objects_to_print = {}, + const std::function &predicate = [](const ObjectOrExtrusionLinef &) { return true; }, + bool ignore_starting_object_intersection = true); + +/** + * @brief Extract parameters and decide wheather the travel can be elevated. + * Then generate the whole travel 3D path - elevated if possible. + */ +Points3 generate_travel_to_extrusion( + const Polyline &xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const double initial_elevation, + const GCode::TravelObstacleTracker &obstacle_tracker, + const Point &xy_path_coord_origin +); +} // namespace Slic3r::GCode::Impl::Travels + +#endif // slic3r_GCode_Travels_hpp_ diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index f9085ee..c5c6be0 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -21,9 +21,9 @@ void Wipe::init(const PrintConfig &config, const std::vector &extr if (config.wipe.get_at(id)) { // Wipe length to extrusion ratio. const double xy_to_e = this->calc_xy_to_e_ratio(config, id); - wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id)); + wipe_xy = std::max(wipe_xy, config.retract_length.get_at(id) / xy_to_e); if (multimaterial) - wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id)); + wipe_xy = std::max(wipe_xy, config.retract_length_toolchange.get_at(id) / xy_to_e); } if (wipe_xy == 0) @@ -37,11 +37,11 @@ void Wipe::set_path(SmoothPath &&path, bool reversed) this->reset_path(); if (this->enabled() && ! path.empty()) { - if (reversed) { + if (coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); reversed) { m_path = std::move(path.back().path); Geometry::ArcWelder::reverse(m_path); int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); - for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) { + for (auto it = std::next(path.rbegin()); len < wipe_len_max_scaled && it != path.rend(); ++ it) { if (it->path_attributes.role.is_bridge()) break; // Do not perform a wipe on bridges. assert(it->path.size() >= 2); @@ -55,7 +55,7 @@ void Wipe::set_path(SmoothPath &&path, bool reversed) } else { m_path = std::move(path.front().path); int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); - for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) { + for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) { if (it->path_attributes.role.is_bridge()) break; // Do not perform a wipe on bridges. assert(it->path.size() >= 2); @@ -185,7 +185,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) return done; }; // Start with the current position, which may be different from the wipe path start in case of loop clipping. - Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos()); + Vec2d prev = gcodegen.point_to_gcode_quantized(*gcodegen.last_position); auto it = this->path().begin(); Vec2d p = gcodegen.point_to_gcode(it->point + m_offset); ++ it; @@ -216,7 +216,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) // add tag for processor assert(p == GCodeFormatter::quantize(p)); gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; - gcodegen.set_last_pos(gcodegen.gcode_to_point(p)); + gcodegen.last_position = gcodegen.gcode_to_point(p); } } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index dcad30b..0f9c2c5 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -530,7 +530,6 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default m_no_sparse_layers(config.wipe_tower_no_sparse_layers), m_gcode_flavor(config.gcode_flavor), m_travel_speed(config.travel_speed), - m_travel_speed_z(config.travel_speed_z), m_infill_speed(default_region_config.infill_speed), m_perimeter_speed(default_region_config.perimeter_speed), m_current_tool(initial_tool), @@ -1560,8 +1559,9 @@ void WipeTower::generate(std::vector> & } } - for (auto& used : m_used_filament_length) // reset used filament stats - used = 0.f; + m_used_filament_length.assign(m_used_filament_length.size(), 0.f); // reset used filament stats + assert(m_used_filament_length_until_layer.empty()); + m_used_filament_length_until_layer.emplace_back(0.f, m_used_filament_length); m_old_temperature = -1; // reset last temperature written in the gcode @@ -1604,6 +1604,9 @@ void WipeTower::generate(std::vector> & } result.emplace_back(std::move(layer_result)); + if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z) + m_used_filament_length_until_layer.emplace_back(); + m_used_filament_length_until_layer.back() = std::make_pair(layer.z, m_used_filament_length); } } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index d28ee38..f75acf8 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -219,7 +219,7 @@ public: return m_current_layer_finished; } - std::vector get_used_filament() const { return m_used_filament_length; } + std::vector>> get_used_filament_until_layer() const { return m_used_filament_length_until_layer; } int get_number_of_toolchanges() const { return m_num_tool_changes; } struct FilamentParameters { @@ -275,7 +275,6 @@ private: size_t m_max_color_changes = 0; // Maximum number of color changes per layer. int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary) float m_travel_speed = 0.f; - float m_travel_speed_z = 0.f; float m_infill_speed = 0.f; float m_perimeter_speed = 0.f; float m_first_layer_speed = 0.f; @@ -380,6 +379,7 @@ private: // Stores information about used filament length per extruder: std::vector m_used_filament_length; + std::vector>> m_used_filament_length_until_layer; // Return index of first toolchange that switches to non-soluble extruder // ot -1 if there is no such toolchange. diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 4b51d27..2021d2b 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -57,12 +57,23 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || is_ramming || will_go_down); // don't dig into the print if (should_travel_to_tower) { + const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + const std::string comment{"Travel to a Wipe Tower"}; + if (gcodegen.m_current_layer_first_position) { + if (gcodegen.last_position) { gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - ExtrusionRole::Mixed, - "Travel to a Wipe Tower"); + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment + ); + } else { + gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); + gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); + } + } else { + const Vec3crd point = to_3d(xy_point, scaled(z)); + gcode += gcodegen.travel_to_first_position(point, current_z); + } gcode += gcodegen.unretract(); } else { // When this is multiextruder printer without any ramming, we can just change @@ -81,10 +92,16 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip if (is_ramming) gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z - if (gcodegen.config().wipe_tower) - deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z"); + if (gcodegen.config().wipe_tower) { + if (tcr.priming) { + const double return_to_z{tcr.print_z + gcodegen.config().z_offset.value}; + deretraction_str += gcodegen.writer().get_travel_to_z_gcode(return_to_z, "set priming layer Z"); + } else { + deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z"); + } deretraction_str += gcodegen.unretract(); + } } assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n'); assert(deretraction_str.empty() || deretraction_str.back() == '\n'); @@ -98,7 +115,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(end_pos.cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); + gcodegen.last_position = wipe_tower_point_to_object_point(gcodegen, end_pos); if (!is_approx(z, current_z)) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); @@ -247,7 +264,7 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) std::string gcode; if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) gcode += gcodegen.generate_travel_gcode( - {{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}}, + {{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}}, "move to safe place for purging" ); gcode += append_tcr(gcodegen, m_final_purge, -1); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp index 52b2762..231e665 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.hpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -26,7 +26,8 @@ public: m_tool_changes(tool_changes), m_final_purge(final_purge), m_layer_idx(-1), - m_tool_change_idx(0) + m_tool_change_idx(0), + m_last_wipe_tower_print_z(print_config.z_offset.value) {} std::string prime(GCodeGenerator &gcodegen); @@ -56,7 +57,7 @@ private: // Current layer index. int m_layer_idx; int m_tool_change_idx; - double m_last_wipe_tower_print_z = 0.f; + double m_last_wipe_tower_print_z; }; } // namespace GCode diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 4b02a31..0bc71ae 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -120,6 +120,7 @@ static inline bool circle_approximation_sufficient(const Circle &circle, const P return true; } +#if 0 static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation) { // The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated. @@ -148,6 +149,7 @@ static inline bool get_deviation_sum_squared(const Circle &circle, const Points: return true; } +#endif double arc_fit_variance(const Point &start_pos, const Point &end_pos, const float radius, bool is_ccw, const Points::const_iterator begin, const Points::const_iterator end) diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index 362fa9f..039d69a 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -2,6 +2,9 @@ #include "clipper.hpp" #include "VoronoiOffset.hpp" +#include "ClipperUtils.hpp" + +#include #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { @@ -463,7 +466,19 @@ void MedialAxis::build(ThickPolylines* polylines) test(l.b.y()); } #endif // NDEBUG - construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); + m_vd.construct_voronoi(m_lines.begin(), m_lines.end()); + + // For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data. + // Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution. + // Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid. + // So we filter out such thin lines and holes and try to compute the Voronoi diagram again. + if (!m_vd.is_valid()) { + m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON))); + m_vd.construct_voronoi(m_lines.begin(), m_lines.end()); + + if (!m_vd.is_valid()) + BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing."; + } Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp new file mode 100644 index 0000000..e1df732 --- /dev/null +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -0,0 +1,354 @@ +#include "Voronoi.hpp" + +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" +#include "libslic3r/Geometry/VoronoiUtils.hpp" +#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" + +#include + +namespace Slic3r::Geometry { + +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; + +// Explicit template instantiation. +template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool); +template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool); +template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool); + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type +VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) { + boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram); + if (try_to_repair_if_needed) { + if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) { + if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) { + BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) { + BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) { + BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { + BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth."; + } else { + BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth."; + } + + if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) { + if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) { + BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; + } else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) { + BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; + } else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) { + BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; + } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { + BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input."; + } else { + BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input."; + } + + m_state = State::REPAIR_UNSUCCESSFUL; + } else { + m_state = State::REPAIR_SUCCESSFUL; + } + } else { + m_state = State::REPAIR_NOT_NEEDED; + m_issue_type = IssueType::NO_ISSUE_DETECTED; + } + } else { + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; + } +} + +void VoronoiDiagram::clear() +{ + if (m_is_modified) { + m_vertices.clear(); + m_edges.clear(); + m_cells.clear(); + m_is_modified = false; + } else { + m_voronoi_diagram.clear(); + } + + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; +} + +void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) { + m_edges.clear(); + m_cells.clear(); + m_vertices.clear(); + + // Copy Voronoi edges. + m_edges.reserve(voronoi_diagram.num_edges()); + for (const edge_type &edge : voronoi_diagram.edges()) { + m_edges.emplace_back(edge.is_linear(), edge.is_primary()); + m_edges.back().color(edge.color()); + } + + // Copy Voronoi cells. + m_cells.reserve(voronoi_diagram.num_cells()); + for (const cell_type &cell : voronoi_diagram.cells()) { + m_cells.emplace_back(cell.source_index(), cell.source_category()); + m_cells.back().color(cell.color()); + + if (cell.incident_edge()) { + size_t incident_edge_idx = cell.incident_edge() - voronoi_diagram.edges().data(); + m_cells.back().incident_edge(&m_edges[incident_edge_idx]); + } + } + + // Copy Voronoi vertices. + m_vertices.reserve(voronoi_diagram.num_vertices()); + for (const vertex_type &vertex : voronoi_diagram.vertices()) { + m_vertices.emplace_back(vertex.x(), vertex.y()); + m_vertices.back().color(vertex.color()); + + if (vertex.incident_edge()) { + size_t incident_edge_idx = vertex.incident_edge() - voronoi_diagram.edges().data(); + m_vertices.back().incident_edge(&m_edges[incident_edge_idx]); + } + } + + // Assign all pointers for each Voronoi edge. + for (const edge_type &old_edge : voronoi_diagram.edges()) { + size_t edge_idx = &old_edge - voronoi_diagram.edges().data(); + edge_type &new_edge = m_edges[edge_idx]; + + if (old_edge.cell()) { + size_t cell_idx = old_edge.cell() - voronoi_diagram.cells().data(); + new_edge.cell(&m_cells[cell_idx]); + } + + if (old_edge.vertex0()) { + size_t vertex0_idx = old_edge.vertex0() - voronoi_diagram.vertices().data(); + new_edge.vertex0(&m_vertices[vertex0_idx]); + } + + if (old_edge.twin()) { + size_t twin_edge_idx = old_edge.twin() - voronoi_diagram.edges().data(); + new_edge.twin(&m_edges[twin_edge_idx]); + } + + if (old_edge.next()) { + size_t next_edge_idx = old_edge.next() - voronoi_diagram.edges().data(); + new_edge.next(&m_edges[next_edge_idx]); + } + + if (old_edge.prev()) { + size_t prev_edge_idx = old_edge.prev() - voronoi_diagram.edges().data(); + new_edge.prev(&m_edges[prev_edge_idx]); + } + } +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end) +{ + if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) { + return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX; + } else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) { + return cell_issue_type; + } else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) { + // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. + return IssueType::NON_PLANAR_VORONOI_DIAGRAM; + } + + return IssueType::NO_ISSUE_DETECTED; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, + const SegmentIterator segment_begin, + const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + using Point = typename boost::polygon::segment_point_type::type; + using SegmentCellRange = SegmentCellRange; + + for (VD::cell_type cell : voronoi_diagram.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment. + + if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) { + // Detection if Voronoi edge is intersecting input segment. + // It detects this type of issue at least in GH issues #8446, #8474 and #8514. + + const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end); + const Vec2d source_segment_from = boost::polygon::segment_traits::get(source_segment, boost::polygon::LOW).template cast(); + const Vec2d source_segment_to = boost::polygon::segment_traits::get(source_segment, boost::polygon::HIGH).template cast(); + const Vec2d source_segment_vec = source_segment_to - source_segment_from; + + // All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid. + for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) { + if (edge->is_infinite()) { + // When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge. + // This happens, for example, in GH issue #8846. + return IssueType::MISSING_VORONOI_VERTEX; + } else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) { + return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; + } + } + } else { + // When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment), + // the returned cell_range is marked as invalid. + // It detects this type of issue at least in GH issue #8846. + return IssueType::MISSING_VORONOI_VERTEX; + } + } + + return IssueType::NO_ISSUE_DETECTED; +} + +bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram) +{ + for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) { + if (edge.is_finite()) { + assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); + if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1())) + return true; + } + } + return false; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + IssueType issue_type = m_issue_type; + + const std::vector fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; + for (const double fix_angle : fix_angles) { + issue_type = try_to_repair_degenerated_voronoi_diagram_by_rotation(segment_begin, segment_end, fix_angle); + if (issue_type == IssueType::NO_ISSUE_DETECTED) { + return issue_type; + } + } + + return issue_type; +} + +inline VD::vertex_type::color_type encode_input_segment_endpoint(const VD::cell_type::source_index_type cell_source_index, const boost::polygon::direction_1d dir) +{ + return (cell_source_index + 1) << 1 | (dir.to_int() ? 1 : 0); +} + +template +inline typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type +decode_input_segment_endpoint(const VD::vertex_type::color_type color, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using SegmentType = typename std::iterator_traits::value_type; + using PointType = typename boost::polygon::segment_traits::point_type; + + const size_t segment_idx = (color >> 1) - 1; + const SegmentIterator segment_it = segment_begin + segment_idx; + const PointType source_point = boost::polygon::segment_traits::get(*segment_it, ((color & 1) ? boost::polygon::HIGH : + boost::polygon::LOW)); + return source_point; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin, + const SegmentIterator segment_end, + const double fix_angle) +{ + using SegmentType = typename std::iterator_traits::value_type; + using PointType = typename boost::polygon::segment_traits::point_type; + + // Copy all segments and rotate their vertices. + std::vector segments_rotated; + segments_rotated.reserve(std::distance(segment_begin, segment_end)); + for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) { + PointType from = boost::polygon::segment_traits::get(*segment_it, boost::polygon::LOW); + PointType to = boost::polygon::segment_traits::get(*segment_it, boost::polygon::HIGH); + segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle)); + } + + VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated; + boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated); + + this->copy_to_local(voronoi_diagram_rotated); + const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end()); + + // We want to remap all Voronoi vertices at the endpoints of input segments + // to ensure that Voronoi vertices at endpoints will be preserved after rotation. + // So we assign every Voronoi vertices color to map this Vertex into input segments. + for (cell_type cell : m_cells) { + if (cell.is_degenerate()) + continue; + + if (cell.contains_segment()) { + if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) { + if (cell_range.edge_end->vertex1()->color() == 0) { + // Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a). + VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW); + cell_range.edge_end->vertex1()->color(color); + } + + if (cell_range.edge_begin->vertex0()->color() == 0) { + // Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b). + VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH); + cell_range.edge_begin->vertex0()->color(color); + } + } else { + // This could happen when there is a missing Voronoi vertex even after rotation. + assert(cell_range.is_valid()); + } + } + + // FIXME @hejllukas: Implement mapping also for source points and not just for source segments. + } + + // Rotate all Voronoi vertices back. + // When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back. + for (vertex_type &vertex : m_vertices) { + if (vertex.color() == 0) { + // This vertex isn't mapped to any vertex, so we rotate it back. + vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle); + } else { + // This vertex can be mapped to the input segment endpoint. + PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end); + vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())}; + endpoint_vertex.incident_edge(vertex.incident_edge()); + endpoint_vertex.color(vertex.color()); + vertex = endpoint_vertex; + } + } + + // We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0. + for (vertex_type &vertex : m_vertices) + vertex.color(0); + + m_voronoi_diagram.clear(); + m_is_modified = true; + + return issue_type; +} + +} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index 5529750..8211fac 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -4,7 +4,6 @@ #include "../Line.hpp" #include "../Polyline.hpp" -#define BOOST_VORONOI_USE_GMP 1 #ifdef _MSC_VER // Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned @@ -16,18 +15,181 @@ #pragma warning(pop) #endif // _MSC_VER -namespace Slic3r { +namespace Slic3r::Geometry { -namespace Geometry { - -class VoronoiDiagram : public boost::polygon::voronoi_diagram { +class VoronoiDiagram +{ public: - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; + using coord_type = double; + using voronoi_diagram_type = boost::polygon::voronoi_diagram; + using point_type = boost::polygon::point_data; + using segment_type = boost::polygon::segment_data; + using rect_type = boost::polygon::rectangle_data; + + using coordinate_type = voronoi_diagram_type::coordinate_type; + using vertex_type = voronoi_diagram_type::vertex_type; + using edge_type = voronoi_diagram_type::edge_type; + using cell_type = voronoi_diagram_type::cell_type; + + using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator; + using const_edge_iterator = voronoi_diagram_type::const_edge_iterator; + using const_cell_iterator = voronoi_diagram_type::const_cell_iterator; + + using vertex_container_type = voronoi_diagram_type::vertex_container_type; + using edge_container_type = voronoi_diagram_type::edge_container_type; + using cell_container_type = voronoi_diagram_type::cell_container_type; + + enum class IssueType { + NO_ISSUE_DETECTED, + FINITE_EDGE_WITH_NON_FINITE_VERTEX, + MISSING_VORONOI_VERTEX, + NON_PLANAR_VORONOI_DIAGRAM, + VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, + UNKNOWN // Repairs are disabled in the constructor. + }; + + enum class State { + REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue. + REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired. + REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired. + UNKNOWN // Repairs are disabled in the constructor. + }; + + VoronoiDiagram() = default; + + virtual ~VoronoiDiagram() = default; + + IssueType get_issue_type() const { return m_issue_type; } + + State get_state() const { return m_state; } + + bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; } + + void clear(); + + const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); } + + const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); } + + const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); } + + std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); } + + std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); } + + std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_cells(); } + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type + construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true); + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type + construct_voronoi(const PointIterator first, const PointIterator last) + { + boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram); + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; + } + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_and< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::gtl_if::value_type>::type>::type>::type>::type, + void>::type + construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last) + { + boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram); + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; + } + + // Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram + // is not planar or some Voronoi edge is intersecting input segment. + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + IssueType>::type + detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type + try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle); + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type + try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end); + +private: + struct Segment + { + Point from; + Point to; + + Segment() = delete; + explicit Segment(const Point &from, const Point &to) : from(from), to(to) {} + }; + + void copy_to_local(voronoi_diagram_type &voronoi_diagram); + + // Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells. + // The first type of issue that can be detected is a missing Voronoi vertex, especially when it is + // missing at one of the endpoints of the input segment. + // The second type of issue that can be detected is a Voronoi edge that intersects the input segment. + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + IssueType>::type + detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); + + static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram); + + voronoi_diagram_type m_voronoi_diagram; + vertex_container_type m_vertices; + edge_container_type m_edges; + cell_container_type m_cells; + bool m_is_modified = false; + State m_state = State::UNKNOWN; + IssueType m_issue_type = IssueType::UNKNOWN; +public: + using SegmentIt = std::vector::iterator; + + friend struct boost::polygon::segment_traits; }; -} } // namespace Slicer::Geometry +} // namespace Slic3r::Geometry + +namespace boost::polygon { +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + using coordinate_type = coord_t; + using point_type = Slic3r::Point; + using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment; + + static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; } +}; +} // namespace boost::polygon #endif // slic3r_Geometry_Voronoi_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp new file mode 100644 index 0000000..1e94363 --- /dev/null +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -0,0 +1,283 @@ +#include + +#include +#include + +#include "VoronoiUtils.hpp" + +namespace Slic3r::Geometry { + +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesIt = ColoredLines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; + +// Explicit template instantiation. +template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); +template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); +template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float); +template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename std::iterator_traits::reference>::type +VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + if (!cell.contains_segment()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!"); + + if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end))) + throw Slic3r::OutOfRange("Voronoi cell source index is out of range!"); + + return *(segment_begin + cell.source_index()); +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type +VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + + if (!cell.contains_point()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!"); + + if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return boost::polygon::segment_traits::get(*segment_it, boost::polygon::LOW); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return boost::polygon::segment_traits::get(*segment_it, boost::polygon::HIGH); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"); + } else { + throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!"); + } +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Arachne::PolygonsPointIndex>::type +VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + if (!cell.contains_point()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!"); + + if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return (*segment_it); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return (*segment_it).next(); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"); + } else { + throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!"); + } +} + +template +typename boost::polygon::enable_if::type>::type>::type, + Points>::type +VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle) +{ + Points discretized; + // x is distance of point projected on the segment ab + // xx is point projected on the segment ab + const Point a = source_segment.from(); + const Point b = source_segment.to(); + const Point ab = b - a; + const Point as = start - a; + const Point ae = end - a; + const coord_t ab_size = ab.cast().norm(); + const coord_t sx = as.cast().dot(ab.cast()) / ab_size; + const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; + const coord_t sxex = ex - sx; + + const Point ap = source_point - a; + const coord_t px = ap.cast().dot(ab.cast()) / ab_size; + + Point pxx; + Line(a, b).distance_to_infinite_squared(source_point, &pxx); + const Point ppxx = pxx - source_point; + const coord_t d = ppxx.cast().norm(); + + const Vec2d rot = perp(ppxx).cast().normalized(); + const double rot_cos_theta = rot.x(); + const double rot_sin_theta = rot.y(); + + if (d == 0) { + discretized.emplace_back(start); + discretized.emplace_back(end); + return discretized; + } + + const double marking_bound = atan(transitioning_angle * 0.5); + int64_t msx = -marking_bound * int64_t(d); // projected marking_start + int64_t mex = marking_bound * int64_t(d); // projected marking_end + + const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; + Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + const int dir = (sx > ex) ? -1 : 1; + if (dir < 0) { + std::swap(marking_start, marking_end); + std::swap(msx, mex); + } + + bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + + const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx; + bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; + + assert(!add_marking_start || !add_marking_end || add_apex); + if (add_marking_start && add_marking_end && !add_apex) + BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; + + const coord_t step_count = lround(static_cast(std::abs(ex - sx)) / approximate_step_size); + discretized.emplace_back(start); + for (coord_t step = 1; step < step_count; ++step) { + const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); + const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); + + if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) { + discretized.emplace_back(marking_start); + add_marking_start = false; + } + + if (add_apex && int64_t(x) * int64_t(dir) > 0) { + discretized.emplace_back(apex); + add_apex = false; // only add the apex just before the + } + + if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) { + discretized.emplace_back(marking_end); + add_marking_end = false; + } + + assert(is_in_range(x) && is_in_range(y)); + const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx; + discretized.emplace_back(result); + } + + if (add_apex) + discretized.emplace_back(apex); + + if (add_marking_end) + discretized.emplace_back(marking_end); + + discretized.emplace_back(end); + return discretized; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::SegmentCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type +VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + using Point = typename boost::polygon::segment_point_type::type; + using SegmentCellRange = SegmentCellRange; + + const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end); + const Point from = boost::polygon::segment_traits::get(source_segment, boost::polygon::LOW); + const Point to = boost::polygon::segment_traits::get(source_segment, boost::polygon::HIGH); + const Vec2i64 from_i64 = from.template cast(); + const Vec2i64 to_i64 = to.template cast(); + + // FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end. + SegmentCellRange cell_range(to, from); + + // Find starting edge and end edge + bool seen_possible_start = false; + bool after_start = false; + bool ending_edge_is_set_before_start = false; + const VD::edge_type *edge = cell.incident_edge(); + do { + if (edge->is_infinite()) + continue; + + Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()); + Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); + assert(v0 != to_i64 || v1 != from_i64); + + if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to + cell_range.edge_begin = edge; + seen_possible_start = true; + } else if (seen_possible_start) { + after_start = true; + } + + if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) { + ending_edge_is_set_before_start = !after_start; + cell_range.edge_end = edge; + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + return cell_range; +} + +Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex) +{ + assert(vertex != nullptr); + return VoronoiUtils::to_point(*vertex); +} + +Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex) +{ + const double x = vertex.x(), y = vertex.y(); + + assert(std::isfinite(x) && std::isfinite(y)); + assert(is_in_range(x) && is_in_range(y)); + + return {std::llround(x), std::llround(y)}; +} + +bool VoronoiUtils::is_finite(const VD::vertex_type &vertex) +{ + return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); +} + +VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle) +{ + const double cos_a = std::cos(angle); + const double sin_a = std::sin(angle); + + const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y()); + const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x()); + + VD::vertex_type rotated_vertex{rotated_x, rotated_y}; + rotated_vertex.incident_edge(vertex.incident_edge()); + rotated_vertex.color(vertex.color()); + + return rotated_vertex; +} + +} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp new file mode 100644 index 0000000..bf63914 --- /dev/null +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -0,0 +1,120 @@ +#ifndef slic3r_VoronoiUtils_hpp_ +#define slic3r_VoronoiUtils_hpp_ + +#include "libslic3r/Geometry/Voronoi.hpp" +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" + +using VD = Slic3r::Geometry::VoronoiDiagram; + +namespace Slic3r::Geometry { + +// Represent trapezoid Voronoi cell around segment. +template struct SegmentCellRange +{ + const PT segment_start_point; // The start point of the source segment of this cell. + const PT segment_end_point; // The end point of the source segment of this cell. + const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. + const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. + + SegmentCellRange() = delete; + explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point) + : segment_start_point(segment_start_point), segment_end_point(segment_end_point) + {} + + bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } +}; + +class VoronoiUtils +{ +public: + static Vec2i64 to_point(const VD::vertex_type *vertex); + + static Vec2i64 to_point(const VD::vertex_type &vertex); + + static bool is_finite(const VD::vertex_type &vertex); + + static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle); + + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename std::iterator_traits::reference>::type + get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type + get_source_point(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Arachne::PolygonsPointIndex>::type + get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + /** + * Discretize a parabola based on (approximate) step size. + * + * Adapted from CuraEngine VoronoiUtils::discretizeParabola by Tim Kuipers @BagelOrb and @Ghostkeeper. + * + * @param approximate_step_size is measured parallel to the source_segment, not along the parabola. + */ + template + static typename boost::polygon::enable_if::type>::type>::type, + Points>::type + discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, coord_t approximate_step_size, float transitioning_angle); + + /** + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a line segment of the medial axis. + * + * This should only be used on cells that belong to a central line segment + * of the skeletal graph, e.g. trapezoid cells, not triangular cells. + * + * The resulting line segments is just the first and the last segment. They + * are linked to the neighboring segments, so you can iterate over the + * segments until you reach the last segment. + * + * Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb, + * Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper. + * + * @param cell The cell to compute the range of line segments for. + * @param segment_begin Begin iterator for all edges of the input Polygons. + * @param segment_end End iterator for all edges of the input Polygons. + * @return Range of line segments that surround the cell. + */ + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::SegmentCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type + compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + template static bool is_in_range(double value) + { + return double(std::numeric_limits::lowest()) <= value && value <= double(std::numeric_limits::max()); + } + + template static bool is_in_range(const VD::vertex_type &vertex) + { + return VoronoiUtils::is_finite(vertex) && is_in_range(vertex.x()) && is_in_range(vertex.y()); + } + + template static bool is_in_range(const VD::edge_type &edge) + { + if (edge.vertex0() == nullptr || edge.vertex1() == nullptr) + return false; + + return is_in_range(*edge.vertex0()) && is_in_range(*edge.vertex1()); + } +}; + +} // namespace Slic3r::Geometry + +#endif // slic3r_VoronoiUtils_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index 832152c..ef650ac 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -3,15 +3,25 @@ #include #include "libslic3r/Geometry/Voronoi.hpp" -#include "libslic3r/Arachne/utils/VoronoiUtils.hpp" +#include "libslic3r/Geometry/VoronoiUtils.hpp" +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" #include "VoronoiUtilsCgal.hpp" using VD = Slic3r::Geometry::VoronoiDiagram; -using namespace Slic3r::Arachne; namespace Slic3r::Geometry { +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; + +// Explicit template instantiation. +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); // The tangent vector of the parabola is computed based on the Proof of the reflective property. // https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property // https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663 @@ -117,30 +127,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered; using CGAL_Point = impl::K::Point_2; -inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; } -inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; } -inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; } +inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; } +inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; } +inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; } -inline static Linef make_linef(const VD::edge_type &edge) +inline Linef make_linef(const VD::edge_type &edge) { const VD::vertex_type *v0 = edge.vertex0(); const VD::vertex_type *v1 = edge.vertex1(); return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())}; } -[[maybe_unused]] inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); } +[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); } // FIXME Lukas H.: Also includes parabolic segments. bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram) { - using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; - using CGAL_Segment = CGAL::Arr_segment_traits_2::Curve_2; - auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; }; + using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; + using CGAL_E_Segment = CGAL::Arr_segment_traits_2::Curve_2; + auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; }; assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(), [](const VD::edge_type &edge) { return edge.color() == 0; })); - std::vector segments; + std::vector segments; segments.reserve(voronoi_diagram.num_edges()); for (const VD::edge_type &edge : voronoi_diagram.edges()) { @@ -159,7 +169,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_ for (const VD::edge_type &edge : voronoi_diagram.edges()) edge.color(0); - std::vector intersections_pt; + std::vector intersections_pt; CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt)); return intersections_pt.empty(); } @@ -174,29 +184,43 @@ struct ParabolicSegment const CGAL::Orientation is_focus_on_left; }; -inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector &segments) +template +inline static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + ParabolicSegment>::type +get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end) { + using Segment = typename std::iterator_traits::value_type; assert(edge.is_curved()); const VD::cell_type *left_cell = edge.cell(); const VD::cell_type *right_cell = edge.twin()->cell(); - const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments); - const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments); + const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end); + const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end); CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN); - return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side}; + const Point directrix_from = boost::polygon::segment_traits::get(directrix, boost::polygon::LOW); + const Point directrix_to = boost::polygon::segment_traits::get(directrix, boost::polygon::HIGH); + return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side}; } -inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector &segments) { +template +inline static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + CGAL::Orientation>::type +orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); CGAL::Orientation orientation; if (edge_a.is_linear() && edge_b.is_linear()) { orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1())); } else if (edge_a.is_curved() && edge_b.is_curved()) { - const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments); - const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments); + const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end); + const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end); orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a), to_cgal_point(parabolic_a.focus), to_cgal_point(parabolic_a.directrix.a), @@ -212,7 +236,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a; const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b; - const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments); + const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end); orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()), to_cgal_point(parabolic.focus), to_cgal_point(parabolic.directrix.a), @@ -226,39 +250,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed return orientation; } -static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector &segments) +template +static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type +check_if_three_edges_are_ccw(const VD::edge_type &edge_first, + const VD::edge_type &edge_second, + const VD::edge_type &edge_third, + const SegmentIterator segment_begin, + const SegmentIterator segment_end) { - assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); + assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0())); - CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments); + CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end); if (orientation == CGAL::Orientation::COLLINEAR) { // The first two edges are collinear, so the third edge must be on the right side on the first of them. - return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN; + return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN; } else if (orientation == CGAL::Orientation::LEFT_TURN) { // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI. // So we need to check if test_pt isn't between them. - CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); - CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); + CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end); + CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end); return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN); } else { assert(orientation == CGAL::Orientation::RIGHT_TURN); // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI. // So we need to check if test_pt is between them. - CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); - CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); + CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end); + CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end); return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN); } } -bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments) +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type +VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, + const SegmentIterator segment_begin, + const SegmentIterator segment_end) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { std::vector edges; const VD::edge_type *edge = vertex.incident_edge(); do { - if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && - VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1())) + if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1())) edges.emplace_back(edge); edge = edge->rot_next(); @@ -267,11 +306,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor // Checking for CCW make sense for three and more edges. if (edges.size() > 2) { for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) { - const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it); - const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it; - const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); + const VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it); + const VD::edge_type *curr_edge = *edge_it; + const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); - if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments)) + if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end)) return false; } } diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp index cad5461..eb146a6 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp @@ -2,7 +2,7 @@ #define slic3r_VoronoiUtilsCgal_hpp_ #include "Voronoi.hpp" -#include "../Arachne/utils/VoronoiUtils.hpp" +#include "../Arachne/utils/PolygonsSegmentIndex.hpp" namespace Slic3r::Geometry { class VoronoiDiagram; @@ -14,7 +14,12 @@ public: static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. - static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments); + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type + is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); }; } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index ad6d6ae..db93720 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -4,7 +4,6 @@ #include "Point.hpp" #include "Polygon.hpp" #include "Print.hpp" -#include "Fill/Fill.hpp" #include "ShortestPath.hpp" #include "SVG.hpp" #include "BoundingBox.hpp" @@ -885,12 +884,14 @@ void Layer::sort_perimeters_into_islands( int sort_region_id = -1; // Temporary vector of fills for reordering. ExPolygons fills_temp; + // Temporary vector of fill_bboxes for reordering. + BoundingBoxes fill_bboxes_temp; // Vector of new positions of the above. std::vector new_positions; do { sort_region_id = -1; for (size_t source_slice_idx = 0; source_slice_idx < fill_expolygons_ranges.size(); ++ source_slice_idx) - if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; fill_range.size() > 1) { + if (const ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; fill_range.size() > 1) { // More than one expolygon exists for a single island. Check whether they are contiguous inside a single LayerRegion::fill_expolygons() vector. uint32_t fill_idx = *fill_range.begin(); if (const int fill_regon_id = map_expolygon_to_region_and_fill[fill_idx].region_id; fill_regon_id != -1) { @@ -928,14 +929,21 @@ void Layer::sort_perimeters_into_islands( new_pos = last ++; // Move just the content of m_fill_expolygons to fills_temp, but don't move the container vector. auto &fills = layerm.m_fill_expolygons; + auto &fill_bboxes = layerm.m_fill_expolygons_bboxes; + + assert(fills.size() == fill_bboxes.size()); assert(last == int(fills.size())); - fills_temp.reserve(fills.size()); - fills_temp.insert(fills_temp.end(), std::make_move_iterator(fills.begin()), std::make_move_iterator(fills.end())); - for (ExPolygon &ex : fills) - ex.clear(); - // Move / reoder the expolygons back into m_fill_expolygons. - for (size_t old_pos = 0; old_pos < new_positions.size(); ++ old_pos) + fills_temp.resize(fills.size()); + fills_temp.assign(std::make_move_iterator(fills.begin()), std::make_move_iterator(fills.end())); + + fill_bboxes_temp.resize(fill_bboxes.size()); + fill_bboxes_temp.assign(std::make_move_iterator(fill_bboxes.begin()), std::make_move_iterator(fill_bboxes.end())); + + // Move / reorder the ExPolygons and BoundingBoxes back into m_fill_expolygons and m_fill_expolygons_bboxes. + for (size_t old_pos = 0; old_pos < new_positions.size(); ++old_pos) { fills[new_positions[old_pos]] = std::move(fills_temp[old_pos]); + fill_bboxes[new_positions[old_pos]] = std::move(fill_bboxes_temp[old_pos]); + } } } while (sort_region_id != -1); } else { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index d4c8205..46eb346 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -373,8 +373,6 @@ public: return false; } void make_perimeters(); - // Phony version of make_fills() without parameters for Perl integration only. - void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } void make_fills(FillAdaptive::Octree *adaptive_fill_octree, FillAdaptive::Octree *support_fill_octree, FillLightning::Generator *lightning_generator); diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 325828f..af67bae 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -270,6 +270,7 @@ class Linef public: Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {} Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {} + virtual ~Linef() = default; Vec2d a; Vec2d b; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index a9af66e..0ea6f70 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -412,19 +412,23 @@ bool Model::looks_like_multipart_object() const { if (this->objects.size() <= 1) return false; - double zmin = std::numeric_limits::max(); + BoundingBoxf3 tbb; for (const ModelObject *obj : this->objects) { if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) return false; - for (const ModelVolume *vol : obj->volumes) { - double zmin_this = vol->mesh().bounding_box().min(2); - if (zmin == std::numeric_limits::max()) - zmin = zmin_this; - else if (std::abs(zmin - zmin_this) > EPSILON) - // The volumes don't share zmin. + BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box(); + + // FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that + // case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's + // just do this non-invasive check. Reordering all the functions could break it much more. + BoundingBoxf3 tbb_this = (! obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this); + + if (!tbb.defined) + tbb = tbb_this; + else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this)) + // The volumes has intersects bounding boxes or share some boundary return true; } - } return false; } @@ -1221,7 +1225,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->supported_facets.assign(volume->supported_facets); vol->seam_facets.assign(volume->seam_facets); - vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets); + vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets); // Perform conversion only if the target "imperial" state is different from the current one. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. @@ -1333,7 +1337,7 @@ void ModelVolume::reset_extra_facets() { this->supported_facets.reset(); this->seam_facets.reset(); - this->mmu_segmentation_facets.reset(); + this->mm_segmentation_facets.reset(); } @@ -1899,7 +1903,7 @@ void ModelVolume::assign_new_unique_ids_recursive() config.set_new_unique_id(); supported_facets.set_new_unique_id(); seam_facets.set_new_unique_id(); - mmu_segmentation_facets.set_new_unique_id(); + mm_segmentation_facets.set_new_unique_id(); } void ModelVolume::rotate(double angle, Axis axis) @@ -2208,7 +2212,7 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec { return model_property_changed(mo, mo_new, [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; }, - [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); }); + [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); }); } bool model_has_parameter_modifiers_in_objects(const Model &model) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 4b6b2a7..458ceee 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -811,8 +811,8 @@ public: // List of seam enforcers/blockers. FacetsAnnotation seam_facets; - // List of mesh facets painted for MMU segmentation. - FacetsAnnotation mmu_segmentation_facets; + // List of mesh facets painted for MM segmentation. + FacetsAnnotation mm_segmentation_facets; // Is set only when volume is Embossed Text type // Contain information how to re-create volume @@ -916,12 +916,12 @@ public: this->config.set_new_unique_id(); this->supported_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id(); - this->mmu_segmentation_facets.set_new_unique_id(); + this->mm_segmentation_facets.set_new_unique_id(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); } - bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } protected: friend class Print; @@ -960,11 +960,11 @@ private: assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); return true; } @@ -990,23 +990,23 @@ private: ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); assert(this->id() == other.id()); assert(this->config.id() == other.config.id()); assert(this->supported_facets.id() == other.supported_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); + assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. @@ -1018,11 +1018,11 @@ private: assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); assert(this->id() != other.id()); assert(this->config.id() == other.config.id()); this->set_material_id(other.material_id()); @@ -1033,11 +1033,11 @@ private: assert(this->config.id() != other.config.id()); assert(this->supported_facets.id() != other.supported_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); + assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id()); assert(this->id() != this->config.id()); assert(this->supported_facets.empty()); assert(this->seam_facets.empty()); - assert(this->mmu_segmentation_facets.empty()); + assert(this->mm_segmentation_facets.empty()); } ModelVolume& operator=(ModelVolume &rhs) = delete; @@ -1045,19 +1045,19 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->supported_facets.id().invalid()); assert(this->seam_facets.id().invalid()); - assert(this->mmu_segmentation_facets.id().invalid()); + assert(this->mm_segmentation_facets.id().invalid()); } template void load(Archive &ar) { bool has_convex_hull; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, seam_facets); - cereal::load_by_value(ar, mmu_segmentation_facets); + cereal::load_by_value(ar, mm_segmentation_facets); cereal::load_by_value(ar, config); cereal::load(ar, text_configuration); cereal::load(ar, emboss_shape); @@ -1075,7 +1075,7 @@ private: ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); - cereal::save_by_value(ar, mmu_segmentation_facets); + cereal::save_by_value(ar, mm_segmentation_facets); cereal::save_by_value(ar, config); cereal::save(ar, text_configuration); cereal::save(ar, emboss_shape); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 0b7a0f1..9acd9dd 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -4,11 +4,11 @@ #include "Layer.hpp" #include "Print.hpp" #include "Geometry/VoronoiVisualUtils.hpp" +#include "Geometry/VoronoiUtils.hpp" #include "MutablePolygon.hpp" #include "format.hpp" #include -#include #include #include @@ -16,36 +16,19 @@ #include #include -namespace Slic3r { -struct ColoredLine { - Line line; - int color; - int poly_idx = -1; - int local_line_idx = -1; -}; -} +//#define MM_SEGMENTATION_DEBUG_GRAPH +//#define MM_SEGMENTATION_DEBUG_REGIONS +//#define MM_SEGMENTATION_DEBUG_INPUT +//#define MM_SEGMENTATION_DEBUG_PAINTED_LINES +//#define MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -#include -namespace boost::polygon { -template <> -struct geometry_concept { typedef segment_concept type; }; +#if defined(MM_SEGMENTATION_DEBUG_GRAPH) || defined(MM_SEGMENTATION_DEBUG_REGIONS) || \ + defined(MM_SEGMENTATION_DEBUG_INPUT) || defined(MM_SEGMENTATION_DEBUG_PAINTED_LINES) || \ + defined(MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) +#define MM_SEGMENTATION_DEBUG +#endif -template <> -struct segment_traits { - typedef coord_t coordinate_type; - typedef Slic3r::Point point_type; - - static inline point_type get(const Slic3r::ColoredLine& line, const direction_1d& dir) { - return dir.to_int() ? line.line.b : line.line.a; - } -}; -} - -//#define MMU_SEGMENTATION_DEBUG_GRAPH -//#define MMU_SEGMENTATION_DEBUG_REGIONS -//#define MMU_SEGMENTATION_DEBUG_INPUT -//#define MMU_SEGMENTATION_DEBUG_PAINTED_LINES -//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +//#define MM_SEGMENTATION_DEBUG_TOP_BOTTOM namespace Slic3r { @@ -149,55 +132,32 @@ struct PaintedLineVisitor static inline const double append_threshold2 = Slic3r::sqr(append_threshold); }; -static Polygon colored_points_to_polygon(const std::vector &lines) -{ - Polygon out; - out.points.reserve(lines.size()); - for (const ColoredLine &l : lines) - out.points.emplace_back(l.line.a); - return out; +BoundingBox get_extents(const std::vector &colored_polygons) { + BoundingBox bbox; + for (const ColoredLines &colored_lines : colored_polygons) { + for (const ColoredLine &colored_line : colored_lines) { + bbox.merge(colored_line.line.a); + bbox.merge(colored_line.line.b); } -static Polygons colored_points_to_polygon(const std::vector> &lines) -{ - Polygons out; - out.reserve(lines.size()); - for (const std::vector &l : lines) - out.emplace_back(colored_points_to_polygon(l)); - return out; + } + return bbox; } // Flatten the vector of vectors into a vector. -static inline std::vector to_lines(const std::vector> &c_lines) +static inline ColoredLines to_lines(const std::vector &c_lines) { size_t n_lines = 0; for (const auto &c_line : c_lines) n_lines += c_line.size(); - std::vector lines; + ColoredLines lines; lines.reserve(n_lines); for (const auto &c_line : c_lines) lines.insert(lines.end(), c_line.begin(), c_line.end()); return lines; } -static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt) -{ - // Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare. - // This should work with any settings of math compiler switches and the C++ compiler - // shall understand the memcpies as type punning and it shall optimize them out. - using ulp_cmp_type = boost::polygon::detail::ulp_comparison; - ulp_cmp_type ulp_cmp; - static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; - return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL && - ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL; -} - -static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt) -{ - return vertex_equal_to_point(*vertex, ipt); -} - -static std::vector> get_segments(const std::vector &polygon) +static std::vector> get_segments(const ColoredLines &polygon) { std::vector> segments; @@ -222,15 +182,6 @@ static std::vector> get_segments(const std::vector>> get_all_segments(const std::vector> &color_poly) -{ - std::vector>> all_segments(color_poly.size()); - for (size_t poly_idx = 0; poly_idx < color_poly.size(); ++poly_idx) { - const std::vector &c_polygon = color_poly[poly_idx]; - all_segments[poly_idx] = get_segments(c_polygon); - } - return all_segments; -} static std::vector filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_lines) { @@ -312,7 +263,7 @@ static std::vector> post_process_painted_lines(const st } #ifndef NDEBUG -static bool are_lines_connected(const std::vector &colored_lines) +static bool are_lines_connected(const ColoredLines &colored_lines) { for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx) if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a) @@ -321,7 +272,7 @@ static bool are_lines_connected(const std::vector &colored_lines) } #endif -static std::vector colorize_line(const Line &line_to_process, +static ColoredLines colorize_line(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_contour) @@ -330,7 +281,7 @@ static std::vector colorize_line(const Line &line_to_process, assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; })); const int filter_eps_value = scale_(0.1f); - std::vector final_lines; + ColoredLines final_lines; const PaintedLine &first_line = painted_contour[start_idx]; if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast().norm(); dist_to_start > filter_eps_value) final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0}); @@ -370,7 +321,7 @@ static std::vector colorize_line(const Line &line_to_process, if (line_1.line.length() <= scale_(0.2)) line_1.color = line_0.color; } - std::vector colored_lines_simple; + ColoredLines colored_lines_simple; colored_lines_simple.emplace_back(final_lines.front()); for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) { const ColoredLine &line_0 = final_lines[line_idx]; @@ -398,7 +349,7 @@ static std::vector colorize_line(const Line &line_to_process, return final_lines; } -static std::vector filter_colorized_polygon(std::vector &&new_lines) { +static ColoredLines filter_colorized_polygon(ColoredLines &&new_lines) { for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) { const ColoredLine &line_0 = new_lines[line_idx - 2]; ColoredLine &line_1 = new_lines[line_idx - 1]; @@ -487,10 +438,10 @@ static std::vector filter_colorized_polygon(std::vector colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { +static ColoredLines colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; })); - std::vector colorized_contour; + ColoredLines colorized_contour; if (painted_contour.empty()) { // Appends contour with default color for lines before the first PaintedLine. colorized_contour.reserve(contour.num_segments()); @@ -527,297 +478,33 @@ static std::vector colorize_contour(const EdgeGrid::Contour &contou return filter_colorized_polygon(std::move(colorized_contour)); } -static std::vector> colorize_contours(const std::vector &contours, const std::vector> &painted_contours) +static std::vector colorize_contours(const std::vector &contours, const std::vector> &painted_contours) { assert(contours.size() == painted_contours.size()); - std::vector> colorized_contours(contours.size()); + std::vector colorized_contours(contours.size()); for (const std::vector &painted_contour : painted_contours) { size_t contour_idx = &painted_contour - &painted_contours.front(); colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]); } - return colorized_contours; -} - -using boost::polygon::voronoi_diagram; - -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; } - -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } - -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } - -static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; } - -static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } - -struct MMU_Graph -{ - enum class ARC_TYPE { BORDER, NON_BORDER }; - - struct Arc - { - size_t from_idx; - size_t to_idx; - int color; - ARC_TYPE type; - - bool operator==(const Arc &rhs) const { return (from_idx == rhs.from_idx) && (to_idx == rhs.to_idx) && (color == rhs.color) && (type == rhs.type); } - bool operator!=(const Arc &rhs) const { return !operator==(rhs); } - }; - - struct Node - { - Vec2d point; - std::list arc_idxs; - - void remove_edge(const size_t to_idx, MMU_Graph &graph) - { - for (auto arc_it = this->arc_idxs.begin(); arc_it != this->arc_idxs.end(); ++arc_it) { - MMU_Graph::Arc &arc = graph.arcs[*arc_it]; - if (arc.to_idx == to_idx) { - assert(arc.type != ARC_TYPE::BORDER); - this->arc_idxs.erase(arc_it); - break; - } - } - } - }; - - std::vector nodes; - std::vector arcs; - size_t all_border_points{}; - - std::vector polygon_idx_offset; - std::vector polygon_sizes; - - void remove_edge(const size_t from_idx, const size_t to_idx) - { - nodes[from_idx].remove_edge(to_idx, *this); - nodes[to_idx].remove_edge(from_idx, *this); - } - - [[nodiscard]] size_t get_global_index(const size_t poly_idx, const size_t point_idx) const { return polygon_idx_offset[poly_idx] + point_idx; } - - void append_edge(const size_t &from_idx, const size_t &to_idx, int color = -1, ARC_TYPE type = ARC_TYPE::NON_BORDER) - { - // Don't append duplicate edges between the same nodes. - for (const size_t &arc_idx : this->nodes[from_idx].arc_idxs) - if (arcs[arc_idx].to_idx == to_idx) - return; - for (const size_t &arc_idx : this->nodes[to_idx].arc_idxs) - if (arcs[arc_idx].to_idx == from_idx) - return; - - this->nodes[from_idx].arc_idxs.push_back(this->arcs.size()); - this->arcs.push_back({from_idx, to_idx, color, type}); - - // Always insert only one directed arc for the input polygons. - // Two directed arcs in both directions are inserted if arcs aren't between points of the input polygons. - if (type == ARC_TYPE::NON_BORDER) { - this->nodes[to_idx].arc_idxs.push_back(this->arcs.size()); - this->arcs.push_back({to_idx, from_idx, color, type}); - } - } - - // It assumes that between points of the input polygons is always only one directed arc, - // with the same direction as lines of the input polygon. - [[nodiscard]] MMU_Graph::Arc get_border_arc(size_t idx) const { - assert(idx < this->all_border_points); - return this->arcs[idx]; - } - - [[nodiscard]] size_t nodes_count() const { return this->nodes.size(); } - - void remove_nodes_with_one_arc() - { - std::queue update_queue; - for (const MMU_Graph::Node &node : this->nodes) { - size_t node_idx = &node - &this->nodes.front(); - // Skip nodes that represent points of input polygons. - if (node.arc_idxs.size() == 1 && node_idx >= this->all_border_points) - update_queue.emplace(&node - &this->nodes.front()); - } - - while (!update_queue.empty()) { - size_t node_from_idx = update_queue.front(); - MMU_Graph::Node &node_from = this->nodes[update_queue.front()]; - update_queue.pop(); - if (node_from.arc_idxs.empty()) - continue; - - assert(node_from.arc_idxs.size() == 1); - size_t node_to_idx = arcs[node_from.arc_idxs.front()].to_idx; - MMU_Graph::Node &node_to = this->nodes[node_to_idx]; - this->remove_edge(node_from_idx, node_to_idx); - if (node_to.arc_idxs.size() == 1 && node_to_idx >= this->all_border_points) - update_queue.emplace(node_to_idx); - } - } - - void add_contours(const std::vector> &color_poly) - { - this->all_border_points = nodes.size(); - this->polygon_sizes = std::vector(color_poly.size()); - for (size_t polygon_idx = 0; polygon_idx < color_poly.size(); ++polygon_idx) - this->polygon_sizes[polygon_idx] = color_poly[polygon_idx].size(); - this->polygon_idx_offset = std::vector(color_poly.size()); - this->polygon_idx_offset[0] = 0; - for (size_t polygon_idx = 1; polygon_idx < color_poly.size(); ++polygon_idx) { - this->polygon_idx_offset[polygon_idx] = this->polygon_idx_offset[polygon_idx - 1] + color_poly[polygon_idx - 1].size(); - } size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { + for (ColoredLines &color_lines : colorized_contours) { size_t line_idx = 0; - for (const ColoredLine &color_line : color_lines) { - size_t from_idx = this->get_global_index(poly_idx, line_idx); - size_t to_idx = this->get_global_index(poly_idx, (line_idx + 1) % color_lines.size()); - this->append_edge(from_idx, to_idx, color_line.color, ARC_TYPE::BORDER); + for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { + color_lines[color_line_idx].poly_idx = int(poly_idx); + color_lines[color_line_idx].local_line_idx = int(line_idx); ++line_idx; } ++poly_idx; } + + return colorized_contours; } - // Nodes 0..all_border_points are only one with are on countour. Other vertexis are consider as not on coouter. So we check if base on attach index - inline bool is_vertex_on_contour(const Voronoi::VD::vertex_type *vertex) const - { - assert(vertex != nullptr); - return vertex->color() < this->all_border_points; - } - - [[nodiscard]] inline bool is_edge_attach_to_contour(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) || this->is_vertex_on_contour(edge_iterator->vertex1()); - } - - [[nodiscard]] inline bool is_edge_connecting_two_contour_vertices(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); - } - - // All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges. - // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. - void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { - bbox.offset(SCALED_EPSILON); - - struct CPoint - { - CPoint() = delete; - CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {} - CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {} - const Vec2d m_point_double; - const Point m_point; - size_t m_point_idx; - size_t m_contour_idx; - - [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } - [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return m_point_double == rhs.m_point_double && m_contour_idx == rhs.m_contour_idx && m_point_idx == rhs.m_point_idx; } - }; - struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; - typedef ClosestPointInRadiusLookup CPointLookupType; - - CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON)); - CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); - for (const Polygon &polygon : color_poly_tmp) - for (const Point &pt : polygon.points) - closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); - - for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { - vertex.color(-1); - Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y()); - Point vertex_point = mk_point(vertex); - - const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - - if (vertex_equal_to_point(&vertex, first_point_double)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point_double)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); - } else if (bbox.contains(vertex_point)) { - if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { - vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) { - closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); - vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point_double}); - } else { - // Boost Voronoi diagram generator sometimes creates two very closed points instead of one point. - // For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases. - std::vector> all_closes_c_points = closest_voronoi_point.find_all(vertex_point); - int merge_to_point = -1; - for (const std::pair &c_point : all_closes_c_points) - if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) { - merge_to_point = int(c_point.first->m_point_idx); - break; - } - - if (merge_to_point != -1) { - vertex.color(merge_to_point); - } else { - closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); - vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point_double}); - } - } - } - } - } - - void garbage_collect() - { - std::vector nodes_map(this->nodes.size(), -1); - int nodes_count = 0; - size_t arcs_count = 0; - for (const MMU_Graph::Node &node : this->nodes) - if (size_t node_idx = &node - &this->nodes.front(); !node.arc_idxs.empty()) { - nodes_map[node_idx] = nodes_count++; - arcs_count += node.arc_idxs.size(); - } - - std::vector new_nodes; - std::vector new_arcs; - new_nodes.reserve(nodes_count); - new_arcs.reserve(arcs_count); - for (const MMU_Graph::Node &node : this->nodes) - if (size_t node_idx = &node - &this->nodes.front(); nodes_map[node_idx] >= 0) { - new_nodes.push_back({node.point}); - for (const size_t &arc_idx : node.arc_idxs) { - const Arc &arc = this->arcs[arc_idx]; - new_nodes.back().arc_idxs.emplace_back(new_arcs.size()); - new_arcs.push_back({size_t(nodes_map[arc.from_idx]), size_t(nodes_map[arc.to_idx]), arc.color, arc.type}); - } - } - - this->nodes = std::move(new_nodes); - this->arcs = std::move(new_arcs); - } -}; - -static inline void mark_processed(const voronoi_diagram::const_edge_iterator &edge_iterator) -{ - edge_iterator->color(true); - edge_iterator->twin()->color(true); -} - -// Return true, if "p" is closer to line.a, then line.b -static inline bool is_point_closer_to_beginning_of_line(const Line &line, const Point &p) -{ - return (p - line.a).cast().squaredNorm() < (p - line.b).cast().squaredNorm(); -} - -static inline bool has_same_color(const ColoredLine &cl1, const ColoredLine &cl2) { return cl1.color == cl2.color; } - // Determines if the line points from the point between two contour lines is pointing inside polygon or outside. static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) -{ - // Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside + { + // TODO: Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { assert(left != middle); assert(middle != right); @@ -830,442 +517,310 @@ static inline bool points_inside(const Line &contour_first, const Line &contour_ double side = inward_normal.dot(edge_norm); // assert(side != 0.); return side > 0.; -} + } -static inline bool line_intersection_with_epsilon(const Line &line_to_extend, const Line &other, Point *intersection) -{ - Line extended_line = line_to_extend; - extended_line.extend(15 * SCALED_EPSILON); - return extended_line.intersection(other, intersection); -} +enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { + VERTEX_ON_CONTOUR = 1, + DELETED = 2 + }; -// For every ColoredLine in lines_colored_out, assign the index of the polygon to which belongs and also the index of this line inside of the polygon. -static inline void init_polygon_indices(const MMU_Graph &graph, - const std::vector> &color_poly, - std::vector &lines_colored_out) -{ - size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { - size_t line_idx = 0; - for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { - size_t from_idx = graph.get_global_index(poly_idx, line_idx); - lines_colored_out[from_idx].poly_idx = int(poly_idx); - lines_colored_out[from_idx].local_line_idx = int(line_idx); - ++line_idx; +#ifdef MM_SEGMENTATION_DEBUG_GRAPH +static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { + const coordf_t stroke_width = scaled(0.05f); + const BoundingBox bbox = get_extents(colored_polygons); + + SVG svg(path.c_str(), bbox); + for (const ColoredLines &colored_lines : colored_polygons) + for (const ColoredLine &colored_line : colored_lines) + svg.draw(colored_line.line, "black", stroke_width); + + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (Geometry::VoronoiUtils::is_in_range(vertex)) { + if (const Point pt = Geometry::VoronoiUtils::to_point(&vertex).cast(); vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) { + svg.draw(pt, "blue", coord_t(stroke_width)); + } else if (vertex.color() != VD_ANNOTATION::DELETED) { + svg.draw(pt, "green", coord_t(stroke_width)); + } } - ++poly_idx; + } + + for (const Voronoi::VD::edge_type &edge : vd.edges()) { + if (edge.is_infinite() || !Geometry::VoronoiUtils::is_in_range(edge)) + continue; + + const Point from = Geometry::VoronoiUtils::to_point(edge.vertex0()).cast(); + const Point to = Geometry::VoronoiUtils::to_point(edge.vertex1()).cast(); + + if (edge.color() != VD_ANNOTATION::DELETED) + svg.draw(Line(from, to), "red", stroke_width); + } + } +#endif // MM_SEGMENTATION_DEBUG_GRAPH + +static size_t non_deleted_edge_count(const VD::vertex_type &vertex) { + size_t non_deleted_edge_cnt = 0; + const VD::edge_type *edge = vertex.incident_edge(); + do { + if (edge->color() != VD_ANNOTATION::DELETED) + ++non_deleted_edge_cnt; + } while (edge = edge->prev()->twin(), edge != vertex.incident_edge()); + + return non_deleted_edge_cnt; +} + +static bool can_vertex_be_deleted(const VD::vertex_type &vertex) { + if (vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || vertex.color() == VD_ANNOTATION::DELETED) + return false; + + return non_deleted_edge_count(vertex) <= 1; +} + +static void delete_vertex_deep(const VD::vertex_type &vertex) { + std::queue vertices_to_delete; + vertices_to_delete.emplace(&vertex); + + while (!vertices_to_delete.empty()) { + const VD::vertex_type &vertex_to_delete = *vertices_to_delete.front(); + vertices_to_delete.pop(); + vertex_to_delete.color(VD_ANNOTATION::DELETED); + + const VD::edge_type *edge = vertex_to_delete.incident_edge(); + do { + edge->color(VD_ANNOTATION::DELETED); + edge->twin()->color(VD_ANNOTATION::DELETED); + + if (edge->is_finite() && can_vertex_be_deleted(*edge->vertex1())) + vertices_to_delete.emplace(edge->vertex1()); + } while (edge = edge->prev()->twin(), edge != vertex_to_delete.incident_edge()); } } -// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t). -// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox. -static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox) -{ +static inline Vec2d mk_point_vec2d(const VD::vertex_type *point) { + assert(point != nullptr); + return {point->x(), point->y()}; +} + +static inline Vec2d mk_vector_vec2d(const VD::edge_type *edge) { + assert(edge != nullptr); + return mk_point_vec2d(edge->vertex1()) - mk_point_vec2d(edge->vertex0()); + } +static inline Vec2d mk_flipped_vector_vec2d(const VD::edge_type *edge) { + assert(edge != nullptr); + return mk_point_vec2d(edge->vertex0()) - mk_point_vec2d(edge->vertex1()); +} + +static double edge_length(const VD::edge_type &edge) { assert(edge.is_finite()); - Vec2d v0 = mk_vec2(edge.vertex0()); - Vec2d v1 = mk_vec2(edge.vertex1()); - bool contains_v0 = bbox.contains(v0); - bool contains_v1 = bbox.contains(v1); - if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1)) - return {mk_point(edge.vertex0()), mk_point(edge.vertex1())}; - - Vec2d vector = (v1 - v0).normalized() * bbox.size().norm(); - if (!contains_v0) - v0 = (v1 - vector); - else - v1 = (v0 + vector); - - return {v0.cast(), v1.cast()}; -} - -static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) -{ - Geometry::VoronoiDiagram vd; - std::vector lines_colored = to_lines(color_poly); - const Polygons color_poly_tmp = colored_points_to_polygon(color_poly); - const Points points = to_points(color_poly_tmp); - const Lines lines = to_lines(color_poly_tmp); - - // The algorithm adds edges to the graph that are between two different colors. - // If a polygon is colored entirely with one color, we need to add at least one edge from that polygon artificially. - // Adding this edge is necessary for cases where the expolygon has an outer contour colored whole with one color - // and a hole colored with a different color. If an edge wasn't added to the graph, - // the entire expolygon would be colored with single random color instead of two different. - std::vector force_edge_adding(color_poly.size()); - - // For each polygon, check if it is all colored with the same color. If it is, we need to force adding one edge to it. - for (const std::vector &c_poly : color_poly) { - bool force_edge = true; - for (const ColoredLine &c_line : c_poly) - if (c_line.color != c_poly.front().color) { - force_edge = false; - break; - } - force_edge_adding[&c_poly - &color_poly.front()] = force_edge; - } - - boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd); - MMU_Graph graph; - graph.nodes.reserve(points.size() + vd.vertices().size()); - for (const Point &point : points) - graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))}); - - graph.add_contours(color_poly); - init_polygon_indices(graph, color_poly, lines_colored); - - assert(graph.nodes.size() == lines_colored.size()); - BoundingBox bbox = get_extents(color_poly_tmp); - graph.append_voronoi_vertices(vd, color_poly_tmp, bbox); - - auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_prev_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx > 0) ? contour_line_local_idx - 1 : contour_line_size - 1); - return lines_colored[contour_prev_idx]; - }; - - auto get_next_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_next_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx + 1) % contour_line_size); - return lines_colored[contour_next_idx]; - }; - - bbox.offset(scale_(10.)); - const BoundingBoxf bbox_clip(bbox.min.cast(), bbox.max.cast()); - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - - // Make a copy of the input segments with the double type. - std::vector segments; - for (const Line &line : lines) - segments.emplace_back(Voronoi::Internal::point_type(double(line.a(0)), double(line.a(1))), - Voronoi::Internal::point_type(double(line.b(0)), double(line.b(1)))); - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_infinite() && (edge_it->vertex0() != nullptr || edge_it->vertex1() != nullptr)) { - // Infinite edge is leading through a point on the counter, but there are no Voronoi vertices. - // So we could fix this case by computing the intersection between the contour line and infinity edge. - std::vector samples; - Voronoi::Internal::clip_infinite_edge(points, segments, *edge_it, bbox_dim_max, &samples); - if (samples.empty()) - continue; - - const Line edge_line(mk_point(samples[0]), mk_point(samples[1])); - const ColoredLine &contour_line = lines_colored[edge_it->cell()->source_index()]; - Point contour_intersection; - - if (line_intersection_with_epsilon(contour_line.line, edge_line, &contour_intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index()); - const size_t from_idx = (edge_it->vertex1() != nullptr) ? edge_it->vertex1()->color() : edge_it->vertex0()->color(); - size_t to_idx = ((contour_line.line.a - contour_intersection).cast().squaredNorm() < - (contour_line.line.b - contour_intersection).cast().squaredNorm()) ? - graph_arc.from_idx : - graph_arc.to_idx; - if (from_idx != to_idx && from_idx < graph.nodes_count() && to_idx < graph.nodes_count()) { - graph.append_edge(from_idx, to_idx); - mark_processed(edge_it); - } - } - } else if (edge_it->is_finite()) { - // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. - if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) - continue; - - const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip); - const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; - const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; - const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); - const ColoredLine contour_line_next = get_next_contour_line(edge_it); - - if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { - enum class Vertex { VERTEX0, VERTEX1 }; - auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram::const_edge_iterator &edge_iterator, const Vertex vertex) { - Point intersection; - Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line; - if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : - graph_arc.to_idx; - graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); - } - mark_processed(edge_iterator); - }; - - if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) - append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0); - - if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) - append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1); - } else if (graph.is_edge_attach_to_contour(edge_it)) { - mark_processed(edge_it); - // Skip edges witch connection two points on a contour - if (graph.is_edge_connecting_two_contour_vertices(edge_it)) - continue; - - const size_t from_idx = edge_it->vertex0()->color(); - const size_t to_idx = edge_it->vertex1()->color(); - if (graph.is_vertex_on_contour(edge_it->vertex0())) { - if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } - } else { - assert(graph.is_vertex_on_contour(edge_it->vertex1())); - if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } - } - } else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - mark_processed(edge_it); - Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point; - Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point; - Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y())); - Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y())); - - if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { - Line first_part(intersection, real_v0); - Line second_part(intersection, real_v1); - - if (!has_same_color(contour_line_prev, colored_line)) { - if (points_inside(contour_line_prev.line, contour_line, first_part.b)) - graph.append_edge(edge_it->vertex0()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); - - if (points_inside(contour_line_prev.line, contour_line, second_part.b)) - graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); - } - } else { - const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; - const Vec2d int_point_double = graph.nodes[int_point_idx].point; - const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y())); - - const Line first_part(int_point, real_v0); - const Line second_part(int_point, real_v1); - - if (!has_same_color(contour_line_next, colored_line)) { - if (points_inside(contour_line, contour_line_next.line, first_part.b)) - graph.append_edge(edge_it->vertex0()->color(), int_point_idx); - - if (points_inside(contour_line, contour_line_next.line, second_part.b)) - graph.append_edge(edge_it->vertex1()->color(), int_point_idx); - } - } - } - } - } - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge and processed edges - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_finite() && !bool(edge_it->color()) && edge_it->vertex0()->color() < graph.nodes_count() && - edge_it->vertex1()->color() < graph.nodes_count()) { - // Skip cases, when the edge is between two same vertices, which is in cases two near vertices were merged together. - if (edge_it->vertex0()->color() == edge_it->vertex1()->color()) - continue; - - size_t from_idx = edge_it->vertex0()->color(); - size_t to_idx = edge_it->vertex1()->color(); - graph.append_edge(from_idx, to_idx); - } - mark_processed(edge_it); - } - - graph.remove_nodes_with_one_arc(); - return graph; -} - -static inline Polygon to_polygon(const std::vector &lines) -{ - Polygon poly_out; - poly_out.points.reserve(lines.size()); - for (const Linef &line : lines) - poly_out.points.emplace_back(mk_point(line.a)); - return poly_out; -} - -// Returns list of polygons and assigned colors. -// It iterates through all nodes on the border between two different colors, and from this point, -// start selection always left most edges for every node to construct CCW polygons. -// Assumes that graph is planar (without self-intersection edges) -static std::vector extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders) -{ - std::vector used_arcs(graph.arcs.size(), false); - // When there is no next arc, then is returned original_arc or edge with is marked as used - auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { - std::vector> sorted_arcs; - for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (graph.nodes[arc.to_idx].point == process_line.a || used_arcs[arc_idx]) - continue; - - assert(original_arc.to_idx == arc.from_idx); - Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized(); - Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized(); - - double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0)); - if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; - - sorted_arcs.emplace_back(&arc, angle); - } - - std::sort(sorted_arcs.begin(), sorted_arcs.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second < r.second; }); - - // Try to return left most edge witch is unused - for (auto &sorted_arc : sorted_arcs) - if (size_t arc_idx = sorted_arc.first - &graph.arcs.front(); !used_arcs[arc_idx]) - return *sorted_arc.first; - - if (sorted_arcs.empty()) - return original_arc; - - return *(sorted_arcs.front().first); - }; - - auto all_arc_used = [&used_arcs](const MMU_Graph::Node &node) -> bool { - return std::all_of(node.arc_idxs.cbegin(), node.arc_idxs.cend(), [&used_arcs](const size_t &arc_idx) -> bool { return used_arcs[arc_idx]; }); - }; - - std::vector expolygons_segments(num_extruders + 1); - for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) { - const MMU_Graph::Node &node = graph.nodes[node_idx]; - - for (const size_t &arc_idx : node.arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx]) - continue; - - Linef process_line(node.point, graph.nodes[arc.to_idx].point); - used_arcs[arc_idx] = true; - - std::vector face_lines; - face_lines.emplace_back(process_line); - Vec2d start_p = process_line.a; - - Linef p_vec = process_line; - const MMU_Graph::Arc *p_arc = &arc; - do { - const MMU_Graph::Arc &next = get_next(p_vec, *p_arc); - size_t next_arc_idx = &next - &graph.arcs.front(); - face_lines.emplace_back(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); - if (used_arcs[next_arc_idx]) - break; - - used_arcs[next_arc_idx] = true; - p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); - p_arc = &next; - } while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); - - if (Polygon poly = to_polygon(face_lines); poly.is_counter_clockwise() && poly.is_valid()) - expolygons_segments[arc.color].emplace_back(std::move(poly)); - } - } - return expolygons_segments; + return mk_vector_vec2d(&edge).norm(); } // Used in remove_multiple_edges_in_vertices() // Returns length of edge with is connected to contour. To this length is include other edges with follows it if they are almost straight (with the // tolerance of 15) And also if node between two subsequent edges is connected only to these two edges. -static inline double compute_edge_length(const MMU_Graph &graph, const size_t start_idx, const size_t &start_arc_idx) +static inline double calc_total_edge_length(const VD::edge_type &starting_edge) { - assert(start_arc_idx < graph.arcs.size()); - std::vector used_arcs(graph.arcs.size(), false); + double total_edge_length = edge_length(starting_edge); + const VD::edge_type *prev = &starting_edge; + do { + if (prev->is_finite() && non_deleted_edge_count(*prev->vertex1()) > 2) + break; - used_arcs[start_arc_idx] = true; - const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx]; - size_t idx = start_idx; - double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); - while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) { - bool found = false; - for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) { - if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) { - Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); - Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); + bool found_next_edge = false; + const VD::edge_type *current = prev->next(); + do { + if (current->color() == VD_ANNOTATION::DELETED) + continue; - Vec2d first_line_vec = (first_line.a - first_line.b); - Vec2d second_line_vec = (second_line.b - second_line.a); - Vec2d first_line_vec_n = first_line_vec.normalized(); - Vec2d second_line_vec_n = second_line_vec.normalized(); - double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); - if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; + Vec2d first_line_vec_n = mk_flipped_vector_vec2d(prev).normalized(); + Vec2d second_line_vec_n = mk_vector_vec2d(current).normalized(); + double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); + if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0) + angle = 2.0 * (double) PI - angle; - if (std::abs(angle - PI) >= (PI / 12)) + if (std::abs(angle - PI) >= (PI / 12)) + continue; + + prev = current; + found_next_edge = true; + total_edge_length += edge_length(*current); + + break; + } while (current = current->prev()->twin(), current != prev->next()); + + if (!found_next_edge) + break; + + } while (prev != &starting_edge); + + return total_edge_length; +} + +// When a Voronoi vertex has more than one Voronoi edge (for example, in concave parts of a polygon), +// we leave just one Voronoi edge in the Voronoi vertex. +// This Voronoi edge is selected based on a heuristic. +static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { + if (non_deleted_edge_count(vertex) <= 1) + return; + + std::vector> edges_to_check; + const VD::edge_type *edge = vertex.incident_edge(); + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + edges_to_check.emplace_back(edge, calc_total_edge_length(*edge)); + } while (edge = edge->prev()->twin(), edge != vertex.incident_edge()); + + std::sort(edges_to_check.begin(), edges_to_check.end(), [](const auto &l, const auto &r) -> bool { + return l.second > r.second; + }); + + while (edges_to_check.size() > 1) { + const VD::edge_type &edge_to_check = *edges_to_check.back().first; + edge_to_check.color(VD_ANNOTATION::DELETED); + edge_to_check.twin()->color(VD_ANNOTATION::DELETED); + + if (const VD::vertex_type &vertex_to_delete = *edge_to_check.vertex1(); can_vertex_be_deleted(vertex_to_delete)) + delete_vertex_deep(vertex_to_delete); + + edges_to_check.pop_back(); + } + } + +// Returns list of ExPolygons for each extruder + 1 for default unpainted regions. +// It iterates through all nodes on the border between two different colors, and from this point, +// start selection always left most edges for every node to construct CCW polygons. +static std::vector extract_colored_segments(const std::vector &colored_polygons, + const size_t num_extruders, + const size_t layer_idx) +{ + const ColoredLines colored_lines = to_lines(colored_polygons); + const BoundingBox bbox = get_extents(colored_polygons); + + auto get_next_contour_line = [&colored_polygons](const ColoredLine &line) -> const ColoredLine & { + size_t contour_line_size = colored_polygons[line.poly_idx].size(); + size_t contour_next_idx = (line.local_line_idx + 1) % contour_line_size; + return colored_polygons[line.poly_idx][contour_next_idx]; + }; + + Voronoi::VD vd; + vd.construct_voronoi(colored_lines.begin(), colored_lines.end()); + + // First, mark each Voronoi vertex on the input polygon to prevent it from being deleted later. + for (const Voronoi::VD::cell_type &cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) continue; - idx = arc->to_idx; - arc = &arc_n; + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) + cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::VERTEX_ON_CONTOUR); + } - line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); - used_arcs[arc_idx] = true; - found = true; - break; - } + // Second, remove all Voronoi vertices that are outside the bounding box of input polygons. + // Such Voronoi vertices are definitely not inside of input polygons, so we don't care about them. + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (vertex.color() == VD_ANNOTATION::DELETED || vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) + continue; + + if (!Geometry::VoronoiUtils::is_in_range(vertex) || !bbox.contains(Geometry::VoronoiUtils::to_point(vertex).cast())) + delete_vertex_deep(vertex); + } + + // Third, remove all Voronoi edges that are infinite. + for (const Voronoi::VD::edge_type &edge : vd.edges()) { + if (edge.color() != VD_ANNOTATION::DELETED && edge.is_infinite()) { + edge.color(VD_ANNOTATION::DELETED); + edge.twin()->color(VD_ANNOTATION::DELETED); + + if (edge.vertex0() != nullptr && can_vertex_be_deleted(*edge.vertex0())) + delete_vertex_deep(*edge.vertex0()); + + if (edge.vertex1() != nullptr && can_vertex_be_deleted(*edge.vertex1())) + delete_vertex_deep(*edge.vertex1()); } - if (!found) + } + + // Fourth, remove all edges that point outward from the input polygon. + for (Voronoi::VD::cell_type cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; + + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) { + const ColoredLine ¤t_line = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); + const ColoredLine &next_line = get_next_contour_line(current_line); + + const VD::edge_type *edge = cell_range.edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + if (!points_inside(current_line.line, next_line.line, Geometry::VoronoiUtils::to_point(edge->vertex1()).cast())) { + edge->color(VD_ANNOTATION::DELETED); + edge->twin()->color(VD_ANNOTATION::DELETED); + delete_vertex_deep(*edge->vertex1()); + } + } while (edge = edge->prev()->twin(), edge != cell_range.edge_begin); + } + } + + // Fifth, if a Voronoi vertex has more than one Voronoi edge, remove all but one of them based on heuristics. + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) + remove_multiple_edges_in_vertex(vertex); +} + +#ifdef MM_SEGMENTATION_DEBUG_GRAPH +{ + static int iRun = 0; + export_graph_to_svg(debug_out_path("mm-graph-%d-%d.svg", layer_idx, iRun++), vd, colored_polygons); +} +#endif // MM_SEGMENTATION_DEBUG_GRAPH + + // Sixth, extract the colored segments from the annotated Voronoi diagram. + std::vector segmented_expolygons_per_extruder(num_extruders + 1); + for (const Voronoi::VD::cell_type &cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; + + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) { + if (cell_range.edge_begin->vertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR) + continue; + + const ColoredLine source_segment = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); + + Polygon segmented_polygon; + segmented_polygon.points.emplace_back(source_segment.line.b); + + // We have ensured that each segmented_polygon have to start at edge_begin->vertex0() and end at edge_end->vertex1(). + const VD::edge_type *edge = cell_range.edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + const VD::vertex_type &next_vertex = *edge->vertex1(); + segmented_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(next_vertex).cast()); + edge->color(VD_ANNOTATION::DELETED); + + if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) { + assert(next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR); break; } - return line_total_length; -} + edge = edge->twin(); + } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); -// Used for fixing double Voronoi edges for concave parts of the polygon. -static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vector> &color_poly) -{ - std::vector>> colored_segments = get_all_segments(color_poly); - for (const std::vector> &colored_segment_p : colored_segments) { - size_t poly_idx = &colored_segment_p - &colored_segments.front(); - for (const std::pair &colored_segment : colored_segment_p) { - size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); - size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); - Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); + if (edge->vertex1() != cell_range.edge_end->vertex1()) + continue; - if (graph.nodes[first_idx].arc_idxs.size() >= 3) { - std::vector> arc_to_check; - for (const size_t &arc_idx : graph.nodes[first_idx].arc_idxs) { - MMU_Graph::Arc &n_arc = graph.arcs[arc_idx]; - if (n_arc.type == MMU_Graph::ARC_TYPE::NON_BORDER) { - double total_len = compute_edge_length(graph, first_idx, arc_idx); - arc_to_check.emplace_back(&n_arc, total_len); + cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::DELETED); + segmented_expolygons_per_extruder[source_segment.color].emplace_back(std::move(segmented_polygon)); } } - std::sort(arc_to_check.begin(), arc_to_check.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second > r.second; }); - while (arc_to_check.size() > 1) { - graph.remove_edge(first_idx, arc_to_check.back().first->to_idx); - arc_to_check.pop_back(); - } - } - } - } + // Merge all polygons together for each extruder + for (auto &segmented_expolygons : segmented_expolygons_per_extruder) + segmented_expolygons = union_ex(segmented_expolygons); + + return segmented_expolygons_per_extruder; } static void cut_segmented_layers(const std::vector &input_expolygons, @@ -1274,7 +829,7 @@ static void cut_segmented_layers(const std::vector &input_exp const float interlocking_depth, const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin"; const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -1290,7 +845,7 @@ static void cut_segmented_layers(const std::vector &input_exp } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end"; } static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) @@ -1301,10 +856,8 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d return false; } -//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM - -// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo -static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object, +// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo +static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object, const std::vector &input_expolygons, const std::function &throw_on_cancel_callback) { @@ -1329,22 +882,22 @@ static inline std::vector> mmu_segmentation_top_and_bott std::vector zs = zs_from_layers(layers); Transform3d object_trafo = print_object.trafo_centered(); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM static int iRun = 0; -#endif // NDEBUG +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM if (max_top_layers > 0 || max_bottom_layers > 0) { for (const ModelVolume *mv : print_object.model_object()->volumes) if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { static int iRun = 0; its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str()); } -#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM if (! painted.indices.empty()) { std::vector top, bottom; if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { @@ -1399,7 +952,7 @@ static inline std::vector> mmu_segmentation_top_and_bott filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f))); filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f))); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" }; static int iRun = 0; @@ -1421,7 +974,7 @@ static inline std::vector> mmu_segmentation_top_and_bott } ++ iRun; } -#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM std::vector> triangles_by_color_bottom(num_extruders); std::vector> triangles_by_color_top(num_extruders); @@ -1552,7 +1105,7 @@ static std::vector> merge_segmented_layers( segmented_regions_merged.assign(num_layers, std::vector(num_extruders)); assert(num_extruders + 1 == top_and_bottom_layers.size()); - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { assert(segmented_regions[layer_idx].size() == num_extruders + 1); @@ -1579,12 +1132,12 @@ static std::vector> merge_segmented_layers( } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end"; return segmented_regions_merged; } -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS +#ifdef MM_SEGMENTATION_DEBUG_REGIONS static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; @@ -1596,35 +1149,15 @@ static void export_regions_to_svg(const std::string &path, const std::vector= 0 && extrude_idx < int(colors.size())) - svg.draw(by_extruder, colors[extrude_idx], stroke_width); - else - svg.draw(by_extruder, "black", stroke_width); - } -} -#endif // MMU_SEGMENTATION_DEBUG_REGIONS - -#ifdef MMU_SEGMENTATION_DEBUG_GRAPH -static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - for (const MMU_Graph::Node &node : graph.nodes) - for (const size_t &arc_idx : node.arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point)); - if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size())) - svg.draw(arc_line, colors[arc.color], stroke_width); + if (extrude_idx < int(colors.size())) + svg.draw(by_extruder, colors[extrude_idx]); else - svg.draw(arc_line, "black", stroke_width); + svg.draw(by_extruder, "black"); } } -#endif // MMU_SEGMENTATION_DEBUG_GRAPH +#endif // MM_SEGMENTATION_DEBUG_REGIONS -#ifdef MMU_SEGMENTATION_DEBUG_INPUT +#ifdef MM_SEGMENTATION_DEBUG_INPUT void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) { coordf_t stroke_width = scale_(0.05); @@ -1634,13 +1167,14 @@ void export_processed_input_expolygons_to_svg(const std::string &path, const Lay ::Slic3r::SVG svg(path.c_str(), bbox); for (LayerRegion *region : regions) - svg.draw_outline(region->slices.surfaces, "blue", "cyan", stroke_width); + for (const Surface &surface : region->slices()) + svg.draw_outline(surface, "blue", "cyan", stroke_width); svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_INPUT +#endif // MM_SEGMENTATION_DEBUG_INPUT -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES static void export_painted_lines_to_svg(const std::string &path, const std::vector> &all_painted_lines, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; @@ -1656,10 +1190,10 @@ static void export_painted_lines_to_svg(const std::string &path, const std::vect for (const PaintedLine &painted_line : painted_lines) svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES -#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -static void export_colorized_polygons_to_svg(const std::string &path, const std::vector> &colorized_polygons, const ExPolygons &lslices) +#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +static void export_colorized_polygons_to_svg(const std::string &path, const std::vector &colorized_polygons, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; coordf_t stroke_width = scale_(0.05); @@ -1667,19 +1201,19 @@ static void export_colorized_polygons_to_svg(const std::string &path, const std: bbox.offset(scale_(1.)); ::Slic3r::SVG svg(path.c_str(), bbox); - for (const std::vector &colorized_polygon : colorized_polygons) + for (const ColoredLines &colorized_polygon : colorized_polygons) for (const ColoredLine &colorized_line : colorized_polygon) svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS // Check if all ColoredLine representing a single layer uses the same color. -static bool has_layer_only_one_color(const std::vector> &colored_polygons) +static bool has_layer_only_one_color(const std::vector &colored_polygons) { assert(!colored_polygons.empty()); assert(!colored_polygons.front().empty()); int first_line_color = colored_polygons.front().front().color; - for (const std::vector &colored_polygon : colored_polygons) + for (const ColoredLines &colored_polygon : colored_polygons) for (const ColoredLine &colored_line : colored_polygon) if (first_line_color != colored_line.color) return false; @@ -1701,8 +1235,11 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); +#ifdef MM_SEGMENTATION_DEBUG + static int iRun = 0; +#endif // MM_SEGMENTATION_DEBUG // Merge all regions and remove small holes - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1724,15 +1261,12 @@ std::vector> multi_material_segmentation_by_painting(con // Calling expolygons_simplify fixed these issues. input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI/6); -#ifdef MMU_SEGMENTATION_DEBUG_INPUT - { - static int iRun = 0; - export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun++), layers[layer_idx]->regions(), input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_INPUT +#ifdef MM_SEGMENTATION_DEBUG_INPUT + export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_INPUT } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end"; std::vector layer_bboxes(num_layers); for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { @@ -1755,12 +1289,12 @@ std::vector> multi_material_segmentation_by_painting(con edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); } - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; for (const ModelVolume *mv : print_object.model_object()->volumes) { tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); if (!mv->is_model_part() || custom_facets.indices.empty()) continue; @@ -1836,39 +1370,30 @@ std::vector> multi_material_segmentation_by_painting(con } }); // end of parallel_for } - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - end"; - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - painted layers count: " + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); if (!painted_lines[layer_idx].empty()) { -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES - { - static int iRun = 0; - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun++), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES std::vector> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx])); -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES - { - static int iRun = 0; - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun++), post_processed_painted_lines, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun), post_processed_painted_lines, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - std::vector> color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); + std::vector color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); -#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - { - static int iRun = 0; - export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun++), color_poly, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS + export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun), color_poly, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS assert(!color_poly.empty()); assert(!color_poly.front().empty()); @@ -1876,30 +1401,16 @@ std::vector> multi_material_segmentation_by_painting(con // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; } else { - MMU_Graph graph = build_graph(layer_idx, color_poly); - remove_multiple_edges_in_vertices(graph, color_poly); - graph.remove_nodes_with_one_arc(); - -#ifdef MMU_SEGMENTATION_DEBUG_GRAPH - { - static int iRun = 0; - export_graph_to_svg(debug_out_path("mm-graph-final-%d-%d.svg", layer_idx, iRun++), graph, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_GRAPH - - segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders); + segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); } -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS - { - static int iRun = 0; - export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun++), segmented_regions[layer_idx], input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_REGIONS +#ifdef MM_SEGMENTATION_DEBUG_REGIONS + export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; throw_on_cancel_callback(); if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { @@ -1908,19 +1419,20 @@ std::vector> multi_material_segmentation_by_painting(con } // The first index is extruder number (includes default extruder), and the second one is layer number - std::vector> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); + std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); throw_on_cancel_callback(); std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback); throw_on_cancel_callback(); -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS - { - static int iRun = 0; +#ifdef MM_SEGMENTATION_DEBUG_REGIONS for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) - export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun++), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_REGIONS + export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS + +#ifdef MM_SEGMENTATION_DEBUG + ++iRun; +#endif // MM_SEGMENTATION_DEBUG return segmented_regions_merged; } diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 4efdc69..9f10db9 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -9,10 +9,38 @@ namespace Slic3r { class PrintObject; class ExPolygon; +using ExPolygons = std::vector; + +struct ColoredLine +{ + Line line; + int color; + int poly_idx = -1; + int local_line_idx = -1; +}; + +using ColoredLines = std::vector; // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); } // namespace Slic3r +namespace boost::polygon { +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + + static inline point_type get(const Slic3r::ColoredLine &line, const direction_1d &dir) + { + return dir.to_int() ? line.line.b : line.line.a; + } +}; +} // namespace boost::polygon #endif // slic3r_MultiMaterialSegmentation_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c17babf..6e77338 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -636,7 +636,25 @@ static std::vector s_Preset_sla_material_options { "material_print_speed", "default_sla_material_profile", "compatible_prints", "compatible_prints_condition", - "compatible_printers", "compatible_printers_condition", "inherits" + "compatible_printers", "compatible_printers_condition", "inherits", + + // overriden options + "material_ow_support_head_front_diameter", + "material_ow_support_head_penetration", + "material_ow_support_head_width", + "material_ow_support_pillar_diameter", + + "material_ow_branchingsupport_head_front_diameter", + "material_ow_branchingsupport_head_penetration", + "material_ow_branchingsupport_head_width", + "material_ow_branchingsupport_pillar_diameter", + + "material_ow_support_points_density_relative", + + "material_ow_relative_correction_x", + "material_ow_relative_correction_y", + "material_ow_relative_correction_z", + "material_ow_elefant_foot_compensation" }; static std::vector s_Preset_sla_printer_options { @@ -763,7 +781,7 @@ void PresetCollection::load_presets( preset.config.apply(std::move(config)); Preset::normalize(preset.config); // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); + std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; @@ -2294,7 +2312,6 @@ size_t ExtruderFilaments::update_compatible_internal(const PresetWithVendorProfi const ConfigOption* opt = active_printer.preset.config.option("nozzle_diameter"); if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); - bool some_compatible = false; // Adjust printer preset config to the first extruder from m_extruder_id Preset printer_preset_adjusted = active_printer.preset; @@ -2322,7 +2339,6 @@ size_t ExtruderFilaments::update_compatible_internal(const PresetWithVendorProfi const PresetWithVendorProfile this_preset_with_vendor_profile = m_filaments->get_preset_with_vendor_profile(*preset); bool was_compatible = extr_filament.is_compatible; extr_filament.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer_adjusted, &config); - some_compatible |= extr_filament.is_compatible; if (active_print != nullptr) extr_filament.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer_adjusted); if (!extr_filament.is_compatible && is_selected && diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 185fc86..32a18c6 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -68,6 +68,8 @@ PresetBundle::PresetBundle() : this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); this->sla_materials.default_preset().compatible_printers_condition(); this->sla_materials.default_preset().inherits(); + // Set all the nullable values to nils. + this->sla_materials.default_preset().config.null_nullables(); this->sla_prints.default_preset().config.optptr("sla_print_settings_id", true); this->sla_prints.default_preset().config.opt_string("output_filename_format", true) = "[input_filename_base].sl1"; @@ -687,6 +689,7 @@ void PresetBundle::export_selections(AppConfig &config) //assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias); config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset_name()); + if (!extruders_filaments.empty()) // Tomas: To prevent crash with SLA overrides config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name()); for (unsigned i = 1; i < extruders_filaments.size(); ++i) { char name[64]; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 01414b2..68fb7c5 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1049,7 +1049,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor this->set_status(90, message); // Create GCode on heap, it has quite a lot of data. - std::unique_ptr gcode(new GCodeGenerator); + std::unique_ptr gcode(new GCodeGenerator(const_cast(this))); gcode->do_export(this, path.c_str(), result, thumbnail_cb); if (m_conflict_result.has_value()) @@ -1509,8 +1509,6 @@ void Print::_make_wipe_tower() // Initialize the wipe tower. WipeTower wipe_tower(m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); - //wipe_tower.set_retract(); - //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < m_config.nozzle_diameter.size(); ++ i) @@ -1578,7 +1576,7 @@ void Print::_make_wipe_tower() m_wipe_tower_data.final_purge = Slic3r::make_unique( wipe_tower.tool_change((unsigned int)(-1))); - m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_data.used_filament_until_layer = wipe_tower.get_used_filament_until_layer(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); m_wipe_tower_data.width = wipe_tower.width(); m_wipe_tower_data.first_layer_height = config().first_layer_height; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c60e00f..2047c43 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -433,7 +433,7 @@ struct WipeTowerData std::unique_ptr> priming; std::vector> tool_changes; std::unique_ptr final_purge; - std::vector used_filament; + std::vector>> used_filament_until_layer; int number_of_toolchanges; // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: @@ -452,7 +452,7 @@ struct WipeTowerData priming.reset(nullptr); tool_changes.clear(); final_purge.reset(nullptr); - used_filament.clear(); + used_filament_until_layer.clear(); number_of_toolchanges = -1; depth = 0.f; z_and_depth_pairs.clear(); @@ -619,9 +619,6 @@ public: // If zero, then the print is empty and the print shall not be executed. unsigned int num_object_instances() const; - // For Perl bindings. - PrintObjectPtrs& objects_mutable() { return m_objects; } - PrintRegionPtrs& print_regions_mutable() { return m_print_regions; } const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 828f96f..efdc5ac 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -74,8 +74,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.supported_facets.assign(mv_src.supported_facets); assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); mv_dst.seam_facets.assign(mv_src.seam_facets); - assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); - mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); + assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id()); + mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -165,34 +165,37 @@ static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_ return true; } -// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. -static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) -{ +// Returns true if va == vb when all CustomGCode items that are not the specified type (not_ignore_type) are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ( + const std::vector &va, + const std::vector &vb, + CustomGCode::Type not_ignore_type +) { auto it_a = va.begin(); auto it_b = vb.begin(); while (it_a != va.end() || it_b != vb.end()) { - if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. + if (it_a != va.end() && it_a->type != not_ignore_type) { + // Skip any CustomGCode items, which are not equal to not_ignore_type. ++ it_a; continue; } - if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. + if (it_b != vb.end() && it_b->type != not_ignore_type) { + // Skip any CustomGCode items, which are not equal to not_ignore_type. ++ it_b; continue; } if (it_a == va.end() || it_b == vb.end()) - // va or vb contains more Tool Changes than the other. + // va or vb contains more items of not_ignore_type than the other. return true; - assert(it_a->type == CustomGCode::ToolChange); - assert(it_b->type == CustomGCode::ToolChange); + assert(it_a->type == not_ignore_type); + assert(it_b->type == not_ignore_type); if (*it_a != *it_b) - // The two Tool Changes differ. + // The two items of not_ignore_type differ. return true; ++ it_a; ++ it_b; } - // There is no change in custom Tool Changes. + // There is no change in specified not_ignore_type items. return false; } @@ -1057,11 +1060,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object_status_db.add(*model_object, ModelObjectStatus::New); } else { if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - update_apply_status(num_extruders_changed || + const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z.mode; + const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z.mode; + + const bool multi_extruder_differ = (current_mode == next_mode) && (current_mode == CustomGCode::MultiExtruder || next_mode == CustomGCode::MultiExtruder); // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? + const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ToolChange); + // For multi-extruder printers, we perform a tool change before a color change. + // So, in that case, we must invalidate tool ordering and wipe tower even if custom color change g-codes differ. + const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ColorChange); + + update_apply_status( + (num_extruders_changed || tool_change_differ || multi_extruder_differ || color_change_differ) ? // The Tool Ordering and the Wipe Tower are no more valid. this->invalidate_steps({ psWipeTower, psGCodeExport }) : // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. @@ -1225,7 +1237,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. + if (model_object.name != model_object_new.name) { + update_apply_status(this->invalidate_step(psGCodeExport)); model_object.name = model_object_new.name; + } model_object.input_file = model_object_new.input_file; // Only refresh ModelInstances if there is any change. if (model_object.instances.size() != model_object_new.instances.size() || @@ -1357,7 +1372,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ std::vector painting_extruders; if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { //FIXME be more specific! Don't enumerate extruders that are not used for painting! painting_extruders.assign(num_extruders, 0); std::iota(painting_extruders.begin(), painting_extruders.end(), 1); diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index dc3078e..acb620d 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -74,7 +74,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str cfg.opt_string("input_filename_base") + default_ext : this->placeholder_parser().process(format, 0, &cfg); if (filename.extension().empty()) - filename = boost::filesystem::change_extension(filename, default_ext); + filename.replace_extension(default_ext); return filename.string(); } catch (std::runtime_error &err) { throw Slic3r::PlaceholderParserError(_u8L("Failed processing of the output_filename_format template.") + "\n" + err.what()); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1b9c037..a33aece 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -600,7 +600,8 @@ void PrintConfigDef::init_fff_params() auto overhang_speed_setting_description = L("Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: " "100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). " "Speeds for overhang sizes in between are calculated via linear interpolation. " - "If set as percentage, the speed is calculated over the external perimeter speed."); + "If set as percentage, the speed is calculated over the external perimeter speed. " + "Note that the speeds generated to gcode will never exceed the max volumetric speed value."); def = this->add("overhang_speed_0", coFloatOrPercent); def->label = L("speed for 0% overlap (bridge)"); @@ -4515,6 +4516,34 @@ void PrintConfigDef::init_sla_params() def->min = float(SCALING_FACTOR); def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.001)); + // Declare retract values for material profile, overriding the print and printer profiles. + for (const char* opt_key : { + // float + "support_head_front_diameter", "branchingsupport_head_front_diameter", + "support_head_penetration", "branchingsupport_head_penetration", + "support_head_width", "branchingsupport_head_width", + "support_pillar_diameter", "branchingsupport_pillar_diameter", + "relative_correction_x", "relative_correction_y", "relative_correction_z", + "elefant_foot_compensation", + // int + "support_points_density_relative" + }) { + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + def = this->add_nullable(std::string("material_ow_") + opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->min = it_opt->second.min; + def->max = it_opt->second.max; + def->mode = it_opt->second.mode; + switch (def->type) { + case coFloat: def->set_default_value(new ConfigOptionFloatNullable{ it_opt->second.default_value->getFloat() }); break; + case coInt: def->set_default_value(new ConfigOptionIntNullable{ it_opt->second.default_value->getInt() }); break; + default: assert(false); + } + } } // Ignore the following obsolete configuration keys: @@ -5102,9 +5131,14 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->cli = "opengl-version"; def->set_default_value(new ConfigOptionString()); + def = this->add("opengl-compatibility", coBool); + def->label = L("OpenGL compatibility profile"); + def->tooltip = L("Enable OpenGL compatibility profile"); + def->cli = "opengl-compatibility"; + def->set_default_value(new ConfigOptionBool(false)); def = this->add("opengl-debug", coBool); def->label = L("OpenGL debug output"); - def->tooltip = L("Activate OpenGL debug output on graphic cards which support it"); + def->tooltip = L("Activate OpenGL debug output on graphic cards which support it (OpenGL 4.3 or higher)"); def->cli = "opengl-debug"; def->set_default_value(new ConfigOptionBool(false)); #endif // ENABLE_GL_CORE_PROFILE @@ -5279,6 +5313,10 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = L("Data directory"); def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); + def = this->add("threads", coInt); + def->label = L("Maximum number of threads"); + def->tooltip = L("Sets the maximum number of threads the slicing process will use. If not defined, it will be decided automatically."); + def->min = 1; def = this->add("loglevel", coInt); def->label = L("Logging level"); def->tooltip = L("Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" @@ -5570,6 +5608,8 @@ static std::map s_CustomGcodeSpecificP {"before_layer_gcode", {"layer_num", "layer_z", "max_layer_z"}}, {"layer_gcode", {"layer_num", "layer_z", "max_layer_z"}}, {"toolchange_gcode", {"layer_num", "layer_z", "max_layer_z", "previous_extruder", "next_extruder", "toolchange_z"}}, + {"color_change_gcode", {"color_change_extruder"}}, + {"pause_print_gcode", {"color_change_extruder"}}, }; const std::map& custom_gcode_specific_placeholders() @@ -5608,6 +5648,10 @@ CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef() def = this->add("toolchange_z", coFloat); def->label = L("Toolchange Z"); def->tooltip = L("Height above the print bed when the toolchange takes place. Usually the same as layer_z, but can be different."); + def = this->add("color_change_extruder", coInt); + // TRN: This is a label in custom g-code editor dialog, belonging to color_change_extruder. Denoted index of the extruder for which color change is performed. + def->label = L("Color change extruder"); + def->tooltip = L("Index of the extruder for which color change will be performed. The index is zero based (first extruder has index 0)."); } const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index dd2c22d..ec0dc7a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1144,6 +1144,20 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, material_correction_y)) ((ConfigOptionFloat, material_correction_z)) ((ConfigOptionEnum, material_print_speed)) + ((ConfigOptionFloatNullable, material_ow_support_pillar_diameter)) + ((ConfigOptionFloatNullable, material_ow_branchingsupport_pillar_diameter)) + ((ConfigOptionFloatNullable, material_ow_support_head_front_diameter)) + ((ConfigOptionFloatNullable, material_ow_branchingsupport_head_front_diameter)) + ((ConfigOptionFloatNullable, material_ow_support_head_penetration)) + ((ConfigOptionFloatNullable, material_ow_branchingsupport_head_penetration)) + ((ConfigOptionFloatNullable, material_ow_support_head_width)) + ((ConfigOptionFloatNullable, material_ow_branchingsupport_head_width)) + ((ConfigOptionIntNullable, material_ow_support_points_density_relative)) + + ((ConfigOptionFloatNullable, material_ow_elefant_foot_compensation)) + ((ConfigOptionFloatNullable, material_ow_relative_correction_x)) + ((ConfigOptionFloatNullable, material_ow_relative_correction_y)) + ((ConfigOptionFloatNullable, material_ow_relative_correction_z)) ) PRINT_CONFIG_CLASS_DEFINE( diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 2ea6646..771003b 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -742,7 +742,7 @@ void PrintObject::slice_volumes() // Is any ModelVolume MMU painted? if (const auto& volumes = this->model_object()->volumes; m_print->config().nozzle_diameter.size() > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mm_segmentation_facets.empty(); }) != volumes.end()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object is multi-material painted. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 9df2862..0b2cbe1 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -3,6 +3,7 @@ #include "CSGMesh/CSGMeshCopy.hpp" #include "CSGMesh/PerformCSGMeshBooleans.hpp" #include "format.hpp" +#include "StaticMap.hpp" #include "Format/SLAArchiveFormatRegistry.hpp" @@ -197,6 +198,61 @@ std::vector SLAPrint::print_object_ids() const return out; } +static t_config_option_keys print_config_diffs(const StaticPrintConfig ¤t_config, + const DynamicPrintConfig &new_full_config, + DynamicPrintConfig &material_overrides) +{ + using namespace std::string_view_literals; + + static const constexpr StaticSet overriden_keys = { + "support_head_front_diameter"sv, + "support_head_penetration"sv, + "support_head_width"sv, + "support_pillar_diameter"sv, + "branchingsupport_head_front_diameter"sv, + "branchingsupport_head_penetration"sv, + "branchingsupport_head_width"sv, + "branchingsupport_pillar_diameter"sv, + "support_points_density_relative"sv, + "relative_correction_x"sv, + "relative_correction_y"sv, + "relative_correction_z"sv, + "elefant_foot_compensation"sv, + }; + + static constexpr auto material_ow_prefix = "material_ow_"; + + t_config_option_keys print_diff; + for (const t_config_option_key &opt_key : current_config.keys()) { + const ConfigOption *opt_old = current_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_override = std::binary_search(overriden_keys.begin(), overriden_keys.end(), opt_key) ? new_full_config.option(material_ow_prefix + opt_key) : nullptr; + if (opt_new_override != nullptr && ! opt_new_override->is_nil()) { + // An override is available at some of the material presets. + bool overriden = opt_new->overriden_by(opt_new_override); + if (overriden || *opt_old != *opt_new) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_override); + bool changed = *opt_old != *opt_copy; + if (changed) + print_diff.emplace_back(opt_key); + if (changed || overriden) { + // overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. + material_overrides.set_key_value(opt_key, opt_copy); + } else + delete opt_copy; + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + + return print_diff; +} SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) { #ifdef _DEBUG @@ -209,12 +265,14 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con config.option("printer_settings_id", true); config.option("physical_printer_settings_id", true); // Collect changes to print config. + DynamicPrintConfig mat_overrides; t_config_option_keys print_diff = m_print_config.diff(config); - t_config_option_keys printer_diff = m_printer_config.diff(config); + t_config_option_keys printer_diff = print_config_diffs(m_printer_config, config, mat_overrides); t_config_option_keys material_diff = m_material_config.diff(config); - t_config_option_keys object_diff = m_default_object_config.diff(config); + t_config_option_keys object_diff = print_config_diffs(m_default_object_config, config, mat_overrides); t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config); + config.apply(mat_overrides, true); // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) @@ -598,6 +656,26 @@ std::string SLAPrint::validate(std::vector*) const if (iexpt_cur < iexpt_min || iexpt_cur > iexpt_max) return _u8L("Initial exposition time is out of printer profile bounds."); + for (const std::string& prefix : { "", "branching" }) { + + double head_penetration = m_full_print_config.opt_float(prefix + "support_head_penetration"); + double head_width = m_full_print_config.opt_float(prefix + "support_head_width"); + + if (head_penetration > head_width) { + return _u8L("Invalid Head penetration\n" + "Head penetration should not be greater than the Head width.\n" + "Please check value of Head penetration in Print Settings or Material Overrides."); + } + + double pinhead_d = m_full_print_config.opt_float(prefix + "support_head_front_diameter"); + double pillar_d = m_full_print_config.opt_float(prefix + "support_pillar_diameter"); + + if (pinhead_d > pillar_d) { + return _u8L("Invalid pinhead diameter\n" + "Pinhead front diameter should be smaller than the Pillar diameter.\n" + "Please check value of Pinhead front diameter in Print Settings or Material Overrides."); + } + } return ""; } @@ -732,66 +810,80 @@ void SLAPrint::process() bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys, bool &invalidate_all_model_objects) { + using namespace std::string_view_literals; if (opt_keys.empty()) return false; - static std::unordered_set steps_full = { - "initial_layer_height", - "material_correction", - "material_correction_x", - "material_correction_y", - "material_correction_z", - "material_print_speed", - "relative_correction", - "relative_correction_x", - "relative_correction_y", - "relative_correction_z", - "absolute_correction", - "elefant_foot_compensation", - "elefant_foot_min_width", - "gamma_correction" + static constexpr StaticSet steps_full = { + "initial_layer_height"sv, + "material_correction"sv, + "material_correction_x"sv, + "material_correction_y"sv, + "material_correction_z"sv, + "material_print_speed"sv, + "relative_correction"sv, + "relative_correction_x"sv, + "relative_correction_y"sv, + "relative_correction_z"sv, + "absolute_correction"sv, + "elefant_foot_compensation"sv, + "elefant_foot_min_width"sv, + "gamma_correction"sv, }; // Cache the plenty of parameters, which influence the final rasterization only, // or they are only notes not influencing the rasterization step. - static std::unordered_set steps_rasterize = { - "min_exposure_time", - "max_exposure_time", - "exposure_time", - "min_initial_exposure_time", - "max_initial_exposure_time", - "initial_exposure_time", - "display_width", - "display_height", - "display_pixels_x", - "display_pixels_y", - "display_mirror_x", - "display_mirror_y", - "display_orientation", - "sla_archive_format", - "sla_output_precision" + static constexpr StaticSet steps_rasterize = { + "min_exposure_time"sv, + "max_exposure_time"sv, + "exposure_time"sv, + "min_initial_exposure_time"sv, + "max_initial_exposure_time"sv, + "initial_exposure_time"sv, + "display_width"sv, + "display_height"sv, + "display_pixels_x"sv, + "display_pixels_y"sv, + "display_mirror_x"sv, + "display_mirror_y"sv, + "display_orientation"sv, + "sla_archive_format"sv, + "sla_output_precision"sv }; - static std::unordered_set steps_ignore = { - "bed_shape", - "max_print_height", - "printer_technology", - "output_filename_format", - "fast_tilt_time", - "slow_tilt_time", - "high_viscosity_tilt_time", - "area_fill", - "bottle_cost", - "bottle_volume", - "bottle_weight", - "material_density" + static StaticSet steps_ignore = { + "bed_shape"sv, + "max_print_height"sv, + "printer_technology"sv, + "output_filename_format"sv, + "fast_tilt_time"sv, + "slow_tilt_time"sv, + "high_viscosity_tilt_time"sv, + "area_fill"sv, + "bottle_cost"sv, + "bottle_volume"sv, + "bottle_weight"sv, + "material_density"sv, + "material_ow_support_pillar_diameter"sv, + "material_ow_support_head_front_diameter"sv, + "material_ow_support_head_penetration"sv, + "material_ow_support_head_width"sv, + "material_ow_branchingsupport_pillar_diameter"sv, + "material_ow_branchingsupport_head_front_diameter"sv, + "material_ow_branchingsupport_head_penetration"sv, + "material_ow_branchingsupport_head_width"sv, + "material_ow_elefant_foot_compensation"sv, + "material_ow_support_points_density_relative"sv, + "material_ow_relative_correction_x"sv, + "material_ow_relative_correction_y"sv, + "material_ow_relative_correction_z"sv }; std::vector steps; std::vector osteps; bool invalidated = false; - for (const t_config_option_key &opt_key : opt_keys) { + for (std::string_view opt_key : opt_keys) { if (steps_rasterize.find(opt_key) != steps_rasterize.end()) { // These options only affect the final rasterization, or they are just notes without influence on the output, // so there is nothing to invalidate. @@ -869,7 +961,8 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { std::lock_guard guard(m_mutex); if (key.second >= LayerIndex(m_data.size())) - return std::optional>{}; - const auto &layer = m_data[key.second]; + return std::nullopt; + + const LayerData &layer = m_data[key.second]; auto it = layer.find(key.first); - return it == layer.end() ? - std::optional>{} : std::optional>{ it->second }; + if (it == layer.end()) + return std::nullopt; + + return std::optional>{it->second}; } // Get a collision area at a given layer for a radius that is a lower or equial to the key radius. std::optional>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const { diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 5af9c53..6c28647 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -7,7 +7,6 @@ #include "../BuildVolume.hpp" #include "../ClipperUtils.hpp" #include "../EdgeGrid.hpp" -#include "../Fill/Fill.hpp" #include "../Layer.hpp" #include "../Print.hpp" #include "../MultiPoint.hpp" diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 2eee1f1..7511f60 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -149,8 +149,13 @@ void SliceConnection::print_info(const std::string &tag) const std::cout << "variance: " << variance.x() << " " << variance.y() << std::endl; std::cout << "covariance: " << covariance << std::endl; } -Integrals::Integrals (const Polygons& polygons) { - for (const Polygon &polygon : polygons) { +Integrals::Integrals(const Polygon &polygon) +{ + if (polygon.points.size() < 3) { + assert(false && "Polygon is expected to have non-zero area!"); + *this = Integrals{}; + return; + } Vec2f p0 = unscaled(polygon.first_point()).cast(); for (size_t i = 2; i < polygon.points.size(); i++) { Vec2f p1 = unscaled(polygon.points[i - 1]).cast(); @@ -167,6 +172,44 @@ Integrals::Integrals (const Polygons& polygons) { this->xy += sign * second_moment_of_area_covariance; } } +Integrals::Integrals(const Polygons &polygons) +{ + for (const Polygon &polygon : polygons) { + *this = *this + Integrals{polygon}; + } +} + +Integrals::Integrals(const Polylines& polylines, const std::vector& widths) { + assert(polylines.size() == widths.size()); + for (size_t i = 0; i < polylines.size(); ++i) { + Lines polyline{polylines[i].lines()}; + float width{widths[i]}; + for (const Line& line : polyline) { + Vec2f line_direction = unscaled(line.vector()).cast(); + Vec2f normal{line_direction.y(), -line_direction.x()}; + normal.normalize(); + + Vec2f line_a = unscaled(line.a).cast(); + Vec2f line_b = unscaled(line.b).cast(); + Vec2crd a = scaled(Vec2f{line_a + normal * width/2}); + Vec2crd b = scaled(Vec2f{line_b + normal * width/2}); + Vec2crd c = scaled(Vec2f{line_b - normal * width/2}); + Vec2crd d = scaled(Vec2f{line_a - normal * width/2}); + + const Polygon ractangle({a, b, c, d}); + Integrals integrals{ractangle}; + *this = *this + integrals; + } + } +} + +Integrals::Integrals(float area, Vec2f x_i, Vec2f x_i_squared, float xy) + : area(area), x_i(std::move(x_i)), x_i_squared(std::move(x_i_squared)), xy(xy) +{} + +Integrals operator+(const Integrals &a, const Integrals &b) +{ + return Integrals{a.area + b.area, a.x_i + b.x_i, a.x_i_squared + b.x_i_squared, a.xy + b.xy}; } SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) @@ -468,9 +511,40 @@ ObjectPart::ObjectPart( continue; } - const Polygons polygons{collection->polygons_covered_by_width()}; + for (const ExtrusionEntity* entity: collection->flatten()) { + Polylines polylines; + std::vector widths; - const Integrals integrals{polygons}; + if ( + const auto* path = dynamic_cast(entity); + path != nullptr + ) { + polylines.push_back(path->as_polyline()); + widths.push_back(path->width()); + } else if ( + const auto* loop = dynamic_cast(entity); + loop != nullptr + ) { + for (const ExtrusionPath& path : loop->paths) { + polylines.push_back(path.as_polyline()); + widths.push_back(path.width()); + } + } else if ( + const auto* multi_path = dynamic_cast(entity); + multi_path != nullptr + ) { + for (const ExtrusionPath& path : multi_path->paths) { + polylines.push_back(path.as_polyline()); + widths.push_back(path.width()); + } + } else { + throw std::runtime_error( + "Failed to construct object part from extrusions!" + " Unknown extrusion type." + ); + } + + const Integrals integrals{polylines, widths}; const float volume = integrals.area * layer_height; this->volume += volume; this->volume_centroid_accumulator += to_3d(integrals.x_i, center_z * integrals.area) / integrals.area * volume; @@ -482,6 +556,7 @@ ObjectPart::ObjectPart( this->sticking_second_moment_of_area_covariance_accumulator += integrals.xy; } } + } if (brim) { Integrals integrals{*brim}; diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 4f7369e..b0df4e5 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -146,16 +146,26 @@ class Integrals{ * @param polygons List of polygons specifing the domain. */ explicit Integrals(const Polygons& polygons); + explicit Integrals(const Polygon& polygon); + /** + * Construct integral x_i int x_i^2 (i=1,2), xy and integral 1 (area) over + * a set of rectangles defined by a "thick" polyline. + */ + explicit Integrals(const Polylines& polylines, const std::vector& widths); // TODO refactor and delete the default constructor Integrals() = default; + Integrals(float area, Vec2f x_i, Vec2f x_i_squared, float xy); float area{}; Vec2f x_i{Vec2f::Zero()}; Vec2f x_i_squared{Vec2f::Zero()}; float xy{}; +private: + void add(const Integrals& other); }; +Integrals operator+(const Integrals& a, const Integrals& b); float compute_second_moment( const Integrals& integrals, const Vec2f& axis_direction diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 7d6532c..8509702 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -49,8 +49,6 @@ #define ENABLE_OPENGL_ES 0 // Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows) #define ENABLE_GL_CORE_PROFILE (1 && !ENABLE_OPENGL_ES) -// Enable OpenGL debug messages using debug context -#define ENABLE_OPENGL_DEBUG_OPTION (1 && ENABLE_GL_CORE_PROFILE) // Enable imgui dialog which allows to set the parameters used to export binarized gcode #define ENABLE_BINARIZED_GCODE_DEBUG_WINDOW 0 diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 9f8b670..5e23a88 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -231,12 +231,11 @@ void name_tbb_thread_pool_threads_set_locale() // const size_t nthreads_hw = std::thread::hardware_concurrency(); const size_t nthreads_hw = tbb::this_task_arena::max_concurrency(); size_t nthreads = nthreads_hw; + if (thread_count) { + nthreads = std::min(nthreads_hw, *thread_count); + } -#if 0 - // Shiny profiler is not thread safe, thus disable parallelization. - disable_multi_threading(); - nthreads = 1; -#endif + enforce_thread_count(nthreads); size_t nthreads_running(0); std::condition_variable cv; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 48f9b29..29246e0 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -16,6 +16,7 @@ namespace boost { namespace filesystem { class directory_entry; }} namespace Slic3r { +inline std::optional thread_count; extern void set_logging_level(unsigned int level); extern unsigned get_logging_level(); // Format memory allocated, separate thousands by comma. @@ -24,7 +25,7 @@ extern std::string format_memsize_MB(size_t n); // The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true. // Latter is used to get the memory info from SysInfoDialog. extern std::string log_memory_info(bool ignore_loglevel = false); -extern void disable_multi_threading(); +extern void enforce_thread_count(std::size_t count); // Returns the size of physical memory (RAM) in bytes. extern size_t total_physical_memory(); @@ -67,9 +68,6 @@ const std::string& data_dir(); // so the user knows where to search for the debugging output. std::string debug_out_path(const char *name, ...); -// A special type for strings encoded in the local Windows 8-bit code page. -// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. -typedef std::string local_encoded_string; // Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character. // Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence. diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 2add2c9..5a25795 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -118,23 +118,22 @@ unsigned get_logging_level() } // Force set_logging_level(<=error) after loading of the DLL. -// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter -// to perform unit and integration tests. +// This is used ot disable logging for unit and integration tests. static struct RunOnInit { RunOnInit() { set_logging_level(1); } } g_RunOnInit; -void disable_multi_threading() +void enforce_thread_count(const std::size_t count) { // Disable parallelization to simplify debugging. #ifdef TBB_HAS_GLOBAL_CONTROL { - static tbb::global_control gc(tbb::global_control::max_allowed_parallelism, 1); + static tbb::global_control gc(tbb::global_control::max_allowed_parallelism, count); } #else // TBB_HAS_GLOBAL_CONTROL - static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1); + static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(count); UNUSED(tbb_init); #endif // TBB_HAS_GLOBAL_CONTROL } @@ -700,7 +699,7 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s // That may happen when copying on some exotic file system, for example Linux on Chrome. copy_file_linux(source, target, ec); #else // __linux__ - boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); + boost::filesystem::copy_file(source, target, boost::filesystem::copy_options::overwrite_existing, ec); #endif // __linux__ if (ec) { error_message = ec.message(); @@ -792,7 +791,8 @@ bool is_idx_file(const boost::filesystem::directory_entry &dir_entry) bool is_gcode_file(const std::string &path) { return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") || - boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc"); + boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc") || + boost::iends_with(path, ".bgcode") || boost::iends_with(path, ".bgc"); } bool is_img_file(const std::string &path) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index b5d5009..3835052 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -496,6 +496,14 @@ int GLVolumeCollection::load_wipe_tower_preview( if (height == 0.0f) height = 0.1f; + // Because the GLVolume is also used for arrangement, it must be safely larger + // than the actual extruded tower, otherwise the arranged tower ends up out of bed. + float offset = 0.3; + pos_x -= offset; + pos_y -= offset; + width += 2.f * offset; + depth += 2.f * offset; + brim_width += offset; static const float brim_height = 0.2f; // const float scaled_brim_height = brim_height / height; diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 2668d88..f45baaa 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -369,6 +369,7 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo std::pair ret; auto& [near_z, far_z] = ret; + set_distance(DefaultDistance); // box in eye space const BoundingBoxf3 eye_box = box.transformed(m_view_matrix); near_z = -eye_box.max.z(); @@ -392,15 +393,6 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo near_z += delta; far_z += delta; } -// The following is commented out because it causes flickering of the 3D scene GUI -// when the bounding box of the scene gets large enough -// We need to introduce some smarter code to move the camera back and forth in such case -// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) { -// float delta = m_distance - DefaultDistance; -// set_distance(DefaultDistance); -// near_z -= delta; -// far_z -= delta; -// } return ret; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 08e4683..5c12435 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -372,39 +372,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("min_bead_width", have_arachne); toggle_field("thin_walls", !have_arachne); - bool is_top_one_wall = config->opt_enum("top_one_wall_type") != TopOneWallType::Disable; - toggle_field("top_area_threshold", is_top_one_wall); - -} - -void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) -{ - double head_penetration = config->opt_float("support_head_penetration"); - double head_width = config->opt_float("support_head_width"); - if (head_penetration > head_width) { - wxString msg_text = _(L("Head penetration should not be greater than the head width.")); - - MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *config; - if (dialog.ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); - apply(config, &new_conf); - } - } - - double pinhead_d = config->opt_float("support_head_front_diameter"); - double pillar_d = config->opt_float("support_pillar_diameter"); - if (pinhead_d > pillar_d) { - wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); - - MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); - - DynamicPrintConfig new_conf = *config; - if (dialog.ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); - apply(config, &new_conf); - } - } } void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index 32ddb52..3ac17fe 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -59,7 +59,6 @@ public: void toggle_print_fff_options(DynamicPrintConfig* config); // SLA print - void update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config = false); void toggle_print_sla_options(DynamicPrintConfig* config); bool is_initialized_support_material_overhangs_queried() { return m_is_initialized_support_material_overhangs_queried; } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 498ba73..1f53fd9 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2789,11 +2789,15 @@ bool ConfigWizard::priv::on_bnt_finish() * than last changes wouldn't be updated for filaments/materials. * SO, do that before check_and_install_missing_materials() */ + if (page_filaments) page_filaments->check_and_update_presets(); + if (page_sla_materials) page_sla_materials->check_and_update_presets(); - // there's no need to check that filament is selected if we have only custom printer - if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; + // Even if we have only custom printer installed, check filament selection. + // Template filaments could be selected in this case. + if (custom_printer_selected && !any_fff_selected && !any_sla_selected) + return check_and_install_missing_materials(T_FFF); // check, that there is selected at least one filament/material return check_and_install_missing_materials(T_ANY); } @@ -2901,7 +2905,7 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo bool no_templates = wxGetApp().app_config->get("no_templates") == "1"; - if (any_fff_selected && (technology & T_FFF)) { + if ((any_fff_selected || custom_printer_selected) && (technology & T_FFF)) { std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS, no_templates); if (! printer_models_without_material.empty()) { if (only_for_model_id.empty()) @@ -3002,7 +3006,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } } - return pt; + return ptAny; }; // QIDI printers are considered first, then 3rd party. if (preferred_pt = get_preferred_printer_technology("QIDITechnology", bundles.qidi_bundle()); @@ -3360,6 +3364,12 @@ ConfigWizard::ConfigWizard(wxWindow *parent) wxGetApp().UpdateDarkUI(p->btn_finish); wxGetApp().UpdateDarkUI(p->btn_cancel); + wxGetApp().SetWindowVariantForButton(p->btn_sel_all); + wxGetApp().SetWindowVariantForButton(p->btn_prev); + wxGetApp().SetWindowVariantForButton(p->btn_next); + wxGetApp().SetWindowVariantForButton(p->btn_finish); + wxGetApp().SetWindowVariantForButton(p->btn_cancel); + const auto qidi_it = p->bundles.find("QIDITechnology"); wxCHECK_RET(qidi_it != p->bundles.cend(), "Vendor QIDITechnology not found"); const VendorProfile *vendor_qidi = qidi_it->second.vendor_profile; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 1171457..f778246 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2236,7 +2236,7 @@ static std::string get_new_color(const std::string& color) data->SetChooseFull(1); data->SetColour(clr); - wxColourDialog dialog(nullptr, data); + wxColourDialog dialog(GUI::wxGetApp().GetTopWindow(), data); dialog.CenterOnParent(); if (dialog.ShowModal() == wxID_OK) return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index 89b36aa..06ce45e 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -36,36 +36,36 @@ std::string FileGet::escape_url(const std::string& unescaped) bool FileGet::is_subdomain(const std::string& url, const std::string& domain) { // domain should be f.e. printables.com (.com including) - // char* host; - // std::string host_string; - // CURLUcode rc; - // CURLU* curl = curl_url(); - // if (!curl) { - // BOOST_LOG_TRIVIAL(error) << "Failed to init Curl library in function is_domain."; - // return false; - // } - // rc = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0); - // if (rc != CURLUE_OK) { - // curl_url_cleanup(curl); - // return false; - // } - // rc = curl_url_get(curl, CURLUPART_HOST, &host, 0); - // if (rc != CURLUE_OK || !host) { - // curl_url_cleanup(curl); - // return false; - // } - // host_string = std::string(host); - // curl_free(host); - // // now host should be subdomain.domain or just domain - // if (domain == host_string) { - // curl_url_cleanup(curl); - // return true; - // } - // if(boost::ends_with(host_string, "." + domain)) { - // curl_url_cleanup(curl); - // return true; - // } - // curl_url_cleanup(curl); + char* host; + std::string host_string; + CURLUcode rc; + CURLU* curl = curl_url(); + if (!curl) { + BOOST_LOG_TRIVIAL(error) << "Failed to init Curl library in function is_domain."; + return false; + } + rc = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0); + if (rc != CURLUE_OK) { + curl_url_cleanup(curl); + return false; + } + rc = curl_url_get(curl, CURLUPART_HOST, &host, 0); + if (rc != CURLUE_OK || !host) { + curl_url_cleanup(curl); + return false; + } + host_string = std::string(host); + curl_free(host); + // now host should be subdomain.domain or just domain + if (domain == host_string) { + curl_url_cleanup(curl); + return true; + } + if(boost::ends_with(host_string, "." + domain)) { + curl_url_cleanup(curl); + return true; + } + curl_url_cleanup(curl); return false; } @@ -134,7 +134,7 @@ void FileGet::priv::get_perform() if (m_written == 0) { boost::filesystem::path dest_path = m_dest_folder / m_filename; - std::string extension = boost::filesystem::extension(dest_path); + std::string extension = dest_path.extension().string(); std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size()); std::string final_filename = just_filename; // Find unsed filename diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index fd4bb1e..0a9e8ea 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -846,6 +846,7 @@ void SpinCtrl::BUILD() { switch (m_opt.type) { case coInt: default_value = m_opt.default_value->getInt(); + m_last_meaningful_value = default_value; break; case coInts: { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9affaba..9bf0583 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3189,7 +3189,10 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux post_event(SimpleEvent(EVT_GLCANVAS_TAB)); } - else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) { + else if (! wxGetApp().is_gcode_viewer() && keyCode == WXK_TAB && + // Use strong condition for modifiers state to avoid cases when Shift can be combined with other modifiers + // (see https://github.com/prusa3d/PrusaSlicer/issues/7799) + evt.GetModifiers() == wxMOD_SHIFT) { // Collapse side-panel with Shift+Tab post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR)); } @@ -4915,6 +4918,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const const ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects; std::vector extruders_colors = get_extruders_colors(); + const bool is_enabled_painted_thumbnail = !model_objects.empty() && !extruders_colors.empty(); //Y18 //B54 if (thumbnail_params.transparent_background) glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); @@ -4930,8 +4934,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const for (GLVolume* vol : visible_volumes) { const int obj_idx = vol->object_idx(); const int vol_idx = vol->volume_idx(); - const bool render_as_painted = (obj_idx >= 0 && vol_idx >= 0) ? - !model_objects[obj_idx]->volumes[vol_idx]->mmu_segmentation_facets.empty() : false; + const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty(); GLShaderProgram* shader = wxGetApp().get_shader(render_as_painted ? "mm_gouraud" : "gouraud_light"); if (shader == nullptr) continue; @@ -4967,7 +4970,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count); TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]); - ts.deserialize(model_volume.mmu_segmentation_facets.get_data(), true); + ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true); ts.request_update_render_data(); ts.render(nullptr, model_matrix); @@ -6860,15 +6863,23 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + if (init_data.vertices_size_bytes() >= MAX_VERTEX_BUFFER_SIZE) { volume->model.init_from(std::move(init_data)); - GLVolume &vol = *volume; - volume = m_volumes.new_toolpath_volume(vol.color); + volume->is_outside = !contains(build_volume, volume->model); + volume = m_volumes.new_toolpath_volume(volume->color); + init_data = GLModel::Geometry(); } } + init_data = GLModel::Geometry(); + if (init_data.is_empty()) { + delete volume; + m_volumes.volumes.pop_back(); + } + else { volume->model.init_from(std::move(init_data)); volume->is_outside = !contains(build_volume, volume->model); } +} void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) { diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index ea4275a..0519061 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -188,8 +188,11 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast(value) != 0 }; config.option(opt_key)->set_at(vec_new, opt_index, 0); break;} - case coInt: - config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value))); + case coInt: { + //config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast(value))); + int& val_new = config.opt_int(opt_key); + val_new = boost::any_cast(value); + } break; case coInts:{ ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast(value) }; diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index cb7cf54..70bc269 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -45,7 +45,6 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false); void show_error(wxWindow* parent, const char* message, bool monospaced_font = false); inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); } -void show_error_id(int id, const std::string& message); // For Perl void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString()); void show_info(wxWindow* parent, const char* message, const char* title = nullptr); inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 138e87c..3fa2ef4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -564,7 +564,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) return file_wildcards(file_wildcards_by_type[file_type], custom_extension); } -wxString sla_wildcards(const char *formatid) +wxString sla_wildcards(const char *formatid, const std::string& custom_extension) { const ArchiveEntry *entry = get_archive_entry(formatid); wxString ret; @@ -584,11 +584,11 @@ wxString sla_wildcards(const char *formatid) wc.file_extensions.emplace_back(ext); } - ret = file_wildcards(wc, {}); + ret = file_wildcards(wc, custom_extension); } if (ret.empty()) - ret = file_wildcards(FT_SL1); + ret = file_wildcards(FT_SL1, custom_extension); return ret; } @@ -886,12 +886,8 @@ std::string GUI_App::get_gl_info(bool for_github) wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) { #if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), - init_params != nullptr ? init_params->opengl_debug : false); -#else - return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0)); -#endif // ENABLE_OPENGL_DEBUG_OPTION + init_params != nullptr ? init_params->opengl_compatibiity_profile : false, init_params != nullptr ? init_params->opengl_debug : false); #else return m_opengl_mgr.init_glcontext(canvas); #endif // ENABLE_GL_CORE_PROFILE @@ -2058,7 +2054,7 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const { input_files.Clear(); wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/QIDI):"), + _L("Choose one or more files (STL/3MF/STEP/OBJ/AMF/SVG):"), from_u8(app_config->get_last_dir()), "", file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a40ccbe..547928c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -80,7 +80,7 @@ enum FileType extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = {}); -wxString sla_wildcards(const char *formatid); +wxString sla_wildcards(const char *formatid, const std::string& custom_extension); enum ConfigMenuIDs { ConfigMenuWizard, @@ -411,7 +411,6 @@ private: bool m_datadir_redefined { false }; bool m_wifi_config_dialog_shown { false }; - bool m_wifi_config_dialog_was_declined { false }; }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index fc22752..f0be9dd 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -471,6 +471,29 @@ std::vector MenuFactory::get_svg_volume_bitmaps() return volume_bmps; } +wxString MenuFactory::get_repaire_result_message( + const std::vector& succes_models, + const std::vector>& failed_models) +{ + // Show info notification + wxString msg; + wxString bullet_suf = "\n - "; + if (!succes_models.empty()) { + msg = _L_PLURAL("The following model was repaired successfully", "The following models were repaired successfully", succes_models.size()) + ":"; + for (auto& model : succes_models) + msg += bullet_suf + from_u8(model); + msg += "\n\n"; + } + if (!failed_models.empty()) { + msg += _L_PLURAL("Folowing model repair failed", "Folowing models repair failed", failed_models.size()) + ":\n"; + for (auto& model : failed_models) + msg += bullet_suf + from_u8(model.first) + ": " + _(model.second); + } + if (msg.IsEmpty()) + msg = _L("Repairing was canceled"); + + return msg; +} void MenuFactory::append_menu_item_delete(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index a0c8997..cf65439 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -37,6 +37,8 @@ public: static std::vector get_text_volume_bitmaps(); static std::vector get_svg_volume_bitmaps(); + static wxString get_repaire_result_message(const std::vector& succes_models, + const std::vector>& failed_models); MenuFactory(); ~MenuFactory() = default; diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index f8eb290..0e4c628 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -26,11 +26,8 @@ namespace Slic3r { namespace GUI { -const std::vector OpenGLVersions::core_str = { "3.2", "3.3", "4.0", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6" }; -const std::vector OpenGLVersions::precore_str = { "2.0", "2.1", "3.0", "3.1" }; const std::vector> OpenGLVersions::core = { {3,2}, {3,3}, {4,0}, {4,1}, {4,2}, {4,3}, {4,4}, {4,5}, {4,6} }; -const std::vector> OpenGLVersions::precore = { {2,0}, {2,1}, {3,0}, {3,1} }; int GUI_Run(GUI_InitParams ¶ms) { diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index 14d0c4e..4cf38ed 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -10,11 +10,8 @@ namespace GUI { struct OpenGLVersions { - static const std::vector core_str; - static const std::vector precore_str; static const std::vector> core; - static const std::vector> precore; }; struct GUI_InitParams @@ -35,9 +32,8 @@ struct GUI_InitParams std::string download_url; #if ENABLE_GL_CORE_PROFILE std::pair opengl_version; -#if ENABLE_OPENGL_DEBUG_OPTION bool opengl_debug; -#endif // ENABLE_OPENGL_DEBUG_OPTION + bool opengl_compatibiity_profile; #endif // ENABLE_GL_CORE_PROFILE }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a7994a1..d1641b7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -688,6 +688,8 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const //update object name with text marker in ObjectList m_objects_model->SetName(get_item_name(obj->name, true), item); } + // Renaming an object should invalidate gcode export - schedule Print::apply call. + wxGetApp().plater()->schedule_background_process(); return; } @@ -1896,7 +1898,7 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) - mv->mmu_segmentation_facets.reset(); + mv->mm_segmentation_facets.reset(); break; case InfoItemType::Sinking: @@ -2888,7 +2890,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio [type](const ModelVolume *mv) { return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() : type == InfoItemType::CustomSeam ? mv->seam_facets.empty() : - mv->mmu_segmentation_facets.empty()); + mv->mm_segmentation_facets.empty()); }); break; @@ -4663,21 +4665,7 @@ void ObjectList::fix_through_winsdk() progress_dlg.Update(100, ""); // Show info notification - wxString msg; - wxString bullet_suf = "\n - "; - if (!succes_models.empty()) { - msg = _L_PLURAL("The following model was repaired successfully", "The following models were repaired successfully", succes_models.size()) + ":"; - for (auto& model : succes_models) - msg += bullet_suf + from_u8(model); - msg += "\n\n"; - } - if (!failed_models.empty()) { - msg += _L_PLURAL("Folowing model repair failed", "Folowing models repair failed", failed_models.size()) + ":\n"; - for (auto& model : failed_models) - msg += bullet_suf + from_u8(model.first) + ": " + _(model.second); - } - if (msg.IsEmpty()) - msg = _L("Repairing was canceled"); + wxString msg = MenuFactory::get_repaire_result_message(succes_models, failed_models); plater->get_notification_manager()->push_notification(NotificationType::RepairFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, boost::nowide::narrow(msg)); } diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index a6d85ce..6bf8e35 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -257,8 +257,8 @@ void ObjectSettings::update_config_values(ModelConfig* config) } main_config.apply(config->get(), true); - printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) : - config_manipulation.update_print_sla_config(&main_config) ; + if (printer_technology == ptFFF) + config_manipulation.update_print_fff_config(&main_config); printer_technology == ptFFF ? config_manipulation.toggle_print_fff_options(&main_config) : config_manipulation.toggle_print_sla_options(&main_config) ; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index a16411b..a22338a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -642,7 +642,7 @@ void Preview::update_layers_slider_mode() if ((volume->config.has("extruder") && volume->config.option("extruder")->getInt() != 0 && // extruder isn't default volume->config.option("extruder")->getInt() != extruder) || - !volume->mmu_segmentation_facets.empty()) + !volume->mm_segmentation_facets.empty()) return false; for (const auto& range : object->layer_config_ranges) @@ -841,7 +841,7 @@ void Preview::load_print_as_fff(bool keep_z_range) wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_canvas->get_custom_gcode_per_print_z(); const bool contains_color_gcodes = std::any_of(std::begin(gcodes), std::end(gcodes), - [] (auto const& item) { return item.type == CustomGCode::Type::ColorChange; }); + [](auto const& item) { return item.type == CustomGCode::Type::ColorChange || item.type == CustomGCode::Type::ToolChange; }); const GCodeViewer::EViewType choice = contains_color_gcodes ? GCodeViewer::EViewType::ColorPrint : (number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType; diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 91b0b24..fe9be31 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -499,7 +499,7 @@ void GalleryDialog::change_thumbnail() png_path.replace_extension("png"); fs::path current = fs::path(into_u8(input_files.Item(0))); - fs::copy_file(current, png_path, fs::copy_option::overwrite_if_exists); + fs::copy_file(current, png_path, fs::copy_options::overwrite_existing); } catch (fs::filesystem_error const& e) { std::cerr << e.what() << '\n'; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 168a1ee..c1edcc2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -5,12 +5,15 @@ #include #include +#include #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/GUI_Factories.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "slic3r/Utils/FixModelByWin10.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" @@ -3255,6 +3258,8 @@ void update_object_cut_id(CutObjectBase& cut_id, ModelObjectCutAttributes attrib static void check_objects_after_cut(const ModelObjectPtrs& objects) { std::vector err_objects_names; + std::vector err_objects_idxs; + int obj_idx{ 0 }; for (const ModelObject* object : objects) { std::vector connectors_names; connectors_names.reserve(object->volumes.size()); @@ -3265,17 +3270,86 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects) sort_remove_duplicates(connectors_names); if (connectors_count != connectors_names.size()) err_objects_names.push_back(object->name); + // check manifol/repairs + auto stats = object->get_object_stl_stats(); + if (!stats.manifold() || stats.repaired()) + err_objects_idxs.push_back(obj_idx); + obj_idx++; } - if (err_objects_names.empty()) - return; + auto plater = wxGetApp().plater(); + if (!err_objects_names.empty()) { wxString names = from_u8(err_objects_names[0]); for (size_t i = 1; i < err_objects_names.size(); i++) names += ", " + from_u8(err_objects_names[i]); - WarningDialog(wxGetApp().plater(), format_wxstr("Objects(%1%) have duplicated connectors. " + WarningDialog(plater, format_wxstr("Objects(%1%) have duplicated connectors. " "Some connectors may be missing in slicing result.\n" "Please report to QIDISlicer team in which scenario this issue happened.\n" "Thank you.", names)).ShowModal(); +} + if (is_windows10() && !err_objects_idxs.empty()) { + auto dlg = WarningDialog(plater, _L("Open edges or errors were detected after the cut.\n" + "Do you want to fix them by Windows repair algorithm?"), + _L("Errors detected after cut operation"), wxYES_NO); + if (dlg.ShowModal() == wxID_YES) { + // model_name + std::vector succes_models; + // model_name failing reason + std::vector> failed_models; + + std::vector model_names; + + for (int obj_idx : err_objects_idxs) + model_names.push_back(objects[obj_idx]->name); + + auto fix_and_update_progress = [model_names, &objects](const int obj_idx, int model_idx, + wxProgressDialog& progress_dlg, + std::vector& succes_models, + std::vector>& failed_models) -> bool + { + const std::string& model_name = model_names[model_idx]; + wxString msg; + if (model_names.size() == 1) + msg = GUI::format(_L("Repairing object %1%"), model_name) + "\n"; + else { + // TRN: This is followed by a list of object which are to be repaired. + msg = _L("Repairing objects:") + "\n"; + for (int i = 0; i < int(model_names.size()); ++i) + msg += (i == model_idx ? " > " : " ") + from_u8(model_names[i]) + "\n"; + msg += "\n"; + } + + std::string res; + if (!fix_model_by_win10_sdk_gui(*objects[obj_idx], -1, progress_dlg, msg, res)) + return false; + + if (res.empty()) + succes_models.push_back(model_name); + else + failed_models.push_back({ model_name, res }); + return true; + }; + + // Open a progress dialog. + // TRN: This shows in a progress dialog while the operation is in progress. + wxProgressDialog progress_dlg(_L("Fixing by Windows repair algorithm"), "", 100, find_toplevel_parent(plater), + wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); + int model_idx{ 0 }; + for (int obj_idx : err_objects_idxs) { + if (!fix_and_update_progress(obj_idx, model_idx, progress_dlg, succes_models, failed_models)) + break; + model_idx++; + } + + // Close the progress dialog + progress_dlg.Update(100, ""); + + // Show info dialog + wxString msg = MenuFactory::get_repaire_result_message(succes_models, failed_models); + // TRN: Title of a dialog informing the user about the result of the model repair operation. + InfoDialog(plater, _L("Repair operation finished"), msg).ShowModal(); + } + } } void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 834d1b8..7eb7702 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -73,6 +73,13 @@ using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; namespace { +// TRN - Title in Undo/Redo stack after rotate with text around emboss axe +const std::string rotation_snapshot_name = L("Text rotate"); +// NOTE: Translation is made in "m_parent.do_rotate()" + +// TRN - Title in Undo/Redo stack after move with text along emboss axe - From surface +const std::string move_snapshot_name = L("Text move"); +// NOTE: Translation is made in "m_parent.do_translate()" template struct Limit { // Limitation for view slider range in GUI MinMax gui; @@ -85,7 +92,7 @@ static const struct Limits { MinMax emboss{0.01, 1e4}; // in mm MinMax size_in_mm{0.1f, 1000.f}; // in mm - Limit boldness{{-200.f, 200.f}, {-2e4f, 2e4f}}; // in font points + Limit boldness{{-.5f, .5f}, {-5e5f, 5e5f}}; // in font points Limit skew{{-1.f, 1.f}, {-100.f, 100.f}}; // ration without unit MinMax char_gap{-20000, 20000}; // in font points MinMax line_gap{-20000, 20000}; // in font points @@ -399,7 +406,7 @@ bool GLGizmoEmboss::re_emboss(const ModelVolume &text_volume, std::shared_ptrcanvas3D()->get_selection(); DataBasePtr base = create_emboss_data_base(tc.text, style_manager, text_lines, selection, text_volume.type(), job_cancel); - DataUpdate data{std::move(base), text_volume.id()}; + DataUpdate data{std::move(base), text_volume.id(), false}; RaycastManager raycast_manager; // Nothing is cached now, so It need to create raycasters return start_update_volume(std::move(data), text_volume, selection, raycast_manager); @@ -1005,7 +1012,7 @@ void GLGizmoEmboss::on_stop_dragging() m_rotate_gizmo.set_angle(PI/2); // apply rotation - m_parent.do_rotate(L("Text-Rotate")); + m_parent.do_rotate(rotation_snapshot_name); m_rotate_start_angle.reset(); @@ -1337,7 +1344,7 @@ namespace { bool is_text_empty(std::string_view text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; } } // namespace -bool GLGizmoEmboss::process() +bool GLGizmoEmboss::process(bool make_snapshot) { // no volume is selected -> selection from right panel assert(m_volume != nullptr); @@ -1351,7 +1358,7 @@ bool GLGizmoEmboss::process() const Selection& selection = m_parent.get_selection(); DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, selection, m_volume->type(), m_job_cancel); - DataUpdate data{std::move(base), m_volume->id()}; + DataUpdate data{std::move(base), m_volume->id(), make_snapshot}; // check valid count of text lines assert(data.base->text_lines.empty() || data.base->text_lines.size() == get_count_lines(m_text)); @@ -2176,7 +2183,9 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty // fix rotation float f_angle = f_angle_opt.value_or(.0f); float t_angle = t_angle_opt.value_or(.0f); - do_local_z_rotate(canvas, t_angle - f_angle); + do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); + std::string no_snapshot; + canvas.do_rotate(no_snapshot); } // fix distance (Z move) when exists difference in styles @@ -2185,7 +2194,9 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty if (!is_approx(f_move_opt, t_move_opt)) { float f_move = f_move_opt.value_or(.0f); float t_move = t_move_opt.value_or(.0f); - do_local_z_move(canvas, t_move - f_move); + do_local_z_move(canvas.get_selection(), t_move - f_move); + std::string no_snapshot; + canvas.do_move(no_snapshot); } } } // namesapce @@ -2451,6 +2462,8 @@ bool GLGizmoEmboss::revertible(const std::string &name, ImGui::SameLine(undo_offset); // change cursor postion if (draw_button(m_icons, IconType::undo)) { value = *default_value; + // !! Fix to detect change of value after revert of float-slider + m_imgui->get_last_slider_status().deactivated_after_edit = true; return true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", undo_tooltip.c_str()); @@ -2691,7 +2704,6 @@ void GLGizmoEmboss::draw_advanced() m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property); #endif // SHOW_FONT_FILE_PROPERTY - bool exist_change = false; auto &tr = m_gui_cfg->translations; const StyleManager::Style *stored_style = nullptr; @@ -2783,6 +2795,7 @@ void GLGizmoEmboss::draw_advanced() auto def_char_gap = stored_style ? &stored_style->prop.char_gap : nullptr; + bool exist_change = false; int half_ascent = font_info.ascent / 2; int min_char_gap = -half_ascent; int max_char_gap = half_ascent; @@ -2798,9 +2811,12 @@ void GLGizmoEmboss::draw_advanced() exist_change = true; } } + bool last_change = false; + if (m_imgui->get_last_slider_status().deactivated_after_edit) + last_change = true; // input gap between lines - bool is_multiline = m_text_lines.get_lines().size() > 1; + bool is_multiline = get_count_lines(m_volume->text_configuration->text) > 1; // TODO: cache count lines m_imgui->disabled_begin(!is_multiline); auto def_line_gap = stored_style ? &stored_style->prop.line_gap : nullptr; @@ -2819,18 +2835,24 @@ void GLGizmoEmboss::draw_advanced() exist_change = true; } } + if (m_imgui->get_last_slider_status().deactivated_after_edit) + last_change = true; m_imgui->disabled_end(); // !is_multiline // input boldness auto def_boldness = stored_style ? &stored_style->prop.boldness : nullptr; + int min_boldness = static_cast(font_info.ascent * limits.boldness.gui.min); + int max_boldness = static_cast(font_info.ascent * limits.boldness.gui.max); if (rev_slider(tr.boldness, current_prop.boldness, def_boldness, _u8L("Undo boldness"), - limits.boldness.gui.min, limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ + min_boldness, max_boldness, units_fmt, _L("Tiny / Wide glyphs"))){ const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; if (!apply(current_prop.boldness, limits.boldness.values) || !volume_boldness.has_value() || volume_boldness != current_prop.boldness) exist_change = true; } + if (m_imgui->get_last_slider_status().deactivated_after_edit) + last_change = true; // input italic auto def_skew = stored_style ? @@ -2842,6 +2864,8 @@ void GLGizmoEmboss::draw_advanced() !volume_skew.has_value() ||volume_skew != current_prop.skew) exist_change = true; } + if (m_imgui->get_last_slider_status().deactivated_after_edit) + last_change = true; // input surface distance bool allowe_surface_distance = !use_surface && !m_volume->is_the_only_one_part(); @@ -2882,11 +2906,17 @@ void GLGizmoEmboss::draw_advanced() if (is_moved){ if (font_prop.per_glyph){ - process(); + process(false); } else { - do_local_z_move(m_parent, distance.value_or(.0f) - prev_distance); + do_local_z_move(m_parent.get_selection(), distance.value_or(.0f) - prev_distance); } } + // Apply move to model(backend) + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + m_parent.do_move(move_snapshot_name); + if (font_prop.per_glyph) + process(); + } m_imgui->disabled_end(); // allowe_surface_distance // slider for Clock-wise angle in degress @@ -2909,7 +2939,7 @@ void GLGizmoEmboss::draw_advanced() double diff_angle = angle_rad - angle; - do_local_z_rotate(m_parent, diff_angle); + do_local_z_rotate(m_parent.get_selection(), diff_angle); // calc angle after rotation const Selection &selection = m_parent.get_selection(); @@ -2924,6 +2954,15 @@ void GLGizmoEmboss::draw_advanced() // recalculate for surface cut if (use_surface || font_prop.per_glyph) + process(false); + } + + // Apply rotation on model (backend) + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + m_parent.do_rotate(rotation_snapshot_name); + + // recalculate for surface cut + if (use_surface || font_prop.per_glyph) process(); } @@ -2956,6 +2995,7 @@ void GLGizmoEmboss::draw_advanced() if (i == 0) current_prop.collection_number.reset(); else current_prop.collection_number = i; exist_change = true; + last_change = true; } ImGui::PopID(); } @@ -2965,13 +3005,13 @@ void GLGizmoEmboss::draw_advanced() } } - if (exist_change) { + if (exist_change || last_change) { m_style_manager.clear_glyphs_cache(); - if (m_style_manager.get_font_prop().per_glyph) + if (font_prop.per_glyph) reinit_text_lines(); else m_text_lines.reset(); - process(); + process(last_change); } if (ImGui::Button(_u8L("Set text to face camera").c_str())) { @@ -3622,7 +3662,7 @@ GuiCfg create_gui_configuration() float space = line_height_with_spacing - line_height; const ImGuiStyle &style = ImGui::GetStyle(); - cfg.max_style_name_width = ImGui::CalcTextSize("Maximal font name, extended").x; + cfg.max_style_name_width = ImGui::CalcTextSize("Maximal style name..").x; cfg.icon_width = static_cast(std::ceil(line_height)); // make size pair number @@ -3741,11 +3781,11 @@ GuiCfg create_gui_configuration() ImVec2(cfg.minimal_window_size_with_advance.x, cfg.minimal_window_size_with_advance.y + input_height); - int max_style_image_width = static_cast(std::round(cfg.max_style_name_width/2 - 2 * style.FramePadding.x)); + int max_style_image_width = static_cast(std::round(cfg.max_style_name_width - 2 * style.FramePadding.x)); int max_style_image_height = static_cast(std::round(1.5 * input_height)); cfg.max_style_image_size = Vec2i(max_style_image_width, max_style_image_height); cfg.face_name_size = Vec2i(cfg.input_width, line_height_with_spacing); - cfg.face_name_texture_offset_x = cfg.face_name_size.x() + space; + cfg.face_name_texture_offset_x = cfg.face_name_size.x() + style.WindowPadding.x + space; return cfg; } } // namespace diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index d1aff65..6954e15 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -111,7 +111,7 @@ private: void reset_volume(); // create volume from text - main functionality - bool process(); + bool process(bool make_snapshot = true); void close(); void draw_window(); void draw_text_input(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 9b9e033..14a127e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -512,7 +512,7 @@ void GLGizmoMmuSegmentation::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get()); } if (updated) { @@ -542,7 +542,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors() size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count); m_triangle_selectors.emplace_back(std::make_unique(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx])); // Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize(). - m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false); + m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false); m_triangle_selectors.back()->request_update_render_data(); } m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 2d08f9d..24841c3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -51,6 +51,13 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) // Private functions to create emboss volume namespace{ +// TRN - Title in Undo/Redo stack after rotate with SVG around emboss axe +const std::string rotation_snapshot_name = L("SVG rotate"); +// NOTE: Translation is made in "m_parent.do_rotate()" + +// TRN - Title in Undo/Redo stack after move with SVG along emboss axe - From surface +const std::string move_snapshot_name = L("SVG move"); +// NOTE: Translation is made in "m_parent.do_translate()" // Variable keep limits for variables const struct Limits { @@ -539,7 +546,7 @@ void GLGizmoSVG::on_stop_dragging() // apply rotation // TRN This is an item label in the undo-redo stack. - m_parent.do_rotate(L("SVG-Rotate")); + m_parent.do_rotate(rotation_snapshot_name); m_rotate_start_angle.reset(); volume_transformation_changed(); @@ -1266,8 +1273,7 @@ void GLGizmoSVG::calculate_scale() { float GLGizmoSVG::get_scale_for_tolerance(){ return std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)); } -bool GLGizmoSVG::process() -{ +bool GLGizmoSVG::process(bool make_snapshot) { // no volume is selected -> selection from right panel assert(m_volume != nullptr); if (m_volume == nullptr) @@ -1288,7 +1294,7 @@ bool GLGizmoSVG::process() EmbossShape shape = m_volume_shape; // copy auto base = std::make_unique(m_volume->name, m_job_cancel, std::move(shape)); base->is_outside = m_volume->type() == ModelVolumeType::MODEL_PART; - DataUpdate data{std::move(base), m_volume_id}; + DataUpdate data{std::move(base), m_volume_id, make_snapshot}; return start_update_volume(std::move(data), *m_volume, m_parent.get_selection(), m_raycast_manager); } @@ -1490,8 +1496,9 @@ void GLGizmoSVG::draw_filename(){ std::string new_path = choose_svg_file(); if (!new_path.empty()) { file_changed = true; - m_volume_shape.svg_file = {}; // clear data - m_volume_shape.svg_file->path = new_path; + EmbossShape::SvgFile svg_file_new; + svg_file_new.path = new_path; + m_volume_shape.svg_file = svg_file_new; // clear data } } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Change to another .svg file").c_str()); @@ -1681,6 +1688,7 @@ void GLGizmoSVG::draw_size() }; std::optional new_relative_scale; + bool make_snap = false; if (m_keep_ratio) { std::stringstream ss; ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? "in" : "mm"); @@ -1699,6 +1707,8 @@ void GLGizmoSVG::draw_size() new_relative_scale = Vec3d(width_ratio, width_ratio, 1.); } } + if (m_imgui->get_last_slider_status().deactivated_after_edit) + make_snap = true; // only last change of slider make snap } else { ImGuiInputTextFlags flags = 0; @@ -1718,6 +1728,7 @@ void GLGizmoSVG::draw_size() if (is_valid_scale_ratio(width_ratio)) { m_scale_width = m_scale_width.value_or(1.f) * width_ratio; new_relative_scale = Vec3d(width_ratio, 1., 1.); + make_snap = true; } } if (ImGui::IsItemHovered()) @@ -1731,6 +1742,7 @@ void GLGizmoSVG::draw_size() if (is_valid_scale_ratio(height_ratio)) { m_scale_height = m_scale_height.value_or(1.f) * height_ratio; new_relative_scale = Vec3d(1., height_ratio, 1.); + make_snap = true; } } if (ImGui::IsItemHovered()) @@ -1752,6 +1764,7 @@ void GLGizmoSVG::draw_size() if (can_reset) { if (reset_button(m_icons)) { new_relative_scale = Vec3d(1./m_scale_width.value_or(1.f), 1./m_scale_height.value_or(1.f), 1.); + make_snap = true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Reset scale").c_str()); } @@ -1765,20 +1778,24 @@ void GLGizmoSVG::draw_size() }; selection_transform(selection, selection_scale_fnc); - m_parent.do_scale(L("Resize")); + std::string snap_name; // Empty mean do not store on undo/redo stack + m_parent.do_scale(snap_name); wxGetApp().obj_manipul()->set_dirty(); // should be the almost same calculate_scale(); - NSVGimage *img = m_volume_shape.svg_file->image.get(); + const NSVGimage *img = m_volume_shape.svg_file->image.get(); assert(img != NULL); if (img != NULL){ NSVGLineParams params{get_tesselation_tolerance(get_scale_for_tolerance())}; m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, params); m_volume_shape.final_shape = {}; // reset cache for final shape - process(); + if (!make_snap) // Be carefull: Last change may be without change of scale + process(false); } } + if (make_snap) + process(); // make undo/redo snap-shot } void GLGizmoSVG::draw_use_surface() @@ -1833,17 +1850,20 @@ void GLGizmoSVG::draw_distance() is_moved = true; } - bool can_reset = m_distance.has_value(); - if (can_reset) { + bool is_stop_sliding = m_imgui->get_last_slider_status().deactivated_after_edit; + bool is_reseted = false; + if (m_distance.has_value()) { if (reset_button(m_icons)) { m_distance.reset(); - is_moved = true; + is_reseted = true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Reset distance").c_str()); } - if (is_moved) - do_local_z_move(m_parent, m_distance.value_or(.0f) - prev_distance); + if (is_moved || is_reseted) + do_local_z_move(m_parent.get_selection(), m_distance.value_or(.0f) - prev_distance); + if (is_stop_sliding || is_reseted) + m_parent.do_move(move_snapshot_name); } void GLGizmoSVG::draw_rotation() @@ -1865,7 +1885,7 @@ void GLGizmoSVG::draw_rotation() double diff_angle = angle_rad - angle; - do_local_z_rotate(m_parent, diff_angle); + do_local_z_rotate(m_parent.get_selection(), diff_angle); // calc angle after rotation m_angle = calc_angle(m_parent.get_selection()); @@ -1874,20 +1894,26 @@ void GLGizmoSVG::draw_rotation() if (m_volume->emboss_shape->projection.use_surface) process(); } + bool is_stop_sliding = m_imgui->get_last_slider_status().deactivated_after_edit; // Reset button + bool is_reseted = false; if (m_angle.has_value()) { if (reset_button(m_icons)) { - do_local_z_rotate(m_parent, -(*m_angle)); + do_local_z_rotate(m_parent.get_selection(), -(*m_angle)); m_angle.reset(); // recalculate for surface cut if (m_volume->emboss_shape->projection.use_surface) process(); + is_reseted = true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Reset rotation").c_str()); } + // Apply rotation on model (backend) + if (is_stop_sliding || is_reseted) + m_parent.do_rotate(rotation_snapshot_name); // Keep up - lock button icon if (!m_volume->is_the_only_one_part()) { ImGui::SameLine(m_gui_cfg->lock_offset); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 36b1258..103051c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -112,7 +112,7 @@ private: void reset_volume(); // create volume from text - main functionality - bool process(); + bool process(bool make_snapshot = true); void close(); void draw_window(); void draw_preview(); diff --git a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp index 417eb0e..c452018 100644 --- a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp @@ -142,8 +142,9 @@ void CreateFontImageJob::finalize(bool canceled, std::exception_ptr &) glsafe(::glBindTexture(target, m_input.texture_id)); GLsizei w = m_tex_size.x(), h = m_tex_size.y(); - GLint xoffset = m_input.size.x() - m_tex_size.x(), // arrange right - yoffset = m_input.size.y() * m_input.index; + GLint xoffset = 0; // align to left + // GLint xoffset = m_input.size.x() - m_tex_size.x(); // align right + GLint yoffset = m_input.size.y() * m_input.index; glsafe(::glTexSubImage2D(target, m_input.level, xoffset, yoffset, w, h, m_input.format, m_input.type, m_result.data())); diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp index 28edc38..689482b 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -42,12 +42,12 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) // create image description StyleManager::StyleImage &image = m_images[index]; BoundingBox &bounding_box = image.bounding_box; - for (ExPolygon &shape : shapes) + for (const ExPolygon &shape : shapes) bounding_box.merge(BoundingBox(shape.contour.points)); for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min); // calculate conversion from FontPoint to screen pixels by size of font - double scale = get_text_shape_scale(item.prop, *item.font.font_file); + double scale = get_text_shape_scale(item.prop, *item.font.font_file) * m_input.ppm; scales[index] = scale; //double scale = font_prop.size_in_mm * SCALING_FACTOR; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index be45703..5eeb95e 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -98,6 +98,8 @@ struct DataCreateObject // Define which gizmo open on the success GLGizmosManager::EType gizmo; + // additionl rotation around Z axe, given by style settings + std::optional angle = {}; }; /// @@ -326,6 +328,11 @@ void CreateObjectJob::process(Ctl &ctl) offset -= m_result.center(); Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z()); m_transformation = Transform3d(tt); + // rotate around Z by style settings + if (m_input.angle.has_value()) { + std::optional distance; // new object ignore surface distance from style settings + apply_transformation(m_input.angle, distance, m_transformation); + } } void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) @@ -1019,9 +1026,12 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss || plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg); - // TRN: This is the name of the action appearing in undo/redo stack. - std::string snap_name = _u8L("Text/SVG attribute change"); + if (data.make_snapshot) { + // TRN: This is the title of the action appearing in undo/redo stack. + // It is same for Text and SVG. + std::string snap_name = _u8L("Emboss attribute change"); Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); + } ModelVolume *volume = get_model_volume(data.volume_id, plater->model().objects); // could appear when user delete edited volume @@ -1484,7 +1494,12 @@ bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss { const Pointfs &bed_shape = input.build_volume.bed_shape(); auto gizmo_type = static_cast(input.gizmo); - DataCreateObject data{std::move(emboss_data), coor, input.camera, bed_shape, gizmo_type}; + DataCreateObject data{std::move(emboss_data), coor, input.camera, bed_shape, gizmo_type, input.angle}; + + // Fix: adding text on print bed with style containing use_surface + if (data.base->shape.projection.use_surface) + // Til the print bed is flat using surface for Object is useless + data.base->shape.projection.use_surface = false; auto job = std::make_unique(std::move(data)); return queue_job(input.worker, std::move(job)); } diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 1b355b4..1707d42 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -107,6 +107,8 @@ struct DataUpdate DataBasePtr base; // unique identifier of volume to change ObjectID volume_id; + // Used for prevent flooding Undo/Redo stack on slider. + bool make_snapshot; }; /// diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 6fd6c4d..6367c8c 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -199,7 +199,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; - h_pos += field->getWindow()->GetSize().x + m_h_gap; + h_pos += (opt.opt.width >= 0 ? opt.opt.width * m_em_unit : field->getWindow()->GetSize().x) + m_h_gap; if (option_set.size() == 1 && option_set.front().opt.full_width) break; @@ -663,7 +663,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos) h_pos += child->GetWindow()->GetSize().x + ctrl->m_h_gap; } else if (field->getWindow()) - h_pos += field->getWindow()->GetSize().x + ctrl->m_h_gap; + h_pos += (opt.opt.width >= 0 ? opt.opt.width * ctrl->m_em_unit : field->getWindow()->GetSize().x) + ctrl->m_h_gap; } // add field diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 06f4aaf..d85f046 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -123,6 +123,8 @@ void OpenGLManager::GLInfo::detect() const float* max_anisotropy = const_cast(&m_max_anisotropy); glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); } + if (!GLEW_ARB_compatibility) + *const_cast(&m_core_profile) = true; *const_cast(&m_detected) = true; } @@ -193,7 +195,7 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; - out << b_start << "GL version: " << b_end << m_version << line_end; + out << b_start << "GL version: " << b_end << m_version << " (" << m_version_string << ")" << line_end; #if ENABLE_GL_CORE_PROFILE out << b_start << "Profile: " << b_end << (is_core_profile() ? "Core" : "Compatibility") << line_end; #endif // ENABLE_GL_CORE_PROFILE @@ -283,14 +285,14 @@ OpenGLManager::~OpenGLManager() #endif //__APPLE__ } -#if ENABLE_OPENGL_DEBUG_OPTION +#if ENABLE_GL_CORE_PROFILE #ifdef _WIN32 static void APIENTRY CustomGLDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char* message, const void* userParam) #else static void CustomGLDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char* message, const void* userParam) #endif // _WIN32 { - if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) + if (severity != GL_DEBUG_SEVERITY_HIGH) return; std::string out = "OpenGL DEBUG message ["; @@ -327,7 +329,7 @@ static void CustomGLDebugOutput(GLenum source, GLenum type, unsigned int id, GLe out += "]:\n"; std::cout << out << "(" << id << "): " << message << "\n\n"; } -#endif // ENABLE_OPENGL_DEBUG_OPTION +#endif // ENABLE_GL_CORE_PROFILE bool OpenGLManager::init_gl() { @@ -365,7 +367,7 @@ bool OpenGLManager::init_gl() #if ENABLE_OPENGL_ES bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); #elif ENABLE_GL_CORE_PROFILE - bool valid_version = s_gl_info.is_core_profile() ? s_gl_info.is_version_greater_or_equal_to(3, 2) : s_gl_info.is_version_greater_or_equal_to(2, 0); + const bool valid_version = s_gl_info.is_version_greater_or_equal_to(3, 2); #else bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); #endif // ENABLE_OPENGL_ES @@ -399,14 +401,15 @@ bool OpenGLManager::init_gl() wxString message = format_wxstr(_L("Unable to load the following shaders:\n%s"), error); wxMessageBox(message, wxString("QIDISlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR); } -#if ENABLE_OPENGL_DEBUG_OPTION - if (m_debug_enabled && GLEW_KHR_debug) { +#if ENABLE_GL_CORE_PROFILE + if (m_debug_enabled && s_gl_info.is_version_greater_or_equal_to(4, 3) && GLEW_KHR_debug) { ::glEnable(GL_DEBUG_OUTPUT); ::glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); ::glDebugMessageCallback(CustomGLDebugOutput, nullptr); ::glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + std::cout << "Enabled OpenGL debug output\n"; } -#endif // ENABLE_OPENGL_DEBUG_OPTION +#endif // ENABLE_GL_CORE_PROFILE } #ifdef _WIN32 @@ -433,11 +436,8 @@ bool OpenGLManager::init_gl() } #if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION -wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_debug) -#else -wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version) -#endif // ENABLE_OPENGL_DEBUG_OPTION +wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_compatibility_profile, + bool enable_debug) #else wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) #endif // ENABLE_GL_CORE_PROFILE @@ -448,33 +448,26 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) attrs.PlatformDefaults().ES2().MajorVersion(2).EndList(); m_context = new wxGLContext(&canvas, nullptr, &attrs); #elif ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION m_debug_enabled = enable_debug; -#endif // ENABLE_OPENGL_DEBUG_OPTION const int gl_major = required_opengl_version.first; const int gl_minor = required_opengl_version.second; - const bool supports_core_profile = (gl_major < 3) ? false : (gl_major > 3) ? true : gl_minor >= 2; + const bool supports_core_profile = + std::find(OpenGLVersions::core.begin(), OpenGLVersions::core.end(), std::make_pair(gl_major, gl_minor)) != OpenGLVersions::core.end(); - if (gl_major == 0) { + if (gl_major == 0 && !enable_compatibility_profile) { // search for highest supported core profile version // disable wxWidgets logging to avoid showing the log dialog in case the following code fails generating a valid gl context wxLogNull logNo; for (auto v = OpenGLVersions::core.rbegin(); v != OpenGLVersions::core.rend(); ++v) { wxGLContextAttrs attrs; -#if ENABLE_OPENGL_DEBUG_OPTION attrs.PlatformDefaults().MajorVersion(v->first).MinorVersion(v->second).CoreProfile().ForwardCompatible(); if (m_debug_enabled) attrs.DebugCtx(); attrs.EndList(); -#else - attrs.PlatformDefaults().MajorVersion(gl_major).MinorVersion(gl_minor).CoreProfile().ForwardCompatible().EndList(); -#endif // ENABLE_OPENGL_DEBUG_OPTION m_context = new wxGLContext(&canvas, nullptr, &attrs); - if (m_context->IsOK()) { - s_gl_info.set_core_profile(true); + if (m_context->IsOK()) break; - } else { delete m_context; m_context = nullptr; @@ -483,31 +476,38 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) } if (m_context == nullptr) { - // search for requested core profile version - if (supports_core_profile) { + // search for requested compatibility profile version + if (enable_compatibility_profile) { + // disable wxWidgets logging to avoid showing the log dialog in case the following code fails generating a valid gl context + wxLogNull logNo; + wxGLContextAttrs attrs; + attrs.PlatformDefaults().CompatibilityProfile(); + if (m_debug_enabled) + attrs.DebugCtx(); + attrs.EndList(); + m_context = new wxGLContext(&canvas, nullptr, &attrs); + if (!m_context->IsOK()) { + delete m_context; + m_context = nullptr; + } + } + // search for requested core profile version + else if (supports_core_profile) { // disable wxWidgets logging to avoid showing the log dialog in case the following code fails generating a valid gl context wxLogNull logNo; wxGLContextAttrs attrs; -#if ENABLE_OPENGL_DEBUG_OPTION attrs.PlatformDefaults().MajorVersion(gl_major).MinorVersion(gl_minor).CoreProfile().ForwardCompatible(); if (m_debug_enabled) attrs.DebugCtx(); attrs.EndList(); -#else - attrs.PlatformDefaults().MajorVersion(gl_major).MinorVersion(gl_minor).CoreProfile().ForwardCompatible().EndList(); -#endif // ENABLE_OPENGL_DEBUG_OPTION m_context = new wxGLContext(&canvas, nullptr, &attrs); if (!m_context->IsOK()) { - BOOST_LOG_TRIVIAL(error) << "Unable to create context for required OpenGL " << gl_major << "." << gl_minor; delete m_context; m_context = nullptr; } - else - s_gl_info.set_core_profile(true); } } -#if ENABLE_OPENGL_DEBUG_OPTION if (m_context == nullptr) { wxGLContextAttrs attrs; attrs.PlatformDefaults(); @@ -517,11 +517,6 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) // if no valid context was created use the default one m_context = new wxGLContext(&canvas, nullptr, &attrs); } -#else - if (m_context == nullptr) - // if no valid context was created use the default one - m_context = new wxGLContext(&canvas); -#endif // ENABLE_OPENGL_DEBUG_OPTION #else m_context = new wxGLContext(&canvas); #endif // ENABLE_OPENGL_ES diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 5e2bdc3..cd941aa 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -50,15 +50,13 @@ public: const std::string& get_renderer() const; bool is_core_profile() const { return m_core_profile; } - void set_core_profile(bool value) { m_core_profile = value; } bool is_mesa() const; bool is_es() const { - return #if ENABLE_OPENGL_ES - true; + return true; #else - false; + return false; #endif // ENABLE_OPENGL_ES } @@ -100,9 +98,7 @@ private: bool m_gl_initialized{ false }; wxGLContext* m_context{ nullptr }; -#if ENABLE_OPENGL_DEBUG_OPTION bool m_debug_enabled{ false }; -#endif // ENABLE_OPENGL_DEBUG_OPTION GLShadersManager m_shaders_manager; static GLInfo s_gl_info; #ifdef __APPLE__ @@ -121,11 +117,7 @@ public: bool init_gl(); #if ENABLE_GL_CORE_PROFILE -#if ENABLE_OPENGL_DEBUG_OPTION - wxGLContext* init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_debug); -#else - wxGLContext* init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version); -#endif // ENABLE_OPENGL_DEBUG_OPTION + wxGLContext* init_glcontext(wxGLCanvas& canvas, const std::pair& required_opengl_version, bool enable_compatibility_profile, bool enable_debug); #else wxGLContext* init_glcontext(wxGLCanvas& canvas); #endif // ENABLE_GL_CORE_PROFILE diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 7ce73b7..c5f60c7 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -858,6 +858,16 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config { switch (opt->type) { + case coFloat: + if (config.option(opt_key)->is_nil()) + ret = _L("N/A"); + else + ret = double_to_string(config.option(opt_key)->value); + + break; + case coInt: + ret = config.option(opt_key)->value; + break; case coPercents: case coFloats: { if (config.option(opt_key)->is_nil()) diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index d738c18..65e63b6 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -194,6 +194,7 @@ public: wxGridSizer* get_grid_sizer() { return m_grid_sizer; } const std::vector& get_lines() { return m_lines; } + Line* get_last_line() { return m_lines.empty() ? nullptr : &m_lines[m_lines.size()-1]; } bool is_legend_line(); // if we have to set the same control alignment for different option groups, // we have to set same max contrtol width to all of them diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8800990..7a2f6ea 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -736,10 +736,35 @@ static wxRichToolTipPopup* get_rtt_popup(wxButton* btn) return nullptr; } +// Help function to find and check if some combobox is dropped down and then dismiss it +static bool found_and_dismiss_shown_dropdown(wxWindow* win) +{ + auto children = win->GetChildren(); + if (children.IsEmpty()) { + if (auto dd = dynamic_cast(win); dd && dd->IsShown()) { + dd->CallDismissAndNotify(); + return true; + } + } + + for (auto child : children) { + if (found_and_dismiss_shown_dropdown(child)) + return true; + } + return false; +} void Sidebar::priv::show_rich_tip(const wxString& tooltip, wxButton* btn) { if (tooltip.IsEmpty()) return; + // Currently state (propably wxWidgets issue) : + // When second wxPopupTransientWindow is popped up, then first wxPopupTransientWindow doesn't receive EVT_DISMISS and stay on the top. + // New comboboxes use wxPopupTransientWindow as DropDown now + // That is why DropDown stay on top, when we show rich tooltip for btn. + // (see https://github.com/prusa3d/PrusaSlicer/issues/11988) + + // So, check the combo boxes and close them if necessary before showing the rich tip. + found_and_dismiss_shown_dropdown(scrolled); wxRichToolTip tip(tooltip, ""); tip.SetIcon(wxICON_NONE); tip.SetTipKind(wxTipKind_BottomRight); @@ -774,7 +799,7 @@ Sidebar::Sidebar(Plater *parent) { SetFont(wxGetApp().normal_font()); p->scrolled = new wxScrolledWindow(this); -// p->scrolled->SetScrollbars(0, 100, 1, 2); // ys_DELETE_after_testing. pixelsPerUnitY = 100 from https://github.com/qidi3d/QIDISlicer/commit/8f019e5fa992eac2c9a1e84311c990a943f80b01, +// p->scrolled->SetScrollbars(0, 100, 1, 2); // ys_DELETE_after_testing. pixelsPerUnitY = 100 from https://github.com/prusa3d/PrusaSlicer/commit/8f019e5fa992eac2c9a1e84311c990a943f80b01, // but this cause the bad layout of the sidebar, when all infoboxes appear. // As a result we can see the empty block at the bottom of the sidebar // But if we set this value to 5, layout will be better @@ -921,8 +946,7 @@ Sidebar::Sidebar(Plater *parent) int bmp_px_cnt = 32; #endif //__APPLE__ ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt); - *btn = new ScalableButton(this, wxID_ANY, bmp, label, wxBU_EXACTFIT); - (*btn)->SetFont(wxGetApp().bold_font()); + *btn = new ScalableButton(this, wxID_ANY, bmp, "", wxBU_EXACTFIT); wxGetApp().SetWindowVariantForButton((*btn)); #ifdef _WIN32 @@ -940,10 +964,9 @@ Sidebar::Sidebar(Plater *parent) (*btn)->Hide(); }; - init_scalable_btn(&p->btn_send_gcode, "export_gcode", _L("Send to printer"), _L("Send to printer") + " " + GUI::shortkey_ctrl_prefix() + "Shift+G"); + init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + " " +GUI::shortkey_ctrl_prefix() + "Shift+G"); // init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device ") + GUI::shortkey_ctrl_prefix() + "T"); - init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export"), _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); -//Y14 + init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + " " + GUI::shortkey_ctrl_prefix() + "U"); // regular buttons "Slice now" and "Export G-code" // const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; @@ -2612,6 +2635,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ config.apply(loaded_printer_technology == ptFFF ? static_cast(FullPrintConfig::defaults()) : static_cast(SLAFullPrintConfig::defaults())); + // Set all the nullable values in defaults to nils. + config.null_nullables(); // and place the loaded config over the base. config += std::move(config_loaded); } @@ -2692,7 +2717,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (imperial_units) // Convert even if the object is big. convert_from_imperial_units(model, false); - else if (model.looks_like_saved_in_meters()) { + else if (!type_3mf && model.looks_like_saved_in_meters()) { auto convert_model_if = [](Model& model, bool condition) { if (condition) //FIXME up-scale only the small parts? @@ -2714,7 +2739,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } convert_model_if(model, answer_convert_from_meters == wxID_YES); } - else if (model.looks_like_imperial_units()) { + else if (!type_3mf && model.looks_like_imperial_units()) { auto convert_model_if = [convert_from_imperial_units](Model& model, bool condition) { if (condition) //FIXME up-scale only the small parts? @@ -3698,7 +3723,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const // We need to make sure that the painted data point to existing triangles. new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->seam_facets.assign(old_volume->seam_facets); - new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); + new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets); } std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); @@ -6202,7 +6227,7 @@ void Plater::convert_gcode_to_ascii() if (res == EResult::InvalidMagicNumber) { in_file.close(); out_file.close(); - boost::filesystem::copy_file(input_path, output_path, boost::filesystem::copy_option::overwrite_if_exists); + boost::filesystem::copy_file(input_path, output_path, boost::filesystem::copy_options::overwrite_existing); } else if (res != EResult::Success) { MessageDialog msg_dlg(this, _L(std::string(translate_result(res))), _L("Error converting G-code file"), wxICON_INFORMATION | wxOK); @@ -6281,7 +6306,7 @@ void Plater::convert_gcode_to_binary() if (res == EResult::AlreadyBinarized) { in_file.close(); out_file.close(); - boost::filesystem::copy_file(input_path, output_path, boost::filesystem::copy_option::overwrite_if_exists); + boost::filesystem::copy_file(input_path, output_path, boost::filesystem::copy_options::overwrite_existing); } else if (res != EResult::Success) { MessageDialog msg_dlg(this, _L(std::string(translate_result(res))), _L("Error converting G-code file"), wxICON_INFORMATION | wxOK); @@ -6513,7 +6538,7 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) std::replace(name.begin(), name.end(), '\\', '/'); // rename if file exists std::string filename = path.filename().string(); - std::string extension = boost::filesystem::extension(path); + std::string extension = path.extension().string(); std::string just_filename = filename.substr(0, filename.size() - extension.size()); std::string final_filename = just_filename; @@ -7357,7 +7382,7 @@ void Plater::export_gcode(bool prefer_removable) start_dir, from_path(default_output_file.filename()), printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : - GUI::sla_wildcards(p->sla_print.printer_config().sla_archive_format.value.c_str()), + GUI::sla_wildcards(p->sla_print.printer_config().sla_archive_format.value.c_str(), ext), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if (dlg.ShowModal() == wxID_OK) { @@ -8496,10 +8521,10 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string ¬ificati // may be different and they would make no sense. bool paint_removed = false; for (ModelVolume* mv : mo->volumes) { - paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty(); + paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty(); mv->supported_facets.reset(); mv->seam_facets.reset(); - mv->mmu_segmentation_facets.reset(); + mv->mm_segmentation_facets.reset(); } if (paint_removed) { // snapshot_time is captured by copy so the lambda knows where to undo/redo to. diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 52cfb51..a91a4cd 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -40,6 +40,8 @@ #include "PhysicalPrinterDialog.hpp" #include "MsgDialog.hpp" +#include "Widgets/ComboBox.hpp" + //B55 #include "../Utils/PrintHost.hpp" // A workaround for a set of issues related to text fitting into gtk widgets: @@ -1171,7 +1173,7 @@ void TabPresetComboBox::update() if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); Preset* preset = m_collection->find_preset(sel_preset_name); - if (!preset) + if (!preset || m_collection->get_selected_preset_name() != sel_preset_name) m_preset_bundle->physical_printers.unselect_printer(); } diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 1848565..51e0cfa 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -183,15 +183,6 @@ void ProgressStatusBar::set_font(const wxFont &font) self->SetFont(font); } -void ProgressStatusBar::show_cancel_button() -{ - if(m_cancelbutton) m_cancelbutton->Show(); -} - -void ProgressStatusBar::hide_cancel_button() -{ - if(m_cancelbutton) m_cancelbutton->Hide(); -} } diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 3ce3c85..553c841 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -58,9 +58,6 @@ public: wxString get_status_text() const; void set_font(const wxFont &font); - // Temporary methods to satisfy Perl side - void show_cancel_button(); - void hide_cancel_button(); void update_dark_ui(); diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 0d3737b..0b83093 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -754,7 +754,7 @@ namespace search_for_drives_internal stat(path.c_str(), &buf); uid_t uid = buf.st_uid; if (getuid() == uid) - out.emplace_back(DriveData{ boost::filesystem::basename(boost::filesystem::path(path)), path }); + out.emplace_back(DriveData{ boost::filesystem::path(path).stem().string(), path }); } } } diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index d2e9cba..062560a 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -118,7 +118,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty int cnt = 0; - if ( type != Preset::TYPE_FILAMENT && !PresetCollection::is_independent_from_extruder_number_option(opt_key) ) + if ( type != Preset::TYPE_FILAMENT && type != Preset::TYPE_SLA_MATERIAL && !PresetCollection::is_independent_from_extruder_number_option(opt_key) ) switch (config->option(opt_key)->type()) { case coInts: change_opt_key(opt_key, config, cnt); break; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ff22763..0081c17 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -925,9 +925,9 @@ const std::pair Selection::get_bounding_sphere() const const TriangleMesh* hull = volume.convex_hull(); const indexed_triangle_set& its = (hull != nullptr) ? hull->its : m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh().its; - const Transform3d& matrix = volume.world_matrix(); + const Transform3f matrix = volume.world_matrix().cast(); for (const Vec3f& v : its.vertices) { - const Vec3d vv = matrix * v.cast(); + const Vec3f vv = matrix * v; points.push_back(Point(vv.x(), vv.y(), vv.z())); } } @@ -1340,6 +1340,7 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& world_trans synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH + if (m_mode == EMode::Instance) ensure_on_bed(); set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp index c6adb28..6226455 100644 --- a/src/slic3r/GUI/SurfaceDrag.cpp +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -194,15 +194,19 @@ std::optional calc_distance(const GLVolume &gl_volume, RaycastManager &ra if (volume->is_the_only_one_part()) return {}; + if (!volume->emboss_shape.has_value()) + return {}; RaycastManager::AllowVolumes condition = create_condition(object->volumes, volume->id()); RaycastManager::Meshes meshes = create_meshes(canvas, condition); raycaster.actualize(*instance, &condition, &meshes); - return calc_distance(gl_volume, raycaster, &condition); + return calc_distance(gl_volume, raycaster, &condition, volume->emboss_shape->fix_3mf_tr); } -std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, const RaycastManager::ISkip *condition) -{ +std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, + const RaycastManager::ISkip *condition, const std::optional& fix) { Transform3d w = gl_volume.world_matrix(); + if (fix.has_value()) + w = w * fix->inverse(); Vec3d p = w.translation(); Vec3d dir = -get_z_base(w); auto hit_opt = raycaster.closest_hit(p, dir, condition); @@ -318,11 +322,11 @@ bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas, co return false; ModelObject &object = *object_ptr; - ModelInstance *instance_ptr = get_model_instance(gl_volume, object); + const ModelInstance *instance_ptr = get_model_instance(gl_volume, object); assert(instance_ptr != nullptr); if (instance_ptr == nullptr) return false; - ModelInstance &instance = *instance_ptr; + const ModelInstance &instance = *instance_ptr; ModelVolume *volume_ptr = get_model_volume(gl_volume, object); assert(volume_ptr != nullptr); @@ -381,9 +385,7 @@ bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas, co return true; } -void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle) -{ - Selection &selection = canvas.get_selection(); +void do_local_z_rotate(Selection &selection, double relative_angle) { assert(!selection.is_empty()); if(selection.is_empty()) return; @@ -415,16 +417,9 @@ void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle) }; selection_transform(selection, selection_rotate_fnc); - std::string snapshot_name; // empty meand no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended - // inside function do_move - // snapshot_name = L("Set text rotation"); - canvas.do_rotate(snapshot_name); } -void do_local_z_move(GLCanvas3D &canvas, double relative_move) { - - Selection &selection = canvas.get_selection(); +void do_local_z_move(Selection &selection, double relative_move) { assert(!selection.is_empty()); if (selection.is_empty()) return; @@ -435,11 +430,6 @@ void do_local_z_move(GLCanvas3D &canvas, double relative_move) { }; selection_transform(selection, selection_translate_fnc); - std::string snapshot_name; // empty mean no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended inside - // function do_move - // snapshot_name = L("Set surface distance"); - canvas.do_move(snapshot_name); } TransformationType get_drag_transformation_type(const Selection &selection) @@ -586,7 +576,7 @@ bool start_dragging(const Vec2d &mouse_pos, std::optional start_distance; if (!volume->emboss_shape->projection.use_surface) - start_distance = calc_distance(gl_volume, raycast_manager, &condition); + start_distance = calc_distance(gl_volume, raycast_manager, &condition, volume->emboss_shape->fix_3mf_tr); surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume_ptr, condition, start_angle, start_distance, true, mouse_offset_without_sla_shift}; diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp index cd87dfc..1310ada 100644 --- a/src/slic3r/GUI/SurfaceDrag.hpp +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -87,7 +87,8 @@ std::optional calc_surface_offset(const Selection &selection, RaycastMana /// Contain model /// Calculated distance from surface std::optional calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D &canvas); -std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, const RaycastManager::ISkip *condition); +std::optional calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, + const RaycastManager::ISkip *condition, const std::optional& fix); /// /// Calculate up vector angle @@ -134,16 +135,16 @@ bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas, co /// /// Rotation around z Axis(emboss direction) /// -/// Selected volume for rotation +/// Selected volume for rotation /// Relative angle to rotate around emboss direction -void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle); +void do_local_z_rotate(Selection &selection, double relative_angle); /// /// Translation along local z Axis (emboss direction) /// -/// Selected volume for translate +/// Selected volume for translate /// Relative move along emboss direction -void do_local_z_move(GLCanvas3D &canvas, double relative_move); +void do_local_z_move(Selection &selection, double relative_move); /// /// Distiguish between object and volume diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 397145d..d66df5b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -483,7 +483,7 @@ void Tab::OnActivate() update_btns_enabling(); m_btn_hide_incompatible_presets->Show(m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER); if (TabFilament* tab = dynamic_cast(this)) - tab->update_extruder_combobox(); + tab->update_extruder_combobox_visibility(); Layout(); } @@ -667,7 +667,7 @@ void Tab::init_options_list() m_options_list.clear(); for (const std::string& opt_key : m_config->keys()) - emplace_option(opt_key, m_type != Preset::TYPE_FILAMENT && !PresetCollection::is_independent_from_extruder_number_option(opt_key)); + emplace_option(opt_key, m_type != Preset::TYPE_FILAMENT && m_type != Preset::TYPE_SLA_MATERIAL && !PresetCollection::is_independent_from_extruder_number_option(opt_key)); } template @@ -967,7 +967,7 @@ void Tab::sys_color_changed() if (m_detach_preset_btn) m_detach_preset_btn->sys_color_changed(); - update_show_hide_incompatible_button(); + m_btn_hide_incompatible_presets->SetBitmap(*get_bmp_bundle(m_show_incompatible_presets ? "flag_red" : "flag_green")); // update icons for tree_ctrl wxVector img_bundles; @@ -1957,13 +1957,13 @@ void TabFilament::update_line_with_near_label_widget(ConfigOptionsGroupShp optgr field->toggle(is_checked); } -std::vector>> option_keys { +std::vector>> filament_overrides_option_keys { {"Travel lift", { "filament_retract_lift", "filament_travel_ramping_lift", "filament_travel_max_lift", "filament_travel_slope", - //"filament_travel_lift_before_obstacle", + "filament_travel_lift_before_obstacle", "filament_retract_lift_above", "filament_retract_lift_below" }}, @@ -1991,7 +1991,7 @@ void TabFilament::add_filament_overrides_page() const int extruder_idx = 0; // #ys_FIXME - for (const auto&[title, keys] : option_keys) { + for (const auto&[title, keys] : filament_overrides_option_keys) { ConfigOptionsGroupShp optgroup = page->new_optgroup(L(title)); for (const std::string& opt_key : keys) { create_line_with_near_label_widget(optgroup, opt_key, extruder_idx); @@ -2037,7 +2037,7 @@ void TabFilament::update_filament_overrides_page() || m_config->opt_float("filament_retract_lift", extruder_idx) > 0 ); - for (const auto&[title, keys] : option_keys) { + for (const auto&[title, keys] : filament_overrides_option_keys) { std::optional optgroup{get_option_group(page, title)}; if (!optgroup) { continue; @@ -2103,14 +2103,17 @@ void TabFilament::create_extruder_combobox() m_h_buttons_sizer->Add(m_extruders_cb, 0, wxALIGN_CENTER_VERTICAL); } -void TabFilament::update_extruder_combobox() +void TabFilament::update_extruder_combobox_visibility() { - if (!m_presets_choice->IsShown()) - return; // it will be updated later, on OnActive() const size_t extruder_cnt = static_cast(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); - m_extruders_cb->Show(extruder_cnt > 1); +} + +void TabFilament::update_extruder_combobox() +{ + const size_t extruder_cnt = m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? m_extruders_cb->GetCount() : + static_cast(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); if (extruder_cnt != m_extruders_cb->GetCount()) { m_extruders_cb->Clear(); @@ -2130,6 +2133,7 @@ void TabFilament::update_extruder_combobox() } m_extruders_cb->SetSelection(m_active_extruder); + m_extruders_cb->Show(extruder_cnt > 1); } bool TabFilament::set_active_extruder(int new_selected_extruder) @@ -3324,7 +3328,7 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders) optgroup->append_single_option_line("travel_ramping_lift", "", extruder_idx); optgroup->append_single_option_line("travel_max_lift", "", extruder_idx); optgroup->append_single_option_line("travel_slope", "", extruder_idx); - //optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx); + optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx); line = { L("Only lift"), "" }; line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); @@ -3578,7 +3582,6 @@ void TabPrinter::toggle_options() bool wipe = m_config->opt_bool("wipe", i); toggle_option("retract_before_wipe", wipe, i); - toggle_option("wipd_distance", wipe, i); if (use_firmware_retraction && wipe) { //wxMessageDialog dialog(parent(), @@ -3600,7 +3603,7 @@ void TabPrinter::toggle_options() load_config(new_conf); } - //toggle_option("travel_lift_before_obstacle", ramping_lift, i); + toggle_option("travel_lift_before_obstacle", ramping_lift, i); toggle_option("retract_length_toolchange", have_multiple_extruders, i); bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; @@ -4524,36 +4527,38 @@ void Tab::delete_preset() void Tab::toggle_show_hide_incompatible() { m_show_incompatible_presets = !m_show_incompatible_presets; - m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); - update_show_hide_incompatible_button(); - update_tab_ui(); + update_compatibility_ui(); } -void Tab::update_show_hide_incompatible_button() +void Tab::update_compatibility_ui() { m_btn_hide_incompatible_presets->SetBitmap(*get_bmp_bundle(m_show_incompatible_presets ? "flag_red" : "flag_green")); m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); + m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); + m_presets_choice->update(); } void Tab::update_ui_from_settings() { - // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled - // in application preferences. - m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; - bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; + // Show the 'show / hide presets' button only for the print and filament tabs + if (m_type == Slic3r::Preset::TYPE_PRINTER) + return; + + // and only if enabled in application preferences. + bool show = wxGetApp().app_config->get_bool("show_incompatible_presets"); + if (m_show_btn_incompatible_presets == show) + return; + + m_show_btn_incompatible_presets = show; + m_btn_hide_incompatible_presets->Show(m_show_btn_incompatible_presets); Layout(); - show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); - // If the 'show / hide presets' button is hidden, hide the incompatible presets. - if (show) { - update_show_hide_incompatible_button(); - } + if (show) + update_compatibility_ui(); else { - if (m_show_incompatible_presets) { - m_show_incompatible_presets = false; - update_tab_ui(); - } + m_presets_choice->set_show_incompatible_presets(false); + m_presets_choice->update(); } } @@ -5391,6 +5396,7 @@ void TabSLAMaterial::build() optgroup->append_line(line); + add_material_overrides_page(); page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); optgroup->label_width = 0; @@ -5431,6 +5437,8 @@ void TabSLAMaterial::toggle_options() const Preset ¤t_printer = wxGetApp().preset_bundle->printers.get_edited_preset(); std::string model = current_printer.config.opt_string("printer_model"); m_config_manipulation.toggle_field("material_print_speed", model != "SL1"); + if (m_active_page->title() == "Material Overrides") + update_material_overrides_page(); } void TabSLAMaterial::update() @@ -5438,6 +5446,7 @@ void TabSLAMaterial::update() if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; + toggle_options(); update_description_lines(); Layout(); @@ -5452,13 +5461,14 @@ void TabSLAMaterial::update() static void add_options_into_line(ConfigOptionsGroupShp &optgroup, const std::vector> &prefixes, - const std::string &optkey) + const std::string &optkey, + const std::string &preprefix = std::string()) { - auto opt = optgroup->get_option(prefixes.front().first + optkey); + auto opt = optgroup->get_option(preprefix + prefixes.front().first + optkey); Line line{ opt.opt.label, "" }; line.full_width = 1; for (auto &prefix : prefixes) { - opt = optgroup->get_option(prefix.first + optkey); + opt = optgroup->get_option(preprefix + prefix.first + optkey); opt.opt.label = prefix.second; opt.opt.width = 12; // TODO line.append_option(opt); @@ -5504,6 +5514,163 @@ void TabSLAPrint::build_sla_support_params(const std::vector get_override_opt_kyes_for_line(const std::string& title, const std::string& key) +{ + const std::string preprefix = "material_ow_"; + + std::vector opt_keys; + opt_keys.reserve(3); + + if (title == "Support head" || title == "Support pillar") { + for (auto& prefix : { "", "branching" }) + opt_keys.push_back(preprefix + prefix + key); + } + else if (key == "relative_correction") { + for (auto& axis : { "x", "y", "z" }) + opt_keys.push_back(preprefix + key + "_" + char(axis[0])); + } + else + opt_keys.push_back(preprefix + key); + + return opt_keys; +} + +void TabSLAMaterial::create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& key) +{ + if (optgroup->title == "Support head" || optgroup->title == "Support pillar") + add_options_into_line(optgroup, { {"", L("Default")}, {"branching", L("Branching")} }, key, "material_ow_"); + else { + const std::string opt_key = std::string("material_ow_") + key; + if (key == "relative_correction") { + Line line = Line{ m_preset_bundle->printers.get_edited_preset().config.def()->get("relative_correction")->full_label, "" }; + for (auto& axis : { "X", "Y", "Z" }) { + auto opt = optgroup->get_option(opt_key + "_" + char(std::tolower(axis[0]))); + opt.opt.label = axis; + line.append_option(opt); + } + optgroup->append_line(line); + } + else + optgroup->append_single_option_line(opt_key); + } + + Line* line = optgroup->get_last_line(); + if (!line) + return; + + line->near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), key](wxWindow* parent) { + wxWindow* check_box = CheckBox::GetNewWin(parent); + wxGetApp().UpdateDarkUI(check_box); + + check_box->Bind(wxEVT_CHECKBOX, [this, optgroup_wk, key](wxCommandEvent& evt) { + const bool is_checked = evt.IsChecked(); + if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) { + auto opt_keys = get_override_opt_kyes_for_line(optgroup_sh->title.ToStdString(), key); + for (const std::string& opt_key : opt_keys) + if (Field* field = optgroup_sh->get_fieldc(opt_key, 0); field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } + } + + toggle_options(); + }); + + m_overrides_options[key] = check_box; + return check_box; + }; +} + +std::vector>> material_overrides_option_keys{ + {"Support head", { + "support_head_front_diameter", + "support_head_penetration", + "support_head_width" + }}, + {"Support pillar", { + "support_pillar_diameter", + }}, + {"Automatic generation", { + "support_points_density_relative" + }}, + {"Corrections", { + "relative_correction", + "elefant_foot_compensation" + }} +}; + +void TabSLAMaterial::add_material_overrides_page() +{ + // TRN: Page title in Material Settings in SLA mode. + PageShp page = add_options_page(L("Material Overrides"), "wrench"); + + for (const auto& [title, keys] : material_overrides_option_keys) { + ConfigOptionsGroupShp optgroup = page->new_optgroup(L(title)); + for (const std::string& opt_key : keys) { + create_line_with_near_label_widget(optgroup, opt_key); + } + } +} + +void TabSLAMaterial::update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& key, bool is_checked/* = true*/) +{ + if (!m_overrides_options[key]) + return; + + const std::string preprefix = "material_ow_"; + + std::vector opt_keys; + opt_keys.reserve(3); + + if (optgroup->title == "Support head" || optgroup->title == "Support pillar") { + for (auto& prefix : { "", "branching" }) { + std::string opt_key = preprefix + prefix + key; + is_checked = !m_config->option(opt_key)->is_nil(); + opt_keys.push_back(opt_key); + } + } + else if (key == "relative_correction") { + for (auto& axis : { "x", "y", "z" }) { + std::string opt_key = preprefix + key + "_" + char(axis[0]); + is_checked = !m_config->option(opt_key)->is_nil(); + opt_keys.push_back(opt_key); + } + } + else { + std::string opt_key = preprefix + key; + is_checked = !m_config->option(opt_key)->is_nil(); + opt_keys.push_back(opt_key); + } + + CheckBox::SetValue(m_overrides_options[key], is_checked); + + for (const std::string& opt_key : opt_keys) { + Field* field = optgroup->get_field(opt_key); + if (field != nullptr) + field->toggle(is_checked); + } +} + +void TabSLAMaterial::update_material_overrides_page() +{ + if (!m_active_page || m_active_page->title() != "Material Overrides") + return; + Page* page = m_active_page; + + for (const auto& [title, keys] : material_overrides_option_keys) { + std::optional optgroup{ get_option_group(page, title) }; + if (!optgroup) { + continue; + } + + for (const std::string& key : keys) { + update_line_with_near_label_widget(*optgroup, key); + } + } +} void TabSLAPrint::build() { m_presets = &m_preset_bundle->sla_prints; @@ -5613,7 +5780,6 @@ void TabSLAPrint::update() m_update_cnt++; - m_config_manipulation.update_print_sla_config(m_config, true); update_description_lines(); Layout(); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 40ca77a..618e9e4 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -336,7 +336,7 @@ public: void rename_preset(); void delete_preset(); void toggle_show_hide_incompatible(); - void update_show_hide_incompatible_button(); + void update_compatibility_ui(); void update_ui_from_settings(); void update_label_colours(); void decorate(); @@ -480,6 +480,7 @@ public: bool set_active_extruder(int new_selected_extruder); void invalidate_active_extruder() { m_active_extruder = -1; } void update_extruder_combobox(); + void update_extruder_combobox_visibility(); int get_active_extruder() const { return m_active_extruder; } const std::string& get_custom_gcode(const t_config_option_key& opt_key) override; @@ -548,6 +549,12 @@ public: class TabSLAMaterial : public Tab { + void create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key); + void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, bool is_checked = true); + void add_material_overrides_page(); + void update_material_overrides_page(); + + std::map m_overrides_options; public: TabSLAMaterial(wxBookCtrlBase* parent) : Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 7442a90..07c0db4 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1094,7 +1094,7 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& switch (opt->type) { case coInt: - return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); + return from_u8((boost::format("%1%") % config.option(opt_key)->getInt()).str()); case coInts: { if (is_nullable) { auto values = config.opt(opt_key); @@ -1139,7 +1139,7 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& return _L("Undef"); } case coFloat: - return double_to_string(config.opt_float(opt_key)); + return double_to_string(config.option(opt_key)->getFloat()); case coFloats: { if (is_nullable) { auto values = config.opt(opt_key); @@ -1294,7 +1294,7 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres m_tree->model->AddPreset(type, from_u8(presets->get_edited_preset().name), old_pt, from_u8(new_selected_preset)); // Collect dirty options. - const bool deep_compare = type != Preset::TYPE_FILAMENT; + const bool deep_compare = type != Preset::TYPE_FILAMENT && type != Preset::TYPE_SLA_MATERIAL; auto dirty_options = presets->current_dirty_options(deep_compare); // process changes of extruders count @@ -1631,7 +1631,7 @@ void DiffPresetDialog::create_edit_sizer() { // Add check box for the edit mode m_use_for_transfer = new wxCheckBox(this, wxID_ANY, _L("Transfer values from left to right")); - m_use_for_transfer->SetToolTip(_L("If enabled, this dialog can be used for transver selected values from left to right preset.")); + m_use_for_transfer->SetToolTip(_L("If checked, this dialog can be used for transferring selected values from the preset on the left to the preset on the right.")); m_use_for_transfer->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { bool use = m_use_for_transfer->GetValue(); m_tree->GetColumn(DiffModel::colToggle)->SetHidden(!use); diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp index 26cc3ab..eb4db2c 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.cpp +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -291,7 +291,6 @@ void ComboBox::mouseDown(wxMouseEvent &event) void ComboBox::mouseWheelMoved(wxMouseEvent &event) { - event.Skip(); if (drop_down) return; auto delta = ((event.GetWheelRotation() < 0) == event.IsWheelInverted()) ? -1 : 1; unsigned int n = GetSelection() + delta; @@ -305,15 +304,12 @@ void ComboBox::keyDown(wxKeyEvent& event) { int key_code = event.GetKeyCode(); switch (key_code) { - case WXK_RETURN: + case WXK_RETURN: { if (drop_down) { drop.DismissAndNotify(); - wxCommandEvent e(wxEVT_COMBOBOX); - e.SetEventObject(this); - e.SetId(GetId()); - e.SetInt(GetSelection()); - GetEventHandler()->ProcessEvent(e); - } else if (drop.HasDismissLongTime()) { + sendComboBoxEvent(); + } + else if (drop.HasDismissLongTime()) { drop.autoPosition(); drop_down = true; drop.Popup(); @@ -321,14 +317,19 @@ void ComboBox::keyDown(wxKeyEvent& event) GetEventHandler()->ProcessEvent(e); } break; + } case WXK_UP: { if (GetSelection() > 0) SetSelection(GetSelection() - 1); + if (!drop.IsShown()) + sendComboBoxEvent(); break; } case WXK_DOWN: { if (GetSelection() + 1 < int(texts.size())) SetSelection(GetSelection() + 1); + if (!drop.IsShown()) + sendComboBoxEvent(); break; } case WXK_LEFT: { diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp index 9fe1a33..a3e27ee 100644 --- a/src/slic3r/GUI/Widgets/DropDown.hpp +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -85,6 +85,7 @@ public: bool HasDismissLongTime(); static void SetTransparentBG(wxDC& dc, wxWindow* win); + void CallDismissAndNotify() { DismissAndNotify(); } protected: void OnDismiss() override; diff --git a/src/slic3r/GUI/WifiConfigDialog.cpp b/src/slic3r/GUI/WifiConfigDialog.cpp index b8a46b9..a5013a7 100644 --- a/src/slic3r/GUI/WifiConfigDialog.cpp +++ b/src/slic3r/GUI/WifiConfigDialog.cpp @@ -29,6 +29,19 @@ WifiConfigDialog::WifiConfigDialog(wxWindow* parent, std::string& file_path, Rem , out_file_path(file_path) , m_removable_manager(removable_manager) { + // Propagation of error in wifi scanner construtor + if (!m_wifi_scanner->is_init()) { + // TRN Error dialog of configuration -> wifi configuration file + wxString msg = format_wxstr(L"%1%\n\n%2%", _L("Failed to scan wireless networks. Please fill SSID manually."), +#ifdef _WIN32 + // TRN Windows specific second line of error dialog of configuration -> wifi configuration file + _L("Library wlanapi.dll was not loaded.") +#else + "" +#endif // _WIN32 + ); + show_error(this, msg); + } wxPanel* panel = new wxPanel(this); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); panel->SetSizer(vsizer); @@ -188,11 +201,14 @@ void WifiConfigDialog::on_rescan_networks(wxCommandEvent& e) void WifiConfigDialog::rescan_networks(bool select) { assert(m_ssid_combo && m_wifi_scanner); + // Do not do anything if scanner is in faulty state (which should has been propageted in constructor call) + if (!m_wifi_scanner->is_init()) + return; m_wifi_scanner->scan(); std::string current = m_wifi_scanner->get_current_ssid(); const auto& map = m_wifi_scanner->get_map(); m_ssid_combo->Clear(); - for (const auto pair : map) { + for (const auto &pair : map) { m_ssid_combo->Append(pair.first); // select ssid of current network (if connected) if (current == pair.first) diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index fef70da..53dc2cf 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -10,6 +10,8 @@ #include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges +#include +#include using namespace Slic3r; using namespace Slic3r::Emboss; @@ -35,6 +37,19 @@ void store_style_index(AppConfig &cfg, size_t index); StyleManager::Styles load_styles(const AppConfig &cfg); void store_styles(AppConfig &cfg, const StyleManager::Styles &styles); void make_unique_name(const StyleManager::Styles &styles, std::string &name); +// Enum map to string and vice versa +using HorizontalAlignToName = boost::bimap; +const HorizontalAlignToName horizontal_align_to_name = +boost::assign::list_of + (FontProp::HorizontalAlign::left, "left") + (FontProp::HorizontalAlign::center, "center") + (FontProp::HorizontalAlign::right, "right"); +using VerticalAlignToName = boost::bimap; +const VerticalAlignToName vertical_align_to_name = +boost::assign::list_of + (FontProp::VerticalAlign::top, "top") + (FontProp::VerticalAlign::center, "middle") + (FontProp::VerticalAlign::bottom, "bottom"); } // namespace void StyleManager::init(AppConfig *app_config) { @@ -532,6 +547,9 @@ const std::string APP_CONFIG_FONT_DESCRIPTOR = "descriptor"; const std::string APP_CONFIG_FONT_LINE_HEIGHT = "line_height"; const std::string APP_CONFIG_FONT_DEPTH = "depth"; const std::string APP_CONFIG_FONT_USE_SURFACE = "use_surface"; +const std::string APP_CONFIG_PER_GLYPH = "per_glyph"; +const std::string APP_CONFIG_VERTICAL_ALIGN = "vertical_align"; +const std::string APP_CONFIG_HORIZONTAL_ALIGN = "horizontal_align"; const std::string APP_CONFIG_FONT_BOLDNESS = "boldness"; const std::string APP_CONFIG_FONT_SKEW = "skew"; const std::string APP_CONFIG_FONT_DISTANCE = "distance"; @@ -558,6 +576,35 @@ bool read(const Section §ion, const std::string &key, bool &value) return true; } +bool read(const Section §ion, const std::string &key, Slic3r::FontProp::HorizontalAlign &value) { + auto item = section.find(key); + if (item == section.end()) + return false; + + const std::string &data = item->second; + if (data.empty()) + return false; + + const auto& map = horizontal_align_to_name.right; + auto it = map.find(data); + value = (it != map.end()) ? it->second : Slic3r::FontProp::HorizontalAlign::center; + return true; +} + +bool read(const Section §ion, const std::string &key, Slic3r::FontProp::VerticalAlign &value) { + auto item = section.find(key); + if (item == section.end()) + return false; + + const std::string &data = item->second; + if (data.empty()) + return false; + + const auto &map = vertical_align_to_name.right; + auto it = map.find(data); + value = (it != map.end()) ? it->second : Slic3r::FontProp::VerticalAlign::center; + return true; +} bool read(const Section §ion, const std::string &key, float &value) { auto item = section.find(key); @@ -647,6 +694,9 @@ std::optional load_style(const Section &app_cfg_section) read(app_cfg_section, APP_CONFIG_FONT_DEPTH, depth); ep.depth = depth; read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, ep.use_surface); + read(app_cfg_section, APP_CONFIG_PER_GLYPH, fp.per_glyph); + read(app_cfg_section, APP_CONFIG_HORIZONTAL_ALIGN, fp.align.first); + read(app_cfg_section, APP_CONFIG_VERTICAL_ALIGN, fp.align.second); read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, s.distance); @@ -668,6 +718,12 @@ void store_style(AppConfig &cfg, const StyleManager::Style &s, unsigned index) data[APP_CONFIG_FONT_DEPTH] = std::to_string(ep.depth); if (ep.use_surface) data[APP_CONFIG_FONT_USE_SURFACE] = "true"; + if (fp.per_glyph) + data[APP_CONFIG_PER_GLYPH] = "true"; + if (fp.align.first != FontProp::HorizontalAlign::center) + data[APP_CONFIG_HORIZONTAL_ALIGN] = horizontal_align_to_name.left.find(fp.align.first)->second; + if (fp.align.second != FontProp::VerticalAlign::center) + data[APP_CONFIG_VERTICAL_ALIGN] = vertical_align_to_name.left.find(fp.align.second)->second; if (fp.boldness.has_value()) data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness); if (fp.skew.has_value()) diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index 9a79263..217bc66 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -210,8 +210,8 @@ public: { return EmbossStyle::operator==(other) && projection == other.projection && - distance == other.distance && - angle == other.angle; + is_approx(distance, other.distance) && + is_approx(angle, other.angle); } // cache for view font name with maximal width in imgui diff --git a/src/slic3r/Utils/WifiScanner.cpp b/src/slic3r/Utils/WifiScanner.cpp index 2605835..8370e37 100644 --- a/src/slic3r/Utils/WifiScanner.cpp +++ b/src/slic3r/Utils/WifiScanner.cpp @@ -8,13 +8,10 @@ #ifdef _WIN32 #include -#include #include #include -// Need to link with Wlanapi.lib and Ole32.lib -#pragma comment(lib, "wlanapi.lib") -#pragma comment(lib, "ole32.lib") +#include "libslic3r//Utils.hpp" #elif __APPLE_ #include "WifiScannerMac.h" #endif @@ -24,6 +21,7 @@ #endif //__linux__ namespace { +#ifdef _WIN32 bool ptree_get_value(const boost::property_tree::ptree& pt, const std::string& target, std::string& result) { // Check if the current node has the target element @@ -41,138 +39,6 @@ bool ptree_get_value(const boost::property_tree::ptree& pt, const std::string& t return false; // Element not found in this subtree } -#ifdef _WIN32 -// Fill SSID map. Implementation from Raspberry Pi imager and Win32 Api examples. -// https://github.com/raspberrypi/rpi-imager/blob/qml/src/windows/winwlancredentials.cpp -// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlangetavailablenetworklist -void fill_wifi_map(Slic3r::WifiSsidPskMap& wifi_map, std::string& connected_ssid) -{ - HANDLE handle; - DWORD supported_version = 0; - DWORD client_version = 2; - PWLAN_INTERFACE_INFO_LIST interface_list = NULL; - - - if (WlanOpenHandle(client_version, NULL, &supported_version, &handle) != ERROR_SUCCESS) - return; - - if (WlanEnumInterfaces(handle, NULL, &interface_list) != ERROR_SUCCESS) - return; - - for (DWORD i = 0; i < interface_list->dwNumberOfItems; i++) - { - if (interface_list->InterfaceInfo[i].isState == wlan_interface_state_connected) - { - PWLAN_CONNECTION_ATTRIBUTES pConnectInfo = NULL; - DWORD connectInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES); - WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_invalid; - - if (WlanQueryInterface(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, - wlan_intf_opcode_current_connection, NULL, - &connectInfoSize, (PVOID*)&pConnectInfo, &opCode) == ERROR_SUCCESS && pConnectInfo && pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength) - { - connected_ssid = std::string((const char*)pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID, - pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength); - } - - WlanFreeMemory(pConnectInfo); - } - - PWLAN_PROFILE_INFO_LIST profile_list = NULL; - PWLAN_INTERFACE_INFO interface_info_entry = NULL; - PWLAN_AVAILABLE_NETWORK_LIST available_network_list = NULL; - WCHAR guid[39] = { 0 }; - - // Get all available networks. - interface_info_entry = (WLAN_INTERFACE_INFO*)&interface_list->InterfaceInfo[i]; - int iRet = StringFromGUID2(interface_info_entry->InterfaceGuid, (LPOLESTR)&guid, - sizeof(guid) / sizeof(*guid)); - - if (WlanGetAvailableNetworkList(handle, - &interface_info_entry->InterfaceGuid, - 0, - NULL, - &available_network_list) - != ERROR_SUCCESS) - { - continue; - } - - for (unsigned int j = 0; j < available_network_list->dwNumberOfItems; j++) - { - PWLAN_AVAILABLE_NETWORK available_network_entry = NULL; - wxString ssid; - - // Store SSID into the map. - available_network_entry = - (WLAN_AVAILABLE_NETWORK*)&available_network_list->Network[j]; - - if (available_network_entry->dot11Ssid.uSSIDLength != 0) - ssid = wxString(available_network_entry->dot11Ssid.ucSSID, - available_network_entry->dot11Ssid.uSSIDLength); - - if (ssid.empty()) - continue; - - if (wifi_map.find(ssid) != wifi_map.end()) - continue; - - wifi_map[ssid] = std::string(); - - if (WlanGetProfileList(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, - NULL, &profile_list) != ERROR_SUCCESS) - { - continue; - } - // enmurate all stored profiles, take password from matching one. - for (DWORD k = 0; k < profile_list->dwNumberOfItems; k++) - { - DWORD flags = WLAN_PROFILE_GET_PLAINTEXT_KEY; - DWORD access = 0; - DWORD ret = 0; - LPWSTR xmlstr = NULL; - wxString s(profile_list->ProfileInfo[k].strProfileName); - - BOOST_LOG_TRIVIAL(debug) << "Enumerating wlan profiles, SSID found:" << s << " looking for:" << ssid; - - if (s != ssid) - continue; - - if ((ret = WlanGetProfile(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, profile_list->ProfileInfo[k].strProfileName, - NULL, &xmlstr, &flags, &access)) == ERROR_SUCCESS && xmlstr) - { - wxString xml(xmlstr); - boost::property_tree::ptree pt; - std::stringstream ss(boost::nowide::narrow(xml)); - boost::property_tree::read_xml(ss, pt); - std::string password; - std::string psk_protected; - - BOOST_LOG_TRIVIAL(debug) << "XML wlan profile:" << xml; - - // break if password is not readable - // TODO: what if there is other line "protected" in the XML? - if (ptree_get_value(pt, "protected", psk_protected) && psk_protected != "false") - break; - - if (ptree_get_value(pt, "keyMaterial", password)) - wifi_map[ssid] = password; - - WlanFreeMemory(xmlstr); - break; - } - } - - if (profile_list) { - WlanFreeMemory(profile_list); - } - } - } - - if (interface_list) - WlanFreeMemory(interface_list); - WlanCloseHandle(handle, NULL); -} #elif __APPLE__ void get_connected_ssid(std::string& connected_ssid) { @@ -393,11 +259,39 @@ void fill_wifi_map(Slic3r::WifiSsidPskMap& wifi_map) namespace Slic3r { WifiScanner::WifiScanner() -{} +{ +#ifdef _WIN32 + m_wlanapi_handle = LoadLibrary(L"wlanapi.dll"); + if (m_wlanapi_handle == NULL) + return; + + wlanOpenHandleFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanOpenHandle")); + wlanEnumInterfacesFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanEnumInterfaces")); + wlanQueryInterfaceFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanQueryInterface")); + wlanFreeMemoryFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanFreeMemory")); + wlanGetProfileFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanGetProfile")); + wlanGetProfileListFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanGetProfileList")); + wlanGetAvailableNetworkListFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanGetAvailableNetworkList")); + wlanCloseHandleFunc = reinterpret_cast(GetProcAddress(m_wlanapi_handle, "WlanCloseHandle")); + + if (!wlanOpenHandleFunc || !wlanEnumInterfacesFunc || !wlanQueryInterfaceFunc || !wlanFreeMemoryFunc + || !wlanGetProfileFunc || !wlanGetProfileListFunc || !wlanGetAvailableNetworkListFunc || !wlanCloseHandleFunc) + return; +#endif // _WIN32 + m_init = true; +} WifiScanner::~WifiScanner() -{} +{ +#ifdef _WIN32 + if (m_wlanapi_handle) + FreeLibrary(m_wlanapi_handle); +#endif // _WIN32 + +} void WifiScanner::scan() { + if (!m_init) + return; m_map.clear(); #ifdef _WIN32 fill_wifi_map(m_map, m_current_ssid); @@ -457,4 +351,139 @@ std::string WifiScanner::get_psk(const std::string& ssid) } return {}; } +#ifdef _WIN32 +// Fill SSID map. Implementation from Raspberry Pi imager and Win32 Api examples. +// https://github.com/raspberrypi/rpi-imager/blob/qml/src/windows/winwlancredentials.cpp +// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlangetavailablenetworklist +void WifiScanner::fill_wifi_map(Slic3r::WifiSsidPskMap& wifi_map, std::string& connected_ssid) +{ + HANDLE handle; + DWORD supported_version = 0; + DWORD client_version = 2; + PWLAN_INTERFACE_INFO_LIST interface_list = NULL; + + if (!m_init) + return; + + if (wlanOpenHandleFunc(client_version, NULL, &supported_version, &handle) != ERROR_SUCCESS) + return; + + Slic3r::ScopeGuard guard([this, &handle] { wlanCloseHandleFunc(handle, NULL); }); + + if (wlanEnumInterfacesFunc(handle, NULL, &interface_list) != ERROR_SUCCESS) + return; + + for (DWORD i = 0; i < interface_list->dwNumberOfItems; i++) + { + if (interface_list->InterfaceInfo[i].isState == wlan_interface_state_connected) + { + PWLAN_CONNECTION_ATTRIBUTES pConnectInfo = NULL; + DWORD connectInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES); + WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_invalid; + + if (wlanQueryInterfaceFunc(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, + wlan_intf_opcode_current_connection, NULL, + &connectInfoSize, (PVOID*)&pConnectInfo, &opCode) == ERROR_SUCCESS && pConnectInfo && pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength) + { + connected_ssid = std::string((const char*)pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID, + pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength); + wlanFreeMemoryFunc(pConnectInfo); + } + } + + PWLAN_PROFILE_INFO_LIST profile_list = NULL; + PWLAN_INTERFACE_INFO interface_info_entry = NULL; + PWLAN_AVAILABLE_NETWORK_LIST available_network_list = NULL; + WCHAR guid[39] = { 0 }; + + // Get all available networks. + interface_info_entry = (WLAN_INTERFACE_INFO*)&interface_list->InterfaceInfo[i]; + int iRet = StringFromGUID2(interface_info_entry->InterfaceGuid, (LPOLESTR)&guid, + sizeof(guid) / sizeof(*guid)); + + if (wlanGetAvailableNetworkListFunc(handle, + &interface_info_entry->InterfaceGuid, + 0, + NULL, + &available_network_list) + != ERROR_SUCCESS) + { + continue; + } + + for (unsigned int j = 0; j < available_network_list->dwNumberOfItems; j++) + { + PWLAN_AVAILABLE_NETWORK available_network_entry = NULL; + wxString ssid; + + // Store SSID into the map. + available_network_entry = + (WLAN_AVAILABLE_NETWORK*)&available_network_list->Network[j]; + + if (available_network_entry->dot11Ssid.uSSIDLength != 0) + ssid = wxString(available_network_entry->dot11Ssid.ucSSID, + available_network_entry->dot11Ssid.uSSIDLength); + + if (ssid.empty()) + continue; + + if (wifi_map.find(ssid) != wifi_map.end()) + continue; + + wifi_map[ssid] = std::string(); + + if (wlanGetProfileListFunc(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, + NULL, &profile_list) != ERROR_SUCCESS) + { + continue; + } + // enmurate all stored profiles, take password from matching one. + for (DWORD k = 0; k < profile_list->dwNumberOfItems; k++) + { + DWORD flags = WLAN_PROFILE_GET_PLAINTEXT_KEY; + DWORD access = 0; + DWORD ret = 0; + LPWSTR xmlstr = NULL; + wxString s(profile_list->ProfileInfo[k].strProfileName); + + BOOST_LOG_TRIVIAL(debug) << "Enumerating wlan profiles, SSID found:" << s << " looking for:" << ssid; + + if (s != ssid) + continue; + + if ((ret = wlanGetProfileFunc(handle, &interface_list->InterfaceInfo[i].InterfaceGuid, profile_list->ProfileInfo[k].strProfileName, + NULL, &xmlstr, &flags, &access)) == ERROR_SUCCESS && xmlstr) + { + wxString xml(xmlstr); + boost::property_tree::ptree pt; + std::stringstream ss(boost::nowide::narrow(xml)); + boost::property_tree::read_xml(ss, pt); + std::string password; + std::string psk_protected; + + BOOST_LOG_TRIVIAL(debug) << "XML wlan profile:" << xml; + + // break if password is not readable + // TODO: what if there is other line "protected" in the XML? + if (ptree_get_value(pt, "protected", psk_protected) && psk_protected != "false") + break; + + if (ptree_get_value(pt, "keyMaterial", password)) + wifi_map[ssid] = password; + + wlanFreeMemoryFunc(xmlstr); + break; + } + } + + if (profile_list) { + wlanFreeMemoryFunc(profile_list); + } + } + } + + if (interface_list) + wlanFreeMemoryFunc(interface_list); +} +#endif // _WIN32 } // Slic3r \ No newline at end of file diff --git a/src/slic3r/Utils/WifiScanner.hpp b/src/slic3r/Utils/WifiScanner.hpp index 96befa5..dbcceac 100644 --- a/src/slic3r/Utils/WifiScanner.hpp +++ b/src/slic3r/Utils/WifiScanner.hpp @@ -6,9 +6,12 @@ #include #include +#ifdef _WIN32 +#include +#endif //_WIN32 namespace Slic3r { -typedef std::map WifiSsidPskMap; +using WifiSsidPskMap = std::map; class WifiScanner { @@ -16,6 +19,7 @@ public: WifiScanner(); ~WifiScanner(); + bool is_init() const { return m_init; } const WifiSsidPskMap& get_map() const { return m_map; } // returns psk for given ssid // used on APPLE where each psk query requires user to give their password @@ -27,7 +31,30 @@ public: private: WifiSsidPskMap m_map; std::string m_current_ssid; -#if __APPLE__ + bool m_init { false }; + +#ifdef _WIN32 + void fill_wifi_map(Slic3r::WifiSsidPskMap& wifi_map, std::string& connected_ssid); + HINSTANCE m_wlanapi_handle; + // Functions of wlanapi used by fill_wifi_map + using WlanOpenHandleFunc = DWORD(WINAPI*)(DWORD, PVOID, PDWORD, PHANDLE); + using WlanEnumInterfacesFunc = DWORD(WINAPI*)(HANDLE, PVOID, PWLAN_INTERFACE_INFO_LIST*); + using WlanQueryInterfaceFunc = DWORD(WINAPI*)(HANDLE, const GUID*, WLAN_INTF_OPCODE, PVOID, PDWORD, PVOID*, PWLAN_OPCODE_VALUE_TYPE); + using WlanFreeMemoryFunc = VOID(WINAPI*)(PVOID); + using WlanGetProfileFunc = DWORD(WINAPI*)(HANDLE, const GUID*, LPCWSTR, PVOID, LPWSTR*, DWORD*, DWORD*); + using WlanGetProfileListFunc = DWORD(WINAPI*)(HANDLE, const GUID*, PVOID, PWLAN_PROFILE_INFO_LIST*); + using WlanGetAvailableNetworkListFunc = DWORD(WINAPI*)(HANDLE, const GUID*, DWORD, PVOID, PWLAN_AVAILABLE_NETWORK_LIST*); + using WlanCloseHandleFunc = DWORD(WINAPI*)(HANDLE, PVOID); + + WlanOpenHandleFunc wlanOpenHandleFunc; + WlanEnumInterfacesFunc wlanEnumInterfacesFunc; + WlanQueryInterfaceFunc wlanQueryInterfaceFunc; + WlanFreeMemoryFunc wlanFreeMemoryFunc; + WlanGetProfileFunc wlanGetProfileFunc; + WlanGetProfileListFunc wlanGetProfileListFunc; + WlanGetAvailableNetworkListFunc wlanGetAvailableNetworkListFunc; + WlanCloseHandleFunc wlanCloseHandleFunc; +#elif __APPLE__ void get_ssids_mac(std::vector& ssids); std::string get_psk_mac(const std::string& ssid); std::string get_current_ssid_mac(); diff --git a/t/gcode.t b/t/gcode.t deleted file mode 100644 index 7bf6b06..0000000 --- a/t/gcode.t +++ /dev/null @@ -1,243 +0,0 @@ -use Test::More tests => 22; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(scale convex_hull); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('wipe', [1]); - $config->set('retract_layer_change', [0]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $have_wipe = 0; - my @retract_speeds = (); - my $extruded_on_this_layer = 0; - my $wiping_on_new_layer = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{travel} && $info->{dist_Z}) { - # changing layer - $extruded_on_this_layer = 0; - } elsif ($info->{extruding} && $info->{dist_XY}) { - $extruded_on_this_layer = 1; - } elsif ($info->{retracting} && $info->{dist_XY} > 0) { - $have_wipe = 1; - $wiping_on_new_layer = 1 if !$extruded_on_this_layer; - my $move_time = $info->{dist_XY} / ($args->{F} // $self->F); - push @retract_speeds, abs($info->{dist_E}) / $move_time; - } - }); - - ok $have_wipe, "wipe"; - ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed'; - ok !$wiping_on_new_layer, 'no wiping after layer change'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('z_offset', 5); - $config->set('start_gcode', ''); - - my $test = sub { - my ($comment) = @_; - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $moves_below_z_offset = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{travel} && exists $args->{Z}) { - $moves_below_z_offset++ if $args->{Z} < $config->z_offset; - } - }); - is $moves_below_z_offset, 0, "no Z moves below Z offset ($comment)"; - }; - - $test->("no lift"); - - $config->set('retract_lift', [3]); - $test->("lift < z_offset"); - - $config->set('retract_lift', [6]); - $test->("lift > z_offset"); -} - -{ - # This tests the following behavior: - # - complete objects does not crash - # - no hard-coded "E" are generated - # - Z moves are correctly generated for both objects - # - no travel moves go outside skirt - # - temperatures are set correctly - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_comments', 1); - $config->set('complete_objects', 1); - $config->set('extrusion_axis', 'A'); - $config->set('start_gcode', ''); # prevent any default extra Z move - $config->set('layer_height', 0.4); - $config->set('first_layer_height', 0.4); - $config->set('temperature', [200]); - $config->set('first_layer_temperature', [210]); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - ok my $gcode = Slic3r::Test::gcode($print), "complete_objects"; - my @z_moves = (); - my @travel_moves = (); # array of scaled points - my @extrusions = (); # array of scaled points - my @temps = (); - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - fail 'unexpected E argument' if defined $args->{E}; - if (defined $args->{Z}) { - push @z_moves, $args->{Z}; - } - - if ($info->{dist_XY}) { - if ($info->{extruding} || $args->{A}) { - push @extrusions, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); - } else { - push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}) - if @extrusions; # skip initial travel move to first skirt point - } - } elsif ($cmd eq 'M104' || $cmd eq 'M109') { - push @temps, $args->{S} if !@temps || $args->{S} != $temps[-1]; - } - }); - my $layer_count = 20/0.4; # cube is 20mm tall - is scalar(@z_moves), 2*$layer_count, 'complete_objects generates the correct number of Z moves'; - is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves'; - - my $convex_hull = convex_hull(\@extrusions); - ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt'; - - is_deeply \@temps, [210, 200, 210, 200, 0], 'expected temperature changes'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('retract_length', [1000000]); - $config->set('use_relative_e_distances', 1); - $config->set('layer_gcode', "G92 E0\n"); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - Slic3r::Test::gcode($print); - ok $print->print->total_used_filament > 0, 'final retraction is not considered in total used filament'; -} - -{ - my $test = sub { - my ($print, $comment) = @_; - - my @percent = (); - my $got_100 = 0; - my $extruding_after_100 = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'M73') { - push @percent, $args->{P}; - $got_100 = 1 if $args->{P} eq '100'; - } - if ($info->{extruding} && $got_100) { - $extruding_after_100 = 1; - } - }); - # the extruder heater is turned off when M73 P100 is reached - ok !(defined first { $_ > 100 } @percent), "M73 is never given more than 100% ($comment)"; - ok !$extruding_after_100, "no extrusions after M73 P100 ($comment)"; - }; - - { - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_flavor', 'sailfish'); - $config->set('raft_layers', 3); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - $test->($print, 'single object'); - } - - { - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_flavor', 'sailfish'); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - $test->($print, 'two copies of single object'); - } - - { - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_flavor', 'sailfish'); - my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); - $test->($print, 'two objects'); - } - - { - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_flavor', 'sailfish'); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]); - $test->($print, 'one layer object'); - } -} - -#{ -# [input_filename] placeholder was removed in 0cbbe96. -# my $config = Slic3r::Config::new_from_defaults; -# $config->set('start_gcode', 'START:[input_filename]'); -# my $print = Slic3r::Test::init_print('20mm_cube', config => $config); -# my $gcode = Slic3r::Test::gcode($print); -# like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code'; -#} - -# The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice, -# therefore the following test is no more valid. -#{ -# my $config = Slic3r::Config::new_from_defaults; -# $config->set('spiral_vase', 1); -# my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); -# -# my $spiral = 0; -# Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { -# my ($self, $cmd, $args, $info) = @_; -# -# if ($cmd eq 'G1' && exists $args->{E} && exists $args->{Z}) { -# $spiral = 1; -# } -# }); -# -# ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops'; -#} - - -{ - # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting - # acceleration, also that M204 Snnn syntax is not generated. - my $config = Slic3r::Config::new_from_defaults; - $config->set('gcode_flavor', 'repetier'); - $config->set('default_acceleration', 1337); - my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); - - my $has_accel = 0; - my $has_m204 = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'M201' && exists $args->{X} && exists $args->{Y}) { - if ($args->{X} == 1337 && $args->{Y} == 1337) { - $has_accel = 1; - } - } - if ($cmd eq 'M204' && exists $args->{S}) { - $has_m204 = 1; - } - }); - ok $has_accel, 'M201 is generated for repetier firmware.'; - ok !$has_m204, 'M204 is not generated for repetier firmware'; -} - -__END__ diff --git a/t/layers.t b/t/layers.t deleted file mode 100644 index 6065d78..0000000 --- a/t/layers.t +++ /dev/null @@ -1,79 +0,0 @@ -use Test::More tests => 5; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Test qw(_eq); - -{ - my $config = Slic3r::Config::new_from_defaults; - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $conf); - - my @z = (); - my @increments = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{dist_Z}) { - push @z, 1*$args->{Z}; - push @increments, $info->{dist_Z}; - } - }); - - fail 'wrong first layer height' - if $z[0] ne $config->get_value('first_layer_height') + $config->z_offset; - - fail 'wrong second layer height' - if $z[1] ne $config->get_value('first_layer_height') + $config->get_value('layer_height') + $config->z_offset; - - fail 'wrong layer height' - if first { !_eq($_, $config->layer_height) } @increments[1..$#increments]; - - 1; - }; - - $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code - $config->set('layer_height', 0.3); - $config->set('first_layer_height', 0.2); - ok $test->(), "absolute first layer height"; - - $config->set('first_layer_height', 0.6 * $config->layer_height); - ok $test->(), "relative first layer height"; - - $config->set('z_offset', 0.9); - ok $test->(), "positive Z offset"; - - $config->set('z_offset', -0.8); - ok $test->(), "negative Z offset"; -} - -{ - my $config = Slic3r::Config->new; - $config->set('fill_density', 0); # just for making the test faster - $config->set('binary_gcode', 0); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - - my @z = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{dist_Z}) { - push @z, 1*$args->{Z}; - } - }); - ok $z[-1] > 20*1.8 && $z[-1] < 20*2.2, 'resulting G-code has reasonable height'; -} - -__END__ diff --git a/t/retraction.t b/t/retraction.t deleted file mode 100644 index c55631e..0000000 --- a/t/retraction.t +++ /dev/null @@ -1,260 +0,0 @@ -use Test::More tests => 26; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(any); -use Slic3r; -use Slic3r::Test qw(_eq); - -{ - my $config = Slic3r::Config::new_from_defaults; - my $duplicate = 1; - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $conf, duplicate => $duplicate); - - my $tool = 0; - my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time - my @retracted = (0); - my @retracted_length = (0); - my $lifted = 0; - my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values - my $changed_tool = 0; - my $wait_for_toolchange = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - $changed_tool = 1; - $wait_for_toolchange = 0; - $toolchange_count[$tool] //= 0; - $toolchange_count[$tool]++; - } elsif ($cmd =~ /^G[01]$/ && !$args->{Z}) { # ignore lift taking place after retraction - fail 'toolchange happens right after retraction' if $wait_for_toolchange; - } - - if ($info->{dist_Z}) { - # lift move or lift + change layer - if (_eq($info->{dist_Z}, $print->print->config->get_at('retract_lift', $tool)) - || (_eq($info->{dist_Z}, $conf->layer_height + $print->print->config->get_at('retract_lift', $tool)) && $print->print->config->get_at('retract_lift', $tool) > 0)) { - fail 'only lifting while retracted' if !$retracted[$tool]; - fail 'double lift' if $lifted; - $lifted = 1; - $lift_dist = $info->{dist_Z}; - } - if ($info->{dist_Z} < 0) { - fail 'going down only after lifting' if !$lifted; - fail 'going down by the same amount of the lift or by the amount needed to get to next layer' - if !_eq($info->{dist_Z}, -$lift_dist) - && !_eq($info->{dist_Z}, -lift_dist + $conf->layer_height); - $lift_dist = 0; - $lifted = 0; - } - fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; - } - if ($info->{retracting}) { - $retracted[$tool] = 1; - $retracted_length[$tool] += -$info->{dist_E}; - if (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length', $tool))) { - # okay - } elsif (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length_toolchange', $tool))) { - $wait_for_toolchange = 1; - } else { - fail 'retracted by the correct amount'; - } - } - if ($info->{extruding}) { - fail 'only extruding while not lifted' if $lifted; - if ($retracted[$tool]) { - my $expected_amount = $retracted_length[$tool] + $print->print->config->get_at('retract_restart_extra', $tool); - if ($changed_tool && $toolchange_count[$tool] > 1) { - $expected_amount = $print->print->config->get_at('retract_length_toolchange', $tool) + $print->print->config->get_at('retract_restart_extra_toolchange', $tool); - $changed_tool = 0; - } - fail 'unretracted by the correct amount' && exit - if !_eq($info->{dist_E}, $expected_amount); - $retracted[$tool] = 0; - $retracted_length[$tool] = 0; - } - } - if ($info->{travel} && $info->{dist_XY} >= $print->print->config->get_at('retract_before_travel', $tool)) { - fail 'retracted before long travel move' if !$retracted[$tool]; - } - }); - - 1; - }; - - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('first_layer_height', $config->layer_height); - $config->set('first_layer_speed', '100%'); - $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code - $config->set('retract_length', [1.5]); - $config->set('retract_before_travel', [3]); - $config->set('only_retract_when_crossing_perimeters', 0); - - my $retract_tests = sub { - my ($descr) = @_; - - ok $test->(), "retraction$descr"; - - my $conf = $config->clone; - $conf->set('retract_restart_extra', [1]); - ok $test->($conf), "restart extra length$descr"; - - $conf->set('retract_restart_extra', [-1]); - ok $test->($conf), "negative restart extra length$descr"; - - $conf->set('retract_lift', [1, 2]); - ok $test->($conf), "lift$descr"; - }; - - $retract_tests->(''); - - $duplicate = 2; - $retract_tests->(' (duplicate)'); - - $duplicate = 1; - $config->set('infill_extruder', 2); - $config->set('skirts', 4); - $config->set('skirt_height', 3); - $retract_tests->(' (dual extruder with multiple skirt layers)'); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection - $config->set('retract_length', [0]); - $config->set('retract_layer_change', [0]); - $config->set('retract_lift', [0.2]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - my $layer_changes_with_retraction = 0; - my $retractions = my $z_restores = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{retracting}) { - $retracted = 1; - $retractions++; - } elsif ($info->{extruding} && $retracted) { - $retracted = 0; - } - - if ($info->{dist_Z} && $retracted) { - $layer_changes_with_retraction++; - } - if ($info->{dist_Z} && $args->{Z} < $self->{Z}) { - $z_restores++; - } - }); - - is $layer_changes_with_retraction, 0, 'no retraction on layer change'; - is $retractions, 0, 'no retractions'; - is $z_restores, 0, 'no lift'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('use_firmware_retraction', 1); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - my $double_retractions = my $double_unretractions = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G10') { - $double_retractions++ if $retracted; - $retracted = 1; - } elsif ($cmd eq 'G11') { - $double_unretractions++ if !$retracted; - $retracted = 0; - } - }); - - is $double_retractions, 0, 'no double retractions'; - is $double_unretractions, 0, 'no double unretractions'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('use_firmware_retraction', 1); - $config->set('retract_length', [0]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G10') { - $retracted = 1; - } - }); - - ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('start_gcode', ''); - $config->set('retract_lift', [3, 4]); - - my @lifted_at = (); - my $test = sub { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - @lifted_at = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_Z} < 0) { - push @lifted_at, $info->{new_Z}; - } - }); - }; - - $config->set('retract_lift_above', [0, 0]); - $config->set('retract_lift_below', [0, 0]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below == 0'; - - $config->set('retract_lift_above', [5, 6]); - $config->set('retract_lift_below', [15, 13]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below != 0'; - ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at), - 'Z is not lifted below the configured value'; - ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at), - 'Z is not lifted above the configured value'; - - # check lifting with different values for 2. extruder - $config->set('perimeter_extruder', 2); - $config->set('infill_extruder', 2); - $config->set('retract_lift_above', [0, 0]); - $config->set('retract_lift_below', [0, 0]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below == 0 for 2. extruder'; - - $config->set('retract_lift_above', [5, 6]); - $config->set('retract_lift_below', [15, 13]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below != 0 for 2. extruder'; - ok !(any { $_ < $config->get_at('retract_lift_above', 1) } @lifted_at), - 'Z is not lifted below the configured value for 2. extruder'; - ok !(any { $_ > $config->get_at('retract_lift_below', 1) } @lifted_at), - 'Z is not lifted above the configured value for 2. extruder'; -} - -__END__ diff --git a/t/skirt_brim.t b/t/skirt_brim.t deleted file mode 100644 index beb3c94..0000000 --- a/t/skirt_brim.t +++ /dev/null @@ -1,146 +0,0 @@ -use Test::More tests => 6; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(unscale convex_hull); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('skirt_height', 2); - $config->set('perimeters', 0); - $config->set('support_material_speed', 99); - $config->set('cooling', [ 0 ]); # to prevent speeds to be altered - $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); - - my %layers_with_skirt = (); # Z => $count - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if (defined $self->Z) { - $layers_with_skirt{$self->Z} //= 0; - $layers_with_skirt{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->support_material_speed*60; - } - }); - fail "wrong number of layers with skirt" - unless (grep $_, values %layers_with_skirt) == $config->skirt_height; - }; - - ok $test->(), "skirt_height is honored when printing multiple objects too"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 0); - $config->set('top_solid_layers', 0); # to prevent solid shells and their speeds - $config->set('bottom_solid_layers', 0); # to prevent solid shells and their speeds - $config->set('brim_width', 5); - $config->set('support_material_speed', 99); - $config->set('cooling', [ 0 ]); # to prevent speeds to be altered - $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my %layers_with_brim = (); # Z => $count - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if (defined $self->Z) { - $layers_with_brim{$self->Z} //= 0; - $layers_with_brim{$self->Z} = 1 - if $info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) != $config->infill_speed*60; - } - }); - is scalar(grep $_, values %layers_with_brim), 1, "brim is generated"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('brim_width', 10); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt is smaller than brim width'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('skirt_height', 0); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt_height = 0 and skirts > 0'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - # Define 4 extruders. - $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', 0.4); - $config->set('skirts', 1); - $config->set('skirt_distance', 0); - $config->set('support_material_speed', 99); - $config->set('perimeter_extruder', 1); - $config->set('support_material_extruder', 2); - $config->set('cooling', [ 0 ]); # to prevent speeds to be altered - $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered - - my $print = Slic3r::Test::init_print('overhang', config => $config); - $print->process; - - # we enable support material after skirt has been generated - $config->set('support_material', 1); - $print->apply($print->print->model->clone, $config); - - my $skirt_length = 0; - my @extrusion_points = (); - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif (defined $self->Z && $self->Z == $config->first_layer_height) { - # we're on first layer - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $speed = ($args->{F} // $self->F) / 60; - if ($speed == $config->support_material_speed && $tool == $config->perimeter_extruder-1) { - # skirt uses support material speed but first object's extruder - $skirt_length += $info->{dist_XY}; - } else { - push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - } - }); - my $convex_hull = convex_hull(\@extrusion_points); - my $hull_perimeter = unscale($convex_hull->split_at_first_point->length); - ok $skirt_length > $hull_perimeter, 'skirt lenght is large enough to contain object with support'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('min_skirt_length', 20); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length'; -} - -__END__ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b69a0aa..ff4e0c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,8 +24,10 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) add_subdirectory(arrange) add_subdirectory(thumbnails) add_subdirectory(libslic3r) -add_subdirectory(slic3rutils) add_subdirectory(fff_print) add_subdirectory(sla_print) add_subdirectory(cpp17 EXCLUDE_FROM_ALL) # does not have to be built all the time +if (SLIC3R_GUI) + add_subdirectory(slic3rutils) +endif() # add_subdirectory(example) diff --git a/tests/arrange/test_arrange.cpp b/tests/arrange/test_arrange.cpp index 76d4e5f..196572a 100644 --- a/tests/arrange/test_arrange.cpp +++ b/tests/arrange/test_arrange.cpp @@ -207,7 +207,7 @@ static void check_nfp(const std::string & outfile_prefix, auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, scaled(EPSILON)); auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -scaled(EPSILON)); - auto bedpoly_offs = offset_ex(bedpoly, SCALED_EPSILON); + auto bedpoly_offs = offset_ex(bedpoly, static_cast(SCALED_EPSILON)); auto check_at_nfppos = [&](const Point &pos) { ExPolygons orb_ex = orb_ex_r; diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index f5a85e7..8c40ac0 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -13,14 +13,17 @@ add_executable(${_TEST_NAME}_tests test_flow.cpp test_gaps.cpp test_gcode.cpp + test_gcode_travels.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp + test_layers.cpp test_model.cpp test_multi.cpp test_perimeters.cpp test_print.cpp test_printgcode.cpp test_printobject.cpp + test_retraction.cpp test_shells.cpp test_skirt_brim.cpp test_support_material.cpp diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp index 1f560d5..14653a0 100644 --- a/tests/fff_print/test_cooling.cpp +++ b/tests/fff_print/test_cooling.cpp @@ -250,10 +250,10 @@ SCENARIO("Cooling integration tests", "[Cooling]") { if (l == 0) l = line.dist_Z(self); if (l > 0.) { - if (layer_times.empty()) - layer_times.emplace_back(0.); + if (!layer_times.empty()) { // Ignore anything before first z move. layer_times.back() += 60. * std::abs(l) / line.new_F(self); } + } if (line.has('F') && line.f() == external_perimeter_speed) ++ layer_external[scaled(self.z())]; } diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp index 0d10907..2dd97e9 100644 --- a/tests/fff_print/test_custom_gcode.cpp +++ b/tests/fff_print/test_custom_gcode.cpp @@ -45,13 +45,21 @@ SCENARIO("Custom G-code", "[CustomGCode]") }); GCodeReader parser; bool last_move_was_z_change = false; + bool first_z_move = true; // First z move is not a layer change. int num_layer_changes_not_applied = 0; parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), - [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { - if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) + if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) { ++ num_layer_changes_not_applied; - last_move_was_z_change = line.dist_Z(self) > 0; + } + if (line.dist_Z(self) > 0 && first_z_move) { + first_z_move = false; + } else if (line.dist_Z(self) > 0){ + last_move_was_z_change = true; + } else { + last_move_was_z_change = false; + } }); THEN("custom layer G-code is applied after Z move and before other moves") { REQUIRE(num_layer_changes_not_applied == 0); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 3d8e82a..00fdef7 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -236,7 +236,7 @@ static bool verbose_gcode() return s == "1" || s == "on" || s == "yes"; } -void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments) +void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count) { DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); config.apply(config_in); @@ -247,10 +247,18 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r for (const TriangleMesh &t : meshes) { ModelObject *object = model.add_object(); object->name += "object.stl"; - object->add_volume(std::move(t)); + object->add_volume(t); object->add_instance(); } - arrange_objects(model, arr2::to_arrange_bed(get_bed_shape(config)), arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config))); + double distance = min_object_distance(config); + arr2::ArrangeSettings arrange_settings{}; + arrange_settings.set_distance_from_objects(distance); + arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config))}; + if (duplicate_count > 1) { + duplicate(model, duplicate_count, bed, arrange_settings); + } + + arrange_objects(model, bed, arrange_settings); model.center_instances_around_point({100, 100}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); @@ -262,36 +270,36 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r print.set_status_silent(); } -void init_print(std::initializer_list test_meshes, Slic3r::Print &print, Slic3r::Model &model, const Slic3r::DynamicPrintConfig &config_in, bool comments) +void init_print(std::initializer_list test_meshes, Slic3r::Print &print, Slic3r::Model &model, const Slic3r::DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count) { std::vector triangle_meshes; triangle_meshes.reserve(test_meshes.size()); for (const TestMesh test_mesh : test_meshes) triangle_meshes.emplace_back(mesh(test_mesh)); - init_print(std::move(triangle_meshes), print, model, config_in, comments); + init_print(std::move(triangle_meshes), print, model, config_in, comments, duplicate_count); } -void init_print(std::initializer_list input_meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments) +void init_print(std::initializer_list input_meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count) { std::vector triangle_meshes; triangle_meshes.reserve(input_meshes.size()); for (const TriangleMesh &input_mesh : input_meshes) triangle_meshes.emplace_back(input_mesh); - init_print(std::move(triangle_meshes), print, model, config_in, comments); + init_print(std::move(triangle_meshes), print, model, config_in, comments, duplicate_count); } -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments) +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments, unsigned duplicate_count) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_deserialize_strict(config_items); - init_print(meshes, print, model, config, comments); + init_print(meshes, print, model, config, comments, duplicate_count); } -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments) +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments, unsigned duplicate_count) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_deserialize_strict(config_items); - init_print(meshes, print, model, config, comments); + init_print(meshes, print, model, config, comments, duplicate_count); } void init_and_process_print(std::initializer_list meshes, Slic3r::Print &print, const DynamicPrintConfig &config, bool comments) diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp index fd110ca..12ff0f5 100644 --- a/tests/fff_print/test_data.hpp +++ b/tests/fff_print/test_data.hpp @@ -63,11 +63,11 @@ template bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; } Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh); -void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false); -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false); -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false); -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list config_items, bool comments = false); -void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list config_items, bool comments = false); +void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false, unsigned duplicate_count = 1); +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate_count = 1); +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate = 1); +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list config_items, bool comments = false, unsigned duplicate = 1); +void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list config_items, bool comments = false, unsigned duplicate = 1); void init_and_process_print(std::initializer_list meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false); void init_and_process_print(std::initializer_list meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false); diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 6d8422c..94b6cab 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -6,7 +6,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Geometry.hpp" diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index 398314d..d71ffab 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -1,11 +1,18 @@ #include #include +#include +#include #include "libslic3r/GCode.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/ModelArrange.hpp" +#include "test_data.hpp" using namespace Slic3r; -using namespace Slic3r::GCode::Impl; +using namespace Test; + +constexpr bool debug_files = false; SCENARIO("Origin manipulation", "[GCode]") { Slic3r::GCodeGenerator gcodegen; @@ -22,219 +29,290 @@ SCENARIO("Origin manipulation", "[GCode]") { } } -struct ApproxEqualsPoints : public Catch::MatcherBase { - ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {} - bool match(const Points& points) const override { - if (points.size() != expected.size()) { - return false; +TEST_CASE("Wiping speeds", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "wipe", "1" }, + { "retract_layer_change", "0" }, + }); + bool have_wipe = false; + std::vector retract_speeds; + bool extruded_on_this_layer = false; + bool wiping_on_new_layer = false; + + GCodeReader parser; + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.travel() && line.dist_Z(self) != 0) { + extruded_on_this_layer = false; + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + extruded_on_this_layer = true; + } else if (line.retracting(self) && line.dist_XY(self) > 0) { + have_wipe = true; + wiping_on_new_layer = !extruded_on_this_layer; + const double f = line.has_f() ? line.f() : self.f(); + double move_time = line.dist_XY(self) / f; + retract_speeds.emplace_back(std::abs(line.dist_E(self)) / move_time); } - for (auto i = 0u; i < points.size(); ++i) { - const Point& point = points[i]; - const Point& expected_point = this->expected[i]; - if ( - std::abs(point.x() - expected_point.x()) > this->tolerance - || std::abs(point.y() - expected_point.y()) > this->tolerance - ) { - return false; + }); + CHECK(have_wipe); + double expected_retract_speed = config.option("retract_speed")->get_at(0) * 60; + for (const double retract_speed : retract_speeds) { + INFO("Wipe moves don\'t retract faster than configured speed"); + CHECK(retract_speed < expected_retract_speed); } + INFO("No wiping after layer change") + CHECK(!wiping_on_new_layer); } - return true; +bool has_moves_below_z_offset(const DynamicPrintConfig& config) { + GCodeReader parser; + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + + unsigned moves_below_z_offset{}; + double configured_offset = config.opt_float("z_offset"); + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.travel() && line.has_z() && line.z() < configured_offset) { + moves_below_z_offset++; } - std::string describe() const override { - std::stringstream ss; - ss << std::endl; - for (const Point& point : expected) { - ss << "(" << point.x() << ", " << point.y() << ")" << std::endl; - } - ss << "With tolerance: " << this->tolerance; - - return "Equals " + ss.str(); - } - -private: - Points expected; - unsigned tolerance; -}; - -Points get_points(const std::vector& result) { - Points result_points; - std::transform( - result.begin(), - result.end(), - std::back_inserter(result_points), - [](const DistancedPoint& point){ - return point.point; - } - ); - return result_points; -} - -std::vector get_distances(const std::vector& result) { - std::vector result_distances; - std::transform( - result.begin(), - result.end(), - std::back_inserter(result_distances), - [](const DistancedPoint& point){ - return point.distance_from_start; - } - ); - return result_distances; -} - -TEST_CASE("Place points at distances - expected use", "[GCode]") { - std::vector line{ - scaled(Vec2f{0, 0}), - scaled(Vec2f{1, 0}), - scaled(Vec2f{2, 1}), - scaled(Vec2f{2, 2}) - }; - std::vector distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0}; - std::vector result = slice_xy_path(line, distances); - - REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{ - scaled(Vec2f{0, 0}), - scaled(Vec2f{0.2, 0}), - scaled(Vec2f{0.5, 0}), - scaled(Vec2f{1, 0}), - scaled(Vec2f{1.5, 0.5}), - scaled(Vec2f{2, 1}), - scaled(Vec2f{2, 1.5}), - scaled(Vec2f{2, 2}) - }, 5)); - - REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector{ - distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2) - })); -} - -TEST_CASE("Place points at distances - edge case", "[GCode]") { - std::vector line{ - scaled(Vec2f{0, 0}), - scaled(Vec2f{1, 0}), - scaled(Vec2f{2, 0}) - }; - std::vector distances{0, 1, 1.5, 2}; - Points result{get_points(slice_xy_path(line, distances))}; - CHECK(result == Points{ - scaled(Vec2f{0, 0}), - scaled(Vec2f{1, 0}), - scaled(Vec2f{1.5, 0}), - scaled(Vec2f{2, 0}) }); -} - -TEST_CASE("Generate elevated travel", "[GCode]") { - std::vector xy_path{ - scaled(Vec2f{0, 0}), - scaled(Vec2f{1, 0}), - }; - std::vector ensure_points_at_distances{0.2, 0.5}; - Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})}; - - CHECK(result == Points3{ - scaled(Vec3f{0, 0, 3.0}), - scaled(Vec3f{0.2, 0, 3.2}), - scaled(Vec3f{0.5, 0, 3.5}), - scaled(Vec3f{1, 0, 4.0}) - }); -} - -TEST_CASE("Get first crossed line distance", "[GCode]") { - // A 2x2 square at 0, 0, with 1x1 square hole in its center. - ExPolygon square_with_hole{ - { - scaled(Vec2f{-1, -1}), - scaled(Vec2f{1, -1}), - scaled(Vec2f{1, 1}), - scaled(Vec2f{-1, 1}) - }, - { - scaled(Vec2f{-0.5, -0.5}), - scaled(Vec2f{0.5, -0.5}), - scaled(Vec2f{0.5, 0.5}), - scaled(Vec2f{-0.5, 0.5}) + return moves_below_z_offset > 0; } - }; - // A 2x2 square above the previous square at (0, 3). - ExPolygon square_above{ - { - scaled(Vec2f{-1, 2}), - scaled(Vec2f{1, 2}), - scaled(Vec2f{1, 4}), - scaled(Vec2f{-1, 4}) - } - }; - - // Bottom-up travel intersecting the squares. - Lines travel{Polyline{ - scaled(Vec2f{0, -2}), - scaled(Vec2f{0, -0.7}), - scaled(Vec2f{0, 0}), - scaled(Vec2f{0, 1}), - scaled(Vec2f{0, 1.3}), - scaled(Vec2f{0, 2.4}), - scaled(Vec2f{0, 4.5}), - scaled(Vec2f{0, 5}), - }.lines()}; - - // Try different cases by skipping lines in the travel. - AABBTreeLines::LinesDistancer distancer = get_expolygons_distancer({square_with_hole, square_above}); - CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1)); - CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2)); - CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5)); - CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case - CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7)); - CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6)); - CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer)); -} - -TEST_CASE("Generate regular polygon", "[GCode]") { - const unsigned points_count{32}; - const Point centroid{scaled(Vec2d{5, -2})}; - const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)}; - const Point oposite_point{centroid * 2}; - - REQUIRE(result.size() == 32); - CHECK(result[16].x() == Approx(oposite_point.x())); - CHECK(result[16].y() == Approx(oposite_point.y())); - - std::vector angles; - angles.reserve(points_count); - for (unsigned index = 0; index < points_count; index++) { - const unsigned previous_index{index == 0 ? points_count - 1 : index - 1}; - const unsigned next_index{index == points_count - 1 ? 0 : index + 1}; - - const Point previous_point = result.points[previous_index]; - const Point current_point = result.points[index]; - const Point next_point = result.points[next_index]; - - angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point})); - } - - std::vector expected; - angles.reserve(points_count); - std::generate_n(std::back_inserter(expected), points_count, [&](){ - return angles.front(); +TEST_CASE("Z moves with offset", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "z_offset", 5 }, + { "start_gcode", "" }, }); - CHECK_THAT(angles, Catch::Matchers::Approx(expected)); + INFO("No lift"); + CHECK(!has_moves_below_z_offset(config)); + + config.set_deserialize_strict({{ "retract_lift", "3" }}); + INFO("Lift < z offset"); + CHECK(!has_moves_below_z_offset(config)); + + config.set_deserialize_strict({{ "retract_lift", "6" }}); + INFO("Lift > z offset"); + CHECK(!has_moves_below_z_offset(config)); } -TEST_CASE("Square bed with padding", "[GCode]") { - const Bed bed{ - { - Vec2d{0, 0}, - Vec2d{100, 0}, - Vec2d{100, 100}, - Vec2d{0, 100} - }, - 10.0 - }; +std::optional parse_axis(const std::string& line, const std::string& axis) { + std::smatch matches; + if (std::regex_search(line, matches, std::regex{axis + "(\\d+)"})) { + std::string matchedValue = matches[1].str(); + return std::stod(matchedValue); + } + return std::nullopt; + } - CHECK(bed.centroid.x() == 50); - CHECK(bed.centroid.y() == 50); - CHECK(bed.contains_within_padding(Vec2d{10, 10})); - CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10})); +/** +* This tests the following behavior: +* - complete objects does not crash +* - no hard-coded "E" are generated +* - Z moves are correctly generated for both objects +* - no travel moves go outside skirt +* - temperatures are set correctly +*/ +TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "gcode_comments", 1 }, + { "complete_objects", 1 }, + { "extrusion_axis", 'A' }, + { "start_gcode", "" }, // prevent any default extra Z move + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "temperature", "200" }, + { "first_layer_temperature", "210" }, + { "retract_length", "0" } + }); -} \ No newline at end of file + std::vector z_moves; + Points travel_moves; + Points extrusions; + std::vector temps; + + GCodeReader parser; + + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2); + std::string gcode = Test::gcode(print); + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + INFO("Unexpected E argument"); + CHECK(!line.has_e()); + + if (line.has_z()) { + z_moves.emplace_back(line.z()); + } + if (line.has_x() || line.has_y()) { + if (line.extruding(self) || line.has_unknown_axis()) { + extrusions.emplace_back(scaled(line.x()), scaled(line.y())); + } else if (!extrusions.empty()){ // skip initial travel move to first skirt point + travel_moves.emplace_back(scaled(line.x()), scaled(line.y())); + } + } else if (line.cmd_is("M104") || line.cmd_is("M109")) { + const std::optional parsed_temperature = parse_axis(line.raw(), "S"); + if (!parsed_temperature) { + FAIL("Failed to parse temperature!"); + } + if (temps.empty() || temps.back() != parsed_temperature) { + temps.emplace_back(*parsed_temperature); + } +} + }); + + const unsigned layer_count = 20 / 0.4; + INFO("Complete_objects generates the correct number of Z moves."); + CHECK(z_moves.size() == layer_count * 2); + auto first_moves = tcb::span{z_moves}.subspan(0, layer_count); + auto second_moves = tcb::span{z_moves}.subspan(layer_count); + + CHECK( std::vector(first_moves.begin(), first_moves.end()) == std::vector(second_moves.begin(), second_moves.end())); + const Polygon convex_hull{Geometry::convex_hull(extrusions)}; + INFO("All travel moves happen within skirt."); + for (const Point& travel_move : travel_moves) { + CHECK(convex_hull.contains(travel_move)); + } + INFO("Expected temperature changes"); + CHECK(temps == std::vector{210, 200, 210, 200, 0}); +} + + +TEST_CASE("Used filament", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "retract_length", "1000000" }, + { "use_relative_e_distances", 1 }, + { "layer_gcode", "G92 E0\n" }, + }); + GCodeReader parser; + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20}, print, model, config); + Test::gcode(print); + + INFO("Final retraction is not considered in total used filament"); + CHECK(print.print_statistics().total_used_filament > 0); +} + +void check_m73s(Print& print){ + std::vector percent{}; + bool got_100 = false; + bool extruding_after_100 = 0; + + GCodeReader parser; + std::string gcode = Slic3r::Test::gcode(print); + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + + if (line.cmd_is("M73")) { + std::optional p = parse_axis(line.raw(), "P"); + if (!p) { + FAIL("Failed to parse percent"); + } + percent.emplace_back(*p); + got_100 = p == Approx(100); + } + if (line.extruding(self) && got_100) { + extruding_after_100 = true; + } + }); + INFO("M73 is never given more than 100%"); + for (const double value : percent) { + CHECK(value <= 100); + } + INFO("No extrusions after M73 P100."); + CHECK(!extruding_after_100); +} + + +TEST_CASE("M73s have correct percent values", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + + SECTION("Single object") { + config.set_deserialize_strict({ + {" gcode_flavor", "sailfish" }, + {" raft_layers", 3 }, + }); + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20}, print, model, config); + check_m73s(print); +} + + SECTION("Two copies of single object") { + config.set_deserialize_strict({ + {" gcode_flavor", "sailfish" }, + }); + Print print; + Model model; + + Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2); + check_m73s(print); + + if constexpr (debug_files) { + std::ofstream gcode_file{"M73_2_copies.gcode"}; + gcode_file << Test::gcode(print); + } + } + + SECTION("Two objects") { + config.set_deserialize_strict({ + {" gcode_flavor", "sailfish" }, + }); + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, print, model, config); + check_m73s(print); + } + + SECTION("One layer object") { + config.set_deserialize_strict({ + {" gcode_flavor", "sailfish" }, + }); + Print print; + Model model; + TriangleMesh test_mesh{mesh(TestMesh::cube_20x20x20)}; + const auto layer_height = static_cast(config.opt_float("layer_height")); + test_mesh.scale(Vec3f{1.0F, 1.0F, layer_height/20.0F}); + Test::init_print({test_mesh}, print, model, config); + check_m73s(print); + + if constexpr (debug_files) { + std::ofstream gcode_file{"M73_one_layer.gcode"}; + gcode_file << Test::gcode(print); + } + } +} + + +TEST_CASE("M201 for acceleation reset", "[GCode]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "gcode_flavor", "repetier" }, + { "default_acceleration", 1337 }, + }); + + GCodeReader parser; + std::string gcode = Slic3r::Test::slice({TestMesh::cube_with_hole}, config); + + bool has_accel = false; + bool has_m204 = false; + + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("M201") && line.has_x() && line.has_y()) { + if (line.x() == 1337 && line.y() == 1337) { + has_accel = true; + } + + } + if (line.cmd_is("M204") && line.raw().find('S') != std::string::npos) { + has_m204 = true; + } + }); + + INFO("M201 is generated for repetier firmware."); + CHECK(has_accel); + INFO("M204 is not generated for repetier firmware"); + CHECK(!has_m204); +} diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp new file mode 100644 index 0000000..895c415 --- /dev/null +++ b/tests/fff_print/test_gcode_travels.cpp @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include + +using namespace Slic3r; +using namespace Slic3r::GCode::Impl::Travels; + +struct ApproxEqualsPoints : public Catch::MatcherBase { + ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {} + bool match(const Points& points) const override { + if (points.size() != expected.size()) { + return false; + } + for (auto i = 0u; i < points.size(); ++i) { + const Point& point = points[i]; + const Point& expected_point = this->expected[i]; + if ( + std::abs(point.x() - expected_point.x()) > int(this->tolerance) + || std::abs(point.y() - expected_point.y()) > int(this->tolerance) + ) { + return false; + } + } + return true; + } + std::string describe() const override { + std::stringstream ss; + ss << std::endl; + for (const Point& point : expected) { + ss << "(" << point.x() << ", " << point.y() << ")" << std::endl; + } + ss << "With tolerance: " << this->tolerance; + + return "Equals " + ss.str(); + } + +private: + Points expected; + unsigned tolerance; +}; + +Points get_points(const std::vector& result) { + Points result_points; + std::transform( + result.begin(), + result.end(), + std::back_inserter(result_points), + [](const DistancedPoint& point){ + return point.point; + } + ); + return result_points; +} + +std::vector get_distances(const std::vector& result) { + std::vector result_distances; + std::transform( + result.begin(), + result.end(), + std::back_inserter(result_distances), + [](const DistancedPoint& point){ + return point.distance_from_start; + } + ); + return result_distances; +} + +TEST_CASE("Place points at distances - expected use", "[GCode]") { + std::vector line{ + scaled(Vec2f{0, 0}), + scaled(Vec2f{1, 0}), + scaled(Vec2f{2, 1}), + scaled(Vec2f{2, 2}) + }; + std::vector distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0}; + std::vector result = slice_xy_path(line, distances); + + REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{ + scaled(Vec2f{0, 0}), + scaled(Vec2f{0.2, 0}), + scaled(Vec2f{0.5, 0}), + scaled(Vec2f{1, 0}), + scaled(Vec2f{1.5, 0.5}), + scaled(Vec2f{2, 1}), + scaled(Vec2f{2, 1.5}), + scaled(Vec2f{2, 2}) + }, 5)); + + REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector{ + distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2) + })); +} + +TEST_CASE("Place points at distances - edge case", "[GCode]") { + std::vector line{ + scaled(Vec2f{0, 0}), + scaled(Vec2f{1, 0}), + scaled(Vec2f{2, 0}) + }; + std::vector distances{0, 1, 1.5, 2}; + Points result{get_points(slice_xy_path(line, distances))}; + CHECK(result == Points{ + scaled(Vec2f{0, 0}), + scaled(Vec2f{1, 0}), + scaled(Vec2f{1.5, 0}), + scaled(Vec2f{2, 0}) + }); +} + +TEST_CASE("Generate elevated travel", "[GCode]") { + std::vector xy_path{ + scaled(Vec2f{0, 0}), + scaled(Vec2f{1, 0}), + }; + std::vector ensure_points_at_distances{0.2, 0.5}; + Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})}; + + CHECK(result == Points3{ + scaled(Vec3f{ 0.f, 0.f, 3.f}), + scaled(Vec3f{0.2f, 0.f, 3.2f}), + scaled(Vec3f{0.5f, 0.f, 3.5f}), + scaled(Vec3f{ 1.f, 0.f, 4.f}) + }); +} + +TEST_CASE("Get first crossed line distance", "[GCode]") { + // A 2x2 square at 0, 0, with 1x1 square hole in its center. + ExPolygon square_with_hole{ + { + scaled(Vec2f{-1, -1}), + scaled(Vec2f{1, -1}), + scaled(Vec2f{1, 1}), + scaled(Vec2f{-1, 1}) + }, + { + scaled(Vec2f{-0.5, -0.5}), + scaled(Vec2f{0.5, -0.5}), + scaled(Vec2f{0.5, 0.5}), + scaled(Vec2f{-0.5, 0.5}) + } + }; + // A 2x2 square above the previous square at (0, 3). + ExPolygon square_above{ + { + scaled(Vec2f{-1, 2}), + scaled(Vec2f{1, 2}), + scaled(Vec2f{1, 4}), + scaled(Vec2f{-1, 4}) + } + }; + + // Bottom-up travel intersecting the squares. + Lines travel{Polyline{ + scaled(Vec2f{0, -2}), + scaled(Vec2f{0, -0.7}), + scaled(Vec2f{0, 0}), + scaled(Vec2f{0, 1}), + scaled(Vec2f{0, 1.3}), + scaled(Vec2f{0, 2.4}), + scaled(Vec2f{0, 4.5}), + scaled(Vec2f{0, 5}), + }.lines()}; + + std::vector lines; + for (const ExPolygon& polygon : {square_with_hole, square_above}) { + for (const Line& line : polygon.lines()) { + lines.emplace_back(unscale(line.a), unscale(line.b)); + } + } + // Try different cases by skipping lines in the travel. + AABBTreeLines::LinesDistancer distancer{std::move(lines)}; + + CHECK(get_first_crossed_line_distance(travel, distancer) == Approx(1)); + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2)); + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5)); + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7)); + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6)); + CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer) == std::numeric_limits::max()); +} + + +TEST_CASE("Elevated travel formula", "[GCode]") { + const double lift_height{10}; + const double slope_end{10}; + const double blend_width{10}; + const ElevatedTravelParams params{lift_height, slope_end, blend_width}; + + ElevatedTravelFormula f{params}; + + const double distance = slope_end - blend_width / 2; + const double slope = (f(distance) - f(0)) / distance; + // At the begining it has given slope. + CHECK(slope == lift_height / slope_end); + // At the end it is flat. + CHECK(f(slope_end + blend_width / 2) == f(slope_end + blend_width)); + // Should be smoothed. + CHECK(f(slope_end) < lift_height); +} diff --git a/tests/fff_print/test_layers.cpp b/tests/fff_print/test_layers.cpp new file mode 100644 index 0000000..87accd3 --- /dev/null +++ b/tests/fff_print/test_layers.cpp @@ -0,0 +1,104 @@ +/** +* Ported from t/layers.t +*/ + +#include +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Slic3r::Test; + +void check_layers(const DynamicPrintConfig& config) { + GCodeReader parser; + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + + std::vector z; + std::vector increments; + + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.has_z()) { + z.emplace_back(line.z()); + increments.emplace_back(line.dist_Z(self)); + } + }); + + const double first_layer_height = config.opt_float("first_layer_height"); + const double z_offset = config.opt_float("z_offset"); + const double layer_height = config.opt_float("layer_height"); + INFO("Correct first layer height."); + CHECK(z.at(0) == Approx(first_layer_height + z_offset)); + INFO("Correct second layer height") + CHECK(z.at(1) == Approx(first_layer_height + layer_height + z_offset)); + + INFO("Correct layer height") + for (const double increment : tcb::span{increments}.subspan(1)) { + CHECK(increment == Approx(layer_height)); + } +} + +TEST_CASE("Layer heights are correct", "[Layers]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "start_gcode", "" }, + { "layer_height", 0.3 }, + { "first_layer_height", 0.2 }, + { "retract_length", "0" } + }); + + SECTION("Absolute first layer height") { + check_layers(config); + } + + SECTION("Relative layer height") { + const double layer_height = config.opt_float("layer_height"); + config.set_deserialize_strict({ + { "first_layer_height", 0.6 * layer_height }, + }); + + check_layers(config); + } + + SECTION("Positive z offset") { + config.set_deserialize_strict({ + { "z_offset", 0.9 }, + }); + + check_layers(config); + } + + SECTION("Negative z offset") { + config.set_deserialize_strict({ + { "z_offset", -0.8 }, + }); + + check_layers(config); + } +} + +TEST_CASE("GCode has reasonable height", "[Layers]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "fill_density", 0 }, + { "gcode_binary", 0 }, + }); + + Print print; + Model model; + TriangleMesh test_mesh{mesh(TestMesh::cube_20x20x20)}; + test_mesh.scale(2); + Test::init_print({test_mesh}, print, model, config); + const std::string gcode{Test::gcode(print)}; + + std::vector z; + + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.dist_Z(self) != Approx(0)) { + z.emplace_back(line.z()); + } + }); + + REQUIRE(!z.empty()); + INFO("Last Z is: " + std::to_string(z.back())); + CHECK((z.back() > 20*1.8 && z.back() < 20*2.2)); +} diff --git a/tests/fff_print/test_retraction.cpp b/tests/fff_print/test_retraction.cpp new file mode 100644 index 0000000..b8cf441 --- /dev/null +++ b/tests/fff_print/test_retraction.cpp @@ -0,0 +1,359 @@ +/** + * Ported from t/retraction.t + */ + +#include + +#include +#include + +#include "test_data.hpp" +#include +#include + +using namespace Slic3r; +using namespace Test; + +constexpr bool debug_files {false}; + +void check_gcode(std::initializer_list meshes, const DynamicPrintConfig& config, const unsigned duplicate) { + constexpr std::size_t tools_count = 4; + std::size_t tool = 0; + std::array toolchange_count{0}; // Track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time + std::array retracted{false}; + std::array retracted_length{0}; + bool lifted = false; + double lift_dist = 0; // Track lifted distance for toolchanges and extruders with different retract_lift values + bool changed_tool = false; + bool wait_for_toolchange = false; + + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, duplicate); + std::string gcode = Test::gcode(print); + + if constexpr(debug_files) { + static int count{0}; + std::ofstream file{"check_gcode_" + std::to_string(count++) + ".gcode"}; + file << gcode; + } + + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + std::regex regex{"^T(\\d+)"}; + std::smatch matches; + std::string cmd{line.cmd()}; + if (std::regex_match(cmd, matches, regex)) { + tool = std::stoul(matches[1].str()); + changed_tool = true; + wait_for_toolchange = false; + toolchange_count[tool]++; + } else if (std::regex_match(cmd, std::regex{"^G[01]$"}) && !line.has(Z)) { // ignore lift taking place after retraction + INFO("Toolchange must not happen right after retraction."); + CHECK(!wait_for_toolchange); + } + + const double retract_length = config.option("retract_length")->get_at(tool); + const double retract_before_travel = config.option("retract_before_travel")->get_at(tool); + const double retract_length_toolchange = config.option("retract_length_toolchange")->get_at(tool); + const double retract_restart_extra = config.option("retract_restart_extra")->get_at(tool); + const double retract_restart_extra_toolchange = config.option("retract_restart_extra_toolchange")->get_at(tool); + + if (line.dist_Z(self) != 0) { + // lift move or lift + change layer + const double retract_lift = config.option("retract_lift")->get_at(tool); + if ( + line.dist_Z(self) == Approx(retract_lift) + || ( + line.dist_Z(self) == Approx(config.opt_float("layer_height") + retract_lift) + && retract_lift > 0 + ) + ) { + INFO("Only lift while retracted"); + CHECK(retracted[tool]); + INFO("No double lift"); + CHECK(!lifted); + lifted = true; + lift_dist = line.dist_Z(self); + } + if (line.dist_Z(self) < 0) { + INFO("Must be lifted before going down.") + CHECK(lifted); + INFO("Going down by the same amount of the lift or by the amount needed to get to next layer"); + CHECK(( + line.dist_Z(self) == Approx(-lift_dist) + || line.dist_Z(self) == Approx(-lift_dist + config.opt_float("layer_height")) + )); + lift_dist = 0; + lifted = false; + } + const double feedrate = line.has_f() ? line.f() : self.f(); + INFO("move Z at travel speed"); + CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60)); + } + if (line.retracting(self)) { + retracted[tool] = true; + retracted_length[tool] += -line.dist_E(self); + if (retracted_length[tool] == Approx(retract_length)) { + // okay + } else if (retracted_length[tool] == Approx(retract_length_toolchange)) { + wait_for_toolchange = true; + } else { + INFO("Not retracted by the correct amount."); + CHECK(false); + } + } + if (line.extruding(self)) { + INFO("Only extruding while not lifted"); + CHECK(!lifted); + if (retracted[tool]) { + double expected_amount = retracted_length[tool] + retract_restart_extra; + if (changed_tool && toolchange_count[tool] > 1) { + expected_amount = retract_length_toolchange + retract_restart_extra_toolchange; + changed_tool = false; + } + INFO("Unretracted by the correct amount"); + REQUIRE(line.dist_E(self) == Approx(expected_amount)); + retracted[tool] = false; + retracted_length[tool] = 0; + } + } + if (line.travel() && line.dist_XY(self) >= retract_before_travel) { + INFO("Retracted before long travel move"); + CHECK(retracted[tool]); + } + }); +} + +void test_slicing(std::initializer_list meshes, DynamicPrintConfig& config, const unsigned duplicate = 1) { + SECTION("Retraction") { + check_gcode(meshes, config, duplicate); + } + + SECTION("Restart extra length") { + config.set_deserialize_strict({{ "retract_restart_extra", "1" }}); + check_gcode(meshes, config, duplicate); + } + + SECTION("Negative restart extra length") { + config.set_deserialize_strict({{ "retract_restart_extra", "-1" }}); + check_gcode(meshes, config, duplicate); + } + + SECTION("Retract_lift") { + config.set_deserialize_strict({{ "retract_lift", "1,2" }}); + check_gcode(meshes, config, duplicate); + } + +} + +TEST_CASE("Slicing with retraction and lifing", "[retraction]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.6,0.6,0.6,0.6" }, + { "first_layer_height", config.opt_float("layer_height") }, + { "first_layer_speed", "100%" }, + { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code + { "retract_length", "1.5" }, + { "retract_before_travel", "3" }, + { "retract_layer_change", "1" }, + { "only_retract_when_crossing_perimeters", 0 }, + }); + + SECTION("Standard run") { + test_slicing({TestMesh::cube_20x20x20}, config); + } + SECTION("With duplicate cube") { + test_slicing({TestMesh::cube_20x20x20}, config, 2); + } + SECTION("Dual extruder with multiple skirt layers") { + config.set_deserialize_strict({ + {"infill_extruder", 2}, + {"skirts", 4}, + {"skirt_height", 3}, + }); + test_slicing({TestMesh::cube_20x20x20}, config); + } +} + +TEST_CASE("Z moves", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code + { "retract_length", "0" }, + { "retract_layer_change", "0" }, + { "retract_lift", "0.2" } + }); + + bool retracted = false; + unsigned layer_changes_with_retraction = 0; + unsigned retractions = 0; + unsigned z_restores = 0; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + + if constexpr(debug_files) { + std::ofstream file{"zmoves.gcode"}; + file << gcode; + } + + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.retracting(self)) { + retracted = true; + retractions++; + } else if (line.extruding(self) && retracted) { + retracted = 0; + } + + if (line.dist_Z(self) != 0 && retracted) { + layer_changes_with_retraction++; + } + + if (line.dist_Z(self) < 0) { + z_restores++; + } + }); + + INFO("no retraction on layer change"); + CHECK(layer_changes_with_retraction == 0); + INFO("no retractions"); + CHECK(retractions == 0); + INFO("no lift"); + CHECK(z_restores == 0); +} + +TEST_CASE("Firmware retraction handling", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "use_firmware_retraction", 1 }, + }); + + bool retracted = false; + unsigned double_retractions = 0; + unsigned double_unretractions = 0; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G10")) { + if (retracted) + double_retractions++; + retracted = true; + } else if (line.cmd_is("G11")) { + if (!retracted) + double_unretractions++; + retracted = 0; + } + }); + INFO("No double retractions"); + CHECK(double_retractions == 0); + INFO("No double unretractions"); + CHECK(double_unretractions == 0); +} + +TEST_CASE("Firmware retraction when length is 0", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "use_firmware_retraction", 1 }, + { "retract_length", "0" }, + }); + + bool retracted = false; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G10")) { + retracted = true; + } + }); + INFO("Retracting also when --retract-length is 0 but --use-firmware-retraction is enabled"); + CHECK(retracted); +} + +std::vector get_lift_layers(const DynamicPrintConfig& config) { + Print print; + Model model; + Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2); + std::string gcode = Test::gcode(print); + + std::vector result; + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1") && line.dist_Z(self) < 0) { + result.push_back(line.new_Z(self)); + } + }); + return result; +} + +bool values_are_in_range(const std::vector& values, double from, double to) { + for (const double& value : values) { + if (value < from || value > to) { + return false; + } + } + return true; +} + +TEST_CASE("Lift above/bellow layers", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.6,0.6,0.6,0.6" }, + { "start_gcode", "" }, + { "retract_lift", "3,4" }, + }); + + config.set_deserialize_strict({ + { "retract_lift_above", "0, 0" }, + { "retract_lift_below", "0, 0" }, + }); + std::vector lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below == 0"); + CHECK(!lift_layers.empty()); + + config.set_deserialize_strict({ + { "retract_lift_above", "5, 6" }, + { "retract_lift_below", "15, 13" }, + }); + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below != 0"); + CHECK(!lift_layers.empty()); + + double retract_lift_above = config.option("retract_lift_above")->get_at(0); + double retract_lift_below = config.option("retract_lift_below")->get_at(0); + + INFO("Z is not lifted above/below the configured value"); + CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below)); + + // check lifting with different values for 2. extruder + config.set_deserialize_strict({ + {"perimeter_extruder", 2}, + {"infill_extruder", 2}, + {"retract_lift_above", "0, 0"}, + {"retract_lift_below", "0, 0"} + }); + + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below == 0 for 2. extruder"); + CHECK(!lift_layers.empty()); + + config.set_deserialize_strict({ + { "retract_lift_above", "5, 6" }, + { "retract_lift_below", "15, 13" }, + }); + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below != 0 for 2. extruder"); + CHECK(!lift_layers.empty()); + + retract_lift_above = config.option("retract_lift_above")->get_at(1); + retract_lift_below = config.option("retract_lift_below")->get_at(1); + + INFO("Z is not lifted above/below the configured value for 2. extruder"); + CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below)); +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index bad0394..4b76f5a 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -2,6 +2,8 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp + test_line.cpp + test_point.cpp test_3mf.cpp test_aabbindirect.cpp test_kdtreeindirect.cpp diff --git a/tests/libslic3r/test_arachne.cpp b/tests/libslic3r/test_arachne.cpp index 220fd87..2212cd8 100644 --- a/tests/libslic3r/test_arachne.cpp +++ b/tests/libslic3r/test_arachne.cpp @@ -478,7 +478,7 @@ TEST_CASE("Arachne - #8849 - Missing part of model", "[ArachneMissingPart8849]") export_perimeters_to_svg(debug_out_path("arachne-missing-part-8849.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour())); #endif - int64_t total_extrusion_length = 0; + [[maybe_unused]] int64_t total_extrusion_length = 0; for (Arachne::VariableWidthLines &perimeter : perimeters) for (Arachne::ExtrusionLine &extrusion_line : perimeter) total_extrusion_length += extrusion_line.getLength(); diff --git a/tests/libslic3r/test_arc_welder.cpp b/tests/libslic3r/test_arc_welder.cpp index 44b8865..f0ab8fb 100644 --- a/tests/libslic3r/test_arc_welder.cpp +++ b/tests/libslic3r/test_arc_welder.cpp @@ -280,7 +280,6 @@ TEST_CASE("least squares arc fitting, interpolating end points", "[ArcWelder]") REQUIRE(new_center_opt); if (new_center_opt) { Vec2d new_center = *new_center_opt; - double new_radius = (new_center - start_pos).norm(); double total_deviation = 0; double new_total_deviation = 0; for (const Vec2d &s : samples) { diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 50e61b3..66ed7c1 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -11,6 +11,242 @@ using namespace Slic3r; +TEST_CASE("Dynamic config serialization - tests ConfigBase", "[Config]"){ + DynamicPrintConfig config; + INFO("Serialize float"); + config.set_key_value("layer_height", new ConfigOptionFloat(0.3)); + CHECK(config.opt_serialize("layer_height") == "0.3"); + + INFO("Serialize int"); + config.set_key_value("perimeters", new ConfigOptionInt(2)); + CHECK(config.opt_serialize("perimeters") == "2"); + + INFO("Serialize float or percent"); + config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(30, true)); + CHECK(config.opt_serialize("first_layer_height") == "30%"); + + INFO("Serialize bool"); + config.set_key_value("use_relative_e_distances", new ConfigOptionBool(true)); + CHECK(config.opt_serialize("use_relative_e_distances") == "1"); + + INFO("Serialize enum"); + config.set_key_value("gcode_flavor", new ConfigOptionEnum(gcfTeacup)); + CHECK(config.opt_serialize("gcode_flavor") == "teacup"); + + INFO("Serialize string"); + config.set_key_value("extrusion_axis", new ConfigOptionString("A")); + CHECK(config.opt_serialize("extrusion_axis") == "A"); + + INFO("Serialize string with newline"); + config.set_key_value("notes", new ConfigOptionString("foo\nbar")); + CHECK(config.opt_serialize("notes") == "foo\\nbar"); + config.set_deserialize_strict("notes", "bar\\nbaz"); + INFO("Deserialize string with newline"); + CHECK(config.opt_string("notes") == "bar\nbaz"); + + INFO("Serialize points"); + config.set_key_value("extruder_offset", new ConfigOptionPoints({{10, 20}, {30, 45}})); + CHECK(config.opt_serialize("extruder_offset") == "10x20,30x45"); + INFO("Deserialize points"); + config.set_deserialize_strict("extruder_offset", "20x10"); + CHECK(config.option("extruder_offset")->values == std::vector{Vec2d{20, 10}}); + + INFO("Serialize floats"); + config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.2, 3})); + CHECK(config.opt_serialize("nozzle_diameter") == "0.2,3"); + INFO("Deserialize floats"); + config.set_deserialize_strict("nozzle_diameter", "0.1,0.4"); + CHECK_THAT(config.option("nozzle_diameter")->values, Catch::Matchers::Approx(std::vector{0.1, 0.4})); + INFO("Deserialize floats from one value"); + config.set_deserialize_strict("nozzle_diameter", "3"); + CHECK_THAT(config.option("nozzle_diameter")->values, Catch::Matchers::Approx(std::vector{3.0})); + + INFO("Serialize ints"); + config.set_key_value("temperature", new ConfigOptionInts({180, 210})); + CHECK(config.opt_serialize("temperature") == "180,210"); + INFO("Deserialize ints"); + config.set_deserialize_strict("temperature", "195,220"); + CHECK(config.option("temperature")->values == std::vector{195,220}); + + INFO("Serialize bools"); + config.set_key_value("wipe", new ConfigOptionBools({true, false})); + CHECK(config.opt_serialize("wipe") == "1,0"); + INFO("Deserialize bools"); + config.set_deserialize_strict("wipe", "0,1,1"); + CHECK(config.option("wipe")->values == std::vector{false, true, true}); + + INFO("Deserialize bools from empty stirng"); + config.set_deserialize_strict("wipe", ""); + CHECK(config.option("wipe")->values == std::vector{}); + + INFO("Deserialize bools from value"); + config.set_deserialize_strict({{"wipe", 1}}); + CHECK(config.option("wipe")->values == std::vector{true}); + + INFO("Serialize strings"); + config.set_key_value("post_process", new ConfigOptionStrings({"foo", "bar"})); + CHECK(config.opt_serialize("post_process") == "foo;bar"); + INFO("Deserialize strings"); + config.set_deserialize_strict("post_process", "bar;baz"); + CHECK(config.option("post_process")->values == std::vector{"bar", "baz"}); +} + +TEST_CASE("Get keys", "[Config]"){ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + CHECK(!config.keys().empty()); +} + +TEST_CASE("Set not already set option", "[Config]") { + DynamicPrintConfig config; + config.set_deserialize_strict("filament_diameter", "3"); +} + +TEST_CASE("Config apply dynamic to static", "[Config]") { + DynamicPrintConfig config; + config.set_deserialize_strict("perimeters", "2"); + + // This trick is taken directly from perl. + StaticPrintConfig* config2 = static_cast(new FullPrintConfig()); + config2->apply(config, true); + + CHECK(config2->opt_int("perimeters") == 2); + delete config2; +} + +TEST_CASE("Config apply static to dynamic", "[Config]") { + // This trick is taken directly from perl. + StaticPrintConfig* config = static_cast(new FullPrintConfig()); + + DynamicPrintConfig config2; + config2.apply(*config, true); + delete config; + + CHECK( + config2.opt_int("perimeters") == + DynamicPrintConfig::full_print_config().opt_int("perimeters") + ); + +} + +TEST_CASE("Config apply dynamic to dynamic", "[Config]") { + + DynamicPrintConfig config; + config.set_key_value("extruder_offset", new ConfigOptionPoints({{0, 0}, {20, 0}, {0, 20}})); + DynamicPrintConfig config2; + config2.apply(config, true); + + CHECK( + config2.option("extruder_offset")->values == + std::vector{{0, 0}, {20, 0}, {0, 20}} + ); +} + +TEST_CASE("Get abs value on percent", "[Config]") { + StaticPrintConfig* config = static_cast(new FullPrintConfig()); + + config->set_deserialize_strict("solid_infill_speed", "60"); + config->set_deserialize_strict("top_solid_infill_speed", "10%"); + CHECK(config->get_abs_value("top_solid_infill_speed") == 6); + delete config; +} + +TEST_CASE("No interference between DynamicConfig objects", "[Config]") { + DynamicPrintConfig config; + config.set_key_value("fill_pattern", new ConfigOptionString("line")); + DynamicPrintConfig config2; + config2.set_key_value("fill_pattern", new ConfigOptionString("hilbertcurve")); + CHECK(config.opt_string("fill_pattern") == "line"); +} + +TEST_CASE("Normalize fdm extruder", "[Config]") { + DynamicPrintConfig config; + config.set("extruder", 2, true); + config.set("perimeter_extruder", 3, true); + config.normalize_fdm(); + INFO("Extruder option is removed after normalize()."); + CHECK(!config.has("extruder")); + INFO("Undefined extruder is populated with default extruder."); + CHECK(config.opt_int("infill_extruder") == 2); + INFO("Defined extruder is not overwritten by default extruder."); + CHECK(config.opt_int("perimeter_extruder") == 3); +} + +TEST_CASE("Normalize fdm infill extruder", "[Config]") { + DynamicPrintConfig config; + config.set("infill_extruder", 2, true); + config.normalize_fdm(); + INFO("Undefined solid infill extruder is populated with infill extruder."); + CHECK(config.opt_int("solid_infill_extruder") == 2); +} + +TEST_CASE("Normalize fdm retract layer change", "[Config]") { + DynamicPrintConfig config; + config.set("spiral_vase", true, true); + config.set_key_value("retract_layer_change", new ConfigOptionBools({true, false})); + config.normalize_fdm(); + CHECK(config.option("retract_layer_change")->values == std::vector{0, 0}); +} + +TEST_CASE("Can read ini with invalid items", "[Config]") { + std::string path = std::string(TEST_DATA_DIR) + "/test_config/bad_config_options.ini"; + + DynamicPrintConfig config; + config.load(path, ForwardCompatibilitySubstitutionRule::Disable); + //Did not crash. +} + +struct SerializationTestData { + std::string name; + std::vector values; + std::string serialized; +}; + +TEST_CASE("Config serialization of multiple values", "[Config]"){ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + std::vector test_data{ + { + "empty", + {}, + "" + }, + { + "single empty", + {""}, + "\"\"" + }, + { + "single noempty, simple", + {"RGB"}, + "RGB" + }, + { + "multiple noempty, simple", + {"ABC", "DEF", "09182745@!#$*(&"}, + "ABC;DEF;09182745@!#$*(&" + }, + { + "multiple, simple, some empty", + {"ABC", "DEF", "", "09182745@!#$*(&", ""}, + "ABC;DEF;;09182745@!#$*(&;" + }, + { + "complex", + {"some \"quoted\" notes", "yet\n some notes", "whatever \n notes", ""}, + "\"some \\\"quoted\\\" notes\";\"yet\\n some notes\";\"whatever \\n notes\";" + } + }; + + for (const SerializationTestData& data : test_data) { + config.set_key_value("filament_notes", new ConfigOptionStrings(data.values)); + CHECK(config.opt_serialize("filament_notes") == data.serialized); + + config.set_deserialize_strict("filament_notes", ""); + CHECK(config.option("filament_notes")->values == std::vector{}); + + config.set_deserialize_strict("filament_notes", data.serialized); + CHECK(config.option("filament_notes")->values == data.values); + } +} SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); diff --git a/tests/libslic3r/test_line.cpp b/tests/libslic3r/test_line.cpp new file mode 100644 index 0000000..4a8edf2 --- /dev/null +++ b/tests/libslic3r/test_line.cpp @@ -0,0 +1,59 @@ +/** + * Ported from xs/t/10_line.t + */ + +#include +#include +#include "test_utils.hpp" + +using namespace Slic3r; + +TEST_CASE("Line can be translated", "[Line]") { + Line line{{100, 100}, {200, 100}}; + + line.translate(10, -5); + CHECK(Points{line.a, line.b} == Points{{110, 95}, {210, 95}}); +} + +TEST_CASE("Check if lines are parallel", "[Line]") { + CHECK(Line{{0, 0}, {100, 0}}.parallel_to(Line{{200, 200}, {0, 200}})); +} + +TEST_CASE("Parallel lines under angles", "[Line]") { + auto base_angle = GENERATE(0, M_PI/3, M_PI/2, M_PI); + + Line line{{0, 0}, {100, 0}}; + line.rotate(base_angle, {0, 0}); + Line clone{line}; + + INFO("Line is parallel to self"); + CHECK(line.parallel_to(clone)); + + clone.reverse(); + INFO("Line is parallel to self + PI"); + CHECK(line.parallel_to(clone)); + + INFO("Line is parallel to its direction"); + CHECK(line.parallel_to(line.direction())); + INFO("Line is parallel to its direction + PI"); + line.parallel_to(line.direction() + M_PI); + INFO("line is parallel to its direction - PI") + line.parallel_to(line.direction() - M_PI); + + SECTION("Line is parallel within epsilon") { + clone = line; + clone.rotate(EPSILON/2, {0, 0}); + CHECK(line.parallel_to(clone)); + clone = line; + clone.rotate(-EPSILON/2, {0, 0}); + CHECK(line.parallel_to(clone)); + } +} + +TEST_CASE("Intersection infinite", "[Line]") { + const Line a{{100, 0}, {200, 0}}; + const Line b{{300, 300}, {300, 100}}; + Point r; + a.intersection_infinite(b, &r); + CHECK(r == Point{300, 0}); +} diff --git a/tests/libslic3r/test_point.cpp b/tests/libslic3r/test_point.cpp new file mode 100644 index 0000000..06d4433 --- /dev/null +++ b/tests/libslic3r/test_point.cpp @@ -0,0 +1,46 @@ + +/** + * Ported from xs/t/03_point.t + * - it used to check ccw() but it does not exist anymore + * and cross product uses doubles + */ + +#include +#include +#include "test_utils.hpp" + +using namespace Slic3r; + +TEST_CASE("Nearest point", "[Point]") { + const Point point{10, 15}; + const Point point2{30, 15}; + + const Point nearest{nearest_point({point2, Point{100, 200}}, point).first}; + CHECK(nearest == point2); +} + +TEST_CASE("Distance to line", "[Point]") { + const Line line{{0, 0}, {100, 0}}; + CHECK(line.distance_to(Point{0, 0}) == Approx(0)); + CHECK(line.distance_to(Point{100, 0}) == Approx(0)); + CHECK(line.distance_to(Point{50, 0}) == Approx(0)); + CHECK(line.distance_to(Point{150, 0}) == Approx(50)); + CHECK(line.distance_to(Point{0, 50}) == Approx(50)); + CHECK(line.distance_to(Point{50, 50}) == Approx(50)); + CHECK(line.perp_distance_to(Point{50, 50}) == Approx(50)); + CHECK(line.perp_distance_to(Point{150, 50}) == Approx(50)); +} + +TEST_CASE("Distance to diagonal line", "[Point]") { + const Line line{{50, 50}, {125, -25}}; + CHECK(std::abs(line.distance_to(Point{100, 0})) == Approx(0)); +} + +TEST_CASE("Perp distance to line does not overflow", "[Point]") { + const Line line{ + {18335846, 18335845}, + {18335846, 1664160}, + }; + + CHECK(line.distance_to(Point{1664161, 18335848}) == Approx(16671685)); +} diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp index 222b35f..6060473 100644 --- a/tests/libslic3r/test_polyline.cpp +++ b/tests/libslic3r/test_polyline.cpp @@ -5,6 +5,73 @@ using namespace Slic3r; +struct PolylineTestCase { + Polyline polyline{ + {100, 100}, + {200, 100}, + {200, 200} + }; +}; + +TEST_CASE_METHOD(PolylineTestCase, "Lines can be retrieved", "[Polyline]") { + + CHECK(polyline.lines() == Lines{ + {{100, 100}, {200, 100}}, + {{200, 100}, {200, 200}}, + }); +} + +TEST_CASE_METHOD(PolylineTestCase, "Clip", "[Polyline]") { + const double len = polyline.length(); + polyline.clip_end(len/3); + CHECK(std::abs(polyline.length() - 2.0/3.0*len) < 1); +} + +TEST_CASE_METHOD(PolylineTestCase, "Append", "[Polyline]") { + Polyline tested_polyline{polyline}; + tested_polyline.append(tested_polyline); + Points expected{polyline.points}; + expected.insert(expected.end(), polyline.points.begin(), polyline.points.end()); + + CHECK(tested_polyline.points == expected); +} + +TEST_CASE_METHOD(PolylineTestCase, "Extend end", "[Polyline]") { + CHECK(polyline.length() == 100*2); + polyline.extend_end(50); + CHECK(polyline.length() == 100*2 + 50); +} + +TEST_CASE_METHOD(PolylineTestCase, "Extend start", "[Polyline]") { + CHECK(polyline.length() == 100*2); + polyline.extend_start(50); + CHECK(polyline.length() == 100*2 + 50); +} + +TEST_CASE_METHOD(PolylineTestCase, "Split", "[Polyline]") { + Polyline p1; + Polyline p2; + const Point point{150, 100}; + polyline.split_at(point, &p1, &p2); + CHECK(p1.size() == 2); + CHECK(p2.size() == 3); + CHECK(p1.last_point() == point); + CHECK(p2.first_point() == point); +} + +TEST_CASE_METHOD(PolylineTestCase, "Split at first point", "[Polyline]") { + Polyline to_split{ + polyline.points[0], + polyline.points[1], + polyline.points[2], + polyline.points[0] + }; + Polyline p1; + Polyline p2; + to_split.split_at(to_split.first_point(), &p1, &p2); + CHECK(p1.size() == 1); + CHECK(p2.size() == 4); +} SCENARIO("Simplify polyne, template", "[Polyline]") { Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} }; @@ -43,4 +110,23 @@ SCENARIO("Simplify polyline", "[Polyline]") } } } + GIVEN("polyline 3") { + auto polyline = Polyline{ {0,0}, {100,0}, {50,10} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(25.); + THEN("not simplified") { + REQUIRE(polyline == Polyline{ {0,0}, {100, 0}, {50,10} }); + } + } + } + + GIVEN("polyline 4") { + auto polyline = Polyline{ {0,0}, {20,0}, {50,0}, {80,0}, {100,0} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(2.); + THEN("not simplified") { + REQUIRE(polyline == Polyline{ {0,0}, {100,0} }); + } + } + } } diff --git a/tests/libslic3r/test_support_spots_generator.cpp b/tests/libslic3r/test_support_spots_generator.cpp index 0cd8c51..119d996 100644 --- a/tests/libslic3r/test_support_spots_generator.cpp +++ b/tests/libslic3r/test_support_spots_generator.cpp @@ -6,7 +6,7 @@ using namespace Slic3r; using namespace SupportSpotsGenerator; -TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") { +namespace Rectangle { const float width = 10; const float height = 20; const Polygon polygon = { @@ -15,13 +15,35 @@ TEST_CASE("Numerical integral calculation compared with exact solution.", "[Supp scaled(Vec2f{width / 2, height / 2}), scaled(Vec2f{-width / 2, height / 2}) }; +} - const Integrals integrals{{polygon}}; - CHECK(integrals.area == Approx(width * height)); +TEST_CASE("Numerical integral over polygon calculation compared with exact solution.", "[SupportSpotsGenerator]") { + const Integrals integrals{Rectangle::polygon}; + + CHECK(integrals.area == Approx(Rectangle::width * Rectangle::height)); CHECK(integrals.x_i.x() == Approx(0)); CHECK(integrals.x_i.y() == Approx(0)); - CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12)); - CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12)); + CHECK(integrals.x_i_squared.x() == Approx(std::pow(Rectangle::width, 3) * Rectangle::height / 12)); + CHECK(integrals.x_i_squared.y() == Approx(Rectangle::width * std::pow(Rectangle::height, 3) / 12)); +} + +TEST_CASE("Integrals over multiple polygons", "[SupportSpotsGenerator]") { + const Integrals integrals{{Rectangle::polygon, Rectangle::polygon}}; + + CHECK(integrals.area == Approx(2 * Rectangle::width * Rectangle::height)); +} + +TEST_CASE("Numerical integral over line calculation compared with exact solution.", "[SupportSpotsGenerator]") { + const float length = 10; + const float width = 20; + const Polyline polyline{scaled(Vec2f{-length/2.0f, 0.0f}), scaled(Vec2f{length/2.0f, 0.0f})}; + + const Integrals integrals{{polyline}, {width}}; + CHECK(integrals.area == Approx(length * width)); + CHECK(integrals.x_i.x() == Approx(0)); + CHECK(integrals.x_i.y() == Approx(0)); + CHECK(integrals.x_i_squared.x() == Approx(std::pow(length, 3) * width / 12)); + CHECK(integrals.x_i_squared.y() == Approx(length * std::pow(width, 3) / 12)); } TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") { @@ -37,7 +59,7 @@ TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") { scaled(Vec2f{0, height}) }; - const Integrals integrals{{polygon}}; + const Integrals integrals{polygon}; const Vec2f x_axis{1, 0}; const float x_axis_moment = compute_second_moment(integrals, x_axis); @@ -69,7 +91,7 @@ TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") { scaled(Vec2f{77.56229640885199, 189.33057746591336}) }; - Integrals integrals{{polygon}}; + Integrals integrals{polygon}; // Meassured counterclockwise from (1, 0) const float angle = 1.432f; @@ -130,7 +152,7 @@ TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion col std::nullopt }; - Integrals expected{{expected_polygon}}; + Integrals expected{expected_polygon}; CHECK(part.connected_to_bed == true); Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume}; diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 967cb0e..d2f7847 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -62,7 +62,7 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]") // Construction of the Voronoi Diagram. VD vd; - construct_voronoi(pts.begin(), pts.end(), &vd); + vd.construct_voronoi(pts.begin(), pts.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(), @@ -190,7 +190,7 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]") Lines lines = to_lines(poly); VD vd; - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(), @@ -298,7 +298,7 @@ TEST_CASE("Voronoi weirdness", "[Voronoi]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-weirdness.svg").c_str(), @@ -322,7 +322,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]") } VD vd; - construct_voronoi(pts.begin(), pts.end(), &vd); + vd.construct_voronoi(pts.begin(), pts.end()); #ifdef VORONOI_DEBUG_OUT // Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly. @@ -1319,7 +1319,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #endif VD vd; - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const auto& edge : vd.edges()) if (edge.is_finite()) { @@ -1360,7 +1360,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]") VD vd; Lines lines = to_lines(poly_with_hole); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const OffsetTest &ot : { OffsetTest { scale_(0.2), 1, 1 }, @@ -1426,7 +1426,7 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const OffsetTest &ot : { OffsetTest { scale_(0.2), 2, 2 }, @@ -1496,7 +1496,7 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const OffsetTest &ot : { OffsetTest { scale_(0.2), 2, 2 }, @@ -1747,7 +1747,7 @@ TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const OffsetTest &ot : { OffsetTest { scale_(0.2), 2, 2 }, @@ -1858,7 +1858,7 @@ TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); for (const OffsetTest &ot : { OffsetTest { scale_(2.8), 1, 1 }, @@ -1916,7 +1916,7 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); Slic3r::Voronoi::annotate_inside_outside(vd, lines); static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); @@ -1966,7 +1966,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines); #endif @@ -2006,7 +2006,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines); #endif @@ -2047,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines); #endif @@ -2091,8 +2091,8 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]") Geometry::VoronoiDiagram vd_2; Lines lines_1 = to_lines(polygon_1); Lines lines_2 = to_lines(polygon_2); - construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1); - construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2); + vd_1.construct_voronoi(lines_1.begin(), lines_1.end()); + vd_2.construct_voronoi(lines_2.begin(), lines_2.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2); @@ -2124,7 +2124,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines); #endif @@ -2164,7 +2164,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines); #endif @@ -2226,10 +2226,66 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines); #endif // REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd)); } + +// This case is extracted from SPE-1729, where several ExPolygon with very thin lines +// and holes formed by very close (1-5nm) vertices that are on the edge of our resolution. +// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid. +TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDiagramThinLinesSPE1729]") +{ + Polygon contour = { + {32247689, -2405501}, + {32247733, -2308514}, + {32247692, -2405496}, + {50484384, 332941}, + {50374839, 1052546}, + {32938040, -1637993}, + {32938024, -1673788}, + {32942107, 7220481}, + {32252205, 7447599}, + {32252476, 8037808}, + {32555965, 8277599}, + {17729260, 8904718}, + {17729236, 8853233}, + {17729259, 8904722}, + {17039259, 8935481}, + {17033440, -3880421}, + {17204385, -3852156}, + {17723645, -3441873}, + {17723762, -3187210}, + {17728957, 8240730}, + {17728945, 8213866}, + {31716233, 7614090}, + {20801623, -1009882}, + {21253963, -1580792}, + {32252082, 7157187}, + {32248022, -1673787}, + {24245653, -2925506}, + {18449246, -3809095}, + {18728385, -4449246} + }; + + Polygon hole = { + {32247789, -2181284}, + {32247870, -2003865}, + {32247872, -2003866}, + {32247752, -2267007} + }; + + Polygons polygons = {contour, hole}; + + VD vd; + Lines lines = to_lines(polygons); + vd.construct_voronoi(lines.begin(), lines.end()); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("invalid-voronoi-diagram-thin-lines.svg").c_str(), vd, Points(), lines); +#endif + +// REQUIRE(vd.is_valid()); +} \ No newline at end of file diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt deleted file mode 100644 index 3127334..0000000 --- a/xs/CMakeLists.txt +++ /dev/null @@ -1,238 +0,0 @@ -project(XS) - -# Find the Perl interpreter, add local-lib to PATH and PERL5LIB environment variables, -# so the locally installed modules (mainly the Alien::wxPerl) will be reached. -if (WIN32) - set(ENV_PATH_SEPARATOR ";") -else() - set(ENV_PATH_SEPARATOR ":") -endif() - -# Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. -set(PERL_LOCAL_LIB_DIR ${PROJECT_SOURCE_DIR}/../local-lib) - -set(ENV{PATH} "${PERL_LOCAL_LIB_DIR}/bin${ENV_PATH_SEPARATOR}$ENV{PATH}") -set(PERL_INCLUDE "${PERL_LOCAL_LIB_DIR}/lib/perl5${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}") -message("PATH: $ENV{PATH}") -message("PERL_INCLUDE: ${PERL_INCLUDE}") -find_package(Perl REQUIRED) -if (WIN32) - # On Windows passing the PERL5LIB variable causes various problems (such as with MAX_PATH and others), - # basically I've found no good way to do it on Windows. - set(PERL5LIB_ENV_CMD "") -else() - set(PERL5LIB_ENV_CMD ${CMAKE_COMMAND} -E env PERL5LIB=${PERL_INCLUDE}) -endif() - -# Perl specific stuff -find_package(PerlLibs REQUIRED) -set(PerlEmbed_DEBUG 1) -find_package(PerlEmbed REQUIRED) - -# Generate the Slic3r Perl module (XS) typemap file. -set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) -add_custom_command( - OUTPUT ${MyTypemap} - DEPENDS ${CMAKE_CURRENT_LIST_DIR}/xsp/my.map - COMMAND ${PERL5LIB_ENV_CMD} ${PERL_EXECUTABLE} -MExtUtils::Typemaps -MExtUtils::Typemaps::Basic -e "$typemap = ExtUtils::Typemaps->new(file => \"${CMAKE_CURRENT_LIST_DIR}/xsp/my.map\"); $typemap->merge(typemap => ExtUtils::Typemaps::Basic->new); $typemap->write(file => \"${MyTypemap}\")" - VERBATIM -) - -# Generate the Slic3r Perl module (XS) main.xs file. -set(XS_MAIN_XS ${CMAKE_CURRENT_BINARY_DIR}/main.xs) -set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp) -#FIXME list the dependecies explicitely, add dependency on the typemap. -set(XS_XSP_FILES - ${XSP_DIR}/Config.xsp - ${XSP_DIR}/ExPolygon.xsp - ${XSP_DIR}/Geometry.xsp - ${XSP_DIR}/Line.xsp - ${XSP_DIR}/Model.xsp - ${XSP_DIR}/Point.xsp - ${XSP_DIR}/Polygon.xsp - ${XSP_DIR}/Polyline.xsp - ${XSP_DIR}/Print.xsp - ${XSP_DIR}/TriangleMesh.xsp - ${XSP_DIR}/XS.xsp -) -foreach (file ${XS_XSP_FILES}) - if (MSVC) - # Visual Studio C compiler has issues with FILE pragmas containing quotes. - set(INCLUDE_COMMANDS "${INCLUDE_COMMANDS}INCLUDE_COMMAND: $^X -MExtUtils::XSpp::Cmd -e xspp -- -t ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt ${file}\n") - else () - set(INCLUDE_COMMANDS "${INCLUDE_COMMANDS}INCLUDE_COMMAND: $^X -MExtUtils::XSpp::Cmd -e xspp -- -t \"${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt\" \"${file}\"\n") - endif () -endforeach () -configure_file(main.xs.in ${XS_MAIN_XS} @ONLY) # Insert INCLUDE_COMMANDS into main.xs - -# Generate the Slic3r Perl module (XS) XS.cpp file. -#FIXME add the dependency on main.xs and typemap. -set(XS_MAIN_CPP ${CMAKE_CURRENT_BINARY_DIR}/XS.cpp) -add_custom_command( - OUTPUT ${XS_MAIN_CPP} - DEPENDS ${MyTypemap} ${XS_XSP_FILES} ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt - COMMAND ${PERL5LIB_ENV_CMD} xsubpp -typemap typemap -output ${XS_MAIN_CPP} -hiertype ${XS_MAIN_XS} - VERBATIM -) - -# Define the Perl XS shared library. -if(APPLE) - set(XS_SHARED_LIBRARY_TYPE MODULE) -else() - set(XS_SHARED_LIBRARY_TYPE SHARED) -endif() -add_library(XS ${XS_SHARED_LIBRARY_TYPE} - ${XS_MAIN_CPP} - src/perlglue.cpp - src/ppport.h - src/xsinit.h - xsp/my.map - # mytype.map is empty. Is it required by Build.PL or the Perl xspp module? - xsp/mytype.map - # Used by Perl xsubpp to generate XS.cpp - xsp/typemap.xspt -) -if(APPLE) - set_target_properties(XS PROPERTIES BUNDLE TRUE) - # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. - target_link_libraries(XS "-undefined dynamic_lookup") -endif() -target_link_libraries(XS libslic3r) - -target_include_directories(XS PRIVATE src ${LIBDIR}/libslic3r) -target_compile_definitions(XS PRIVATE -DSLIC3RXS) -set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so - -if (APPLE) - # -liconv: boost links to libiconv by default - target_link_libraries(XS "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) -elseif (MSVC) - target_link_libraries(XS ) -else () - target_link_libraries(XS -lstdc++) -endif () - -# Windows specific stuff -if (WIN32) - target_compile_definitions(XS PRIVATE -DNOGDI -DNOMINMAX -DHAS_BOOL) -endif () - -# SLIC3R_MSVC_PDB -if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release") - set_target_properties(XS PROPERTIES - COMPILE_FLAGS "/Zi" - LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF" - ) -endif() - -if (CMAKE_BUILD_TYPE MATCHES DEBUG) - target_compile_definitions(XS PRIVATE -DSLIC3R_DEBUG -DDEBUG -D_DEBUG) -else () - target_compile_definitions(XS PRIVATE -DNDEBUG) -endif () - -target_include_directories(XS PRIVATE ${PERL_INCLUDE_PATH}) -target_compile_options(XS PRIVATE ${PerlEmbed_CCFLAGS}) - -if (WIN32) - target_link_libraries(XS ${PERL_LIBRARY}) -endif() - - -set(PERL_LOCAL_LIB_ARCH_DIR "${PERL_LOCAL_LIB_DIR}/lib/perl5/${PerlEmbed_ARCHNAME}") -add_custom_command( - TARGET XS - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" - COMMAND ${CMAKE_COMMAND} -E copy "$" "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" - COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_ARCH_DIR}/Slic3r/" - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/lib/Slic3r/XS.pm" "${PERL_LOCAL_LIB_ARCH_DIR}/Slic3r/" - COMMENT "Installing XS.pm and XS.{so,dll,bundle} into the local-lib directory ..." -) -if(APPLE) - add_custom_command( - TARGET XS - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rename "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/XS" "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/XS.bundle" - ) -endif() - -if (MSVC) - # Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box. - get_filename_component(PROPS_PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) - string(REPLACE "/" "\\" PROPS_PERL_BIN_PATH "${PROPS_PERL_BIN_PATH}") - string(REPLACE "/" "\\" PROPS_PERL_EXECUTABLE "${PERL_EXECUTABLE}") - string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") - configure_file("../cmake/msvc/xs.wperl.props.in" "${CMAKE_BINARY_DIR}/xs.wperl.props" NEWLINE_STYLE CRLF) - set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl.props") - - if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") - set(_bits 64) - elseif ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") - set(_bits 32) - endif () - add_custom_command(TARGET XS POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TOP_LEVEL_PROJECT_DIR}/deps/+GMP/gmp/lib/win${_bits}/libgmp-10.dll "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" - COMMENT "Installing gmp runtime into the local-lib directory ..." - VERBATIM) - - add_custom_command(TARGET XS POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TOP_LEVEL_PROJECT_DIR}/deps/+MPFR/mpfr/lib/win${_bits}/libmpfr-4.dll "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" - COMMENT "Installing mpfr runtime into the local-lib directory ..." - VERBATIM) -endif() - -# Installation -install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS) -install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r) - -# Unit / integration tests -enable_testing() -get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) -if (MSVC) - set(PERL_PROVE "${PERL_BIN_PATH}/prove.bat") -else () - set(PERL_PROVE "${PERL_BIN_PATH}/prove") -endif () - -set(PERL_ENV_VARS "") -if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT CMAKE_CROSSCOMPILING AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - if (SLIC3R_ASAN OR SLIC3R_UBSAN) - set(PERL_ENV_VARS env) - endif () - - if (SLIC3R_ASAN) - # Find the location of libasan.so for passing it into LD_PRELOAD. It works with GCC and Clang on Linux. - # On Centos 7 calling "gcc -print-file-name=libasan.so" returns path to "ld script" instead of path to shared library. - set(_asan_compiled_bin ${CMAKE_CURRENT_BINARY_DIR}/detect_libasan) - set(_asan_source_file ${_asan_compiled_bin}.c) - # Compile and link simple C application with enabled address sanitizer. - file(WRITE ${_asan_source_file} "int main(){}") - include(GetPrerequisites) - execute_process(COMMAND ${CMAKE_C_COMPILER} ${_asan_source_file} -fsanitize=address -lasan -o ${_asan_compiled_bin}) - # Extract from the compiled application absolute path of libasan. - get_prerequisites(${_asan_compiled_bin} _asan_shared_libraries_list 0 0 "" "") - list(FILTER _asan_shared_libraries_list INCLUDE REGEX libasan) - set(PERL_ENV_VARS ${PERL_ENV_VARS} "LD_PRELOAD=${_asan_shared_libraries_list}") - - # Suppressed memory leak reports that come from Perl. - set(PERL_LEAK_SUPPRESSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/leak_suppression.txt) - file(WRITE ${PERL_LEAK_SUPPRESSION_FILE} - "leak:Perl_safesysmalloc\n" - "leak:Perl_safesyscalloc\n" - "leak:Perl_safesysrealloc\n" - "leak:__newlocale\n") - - # Suppress a few memory leak reports and disable informing about suppressions. - # Print reports about memory leaks but exit with zero exit code when any memory leaks is found to make unit tests pass. - set(PERL_ENV_VARS ${PERL_ENV_VARS} "LSAN_OPTIONS=suppressions=${PERL_LEAK_SUPPRESSION_FILE}:print_suppressions=0:exitcode=0") - endif () - - if (SLIC3R_UBSAN) - # Do not show full stacktrace for reports from UndefinedBehaviorSanitizer in Perl tests. - set(PERL_ENV_VARS ${PERL_ENV_VARS} "UBSAN_OPTIONS=print_stacktrace=0") - endif () -endif () - -add_test (NAME xs COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PERL_LOCAL_LIB_DIR}/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) -add_test (NAME integration COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..) diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm deleted file mode 100644 index 60c9a93..0000000 --- a/xs/lib/Slic3r/XS.pm +++ /dev/null @@ -1,125 +0,0 @@ -package Slic3r::XS; -use warnings; -use strict; - -our $VERSION = '0.01'; - -use Carp qw(); -use XSLoader; -XSLoader::load(__PACKAGE__, $VERSION); - -package Slic3r::Line; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Point; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Pointf; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Pointf3; -use overload - '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, - 'fallback' => 1; - -sub pp { - my ($self) = @_; - return [ @$self ]; -} - -package Slic3r::ExPolygon; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Polyline; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Polygon; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -package Slic3r::Surface; - -sub new { - my ($class, %args) = @_; - - # defensive programming: make sure no negative bridge_angle is supplied - die "Error: invalid negative bridge_angle\n" - if defined $args{bridge_angle} && $args{bridge_angle} < 0; - - return $class->_new( - $args{expolygon} // (die "Missing required expolygon\n"), - $args{surface_type} // (die "Missing required surface_type\n"), - $args{thickness} // -1, - $args{thickness_layers} // 1, - $args{bridge_angle} // -1, - $args{extra_perimeters} // 0, - ); -} - -sub clone { - my ($self, %args) = @_; - - return (ref $self)->_new( - delete $args{expolygon} // $self->expolygon, - delete $args{surface_type} // $self->surface_type, - delete $args{thickness} // $self->thickness, - delete $args{thickness_layers} // $self->thickness_layers, - delete $args{bridge_angle} // $self->bridge_angle, - delete $args{extra_perimeters} // $self->extra_perimeters, - ); -} - -package Slic3r::Surface::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new { - my ($class, @surfaces) = @_; - - my $self = $class->_new; - $self->append($_) for @surfaces; - return $self; -} - -package main; -for my $class (qw( - Slic3r::Config - Slic3r::Config::GCode - Slic3r::Config::Print - Slic3r::Config::Static - Slic3r::ExPolygon - Slic3r::Line - Slic3r::Model - Slic3r::Model::Instance - Slic3r::Model::Material - Slic3r::Model::Object - Slic3r::Model::Volume - Slic3r::Point - Slic3r::Point3 - Slic3r::Pointf - Slic3r::Pointf3 - Slic3r::Polygon - Slic3r::Polyline - Slic3r::Polyline::Collection - Slic3r::Print - Slic3r::TriangleMesh - )) -{ - no strict 'refs'; - my $ref_class = $class . "::Ref"; - eval "package $ref_class; our \@ISA = '$class'; sub DESTROY {};"; -} - -1; diff --git a/xs/main.xs.in b/xs/main.xs.in deleted file mode 100644 index d8db108..0000000 --- a/xs/main.xs.in +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include -#include - -#ifdef __cplusplus -/* extern "C" { */ -#endif -#include "EXTERN.h" -#include "perl.h" -#include "XSUB.h" -#include "ppport.h" -#undef do_open -#undef do_close -#ifdef __cplusplus -/* } */ -#endif - -#ifdef _WIN32 - #undef XS_EXTERNAL - #define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name) -#endif /* MSVC */ - -MODULE = Slic3r::XS PACKAGE = Slic3r::XS - -@INCLUDE_COMMANDS@ \ No newline at end of file diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp deleted file mode 100644 index 500cbee..0000000 --- a/xs/src/perlglue.cpp +++ /dev/null @@ -1,527 +0,0 @@ -#ifdef SLIC3RXS -#include - -namespace Slic3r { - -REGISTER_CLASS(ExPolygon, "ExPolygon"); -REGISTER_CLASS(GCodeGenerator, "GCode"); -REGISTER_CLASS(Line, "Line"); -REGISTER_CLASS(Polygon, "Polygon"); -REGISTER_CLASS(Polyline, "Polyline"); -REGISTER_CLASS(Print, "Print"); -REGISTER_CLASS(PrintObject, "Print::Object"); -REGISTER_CLASS(PrintRegion, "Print::Region"); -REGISTER_CLASS(Model, "Model"); -REGISTER_CLASS(ModelMaterial, "Model::Material"); -REGISTER_CLASS(ModelObject, "Model::Object"); -REGISTER_CLASS(ModelVolume, "Model::Volume"); -REGISTER_CLASS(ModelInstance, "Model::Instance"); -REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); -REGISTER_CLASS(Point, "Point"); -__REGISTER_CLASS(Vec2d, "Pointf"); -__REGISTER_CLASS(Vec3d, "Pointf3"); -REGISTER_CLASS(DynamicPrintConfig, "Config"); -REGISTER_CLASS(StaticPrintConfig, "Config::Static"); -REGISTER_CLASS(GCodeConfig, "Config::GCode"); -REGISTER_CLASS(PrintConfig, "Config::Print"); -REGISTER_CLASS(Surface, "Surface"); -REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); -REGISTER_CLASS(FullPrintConfig, "Config::Full"); -REGISTER_CLASS(TriangleMesh, "TriangleMesh"); - -SV* ConfigBase__as_hash(ConfigBase* THIS) -{ - HV* hv = newHV(); - for (auto &key : THIS->keys()) - (void)hv_store(hv, key.c_str(), key.length(), ConfigBase__get(THIS, key), 0); - return newRV_noinc((SV*)hv); -} - -SV* ConfigBase__get(ConfigBase* THIS, const t_config_option_key &opt_key) -{ - ConfigOption *opt = THIS->option(opt_key, false); - return (opt == nullptr) ? - &PL_sv_undef : - ConfigOption_to_SV(*opt, *THIS->def()->get(opt_key)); -} - -SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def) -{ - switch (def.type) { - case coFloat: - case coPercent: - return newSVnv(static_cast(&opt)->value); - case coFloats: - case coPercents: - { - auto optv = static_cast(&opt); - AV* av = newAV(); - av_fill(av, optv->values.size()-1); - for (const double &v : optv->values) - av_store(av, &v - optv->values.data(), newSVnv(v)); - return newRV_noinc((SV*)av); - } - case coInt: - return newSViv(static_cast(&opt)->value); - case coInts: - { - auto optv = static_cast(&opt); - AV* av = newAV(); - av_fill(av, optv->values.size()-1); - for (const int &v : optv->values) - av_store(av, &v - optv->values.data(), newSViv(v)); - return newRV_noinc((SV*)av); - } - case coString: - { - auto optv = static_cast(&opt); - // we don't serialize() because that would escape newlines - return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true); - } - case coStrings: - { - auto optv = static_cast(&opt); - AV* av = newAV(); - av_fill(av, optv->values.size()-1); - for (const std::string &v : optv->values) - av_store(av, &v - optv->values.data(), newSVpvn_utf8(v.c_str(), v.length(), true)); - return newRV_noinc((SV*)av); - } - case coPoint: - return perl_to_SV_clone_ref(static_cast(&opt)->value); - case coPoint3: - return perl_to_SV_clone_ref(static_cast(&opt)->value); - case coPoints: - { - auto optv = static_cast(&opt); - AV* av = newAV(); - av_fill(av, optv->values.size()-1); - for (const Vec2d &v : optv->values) - av_store(av, &v - optv->values.data(), perl_to_SV_clone_ref(v)); - return newRV_noinc((SV*)av); - } - case coBool: - return newSViv(static_cast(&opt)->value ? 1 : 0); - case coBools: - { - auto optv = static_cast(&opt); - AV* av = newAV(); - av_fill(av, optv->values.size()-1); - for (size_t i = 0; i < optv->values.size(); ++ i) - av_store(av, i, newSViv(optv->values[i] ? 1 : 0)); - return newRV_noinc((SV*)av); - } - default: - std::string serialized = opt.serialize(); - return newSVpvn_utf8(serialized.c_str(), serialized.length(), true); - } -} - -SV* ConfigBase__get_at(ConfigBase* THIS, const t_config_option_key &opt_key, size_t i) -{ - ConfigOption* opt = THIS->option(opt_key, false); - if (opt == nullptr) - return &PL_sv_undef; - - const ConfigOptionDef* def = THIS->def()->get(opt_key); - switch (def->type) { - case coFloats: - case coPercents: - return newSVnv(static_cast(opt)->get_at(i)); - case coInts: - return newSViv(static_cast(opt)->get_at(i)); - case coStrings: - { - // we don't serialize() because that would escape newlines - const std::string &val = static_cast(opt)->get_at(i); - return newSVpvn_utf8(val.c_str(), val.length(), true); - } - case coPoints: - return perl_to_SV_clone_ref(static_cast(opt)->get_at(i)); - case coBools: - return newSViv(static_cast(opt)->get_at(i) ? 1 : 0); - default: - return &PL_sv_undef; - } -} - -bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value) -{ - ConfigOption* opt = THIS->option(opt_key, true); - if (opt == nullptr) - CONFESS("Trying to set non-existing option"); - const ConfigOptionDef* def = THIS->def()->get(opt_key); - if (opt->type() != def->type) - CONFESS("Option type is different from the definition"); - switch (def->type) { - case coFloat: - if (!looks_like_number(value)) - return false; - static_cast(opt)->value = SvNV(value); - break; - case coFloats: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; ++ i) { - SV** elem = av_fetch(av, i, 0); - if (elem == NULL || !looks_like_number(*elem)) return false; - values.emplace_back(SvNV(*elem)); - } - break; - } - case coPercents: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - if (elem == NULL || !looks_like_number(*elem)) return false; - values.emplace_back(SvNV(*elem)); - } - break; - } - case coInt: - if (!looks_like_number(value)) return false; - static_cast(opt)->value = SvIV(value); - break; - case coInts: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - if (elem == NULL || !looks_like_number(*elem)) return false; - values.emplace_back(SvIV(*elem)); - } - break; - } - case coString: - static_cast(opt)->value = std::string(SvPV_nolen(value), SvCUR(value)); - break; - case coStrings: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - if (elem == NULL) return false; - values.emplace_back(std::string(SvPV_nolen(*elem), SvCUR(*elem))); - } - break; - } - case coPoint: - return from_SV_check(value, &static_cast(opt)->value); -// case coPoint3: - // not gonna fix it, die Perl die! -// return from_SV_check(value, &static_cast(opt)->value); - case coPoints: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - Vec2d point(Vec2d::Zero()); - if (elem == NULL || !from_SV_check(*elem, &point)) return false; - values.emplace_back(point); - } - break; - } - case coBool: - static_cast(opt)->value = SvTRUE(value); - break; - case coBools: - { - std::vector &values = static_cast(opt)->values; - AV* av = (AV*)SvRV(value); - const size_t len = av_len(av)+1; - values.clear(); - values.reserve(len); - for (size_t i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - if (elem == NULL) return false; - values.emplace_back(SvTRUE(*elem)); - } - break; - } - default: - if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable)) - return false; - } - return true; -} - -/* This method is implemented as a workaround for this typemap bug: - https://rt.cpan.org/Public/Bug/Display.html?id=94110 */ -bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &opt_key, SV* str) -{ - size_t len; - const char * c = SvPV(str, len); - std::string value(c, len); - ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; - return THIS->set_deserialize_nothrow(opt_key, value, ctxt); -} - -void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize) -{ - if (THIS->has(opt_key)) - return; - if (deserialize) - ConfigBase__set_deserialize(THIS, opt_key, value); - else - ConfigBase__set(THIS, opt_key, value); -} - -bool StaticConfig__set(StaticConfig* THIS, const t_config_option_key &opt_key, SV* value) -{ - const ConfigOptionDef* optdef = THIS->def()->get(opt_key); - if (optdef->shortcut.empty()) - return ConfigBase__set(THIS, opt_key, value); - for (const t_config_option_key &key : optdef->shortcut) - if (! StaticConfig__set(THIS, key, value)) - return false; - return true; -} - -SV* to_AV(ExPolygon* expolygon) -{ - const unsigned int num_holes = expolygon->holes.size(); - AV* av = newAV(); - av_extend(av, num_holes); // -1 +1 - - av_store(av, 0, perl_to_SV_ref(expolygon->contour)); - - for (unsigned int i = 0; i < num_holes; i++) { - av_store(av, i+1, perl_to_SV_ref(expolygon->holes[i])); - } - return newRV_noinc((SV*)av); -} - -SV* to_SV_pureperl(const ExPolygon* expolygon) -{ - const unsigned int num_holes = expolygon->holes.size(); - AV* av = newAV(); - av_extend(av, num_holes); // -1 +1 - av_store(av, 0, to_SV_pureperl(&expolygon->contour)); - for (unsigned int i = 0; i < num_holes; i++) { - av_store(av, i+1, to_SV_pureperl(&expolygon->holes[i])); - } - return newRV_noinc((SV*)av); -} - -void from_SV(SV* expoly_sv, ExPolygon* expolygon) -{ - AV* expoly_av = (AV*)SvRV(expoly_sv); - const unsigned int num_polygons = av_len(expoly_av)+1; - expolygon->holes.resize(num_polygons-1); - - SV** polygon_sv = av_fetch(expoly_av, 0, 0); - from_SV(*polygon_sv, &expolygon->contour); - for (unsigned int i = 0; i < num_polygons-1; i++) { - polygon_sv = av_fetch(expoly_av, i+1, 0); - from_SV(*polygon_sv, &expolygon->holes[i]); - } -} - -void from_SV_check(SV* expoly_sv, ExPolygon* expolygon) -{ - if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) { - if (!sv_isa(expoly_sv, perl_class_name(expolygon)) && !sv_isa(expoly_sv, perl_class_name_ref(expolygon))) - CONFESS("Not a valid %s object", perl_class_name(expolygon)); - // a XS ExPolygon was supplied - *expolygon = *(ExPolygon *)SvIV((SV*)SvRV( expoly_sv )); - } else { - // a Perl arrayref was supplied - from_SV(expoly_sv, expolygon); - } -} - -void from_SV(SV* line_sv, Line* THIS) -{ - AV* line_av = (AV*)SvRV(line_sv); - from_SV_check(*av_fetch(line_av, 0, 0), &THIS->a); - from_SV_check(*av_fetch(line_av, 1, 0), &THIS->b); -} - -void from_SV_check(SV* line_sv, Line* THIS) -{ - if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) { - if (!sv_isa(line_sv, perl_class_name(THIS)) && !sv_isa(line_sv, perl_class_name_ref(THIS))) - CONFESS("Not a valid %s object", perl_class_name(THIS)); - *THIS = *(Line*)SvIV((SV*)SvRV( line_sv )); - } else { - from_SV(line_sv, THIS); - } -} - -SV* to_AV(Line* THIS) -{ - AV* av = newAV(); - av_extend(av, 1); - - av_store(av, 0, perl_to_SV_ref(THIS->a)); - av_store(av, 1, perl_to_SV_ref(THIS->b)); - - return newRV_noinc((SV*)av); -} - -SV* to_SV_pureperl(const Line* THIS) -{ - AV* av = newAV(); - av_extend(av, 1); - av_store(av, 0, to_SV_pureperl(&THIS->a)); - av_store(av, 1, to_SV_pureperl(&THIS->b)); - return newRV_noinc((SV*)av); -} - -void from_SV(SV* poly_sv, MultiPoint* THIS) -{ - AV* poly_av = (AV*)SvRV(poly_sv); - const unsigned int num_points = av_len(poly_av)+1; - THIS->points.resize(num_points); - - for (unsigned int i = 0; i < num_points; i++) { - SV** point_sv = av_fetch(poly_av, i, 0); - from_SV_check(*point_sv, &THIS->points[i]); - } -} - -void from_SV_check(SV* poly_sv, MultiPoint* THIS) -{ - if (sv_isobject(poly_sv) && (SvTYPE(SvRV(poly_sv)) == SVt_PVMG)) { - *THIS = *(MultiPoint*)SvIV((SV*)SvRV( poly_sv )); - } else { - from_SV(poly_sv, THIS); - } -} - -SV* to_AV(MultiPoint* THIS) -{ - const unsigned int num_points = THIS->points.size(); - AV* av = newAV(); - if (num_points > 0) av_extend(av, num_points-1); - for (unsigned int i = 0; i < num_points; i++) { - av_store(av, i, perl_to_SV_ref(THIS->points[i])); - } - return newRV_noinc((SV*)av); -} - -SV* to_SV_pureperl(const MultiPoint* THIS) -{ - const unsigned int num_points = THIS->points.size(); - AV* av = newAV(); - if (num_points > 0) av_extend(av, num_points-1); - for (unsigned int i = 0; i < num_points; i++) { - av_store(av, i, to_SV_pureperl(&THIS->points[i])); - } - return newRV_noinc((SV*)av); -} - -void from_SV_check(SV* poly_sv, Polygon* THIS) -{ - if (sv_isobject(poly_sv) && !sv_isa(poly_sv, perl_class_name(THIS)) && !sv_isa(poly_sv, perl_class_name_ref(THIS))) - CONFESS("Not a valid %s object", perl_class_name(THIS)); - - from_SV_check(poly_sv, (MultiPoint*)THIS); -} - -void from_SV_check(SV* poly_sv, Polyline* THIS) -{ - if (!sv_isa(poly_sv, perl_class_name(THIS)) && !sv_isa(poly_sv, perl_class_name_ref(THIS))) - CONFESS("Not a valid %s object", perl_class_name(THIS)); - - from_SV_check(poly_sv, (MultiPoint*)THIS); -} - -SV* to_SV_pureperl(const Point* THIS) -{ - AV* av = newAV(); - av_fill(av, 1); - av_store(av, 0, newSViv((*THIS)(0))); - av_store(av, 1, newSViv((*THIS)(1))); - return newRV_noinc((SV*)av); -} - -void from_SV(SV* point_sv, Point* point) -{ - AV* point_av = (AV*)SvRV(point_sv); - // get a double from Perl and round it, otherwise - // it would get truncated - (*point) = Point(SvNV(*av_fetch(point_av, 0, 0)), SvNV(*av_fetch(point_av, 1, 0))); -} - -void from_SV_check(SV* point_sv, Point* point) -{ - if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { - if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point))) - CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv)))); - *point = *(Point*)SvIV((SV*)SvRV( point_sv )); - } else { - from_SV(point_sv, point); - } -} - -SV* to_SV_pureperl(const Vec2d* point) -{ - AV* av = newAV(); - av_fill(av, 1); - av_store(av, 0, newSVnv((*point)(0))); - av_store(av, 1, newSVnv((*point)(1))); - return newRV_noinc((SV*)av); -} - -bool from_SV(SV* point_sv, Vec2d* point) -{ - AV* point_av = (AV*)SvRV(point_sv); - SV* sv_x = *av_fetch(point_av, 0, 0); - SV* sv_y = *av_fetch(point_av, 1, 0); - if (!looks_like_number(sv_x) || !looks_like_number(sv_y)) return false; - - *point = Vec2d(SvNV(sv_x), SvNV(sv_y)); - return true; -} - -bool from_SV_check(SV* point_sv, Vec2d* point) -{ - if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { - if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point))) - CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv)))); - *point = *(Vec2d*)SvIV((SV*)SvRV( point_sv )); - return true; - } else { - return from_SV(point_sv, point); - } -} - -void from_SV_check(SV* surface_sv, Surface* THIS) -{ - if (!sv_isa(surface_sv, perl_class_name(THIS)) && !sv_isa(surface_sv, perl_class_name_ref(THIS))) - CONFESS("Not a valid %s object", perl_class_name(THIS)); - // a XS Surface was supplied - *THIS = *(Surface *)SvIV((SV*)SvRV( surface_sv )); -} - -SV* to_SV(TriangleMesh* THIS) -{ - SV* sv = newSV(0); - sv_setref_pv( sv, perl_class_name(THIS), (void*)THIS ); - return sv; -} - -} -#endif diff --git a/xs/src/ppport.h b/xs/src/ppport.h deleted file mode 100644 index ec2f1cc..0000000 --- a/xs/src/ppport.h +++ /dev/null @@ -1,7063 +0,0 @@ -#if 0 -<<'SKIP'; -#endif -/* ----------------------------------------------------------------------- - - ppport.h -- Perl/Pollution/Portability Version 3.19 - - Automatically created by Devel::PPPort running under perl 5.010000. - - Do NOT edit this file directly! -- Edit PPPort_pm.PL and the - includes in parts/inc/ instead. - - Use 'perldoc ppport.h' to view the documentation below. - ----------------------------------------------------------------------- - -SKIP - -=pod - -=head1 NAME - -ppport.h - Perl/Pollution/Portability version 3.19 - -=head1 SYNOPSIS - - perl ppport.h [options] [source files] - - Searches current directory for files if no [source files] are given - - --help show short help - - --version show version - - --patch=file write one patch file with changes - --copy=suffix write changed copies with suffix - --diff=program use diff program and options - - --compat-version=version provide compatibility with Perl version - --cplusplus accept C++ comments - - --quiet don't output anything except fatal errors - --nodiag don't show diagnostics - --nohints don't show hints - --nochanges don't suggest changes - --nofilter don't filter input files - - --strip strip all script and doc functionality from - ppport.h - - --list-provided list provided API - --list-unsupported list unsupported API - --api-info=name show Perl API portability information - -=head1 COMPATIBILITY - -This version of F is designed to support operation with Perl -installations back to 5.003, and has been tested up to 5.10.0. - -=head1 OPTIONS - -=head2 --help - -Display a brief usage summary. - -=head2 --version - -Display the version of F. - -=head2 --patch=I - -If this option is given, a single patch file will be created if -any changes are suggested. This requires a working diff program -to be installed on your system. - -=head2 --copy=I - -If this option is given, a copy of each file will be saved with -the given suffix that contains the suggested changes. This does -not require any external programs. Note that this does not -automagially add a dot between the original filename and the -suffix. If you want the dot, you have to include it in the option -argument. - -If neither C<--patch> or C<--copy> are given, the default is to -simply print the diffs for each file. This requires either -C or a C program to be installed. - -=head2 --diff=I - -Manually set the diff program and options to use. The default -is to use C, when installed, and output unified -context diffs. - -=head2 --compat-version=I - -Tell F to check for compatibility with the given -Perl version. The default is to check for compatibility with Perl -version 5.003. You can use this option to reduce the output -of F if you intend to be backward compatible only -down to a certain Perl version. - -=head2 --cplusplus - -Usually, F will detect C++ style comments and -replace them with C style comments for portability reasons. -Using this option instructs F to leave C++ -comments untouched. - -=head2 --quiet - -Be quiet. Don't print anything except fatal errors. - -=head2 --nodiag - -Don't output any diagnostic messages. Only portability -alerts will be printed. - -=head2 --nohints - -Don't output any hints. Hints often contain useful portability -notes. Warnings will still be displayed. - -=head2 --nochanges - -Don't suggest any changes. Only give diagnostic output and hints -unless these are also deactivated. - -=head2 --nofilter - -Don't filter the list of input files. By default, files not looking -like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped. - -=head2 --strip - -Strip all script and documentation functionality from F. -This reduces the size of F dramatically and may be useful -if you want to include F in smaller modules without -increasing their distribution size too much. - -The stripped F will have a C<--unstrip> option that allows -you to undo the stripping, but only if an appropriate C -module is installed. - -=head2 --list-provided - -Lists the API elements for which compatibility is provided by -F. Also lists if it must be explicitly requested, -if it has dependencies, and if there are hints or warnings for it. - -=head2 --list-unsupported - -Lists the API elements that are known not to be supported by -F and below which version of Perl they probably -won't be available or work. - -=head2 --api-info=I - -Show portability information for API elements matching I. -If I is surrounded by slashes, it is interpreted as a regular -expression. - -=head1 DESCRIPTION - -In order for a Perl extension (XS) module to be as portable as possible -across differing versions of Perl itself, certain steps need to be taken. - -=over 4 - -=item * - -Including this header is the first major one. This alone will give you -access to a large part of the Perl API that hasn't been available in -earlier Perl releases. Use - - perl ppport.h --list-provided - -to see which API elements are provided by ppport.h. - -=item * - -You should avoid using deprecated parts of the API. For example, using -global Perl variables without the C prefix is deprecated. Also, -some API functions used to have a C prefix. Using this form is -also deprecated. You can safely use the supported API, as F -will provide wrappers for older Perl versions. - -=item * - -If you use one of a few functions or variables that were not present in -earlier versions of Perl, and that can't be provided using a macro, you -have to explicitly request support for these functions by adding one or -more C<#define>s in your source code before the inclusion of F. - -These functions or variables will be marked C in the list shown -by C<--list-provided>. - -Depending on whether you module has a single or multiple files that -use such functions or variables, you want either C or global -variants. - -For a C function or variable (used only in a single source -file), use: - - #define NEED_function - #define NEED_variable - -For a global function or variable (used in multiple source files), -use: - - #define NEED_function_GLOBAL - #define NEED_variable_GLOBAL - -Note that you mustn't have more than one global request for the -same function or variable in your project. - - Function / Variable Static Request Global Request - ----------------------------------------------------------------------------------------- - PL_parser NEED_PL_parser NEED_PL_parser_GLOBAL - PL_signals NEED_PL_signals NEED_PL_signals_GLOBAL - eval_pv() NEED_eval_pv NEED_eval_pv_GLOBAL - grok_bin() NEED_grok_bin NEED_grok_bin_GLOBAL - grok_hex() NEED_grok_hex NEED_grok_hex_GLOBAL - grok_number() NEED_grok_number NEED_grok_number_GLOBAL - grok_numeric_radix() NEED_grok_numeric_radix NEED_grok_numeric_radix_GLOBAL - grok_oct() NEED_grok_oct NEED_grok_oct_GLOBAL - load_module() NEED_load_module NEED_load_module_GLOBAL - my_snprintf() NEED_my_snprintf NEED_my_snprintf_GLOBAL - my_sprintf() NEED_my_sprintf NEED_my_sprintf_GLOBAL - my_strlcat() NEED_my_strlcat NEED_my_strlcat_GLOBAL - my_strlcpy() NEED_my_strlcpy NEED_my_strlcpy_GLOBAL - newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL - newRV_noinc() NEED_newRV_noinc NEED_newRV_noinc_GLOBAL - newSV_type() NEED_newSV_type NEED_newSV_type_GLOBAL - newSVpvn_flags() NEED_newSVpvn_flags NEED_newSVpvn_flags_GLOBAL - newSVpvn_share() NEED_newSVpvn_share NEED_newSVpvn_share_GLOBAL - pv_display() NEED_pv_display NEED_pv_display_GLOBAL - pv_escape() NEED_pv_escape NEED_pv_escape_GLOBAL - pv_pretty() NEED_pv_pretty NEED_pv_pretty_GLOBAL - sv_2pv_flags() NEED_sv_2pv_flags NEED_sv_2pv_flags_GLOBAL - sv_2pvbyte() NEED_sv_2pvbyte NEED_sv_2pvbyte_GLOBAL - sv_catpvf_mg() NEED_sv_catpvf_mg NEED_sv_catpvf_mg_GLOBAL - sv_catpvf_mg_nocontext() NEED_sv_catpvf_mg_nocontext NEED_sv_catpvf_mg_nocontext_GLOBAL - sv_pvn_force_flags() NEED_sv_pvn_force_flags NEED_sv_pvn_force_flags_GLOBAL - sv_setpvf_mg() NEED_sv_setpvf_mg NEED_sv_setpvf_mg_GLOBAL - sv_setpvf_mg_nocontext() NEED_sv_setpvf_mg_nocontext NEED_sv_setpvf_mg_nocontext_GLOBAL - vload_module() NEED_vload_module NEED_vload_module_GLOBAL - vnewSVpvf() NEED_vnewSVpvf NEED_vnewSVpvf_GLOBAL - warner() NEED_warner NEED_warner_GLOBAL - -To avoid namespace conflicts, you can change the namespace of the -explicitly exported functions / variables using the C -macro. Just C<#define> the macro before including C: - - #define DPPP_NAMESPACE MyOwnNamespace_ - #include "ppport.h" - -The default namespace is C. - -=back - -The good thing is that most of the above can be checked by running -F on your source code. See the next section for -details. - -=head1 EXAMPLES - -To verify whether F is needed for your module, whether you -should make any changes to your code, and whether any special defines -should be used, F can be run as a Perl script to check your -source code. Simply say: - - perl ppport.h - -The result will usually be a list of patches suggesting changes -that should at least be acceptable, if not necessarily the most -efficient solution, or a fix for all possible problems. - -If you know that your XS module uses features only available in -newer Perl releases, if you're aware that it uses C++ comments, -and if you want all suggestions as a single patch file, you could -use something like this: - - perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff - -If you only want your code to be scanned without any suggestions -for changes, use: - - perl ppport.h --nochanges - -You can specify a different C program or options, using -the C<--diff> option: - - perl ppport.h --diff='diff -C 10' - -This would output context diffs with 10 lines of context. - -If you want to create patched copies of your files instead, use: - - perl ppport.h --copy=.new - -To display portability information for the C function, -use: - - perl ppport.h --api-info=newSVpvn - -Since the argument to C<--api-info> can be a regular expression, -you can use - - perl ppport.h --api-info=/_nomg$/ - -to display portability information for all C<_nomg> functions or - - perl ppport.h --api-info=/./ - -to display information for all known API elements. - -=head1 BUGS - -If this version of F is causing failure during -the compilation of this module, please check if newer versions -of either this module or C are available on CPAN -before sending a bug report. - -If F was generated using the latest version of -C and is causing failure of this module, please -file a bug report using the CPAN Request Tracker at L. - -Please include the following information: - -=over 4 - -=item 1. - -The complete output from running "perl -V" - -=item 2. - -This file. - -=item 3. - -The name and version of the module you were trying to build. - -=item 4. - -A full log of the build that failed. - -=item 5. - -Any other information that you think could be relevant. - -=back - -For the latest version of this code, please get the C -module from CPAN. - -=head1 COPYRIGHT - -Version 3.x, Copyright (c) 2004-2009, Marcus Holland-Moritz. - -Version 2.x, Copyright (C) 2001, Paul Marquess. - -Version 1.x, Copyright (C) 1999, Kenneth Albanowski. - -This program is free software; you can redistribute it and/or -modify it under the same terms as Perl itself. - -=head1 SEE ALSO - -See L. - -=cut - -use strict; - -# Disable broken TRIE-optimization -BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 } - -my $VERSION = 3.19; - -my %opt = ( - quiet => 0, - diag => 1, - hints => 1, - changes => 1, - cplusplus => 0, - filter => 1, - strip => 0, - version => 0, -); - -my($ppport) = $0 =~ /([\w.]+)$/; -my $LF = '(?:\r\n|[\r\n])'; # line feed -my $HS = "[ \t]"; # horizontal whitespace - -# Never use C comments in this file! -my $ccs = '/'.'*'; -my $cce = '*'.'/'; -my $rccs = quotemeta $ccs; -my $rcce = quotemeta $cce; - -eval { - require Getopt::Long; - Getopt::Long::GetOptions(\%opt, qw( - help quiet diag! filter! hints! changes! cplusplus strip version - patch=s copy=s diff=s compat-version=s - list-provided list-unsupported api-info=s - )) or usage(); -}; - -if ($@ and grep /^-/, @ARGV) { - usage() if "@ARGV" =~ /^--?h(?:elp)?$/; - die "Getopt::Long not found. Please don't use any options.\n"; -} - -if ($opt{version}) { - print "This is $0 $VERSION.\n"; - exit 0; -} - -usage() if $opt{help}; -strip() if $opt{strip}; - -if (exists $opt{'compat-version'}) { - my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) }; - if ($@) { - die "Invalid version number format: '$opt{'compat-version'}'\n"; - } - die "Only Perl 5 is supported\n" if $r != 5; - die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000; - $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s; -} -else { - $opt{'compat-version'} = 5; -} - -my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/ - ? ( $1 => { - ($2 ? ( base => $2 ) : ()), - ($3 ? ( todo => $3 ) : ()), - (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()), - (index($4, 'p') >= 0 ? ( provided => 1 ) : ()), - (index($4, 'n') >= 0 ? ( nothxarg => 1 ) : ()), - } ) - : die "invalid spec: $_" } qw( -AvFILLp|5.004050||p -AvFILL||| -CLASS|||n -CPERLscope|5.005000||p -CX_CURPAD_SAVE||| -CX_CURPAD_SV||| -CopFILEAV|5.006000||p -CopFILEGV_set|5.006000||p -CopFILEGV|5.006000||p -CopFILESV|5.006000||p -CopFILE_set|5.006000||p -CopFILE|5.006000||p -CopSTASHPV_set|5.006000||p -CopSTASHPV|5.006000||p -CopSTASH_eq|5.006000||p -CopSTASH_set|5.006000||p -CopSTASH|5.006000||p -CopyD|5.009002||p -Copy||| -CvPADLIST||| -CvSTASH||| -CvWEAKOUTSIDE||| -DEFSV_set|5.011000||p -DEFSV|5.004050||p -END_EXTERN_C|5.005000||p -ENTER||| -ERRSV|5.004050||p -EXTEND||| -EXTERN_C|5.005000||p -F0convert|||n -FREETMPS||| -GIMME_V||5.004000|n -GIMME|||n -GROK_NUMERIC_RADIX|5.007002||p -G_ARRAY||| -G_DISCARD||| -G_EVAL||| -G_METHOD|5.006001||p -G_NOARGS||| -G_SCALAR||| -G_VOID||5.004000| -GetVars||| -GvSVn|5.009003||p -GvSV||| -Gv_AMupdate||| -HEf_SVKEY||5.004000| -HeHASH||5.004000| -HeKEY||5.004000| -HeKLEN||5.004000| -HePV||5.004000| -HeSVKEY_force||5.004000| -HeSVKEY_set||5.004000| -HeSVKEY||5.004000| -HeUTF8||5.011000| -HeVAL||5.004000| -HvNAMELEN_get|5.009003||p -HvNAME_get|5.009003||p -HvNAME||| -INT2PTR|5.006000||p -IN_LOCALE_COMPILETIME|5.007002||p -IN_LOCALE_RUNTIME|5.007002||p -IN_LOCALE|5.007002||p -IN_PERL_COMPILETIME|5.008001||p -IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p -IS_NUMBER_INFINITY|5.007002||p -IS_NUMBER_IN_UV|5.007002||p -IS_NUMBER_NAN|5.007003||p -IS_NUMBER_NEG|5.007002||p -IS_NUMBER_NOT_INT|5.007002||p -IVSIZE|5.006000||p -IVTYPE|5.006000||p -IVdf|5.006000||p -LEAVE||| -LVRET||| -MARK||| -MULTICALL||5.011000| -MY_CXT_CLONE|5.009002||p -MY_CXT_INIT|5.007003||p -MY_CXT|5.007003||p -MoveD|5.009002||p -Move||| -NOOP|5.005000||p -NUM2PTR|5.006000||p -NVTYPE|5.006000||p -NVef|5.006001||p -NVff|5.006001||p -NVgf|5.006001||p -Newxc|5.009003||p -Newxz|5.009003||p -Newx|5.009003||p -Nullav||| -Nullch||| -Nullcv||| -Nullhv||| -Nullsv||| -ORIGMARK||| -PAD_BASE_SV||| -PAD_CLONE_VARS||| -PAD_COMPNAME_FLAGS||| -PAD_COMPNAME_GEN_set||| -PAD_COMPNAME_GEN||| -PAD_COMPNAME_OURSTASH||| -PAD_COMPNAME_PV||| -PAD_COMPNAME_TYPE||| -PAD_DUP||| -PAD_RESTORE_LOCAL||| -PAD_SAVE_LOCAL||| -PAD_SAVE_SETNULLPAD||| -PAD_SETSV||| -PAD_SET_CUR_NOSAVE||| -PAD_SET_CUR||| -PAD_SVl||| -PAD_SV||| -PERLIO_FUNCS_CAST|5.009003||p -PERLIO_FUNCS_DECL|5.009003||p -PERL_ABS|5.008001||p -PERL_BCDVERSION|5.011000||p -PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p -PERL_HASH|5.004000||p -PERL_INT_MAX|5.004000||p -PERL_INT_MIN|5.004000||p -PERL_LONG_MAX|5.004000||p -PERL_LONG_MIN|5.004000||p -PERL_MAGIC_arylen|5.007002||p -PERL_MAGIC_backref|5.007002||p -PERL_MAGIC_bm|5.007002||p -PERL_MAGIC_collxfrm|5.007002||p -PERL_MAGIC_dbfile|5.007002||p -PERL_MAGIC_dbline|5.007002||p -PERL_MAGIC_defelem|5.007002||p -PERL_MAGIC_envelem|5.007002||p -PERL_MAGIC_env|5.007002||p -PERL_MAGIC_ext|5.007002||p -PERL_MAGIC_fm|5.007002||p -PERL_MAGIC_glob|5.011000||p -PERL_MAGIC_isaelem|5.007002||p -PERL_MAGIC_isa|5.007002||p -PERL_MAGIC_mutex|5.011000||p -PERL_MAGIC_nkeys|5.007002||p -PERL_MAGIC_overload_elem|5.007002||p -PERL_MAGIC_overload_table|5.007002||p -PERL_MAGIC_overload|5.007002||p -PERL_MAGIC_pos|5.007002||p -PERL_MAGIC_qr|5.007002||p -PERL_MAGIC_regdata|5.007002||p -PERL_MAGIC_regdatum|5.007002||p -PERL_MAGIC_regex_global|5.007002||p -PERL_MAGIC_shared_scalar|5.007003||p -PERL_MAGIC_shared|5.007003||p -PERL_MAGIC_sigelem|5.007002||p -PERL_MAGIC_sig|5.007002||p -PERL_MAGIC_substr|5.007002||p -PERL_MAGIC_sv|5.007002||p -PERL_MAGIC_taint|5.007002||p -PERL_MAGIC_tiedelem|5.007002||p -PERL_MAGIC_tiedscalar|5.007002||p -PERL_MAGIC_tied|5.007002||p -PERL_MAGIC_utf8|5.008001||p -PERL_MAGIC_uvar_elem|5.007003||p -PERL_MAGIC_uvar|5.007002||p -PERL_MAGIC_vec|5.007002||p -PERL_MAGIC_vstring|5.008001||p -PERL_PV_ESCAPE_ALL|5.009004||p -PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p -PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p -PERL_PV_ESCAPE_NOCLEAR|5.009004||p -PERL_PV_ESCAPE_QUOTE|5.009004||p -PERL_PV_ESCAPE_RE|5.009005||p -PERL_PV_ESCAPE_UNI_DETECT|5.009004||p -PERL_PV_ESCAPE_UNI|5.009004||p -PERL_PV_PRETTY_DUMP|5.009004||p -PERL_PV_PRETTY_ELLIPSES|5.010000||p -PERL_PV_PRETTY_LTGT|5.009004||p -PERL_PV_PRETTY_NOCLEAR|5.010000||p -PERL_PV_PRETTY_QUOTE|5.009004||p -PERL_PV_PRETTY_REGPROP|5.009004||p -PERL_QUAD_MAX|5.004000||p -PERL_QUAD_MIN|5.004000||p -PERL_REVISION|5.006000||p -PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p -PERL_SCAN_DISALLOW_PREFIX|5.007003||p -PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p -PERL_SCAN_SILENT_ILLDIGIT|5.008001||p -PERL_SHORT_MAX|5.004000||p -PERL_SHORT_MIN|5.004000||p -PERL_SIGNALS_UNSAFE_FLAG|5.008001||p -PERL_SUBVERSION|5.006000||p -PERL_SYS_INIT3||5.006000| -PERL_SYS_INIT||| -PERL_SYS_TERM||5.011000| -PERL_UCHAR_MAX|5.004000||p -PERL_UCHAR_MIN|5.004000||p -PERL_UINT_MAX|5.004000||p -PERL_UINT_MIN|5.004000||p -PERL_ULONG_MAX|5.004000||p -PERL_ULONG_MIN|5.004000||p -PERL_UNUSED_ARG|5.009003||p -PERL_UNUSED_CONTEXT|5.009004||p -PERL_UNUSED_DECL|5.007002||p -PERL_UNUSED_VAR|5.007002||p -PERL_UQUAD_MAX|5.004000||p -PERL_UQUAD_MIN|5.004000||p -PERL_USE_GCC_BRACE_GROUPS|5.009004||p -PERL_USHORT_MAX|5.004000||p -PERL_USHORT_MIN|5.004000||p -PERL_VERSION|5.006000||p -PL_DBsignal|5.005000||p -PL_DBsingle|||pn -PL_DBsub|||pn -PL_DBtrace|||pn -PL_Sv|5.005000||p -PL_bufend|5.011000||p -PL_bufptr|5.011000||p -PL_compiling|5.004050||p -PL_copline|5.011000||p -PL_curcop|5.004050||p -PL_curstash|5.004050||p -PL_debstash|5.004050||p -PL_defgv|5.004050||p -PL_diehook|5.004050||p -PL_dirty|5.004050||p -PL_dowarn|||pn -PL_errgv|5.004050||p -PL_error_count|5.011000||p -PL_expect|5.011000||p -PL_hexdigit|5.005000||p -PL_hints|5.005000||p -PL_in_my_stash|5.011000||p -PL_in_my|5.011000||p -PL_last_in_gv|||n -PL_laststatval|5.005000||p -PL_lex_state|5.011000||p -PL_lex_stuff|5.011000||p -PL_linestr|5.011000||p -PL_modglobal||5.005000|n -PL_na|5.004050||pn -PL_no_modify|5.006000||p -PL_ofsgv|||n -PL_parser|5.009005||p -PL_perl_destruct_level|5.004050||p -PL_perldb|5.004050||p -PL_ppaddr|5.006000||p -PL_rsfp_filters|5.004050||p -PL_rsfp|5.004050||p -PL_rs|||n -PL_signals|5.008001||p -PL_stack_base|5.004050||p -PL_stack_sp|5.004050||p -PL_statcache|5.005000||p -PL_stdingv|5.004050||p -PL_sv_arenaroot|5.004050||p -PL_sv_no|5.004050||pn -PL_sv_undef|5.004050||pn -PL_sv_yes|5.004050||pn -PL_tainted|5.004050||p -PL_tainting|5.004050||p -PL_tokenbuf|5.011000||p -POP_MULTICALL||5.011000| -POPi|||n -POPl|||n -POPn|||n -POPpbytex||5.007001|n -POPpx||5.005030|n -POPp|||n -POPs|||n -PTR2IV|5.006000||p -PTR2NV|5.006000||p -PTR2UV|5.006000||p -PTR2nat|5.009003||p -PTR2ul|5.007001||p -PTRV|5.006000||p -PUSHMARK||| -PUSH_MULTICALL||5.011000| -PUSHi||| -PUSHmortal|5.009002||p -PUSHn||| -PUSHp||| -PUSHs||| -PUSHu|5.004000||p -PUTBACK||| -PerlIO_clearerr||5.007003| -PerlIO_close||5.007003| -PerlIO_context_layers||5.009004| -PerlIO_eof||5.007003| -PerlIO_error||5.007003| -PerlIO_fileno||5.007003| -PerlIO_fill||5.007003| -PerlIO_flush||5.007003| -PerlIO_get_base||5.007003| -PerlIO_get_bufsiz||5.007003| -PerlIO_get_cnt||5.007003| -PerlIO_get_ptr||5.007003| -PerlIO_read||5.007003| -PerlIO_seek||5.007003| -PerlIO_set_cnt||5.007003| -PerlIO_set_ptrcnt||5.007003| -PerlIO_setlinebuf||5.007003| -PerlIO_stderr||5.007003| -PerlIO_stdin||5.007003| -PerlIO_stdout||5.007003| -PerlIO_tell||5.007003| -PerlIO_unread||5.007003| -PerlIO_write||5.007003| -Perl_signbit||5.009005|n -PoisonFree|5.009004||p -PoisonNew|5.009004||p -PoisonWith|5.009004||p -Poison|5.008000||p -RETVAL|||n -Renewc||| -Renew||| -SAVECLEARSV||| -SAVECOMPPAD||| -SAVEPADSV||| -SAVETMPS||| -SAVE_DEFSV|5.004050||p -SPAGAIN||| -SP||| -START_EXTERN_C|5.005000||p -START_MY_CXT|5.007003||p -STMT_END|||p -STMT_START|||p -STR_WITH_LEN|5.009003||p -ST||| -SV_CONST_RETURN|5.009003||p -SV_COW_DROP_PV|5.008001||p -SV_COW_SHARED_HASH_KEYS|5.009005||p -SV_GMAGIC|5.007002||p -SV_HAS_TRAILING_NUL|5.009004||p -SV_IMMEDIATE_UNREF|5.007001||p -SV_MUTABLE_RETURN|5.009003||p -SV_NOSTEAL|5.009002||p -SV_SMAGIC|5.009003||p -SV_UTF8_NO_ENCODING|5.008001||p -SVfARG|5.009005||p -SVf_UTF8|5.006000||p -SVf|5.006000||p -SVt_IV||| -SVt_NV||| -SVt_PVAV||| -SVt_PVCV||| -SVt_PVHV||| -SVt_PVMG||| -SVt_PV||| -Safefree||| -Slab_Alloc||| -Slab_Free||| -Slab_to_rw||| -StructCopy||| -SvCUR_set||| -SvCUR||| -SvEND||| -SvGAMAGIC||5.006001| -SvGETMAGIC|5.004050||p -SvGROW||| -SvIOK_UV||5.006000| -SvIOK_notUV||5.006000| -SvIOK_off||| -SvIOK_only_UV||5.006000| -SvIOK_only||| -SvIOK_on||| -SvIOKp||| -SvIOK||| -SvIVX||| -SvIV_nomg|5.009001||p -SvIV_set||| -SvIVx||| -SvIV||| -SvIsCOW_shared_hash||5.008003| -SvIsCOW||5.008003| -SvLEN_set||| -SvLEN||| -SvLOCK||5.007003| -SvMAGIC_set|5.009003||p -SvNIOK_off||| -SvNIOKp||| -SvNIOK||| -SvNOK_off||| -SvNOK_only||| -SvNOK_on||| -SvNOKp||| -SvNOK||| -SvNVX||| -SvNV_set||| -SvNVx||| -SvNV||| -SvOK||| -SvOOK_offset||5.011000| -SvOOK||| -SvPOK_off||| -SvPOK_only_UTF8||5.006000| -SvPOK_only||| -SvPOK_on||| -SvPOKp||| -SvPOK||| -SvPVX_const|5.009003||p -SvPVX_mutable|5.009003||p -SvPVX||| -SvPV_const|5.009003||p -SvPV_flags_const_nolen|5.009003||p -SvPV_flags_const|5.009003||p -SvPV_flags_mutable|5.009003||p -SvPV_flags|5.007002||p -SvPV_force_flags_mutable|5.009003||p -SvPV_force_flags_nolen|5.009003||p -SvPV_force_flags|5.007002||p -SvPV_force_mutable|5.009003||p -SvPV_force_nolen|5.009003||p -SvPV_force_nomg_nolen|5.009003||p -SvPV_force_nomg|5.007002||p -SvPV_force|||p -SvPV_mutable|5.009003||p -SvPV_nolen_const|5.009003||p -SvPV_nolen|5.006000||p -SvPV_nomg_const_nolen|5.009003||p -SvPV_nomg_const|5.009003||p -SvPV_nomg|5.007002||p -SvPV_renew|5.009003||p -SvPV_set||| -SvPVbyte_force||5.009002| -SvPVbyte_nolen||5.006000| -SvPVbytex_force||5.006000| -SvPVbytex||5.006000| -SvPVbyte|5.006000||p -SvPVutf8_force||5.006000| -SvPVutf8_nolen||5.006000| -SvPVutf8x_force||5.006000| -SvPVutf8x||5.006000| -SvPVutf8||5.006000| -SvPVx||| -SvPV||| -SvREFCNT_dec||| -SvREFCNT_inc_NN|5.009004||p -SvREFCNT_inc_simple_NN|5.009004||p -SvREFCNT_inc_simple_void_NN|5.009004||p -SvREFCNT_inc_simple_void|5.009004||p -SvREFCNT_inc_simple|5.009004||p -SvREFCNT_inc_void_NN|5.009004||p -SvREFCNT_inc_void|5.009004||p -SvREFCNT_inc|||p -SvREFCNT||| -SvROK_off||| -SvROK_on||| -SvROK||| -SvRV_set|5.009003||p -SvRV||| -SvRXOK||5.009005| -SvRX||5.009005| -SvSETMAGIC||| -SvSHARED_HASH|5.009003||p -SvSHARE||5.007003| -SvSTASH_set|5.009003||p -SvSTASH||| -SvSetMagicSV_nosteal||5.004000| -SvSetMagicSV||5.004000| -SvSetSV_nosteal||5.004000| -SvSetSV||| -SvTAINTED_off||5.004000| -SvTAINTED_on||5.004000| -SvTAINTED||5.004000| -SvTAINT||| -SvTRUE||| -SvTYPE||| -SvUNLOCK||5.007003| -SvUOK|5.007001|5.006000|p -SvUPGRADE||| -SvUTF8_off||5.006000| -SvUTF8_on||5.006000| -SvUTF8||5.006000| -SvUVXx|5.004000||p -SvUVX|5.004000||p -SvUV_nomg|5.009001||p -SvUV_set|5.009003||p -SvUVx|5.004000||p -SvUV|5.004000||p -SvVOK||5.008001| -SvVSTRING_mg|5.009004||p -THIS|||n -UNDERBAR|5.009002||p -UTF8_MAXBYTES|5.009002||p -UVSIZE|5.006000||p -UVTYPE|5.006000||p -UVXf|5.007001||p -UVof|5.006000||p -UVuf|5.006000||p -UVxf|5.006000||p -WARN_ALL|5.006000||p -WARN_AMBIGUOUS|5.006000||p -WARN_ASSERTIONS|5.011000||p -WARN_BAREWORD|5.006000||p -WARN_CLOSED|5.006000||p -WARN_CLOSURE|5.006000||p -WARN_DEBUGGING|5.006000||p -WARN_DEPRECATED|5.006000||p -WARN_DIGIT|5.006000||p -WARN_EXEC|5.006000||p -WARN_EXITING|5.006000||p -WARN_GLOB|5.006000||p -WARN_INPLACE|5.006000||p -WARN_INTERNAL|5.006000||p -WARN_IO|5.006000||p -WARN_LAYER|5.008000||p -WARN_MALLOC|5.006000||p -WARN_MISC|5.006000||p -WARN_NEWLINE|5.006000||p -WARN_NUMERIC|5.006000||p -WARN_ONCE|5.006000||p -WARN_OVERFLOW|5.006000||p -WARN_PACK|5.006000||p -WARN_PARENTHESIS|5.006000||p -WARN_PIPE|5.006000||p -WARN_PORTABLE|5.006000||p -WARN_PRECEDENCE|5.006000||p -WARN_PRINTF|5.006000||p -WARN_PROTOTYPE|5.006000||p -WARN_QW|5.006000||p -WARN_RECURSION|5.006000||p -WARN_REDEFINE|5.006000||p -WARN_REGEXP|5.006000||p -WARN_RESERVED|5.006000||p -WARN_SEMICOLON|5.006000||p -WARN_SEVERE|5.006000||p -WARN_SIGNAL|5.006000||p -WARN_SUBSTR|5.006000||p -WARN_SYNTAX|5.006000||p -WARN_TAINT|5.006000||p -WARN_THREADS|5.008000||p -WARN_UNINITIALIZED|5.006000||p -WARN_UNOPENED|5.006000||p -WARN_UNPACK|5.006000||p -WARN_UNTIE|5.006000||p -WARN_UTF8|5.006000||p -WARN_VOID|5.006000||p -XCPT_CATCH|5.009002||p -XCPT_RETHROW|5.009002||p -XCPT_TRY_END|5.009002||p -XCPT_TRY_START|5.009002||p -XPUSHi||| -XPUSHmortal|5.009002||p -XPUSHn||| -XPUSHp||| -XPUSHs||| -XPUSHu|5.004000||p -XSPROTO|5.010000||p -XSRETURN_EMPTY||| -XSRETURN_IV||| -XSRETURN_NO||| -XSRETURN_NV||| -XSRETURN_PV||| -XSRETURN_UNDEF||| -XSRETURN_UV|5.008001||p -XSRETURN_YES||| -XSRETURN|||p -XST_mIV||| -XST_mNO||| -XST_mNV||| -XST_mPV||| -XST_mUNDEF||| -XST_mUV|5.008001||p -XST_mYES||| -XS_VERSION_BOOTCHECK||| -XS_VERSION||| -XSprePUSH|5.006000||p -XS||| -ZeroD|5.009002||p -Zero||| -_aMY_CXT|5.007003||p -_pMY_CXT|5.007003||p -aMY_CXT_|5.007003||p -aMY_CXT|5.007003||p -aTHXR_|5.011000||p -aTHXR|5.011000||p -aTHX_|5.006000||p -aTHX|5.006000||p -add_data|||n -addmad||| -allocmy||| -amagic_call||| -amagic_cmp_locale||| -amagic_cmp||| -amagic_i_ncmp||| -amagic_ncmp||| -any_dup||| -ao||| -append_elem||| -append_list||| -append_madprops||| -apply_attrs_my||| -apply_attrs_string||5.006001| -apply_attrs||| -apply||| -atfork_lock||5.007003|n -atfork_unlock||5.007003|n -av_arylen_p||5.009003| -av_clear||| -av_create_and_push||5.009005| -av_create_and_unshift_one||5.009005| -av_delete||5.006000| -av_exists||5.006000| -av_extend||| -av_fetch||| -av_fill||| -av_iter_p||5.011000| -av_len||| -av_make||| -av_pop||| -av_push||| -av_reify||| -av_shift||| -av_store||| -av_undef||| -av_unshift||| -ax|||n -bad_type||| -bind_match||| -block_end||| -block_gimme||5.004000| -block_start||| -boolSV|5.004000||p -boot_core_PerlIO||| -boot_core_UNIVERSAL||| -boot_core_mro||| -bytes_from_utf8||5.007001| -bytes_to_uni|||n -bytes_to_utf8||5.006001| -call_argv|5.006000||p -call_atexit||5.006000| -call_list||5.004000| -call_method|5.006000||p -call_pv|5.006000||p -call_sv|5.006000||p -calloc||5.007002|n -cando||| -cast_i32||5.006000| -cast_iv||5.006000| -cast_ulong||5.006000| -cast_uv||5.006000| -check_type_and_open||| -check_uni||| -checkcomma||| -checkposixcc||| -ckWARN|5.006000||p -ck_anoncode||| -ck_bitop||| -ck_concat||| -ck_defined||| -ck_delete||| -ck_die||| -ck_each||| -ck_eof||| -ck_eval||| -ck_exec||| -ck_exists||| -ck_exit||| -ck_ftst||| -ck_fun||| -ck_glob||| -ck_grep||| -ck_index||| -ck_join||| -ck_lfun||| -ck_listiob||| -ck_match||| -ck_method||| -ck_null||| -ck_open||| -ck_readline||| -ck_repeat||| -ck_require||| -ck_return||| -ck_rfun||| -ck_rvconst||| -ck_sassign||| -ck_select||| -ck_shift||| -ck_sort||| -ck_spair||| -ck_split||| -ck_subr||| -ck_substr||| -ck_svconst||| -ck_trunc||| -ck_unpack||| -ckwarn_d||5.009003| -ckwarn||5.009003| -cl_and|||n -cl_anything|||n -cl_init_zero|||n -cl_init|||n -cl_is_anything|||n -cl_or|||n -clear_placeholders||| -closest_cop||| -convert||| -cop_free||| -cr_textfilter||| -create_eval_scope||| -croak_nocontext|||vn -croak_xs_usage||5.011000| -croak|||v -csighandler||5.009003|n -curmad||| -custom_op_desc||5.007003| -custom_op_name||5.007003| -cv_ckproto_len||| -cv_clone||| -cv_const_sv||5.004000| -cv_dump||| -cv_undef||| -cx_dump||5.005000| -cx_dup||| -cxinc||| -dAXMARK|5.009003||p -dAX|5.007002||p -dITEMS|5.007002||p -dMARK||| -dMULTICALL||5.009003| -dMY_CXT_SV|5.007003||p -dMY_CXT|5.007003||p -dNOOP|5.006000||p -dORIGMARK||| -dSP||| -dTHR|5.004050||p -dTHXR|5.011000||p -dTHXa|5.006000||p -dTHXoa|5.006000||p -dTHX|5.006000||p -dUNDERBAR|5.009002||p -dVAR|5.009003||p -dXCPT|5.009002||p -dXSARGS||| -dXSI32||| -dXSTARG|5.006000||p -deb_curcv||| -deb_nocontext|||vn -deb_stack_all||| -deb_stack_n||| -debop||5.005000| -debprofdump||5.005000| -debprof||| -debstackptrs||5.007003| -debstack||5.007003| -debug_start_match||| -deb||5.007003|v -del_sv||| -delete_eval_scope||| -delimcpy||5.004000| -deprecate_old||| -deprecate||| -despatch_signals||5.007001| -destroy_matcher||| -die_nocontext|||vn -die_where||| -die|||v -dirp_dup||| -div128||| -djSP||| -do_aexec5||| -do_aexec||| -do_aspawn||| -do_binmode||5.004050| -do_chomp||| -do_chop||| -do_close||| -do_dump_pad||| -do_eof||| -do_exec3||| -do_execfree||| -do_exec||| -do_gv_dump||5.006000| -do_gvgv_dump||5.006000| -do_hv_dump||5.006000| -do_ipcctl||| -do_ipcget||| -do_join||| -do_kv||| -do_magic_dump||5.006000| -do_msgrcv||| -do_msgsnd||| -do_oddball||| -do_op_dump||5.006000| -do_op_xmldump||| -do_open9||5.006000| -do_openn||5.007001| -do_open||5.004000| -do_pmop_dump||5.006000| -do_pmop_xmldump||| -do_print||| -do_readline||| -do_seek||| -do_semop||| -do_shmio||| -do_smartmatch||| -do_spawn_nowait||| -do_spawn||| -do_sprintf||| -do_sv_dump||5.006000| -do_sysseek||| -do_tell||| -do_trans_complex_utf8||| -do_trans_complex||| -do_trans_count_utf8||| -do_trans_count||| -do_trans_simple_utf8||| -do_trans_simple||| -do_trans||| -do_vecget||| -do_vecset||| -do_vop||| -docatch||| -doeval||| -dofile||| -dofindlabel||| -doform||| -doing_taint||5.008001|n -dooneliner||| -doopen_pm||| -doparseform||| -dopoptoeval||| -dopoptogiven||| -dopoptolabel||| -dopoptoloop||| -dopoptosub_at||| -dopoptowhen||| -doref||5.009003| -dounwind||| -dowantarray||| -dump_all||5.006000| -dump_eval||5.006000| -dump_exec_pos||| -dump_fds||| -dump_form||5.006000| -dump_indent||5.006000|v -dump_mstats||| -dump_packsubs||5.006000| -dump_sub||5.006000| -dump_sv_child||| -dump_trie_interim_list||| -dump_trie_interim_table||| -dump_trie||| -dump_vindent||5.006000| -dumpuntil||| -dup_attrlist||| -emulate_cop_io||| -eval_pv|5.006000||p -eval_sv|5.006000||p -exec_failed||| -expect_number||| -fbm_compile||5.005000| -fbm_instr||5.005000| -feature_is_enabled||| -fetch_cop_label||5.011000| -filter_add||| -filter_del||| -filter_gets||| -filter_read||| -find_and_forget_pmops||| -find_array_subscript||| -find_beginning||| -find_byclass||| -find_hash_subscript||| -find_in_my_stash||| -find_runcv||5.008001| -find_rundefsvoffset||5.009002| -find_script||| -find_uninit_var||| -first_symbol|||n -fold_constants||| -forbid_setid||| -force_ident||| -force_list||| -force_next||| -force_version||| -force_word||| -forget_pmop||| -form_nocontext|||vn -form||5.004000|v -fp_dup||| -fprintf_nocontext|||vn -free_global_struct||| -free_tied_hv_pool||| -free_tmps||| -gen_constant_list||| -get_arena||| -get_aux_mg||| -get_av|5.006000||p -get_context||5.006000|n -get_cvn_flags||5.009005| -get_cv|5.006000||p -get_db_sub||| -get_debug_opts||| -get_hash_seed||| -get_hv|5.006000||p -get_isa_hash||| -get_mstats||| -get_no_modify||| -get_num||| -get_op_descs||5.005000| -get_op_names||5.005000| -get_opargs||| -get_ppaddr||5.006000| -get_re_arg||| -get_sv|5.006000||p -get_vtbl||5.005030| -getcwd_sv||5.007002| -getenv_len||| -glob_2number||| -glob_assign_glob||| -glob_assign_ref||| -gp_dup||| -gp_free||| -gp_ref||| -grok_bin|5.007003||p -grok_hex|5.007003||p -grok_number|5.007002||p -grok_numeric_radix|5.007002||p -grok_oct|5.007003||p -group_end||| -gv_AVadd||| -gv_HVadd||| -gv_IOadd||| -gv_SVadd||| -gv_autoload4||5.004000| -gv_check||| -gv_const_sv||5.009003| -gv_dump||5.006000| -gv_efullname3||5.004000| -gv_efullname4||5.006001| -gv_efullname||| -gv_ename||| -gv_fetchfile_flags||5.009005| -gv_fetchfile||| -gv_fetchmeth_autoload||5.007003| -gv_fetchmethod_autoload||5.004000| -gv_fetchmethod_flags||5.011000| -gv_fetchmethod||| -gv_fetchmeth||| -gv_fetchpvn_flags|5.009002||p -gv_fetchpvs|5.009004||p -gv_fetchpv||| -gv_fetchsv||5.009002| -gv_fullname3||5.004000| -gv_fullname4||5.006001| -gv_fullname||| -gv_get_super_pkg||| -gv_handler||5.007001| -gv_init_sv||| -gv_init||| -gv_name_set||5.009004| -gv_stashpvn|5.004000||p -gv_stashpvs|5.009003||p -gv_stashpv||| -gv_stashsv||| -he_dup||| -hek_dup||| -hfreeentries||| -hsplit||| -hv_assert||5.011000| -hv_auxinit|||n -hv_backreferences_p||| -hv_clear_placeholders||5.009001| -hv_clear||| -hv_common_key_len||5.010000| -hv_common||5.010000| -hv_copy_hints_hv||| -hv_delayfree_ent||5.004000| -hv_delete_common||| -hv_delete_ent||5.004000| -hv_delete||| -hv_eiter_p||5.009003| -hv_eiter_set||5.009003| -hv_exists_ent||5.004000| -hv_exists||| -hv_fetch_ent||5.004000| -hv_fetchs|5.009003||p -hv_fetch||| -hv_free_ent||5.004000| -hv_iterinit||| -hv_iterkeysv||5.004000| -hv_iterkey||| -hv_iternext_flags||5.008000| -hv_iternextsv||| -hv_iternext||| -hv_iterval||| -hv_kill_backrefs||| -hv_ksplit||5.004000| -hv_magic_check|||n -hv_magic||| -hv_name_set||5.009003| -hv_notallowed||| -hv_placeholders_get||5.009003| -hv_placeholders_p||5.009003| -hv_placeholders_set||5.009003| -hv_riter_p||5.009003| -hv_riter_set||5.009003| -hv_scalar||5.009001| -hv_store_ent||5.004000| -hv_store_flags||5.008000| -hv_stores|5.009004||p -hv_store||| -hv_undef||| -ibcmp_locale||5.004000| -ibcmp_utf8||5.007003| -ibcmp||| -incline||| -incpush_if_exists||| -incpush_use_sep||| -incpush||| -ingroup||| -init_argv_symbols||| -init_debugger||| -init_global_struct||| -init_i18nl10n||5.006000| -init_i18nl14n||5.006000| -init_ids||| -init_interp||| -init_main_stash||| -init_perllib||| -init_postdump_symbols||| -init_predump_symbols||| -init_stacks||5.005000| -init_tm||5.007002| -instr||| -intro_my||| -intuit_method||| -intuit_more||| -invert||| -io_close||| -isALNUMC|5.006000||p -isALNUM||| -isALPHA||| -isASCII|5.006000||p -isBLANK|5.006001||p -isCNTRL|5.006000||p -isDIGIT||| -isGRAPH|5.006000||p -isGV_with_GP|5.009004||p -isLOWER||| -isPRINT|5.004000||p -isPSXSPC|5.006001||p -isPUNCT|5.006000||p -isSPACE||| -isUPPER||| -isXDIGIT|5.006000||p -is_an_int||| -is_gv_magical_sv||| -is_handle_constructor|||n -is_list_assignment||| -is_lvalue_sub||5.007001| -is_uni_alnum_lc||5.006000| -is_uni_alnumc_lc||5.006000| -is_uni_alnumc||5.006000| -is_uni_alnum||5.006000| -is_uni_alpha_lc||5.006000| -is_uni_alpha||5.006000| -is_uni_ascii_lc||5.006000| -is_uni_ascii||5.006000| -is_uni_cntrl_lc||5.006000| -is_uni_cntrl||5.006000| -is_uni_digit_lc||5.006000| -is_uni_digit||5.006000| -is_uni_graph_lc||5.006000| -is_uni_graph||5.006000| -is_uni_idfirst_lc||5.006000| -is_uni_idfirst||5.006000| -is_uni_lower_lc||5.006000| -is_uni_lower||5.006000| -is_uni_print_lc||5.006000| -is_uni_print||5.006000| -is_uni_punct_lc||5.006000| -is_uni_punct||5.006000| -is_uni_space_lc||5.006000| -is_uni_space||5.006000| -is_uni_upper_lc||5.006000| -is_uni_upper||5.006000| -is_uni_xdigit_lc||5.006000| -is_uni_xdigit||5.006000| -is_utf8_alnumc||5.006000| -is_utf8_alnum||5.006000| -is_utf8_alpha||5.006000| -is_utf8_ascii||5.006000| -is_utf8_char_slow|||n -is_utf8_char||5.006000| -is_utf8_cntrl||5.006000| -is_utf8_common||| -is_utf8_digit||5.006000| -is_utf8_graph||5.006000| -is_utf8_idcont||5.008000| -is_utf8_idfirst||5.006000| -is_utf8_lower||5.006000| -is_utf8_mark||5.006000| -is_utf8_print||5.006000| -is_utf8_punct||5.006000| -is_utf8_space||5.006000| -is_utf8_string_loclen||5.009003| -is_utf8_string_loc||5.008001| -is_utf8_string||5.006001| -is_utf8_upper||5.006000| -is_utf8_xdigit||5.006000| -isa_lookup||| -items|||n -ix|||n -jmaybe||| -join_exact||| -keyword||| -leave_scope||| -lex_end||| -lex_start||| -linklist||| -listkids||| -list||| -load_module_nocontext|||vn -load_module|5.006000||pv -localize||| -looks_like_bool||| -looks_like_number||| -lop||| -mPUSHi|5.009002||p -mPUSHn|5.009002||p -mPUSHp|5.009002||p -mPUSHs|5.011000||p -mPUSHu|5.009002||p -mXPUSHi|5.009002||p -mXPUSHn|5.009002||p -mXPUSHp|5.009002||p -mXPUSHs|5.011000||p -mXPUSHu|5.009002||p -mad_free||| -madlex||| -madparse||| -magic_clear_all_env||| -magic_clearenv||| -magic_clearhint||| -magic_clearisa||| -magic_clearpack||| -magic_clearsig||| -magic_dump||5.006000| -magic_existspack||| -magic_freearylen_p||| -magic_freeovrld||| -magic_getarylen||| -magic_getdefelem||| -magic_getnkeys||| -magic_getpack||| -magic_getpos||| -magic_getsig||| -magic_getsubstr||| -magic_gettaint||| -magic_getuvar||| -magic_getvec||| -magic_get||| -magic_killbackrefs||| -magic_len||| -magic_methcall||| -magic_methpack||| -magic_nextpack||| -magic_regdata_cnt||| -magic_regdatum_get||| -magic_regdatum_set||| -magic_scalarpack||| -magic_set_all_env||| -magic_setamagic||| -magic_setarylen||| -magic_setcollxfrm||| -magic_setdbline||| -magic_setdefelem||| -magic_setenv||| -magic_sethint||| -magic_setisa||| -magic_setmglob||| -magic_setnkeys||| -magic_setpack||| -magic_setpos||| -magic_setregexp||| -magic_setsig||| -magic_setsubstr||| -magic_settaint||| -magic_setutf8||| -magic_setuvar||| -magic_setvec||| -magic_set||| -magic_sizepack||| -magic_wipepack||| -make_matcher||| -make_trie_failtable||| -make_trie||| -malloc_good_size|||n -malloced_size|||n -malloc||5.007002|n -markstack_grow||| -matcher_matches_sv||| -measure_struct||| -memEQ|5.004000||p -memNE|5.004000||p -mem_collxfrm||| -mem_log_common|||n -mess_alloc||| -mess_nocontext|||vn -mess||5.006000|v -method_common||| -mfree||5.007002|n -mg_clear||| -mg_copy||| -mg_dup||| -mg_find||| -mg_free||| -mg_get||| -mg_length||5.005000| -mg_localize||| -mg_magical||| -mg_set||| -mg_size||5.005000| -mini_mktime||5.007002| -missingterm||| -mode_from_discipline||| -modkids||| -mod||| -more_bodies||| -more_sv||| -moreswitches||| -mro_get_from_name||5.011000| -mro_get_linear_isa_dfs||| -mro_get_linear_isa||5.009005| -mro_get_private_data||5.011000| -mro_isa_changed_in||| -mro_meta_dup||| -mro_meta_init||| -mro_method_changed_in||5.009005| -mro_register||5.011000| -mro_set_mro||5.011000| -mro_set_private_data||5.011000| -mul128||| -mulexp10|||n -my_atof2||5.007002| -my_atof||5.006000| -my_attrs||| -my_bcopy|||n -my_betoh16|||n -my_betoh32|||n -my_betoh64|||n -my_betohi|||n -my_betohl|||n -my_betohs|||n -my_bzero|||n -my_chsize||| -my_clearenv||| -my_cxt_index||| -my_cxt_init||| -my_dirfd||5.009005| -my_exit_jump||| -my_exit||| -my_failure_exit||5.004000| -my_fflush_all||5.006000| -my_fork||5.007003|n -my_htobe16|||n -my_htobe32|||n -my_htobe64|||n -my_htobei|||n -my_htobel|||n -my_htobes|||n -my_htole16|||n -my_htole32|||n -my_htole64|||n -my_htolei|||n -my_htolel|||n -my_htoles|||n -my_htonl||| -my_kid||| -my_letoh16|||n -my_letoh32|||n -my_letoh64|||n -my_letohi|||n -my_letohl|||n -my_letohs|||n -my_lstat||| -my_memcmp||5.004000|n -my_memset|||n -my_ntohl||| -my_pclose||5.004000| -my_popen_list||5.007001| -my_popen||5.004000| -my_setenv||| -my_snprintf|5.009004||pvn -my_socketpair||5.007003|n -my_sprintf|5.009003||pvn -my_stat||| -my_strftime||5.007002| -my_strlcat|5.009004||pn -my_strlcpy|5.009004||pn -my_swabn|||n -my_swap||| -my_unexec||| -my_vsnprintf||5.009004|n -need_utf8|||n -newANONATTRSUB||5.006000| -newANONHASH||| -newANONLIST||| -newANONSUB||| -newASSIGNOP||| -newATTRSUB||5.006000| -newAVREF||| -newAV||| -newBINOP||| -newCONDOP||| -newCONSTSUB|5.004050||p -newCVREF||| -newDEFSVOP||| -newFORM||| -newFOROP||| -newGIVENOP||5.009003| -newGIVWHENOP||| -newGP||| -newGVOP||| -newGVREF||| -newGVgen||| -newHVREF||| -newHVhv||5.005000| -newHV||| -newIO||| -newLISTOP||| -newLOGOP||| -newLOOPEX||| -newLOOPOP||| -newMADPROP||| -newMADsv||| -newMYSUB||| -newNULLLIST||| -newOP||| -newPADOP||| -newPMOP||| -newPROG||| -newPVOP||| -newRANGE||| -newRV_inc|5.004000||p -newRV_noinc|5.004000||p -newRV||| -newSLICEOP||| -newSTATEOP||| -newSUB||| -newSVOP||| -newSVREF||| -newSV_type|5.009005||p -newSVhek||5.009003| -newSViv||| -newSVnv||| -newSVpvf_nocontext|||vn -newSVpvf||5.004000|v -newSVpvn_flags|5.011000||p -newSVpvn_share|5.007001||p -newSVpvn_utf8|5.011000||p -newSVpvn|5.004050||p -newSVpvs_flags|5.011000||p -newSVpvs_share||5.009003| -newSVpvs|5.009003||p -newSVpv||| -newSVrv||| -newSVsv||| -newSVuv|5.006000||p -newSV||| -newTOKEN||| -newUNOP||| -newWHENOP||5.009003| -newWHILEOP||5.009003| -newXS_flags||5.009004| -newXSproto||5.006000| -newXS||5.006000| -new_collate||5.006000| -new_constant||| -new_ctype||5.006000| -new_he||| -new_logop||| -new_numeric||5.006000| -new_stackinfo||5.005000| -new_version||5.009000| -new_warnings_bitfield||| -next_symbol||| -nextargv||| -nextchar||| -ninstr||| -no_bareword_allowed||| -no_fh_allowed||| -no_op||| -not_a_number||| -nothreadhook||5.008000| -nuke_stacks||| -num_overflow|||n -offer_nice_chunk||| -oopsAV||| -oopsHV||| -op_clear||| -op_const_sv||| -op_dump||5.006000| -op_free||| -op_getmad_weak||| -op_getmad||| -op_null||5.007002| -op_refcnt_dec||| -op_refcnt_inc||| -op_refcnt_lock||5.009002| -op_refcnt_unlock||5.009002| -op_xmldump||| -open_script||| -pMY_CXT_|5.007003||p -pMY_CXT|5.007003||p -pTHX_|5.006000||p -pTHX|5.006000||p -packWARN|5.007003||p -pack_cat||5.007003| -pack_rec||| -package||| -packlist||5.008001| -pad_add_anon||| -pad_add_name||| -pad_alloc||| -pad_block_start||| -pad_check_dup||| -pad_compname_type||| -pad_findlex||| -pad_findmy||| -pad_fixup_inner_anons||| -pad_free||| -pad_leavemy||| -pad_new||| -pad_peg|||n -pad_push||| -pad_reset||| -pad_setsv||| -pad_sv||5.011000| -pad_swipe||| -pad_tidy||| -pad_undef||| -parse_body||| -parse_unicode_opts||| -parser_dup||| -parser_free||| -path_is_absolute|||n -peep||| -pending_Slabs_to_ro||| -perl_alloc_using|||n -perl_alloc|||n -perl_clone_using|||n -perl_clone|||n -perl_construct|||n -perl_destruct||5.007003|n -perl_free|||n -perl_parse||5.006000|n -perl_run|||n -pidgone||| -pm_description||| -pmflag||| -pmop_dump||5.006000| -pmop_xmldump||| -pmruntime||| -pmtrans||| -pop_scope||| -pregcomp||5.009005| -pregexec||| -pregfree2||5.011000| -pregfree||| -prepend_elem||| -prepend_madprops||| -printbuf||| -printf_nocontext|||vn -process_special_blocks||| -ptr_table_clear||5.009005| -ptr_table_fetch||5.009005| -ptr_table_find|||n -ptr_table_free||5.009005| -ptr_table_new||5.009005| -ptr_table_split||5.009005| -ptr_table_store||5.009005| -push_scope||| -put_byte||| -pv_display|5.006000||p -pv_escape|5.009004||p -pv_pretty|5.009004||p -pv_uni_display||5.007003| -qerror||| -qsortsvu||| -re_compile||5.009005| -re_croak2||| -re_dup_guts||| -re_intuit_start||5.009005| -re_intuit_string||5.006000| -readpipe_override||| -realloc||5.007002|n -reentrant_free||| -reentrant_init||| -reentrant_retry|||vn -reentrant_size||| -ref_array_or_hash||| -refcounted_he_chain_2hv||| -refcounted_he_fetch||| -refcounted_he_free||| -refcounted_he_new_common||| -refcounted_he_new||| -refcounted_he_value||| -refkids||| -refto||| -ref||5.011000| -reg_check_named_buff_matched||| -reg_named_buff_all||5.009005| -reg_named_buff_exists||5.009005| -reg_named_buff_fetch||5.009005| -reg_named_buff_firstkey||5.009005| -reg_named_buff_iter||| -reg_named_buff_nextkey||5.009005| -reg_named_buff_scalar||5.009005| -reg_named_buff||| -reg_namedseq||| -reg_node||| -reg_numbered_buff_fetch||| -reg_numbered_buff_length||| -reg_numbered_buff_store||| -reg_qr_package||| -reg_recode||| -reg_scan_name||| -reg_skipcomment||| -reg_temp_copy||| -reganode||| -regatom||| -regbranch||| -regclass_swash||5.009004| -regclass||| -regcppop||| -regcppush||| -regcurly|||n -regdump_extflags||| -regdump||5.005000| -regdupe_internal||| -regexec_flags||5.005000| -regfree_internal||5.009005| -reghop3|||n -reghop4|||n -reghopmaybe3|||n -reginclass||| -reginitcolors||5.006000| -reginsert||| -regmatch||| -regnext||5.005000| -regpiece||| -regpposixcc||| -regprop||| -regrepeat||| -regtail_study||| -regtail||| -regtry||| -reguni||| -regwhite|||n -reg||| -repeatcpy||| -report_evil_fh||| -report_uninit||| -require_pv||5.006000| -require_tie_mod||| -restore_magic||| -rninstr||| -rsignal_restore||| -rsignal_save||| -rsignal_state||5.004000| -rsignal||5.004000| -run_body||| -run_user_filter||| -runops_debug||5.005000| -runops_standard||5.005000| -rvpv_dup||| -rxres_free||| -rxres_restore||| -rxres_save||| -safesyscalloc||5.006000|n -safesysfree||5.006000|n -safesysmalloc||5.006000|n -safesysrealloc||5.006000|n -same_dirent||| -save_I16||5.004000| -save_I32||| -save_I8||5.006000| -save_adelete||5.011000| -save_aelem||5.004050| -save_alloc||5.006000| -save_aptr||| -save_ary||| -save_bool||5.008001| -save_clearsv||| -save_delete||| -save_destructor_x||5.006000| -save_destructor||5.006000| -save_freeop||| -save_freepv||| -save_freesv||| -save_generic_pvref||5.006001| -save_generic_svref||5.005030| -save_gp||5.004000| -save_hash||| -save_hek_flags|||n -save_helem_flags||5.011000| -save_helem||5.004050| -save_hints||| -save_hptr||| -save_int||| -save_item||| -save_iv||5.005000| -save_lines||| -save_list||| -save_long||| -save_magic||| -save_mortalizesv||5.007001| -save_nogv||| -save_op||| -save_padsv_and_mortalize||5.011000| -save_pptr||| -save_pushi32ptr||| -save_pushptri32ptr||| -save_pushptrptr||| -save_pushptr||5.011000| -save_re_context||5.006000| -save_scalar_at||| -save_scalar||| -save_set_svflags||5.009000| -save_shared_pvref||5.007003| -save_sptr||| -save_svref||| -save_vptr||5.006000| -savepvn||| -savepvs||5.009003| -savepv||| -savesharedpvn||5.009005| -savesharedpv||5.007003| -savestack_grow_cnt||5.008001| -savestack_grow||| -savesvpv||5.009002| -sawparens||| -scalar_mod_type|||n -scalarboolean||| -scalarkids||| -scalarseq||| -scalarvoid||| -scalar||| -scan_bin||5.006000| -scan_commit||| -scan_const||| -scan_formline||| -scan_heredoc||| -scan_hex||| -scan_ident||| -scan_inputsymbol||| -scan_num||5.007001| -scan_oct||| -scan_pat||| -scan_str||| -scan_subst||| -scan_trans||| -scan_version||5.009001| -scan_vstring||5.009005| -scan_word||| -scope||| -screaminstr||5.005000| -search_const||| -seed||5.008001| -sequence_num||| -sequence_tail||| -sequence||| -set_context||5.006000|n -set_numeric_local||5.006000| -set_numeric_radix||5.006000| -set_numeric_standard||5.006000| -setdefout||| -share_hek_flags||| -share_hek||5.004000| -si_dup||| -sighandler|||n -simplify_sort||| -skipspace0||| -skipspace1||| -skipspace2||| -skipspace||| -softref2xv||| -sortcv_stacked||| -sortcv_xsub||| -sortcv||| -sortsv_flags||5.009003| -sortsv||5.007003| -space_join_names_mortal||| -ss_dup||| -stack_grow||| -start_force||| -start_glob||| -start_subparse||5.004000| -stashpv_hvname_match||5.011000| -stdize_locale||| -store_cop_label||| -strEQ||| -strGE||| -strGT||| -strLE||| -strLT||| -strNE||| -str_to_version||5.006000| -strip_return||| -strnEQ||| -strnNE||| -study_chunk||| -sub_crush_depth||| -sublex_done||| -sublex_push||| -sublex_start||| -sv_2bool||| -sv_2cv||| -sv_2io||| -sv_2iuv_common||| -sv_2iuv_non_preserve||| -sv_2iv_flags||5.009001| -sv_2iv||| -sv_2mortal||| -sv_2num||| -sv_2nv||| -sv_2pv_flags|5.007002||p -sv_2pv_nolen|5.006000||p -sv_2pvbyte_nolen|5.006000||p -sv_2pvbyte|5.006000||p -sv_2pvutf8_nolen||5.006000| -sv_2pvutf8||5.006000| -sv_2pv||| -sv_2uv_flags||5.009001| -sv_2uv|5.004000||p -sv_add_arena||| -sv_add_backref||| -sv_backoff||| -sv_bless||| -sv_cat_decode||5.008001| -sv_catpv_mg|5.004050||p -sv_catpvf_mg_nocontext|||pvn -sv_catpvf_mg|5.006000|5.004000|pv -sv_catpvf_nocontext|||vn -sv_catpvf||5.004000|v -sv_catpvn_flags||5.007002| -sv_catpvn_mg|5.004050||p -sv_catpvn_nomg|5.007002||p -sv_catpvn||| -sv_catpvs|5.009003||p -sv_catpv||| -sv_catsv_flags||5.007002| -sv_catsv_mg|5.004050||p -sv_catsv_nomg|5.007002||p -sv_catsv||| -sv_catxmlpvn||| -sv_catxmlsv||| -sv_chop||| -sv_clean_all||| -sv_clean_objs||| -sv_clear||| -sv_cmp_locale||5.004000| -sv_cmp||| -sv_collxfrm||| -sv_compile_2op||5.008001| -sv_copypv||5.007003| -sv_dec||| -sv_del_backref||| -sv_derived_from||5.004000| -sv_destroyable||5.010000| -sv_does||5.009004| -sv_dump||| -sv_dup_inc_multiple||| -sv_dup||| -sv_eq||| -sv_exp_grow||| -sv_force_normal_flags||5.007001| -sv_force_normal||5.006000| -sv_free2||| -sv_free_arenas||| -sv_free||| -sv_gets||5.004000| -sv_grow||| -sv_i_ncmp||| -sv_inc||| -sv_insert_flags||5.011000| -sv_insert||| -sv_isa||| -sv_isobject||| -sv_iv||5.005000| -sv_kill_backrefs||| -sv_len_utf8||5.006000| -sv_len||| -sv_magic_portable|5.011000|5.004000|p -sv_magicext||5.007003| -sv_magic||| -sv_mortalcopy||| -sv_ncmp||| -sv_newmortal||| -sv_newref||| -sv_nolocking||5.007003| -sv_nosharing||5.007003| -sv_nounlocking||| -sv_nv||5.005000| -sv_peek||5.005000| -sv_pos_b2u_midway||| -sv_pos_b2u||5.006000| -sv_pos_u2b_cached||| -sv_pos_u2b_forwards|||n -sv_pos_u2b_midway|||n -sv_pos_u2b||5.006000| -sv_pvbyten_force||5.006000| -sv_pvbyten||5.006000| -sv_pvbyte||5.006000| -sv_pvn_force_flags|5.007002||p -sv_pvn_force||| -sv_pvn_nomg|5.007003|5.005000|p -sv_pvn||5.005000| -sv_pvutf8n_force||5.006000| -sv_pvutf8n||5.006000| -sv_pvutf8||5.006000| -sv_pv||5.006000| -sv_recode_to_utf8||5.007003| -sv_reftype||| -sv_release_COW||| -sv_replace||| -sv_report_used||| -sv_reset||| -sv_rvweaken||5.006000| -sv_setiv_mg|5.004050||p -sv_setiv||| -sv_setnv_mg|5.006000||p -sv_setnv||| -sv_setpv_mg|5.004050||p -sv_setpvf_mg_nocontext|||pvn -sv_setpvf_mg|5.006000|5.004000|pv -sv_setpvf_nocontext|||vn -sv_setpvf||5.004000|v -sv_setpviv_mg||5.008001| -sv_setpviv||5.008001| -sv_setpvn_mg|5.004050||p -sv_setpvn||| -sv_setpvs|5.009004||p -sv_setpv||| -sv_setref_iv||| -sv_setref_nv||| -sv_setref_pvn||| -sv_setref_pv||| -sv_setref_uv||5.007001| -sv_setsv_cow||| -sv_setsv_flags||5.007002| -sv_setsv_mg|5.004050||p -sv_setsv_nomg|5.007002||p -sv_setsv||| -sv_setuv_mg|5.004050||p -sv_setuv|5.004000||p -sv_tainted||5.004000| -sv_taint||5.004000| -sv_true||5.005000| -sv_unglob||| -sv_uni_display||5.007003| -sv_unmagic||| -sv_unref_flags||5.007001| -sv_unref||| -sv_untaint||5.004000| -sv_upgrade||| -sv_usepvn_flags||5.009004| -sv_usepvn_mg|5.004050||p -sv_usepvn||| -sv_utf8_decode||5.006000| -sv_utf8_downgrade||5.006000| -sv_utf8_encode||5.006000| -sv_utf8_upgrade_flags_grow||5.011000| -sv_utf8_upgrade_flags||5.007002| -sv_utf8_upgrade_nomg||5.007002| -sv_utf8_upgrade||5.007001| -sv_uv|5.005000||p -sv_vcatpvf_mg|5.006000|5.004000|p -sv_vcatpvfn||5.004000| -sv_vcatpvf|5.006000|5.004000|p -sv_vsetpvf_mg|5.006000|5.004000|p -sv_vsetpvfn||5.004000| -sv_vsetpvf|5.006000|5.004000|p -sv_xmlpeek||| -svtype||| -swallow_bom||| -swap_match_buff||| -swash_fetch||5.007002| -swash_get||| -swash_init||5.006000| -sys_init3||5.010000|n -sys_init||5.010000|n -sys_intern_clear||| -sys_intern_dup||| -sys_intern_init||| -sys_term||5.010000|n -taint_env||| -taint_proper||| -tmps_grow||5.006000| -toLOWER||| -toUPPER||| -to_byte_substr||| -to_uni_fold||5.007003| -to_uni_lower_lc||5.006000| -to_uni_lower||5.007003| -to_uni_title_lc||5.006000| -to_uni_title||5.007003| -to_uni_upper_lc||5.006000| -to_uni_upper||5.007003| -to_utf8_case||5.007003| -to_utf8_fold||5.007003| -to_utf8_lower||5.007003| -to_utf8_substr||| -to_utf8_title||5.007003| -to_utf8_upper||5.007003| -token_free||| -token_getmad||| -tokenize_use||| -tokeq||| -tokereport||| -too_few_arguments||| -too_many_arguments||| -uiv_2buf|||n -unlnk||| -unpack_rec||| -unpack_str||5.007003| -unpackstring||5.008001| -unshare_hek_or_pvn||| -unshare_hek||| -unsharepvn||5.004000| -unwind_handler_stack||| -update_debugger_info||| -upg_version||5.009005| -usage||| -utf16_to_utf8_reversed||5.006001| -utf16_to_utf8||5.006001| -utf8_distance||5.006000| -utf8_hop||5.006000| -utf8_length||5.007001| -utf8_mg_pos_cache_update||| -utf8_to_bytes||5.006001| -utf8_to_uvchr||5.007001| -utf8_to_uvuni||5.007001| -utf8n_to_uvchr||| -utf8n_to_uvuni||5.007001| -utilize||| -uvchr_to_utf8_flags||5.007003| -uvchr_to_utf8||| -uvuni_to_utf8_flags||5.007003| -uvuni_to_utf8||5.007001| -validate_suid||| -varname||| -vcmp||5.009000| -vcroak||5.006000| -vdeb||5.007003| -vdie_common||| -vdie_croak_common||| -vdie||| -vform||5.006000| -visit||| -vivify_defelem||| -vivify_ref||| -vload_module|5.006000||p -vmess||5.006000| -vnewSVpvf|5.006000|5.004000|p -vnormal||5.009002| -vnumify||5.009000| -vstringify||5.009000| -vverify||5.009003| -vwarner||5.006000| -vwarn||5.006000| -wait4pid||| -warn_nocontext|||vn -warner_nocontext|||vn -warner|5.006000|5.004000|pv -warn|||v -watch||| -whichsig||| -write_no_mem||| -write_to_stderr||| -xmldump_all||| -xmldump_attr||| -xmldump_eval||| -xmldump_form||| -xmldump_indent|||v -xmldump_packsubs||| -xmldump_sub||| -xmldump_vindent||| -yyerror||| -yylex||| -yyparse||| -yywarn||| -); - -if (exists $opt{'list-unsupported'}) { - my $f; - for $f (sort { lc $a cmp lc $b } keys %API) { - next unless $API{$f}{todo}; - print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n"; - } - exit 0; -} - -# Scan for possible replacement candidates - -my(%replace, %need, %hints, %warnings, %depends); -my $replace = 0; -my($hint, $define, $function); - -sub find_api -{ - my $code = shift; - $code =~ s{ - / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*) - | "[^"\\]*(?:\\.[^"\\]*)*" - | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx; - grep { exists $API{$_} } $code =~ /(\w+)/mg; -} - -while () { - if ($hint) { - my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings; - if (m{^\s*\*\s(.*?)\s*$}) { - for (@{$hint->[1]}) { - $h->{$_} ||= ''; # suppress warning with older perls - $h->{$_} .= "$1\n"; - } - } - else { undef $hint } - } - - $hint = [$1, [split /,?\s+/, $2]] - if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$}; - - if ($define) { - if ($define->[1] =~ /\\$/) { - $define->[1] .= $_; - } - else { - if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) { - my @n = find_api($define->[1]); - push @{$depends{$define->[0]}}, @n if @n - } - undef $define; - } - } - - $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)}; - - if ($function) { - if (/^}/) { - if (exists $API{$function->[0]}) { - my @n = find_api($function->[1]); - push @{$depends{$function->[0]}}, @n if @n - } - undef $function; - } - else { - $function->[1] .= $_; - } - } - - $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)}; - - $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$}; - $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)}; - $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce}; - $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$}; - - if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) { - my @deps = map { s/\s+//g; $_ } split /,/, $3; - my $d; - for $d (map { s/\s+//g; $_ } split /,/, $1) { - push @{$depends{$d}}, @deps; - } - } - - $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)}; -} - -for (values %depends) { - my %s; - $_ = [sort grep !$s{$_}++, @$_]; -} - -if (exists $opt{'api-info'}) { - my $f; - my $count = 0; - my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$"; - for $f (sort { lc $a cmp lc $b } keys %API) { - next unless $f =~ /$match/; - print "\n=== $f ===\n\n"; - my $info = 0; - if ($API{$f}{base} || $API{$f}{todo}) { - my $base = format_version($API{$f}{base} || $API{$f}{todo}); - print "Supported at least starting from perl-$base.\n"; - $info++; - } - if ($API{$f}{provided}) { - my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003"; - print "Support by $ppport provided back to perl-$todo.\n"; - print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f}; - print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f}; - print "\n$hints{$f}" if exists $hints{$f}; - print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f}; - $info++; - } - print "No portability information available.\n" unless $info; - $count++; - } - $count or print "Found no API matching '$opt{'api-info'}'."; - print "\n"; - exit 0; -} - -if (exists $opt{'list-provided'}) { - my $f; - for $f (sort { lc $a cmp lc $b } keys %API) { - next unless $API{$f}{provided}; - my @flags; - push @flags, 'explicit' if exists $need{$f}; - push @flags, 'depend' if exists $depends{$f}; - push @flags, 'hint' if exists $hints{$f}; - push @flags, 'warning' if exists $warnings{$f}; - my $flags = @flags ? ' ['.join(', ', @flags).']' : ''; - print "$f$flags\n"; - } - exit 0; -} - -my @files; -my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc ); -my $srcext = join '|', map { quotemeta $_ } @srcext; - -if (@ARGV) { - my %seen; - for (@ARGV) { - if (-e) { - if (-f) { - push @files, $_ unless $seen{$_}++; - } - else { warn "'$_' is not a file.\n" } - } - else { - my @new = grep { -f } glob $_ - or warn "'$_' does not exist.\n"; - push @files, grep { !$seen{$_}++ } @new; - } - } -} -else { - eval { - require File::Find; - File::Find::find(sub { - $File::Find::name =~ /($srcext)$/i - and push @files, $File::Find::name; - }, '.'); - }; - if ($@) { - @files = map { glob "*$_" } @srcext; - } -} - -if (!@ARGV || $opt{filter}) { - my(@in, @out); - my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files; - for (@files) { - my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i; - push @{ $out ? \@out : \@in }, $_; - } - if (@ARGV && @out) { - warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out); - } - @files = @in; -} - -die "No input files given!\n" unless @files; - -my(%files, %global, %revreplace); -%revreplace = reverse %replace; -my $filename; -my $patch_opened = 0; - -for $filename (@files) { - unless (open IN, "<$filename") { - warn "Unable to read from $filename: $!\n"; - next; - } - - info("Scanning $filename ..."); - - my $c = do { local $/; }; - close IN; - - my %file = (orig => $c, changes => 0); - - # Temporarily remove C/XS comments and strings from the code - my @ccom; - - $c =~ s{ - ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]* - | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* ) - | ( ^$HS*\#[^\r\n]* - | "[^"\\]*(?:\\.[^"\\]*)*" - | '[^'\\]*(?:\\.[^'\\]*)*' - | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) ) - }{ defined $2 and push @ccom, $2; - defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex; - - $file{ccom} = \@ccom; - $file{code} = $c; - $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m; - - my $func; - - for $func (keys %API) { - my $match = $func; - $match .= "|$revreplace{$func}" if exists $revreplace{$func}; - if ($c =~ /\b(?:Perl_)?($match)\b/) { - $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func}; - $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/; - if (exists $API{$func}{provided}) { - $file{uses_provided}{$func}++; - if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) { - $file{uses}{$func}++; - my @deps = rec_depend($func); - if (@deps) { - $file{uses_deps}{$func} = \@deps; - for (@deps) { - $file{uses}{$_} = 0 unless exists $file{uses}{$_}; - } - } - for ($func, @deps) { - $file{needs}{$_} = 'static' if exists $need{$_}; - } - } - } - if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) { - if ($c =~ /\b$func\b/) { - $file{uses_todo}{$func}++; - } - } - } - } - - while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) { - if (exists $need{$2}) { - $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++; - } - else { warning("Possibly wrong #define $1 in $filename") } - } - - for (qw(uses needs uses_todo needed_global needed_static)) { - for $func (keys %{$file{$_}}) { - push @{$global{$_}{$func}}, $filename; - } - } - - $files{$filename} = \%file; -} - -# Globally resolve NEED_'s -my $need; -for $need (keys %{$global{needs}}) { - if (@{$global{needs}{$need}} > 1) { - my @targets = @{$global{needs}{$need}}; - my @t = grep $files{$_}{needed_global}{$need}, @targets; - @targets = @t if @t; - @t = grep /\.xs$/i, @targets; - @targets = @t if @t; - my $target = shift @targets; - $files{$target}{needs}{$need} = 'global'; - for (@{$global{needs}{$need}}) { - $files{$_}{needs}{$need} = 'extern' if $_ ne $target; - } - } -} - -for $filename (@files) { - exists $files{$filename} or next; - - info("=== Analyzing $filename ==="); - - my %file = %{$files{$filename}}; - my $func; - my $c = $file{code}; - my $warnings = 0; - - for $func (sort keys %{$file{uses_Perl}}) { - if ($API{$func}{varargs}) { - unless ($API{$func}{nothxarg}) { - my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))} - { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge); - if ($changes) { - warning("Doesn't pass interpreter argument aTHX to Perl_$func"); - $file{changes} += $changes; - } - } - } - else { - warning("Uses Perl_$func instead of $func"); - $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*} - {$func$1(}g); - } - } - - for $func (sort keys %{$file{uses_replace}}) { - warning("Uses $func instead of $replace{$func}"); - $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g); - } - - for $func (sort keys %{$file{uses_provided}}) { - if ($file{uses}{$func}) { - if (exists $file{uses_deps}{$func}) { - diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}})); - } - else { - diag("Uses $func"); - } - } - $warnings += hint($func); - } - - unless ($opt{quiet}) { - for $func (sort keys %{$file{uses_todo}}) { - print "*** WARNING: Uses $func, which may not be portable below perl ", - format_version($API{$func}{todo}), ", even with '$ppport'\n"; - $warnings++; - } - } - - for $func (sort keys %{$file{needed_static}}) { - my $message = ''; - if (not exists $file{uses}{$func}) { - $message = "No need to define NEED_$func if $func is never used"; - } - elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') { - $message = "No need to define NEED_$func when already needed globally"; - } - if ($message) { - diag($message); - $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg); - } - } - - for $func (sort keys %{$file{needed_global}}) { - my $message = ''; - if (not exists $global{uses}{$func}) { - $message = "No need to define NEED_${func}_GLOBAL if $func is never used"; - } - elsif (exists $file{needs}{$func}) { - if ($file{needs}{$func} eq 'extern') { - $message = "No need to define NEED_${func}_GLOBAL when already needed globally"; - } - elsif ($file{needs}{$func} eq 'static') { - $message = "No need to define NEED_${func}_GLOBAL when only used in this file"; - } - } - if ($message) { - diag($message); - $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg); - } - } - - $file{needs_inc_ppport} = keys %{$file{uses}}; - - if ($file{needs_inc_ppport}) { - my $pp = ''; - - for $func (sort keys %{$file{needs}}) { - my $type = $file{needs}{$func}; - next if $type eq 'extern'; - my $suffix = $type eq 'global' ? '_GLOBAL' : ''; - unless (exists $file{"needed_$type"}{$func}) { - if ($type eq 'global') { - diag("Files [@{$global{needs}{$func}}] need $func, adding global request"); - } - else { - diag("File needs $func, adding static request"); - } - $pp .= "#define NEED_$func$suffix\n"; - } - } - - if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) { - $pp = ''; - $file{changes}++; - } - - unless ($file{has_inc_ppport}) { - diag("Needs to include '$ppport'"); - $pp .= qq(#include "$ppport"\n) - } - - if ($pp) { - $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms) - || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m) - || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m) - || ($c =~ s/^/$pp/); - } - } - else { - if ($file{has_inc_ppport}) { - diag("No need to include '$ppport'"); - $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m); - } - } - - # put back in our C comments - my $ix; - my $cppc = 0; - my @ccom = @{$file{ccom}}; - for $ix (0 .. $#ccom) { - if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) { - $cppc++; - $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/; - } - else { - $c =~ s/$rccs$ix$rcce/$ccom[$ix]/; - } - } - - if ($cppc) { - my $s = $cppc != 1 ? 's' : ''; - warning("Uses $cppc C++ style comment$s, which is not portable"); - } - - my $s = $warnings != 1 ? 's' : ''; - my $warn = $warnings ? " ($warnings warning$s)" : ''; - info("Analysis completed$warn"); - - if ($file{changes}) { - if (exists $opt{copy}) { - my $newfile = "$filename$opt{copy}"; - if (-e $newfile) { - error("'$newfile' already exists, refusing to write copy of '$filename'"); - } - else { - local *F; - if (open F, ">$newfile") { - info("Writing copy of '$filename' with changes to '$newfile'"); - print F $c; - close F; - } - else { - error("Cannot open '$newfile' for writing: $!"); - } - } - } - elsif (exists $opt{patch} || $opt{changes}) { - if (exists $opt{patch}) { - unless ($patch_opened) { - if (open PATCH, ">$opt{patch}") { - $patch_opened = 1; - } - else { - error("Cannot open '$opt{patch}' for writing: $!"); - delete $opt{patch}; - $opt{changes} = 1; - goto fallback; - } - } - mydiff(\*PATCH, $filename, $c); - } - else { -fallback: - info("Suggested changes:"); - mydiff(\*STDOUT, $filename, $c); - } - } - else { - my $s = $file{changes} == 1 ? '' : 's'; - info("$file{changes} potentially required change$s detected"); - } - } - else { - info("Looks good"); - } -} - -close PATCH if $patch_opened; - -exit 0; - - -sub try_use { eval "use @_;"; return $@ eq '' } - -sub mydiff -{ - local *F = shift; - my($file, $str) = @_; - my $diff; - - if (exists $opt{diff}) { - $diff = run_diff($opt{diff}, $file, $str); - } - - if (!defined $diff and try_use('Text::Diff')) { - $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' }); - $diff = <
$tmp") { - print F $str; - close F; - - if (open F, "$prog $file $tmp |") { - while () { - s/\Q$tmp\E/$file.patched/; - $diff .= $_; - } - close F; - unlink $tmp; - return $diff; - } - - unlink $tmp; - } - else { - error("Cannot open '$tmp' for writing: $!"); - } - - return undef; -} - -sub rec_depend -{ - my($func, $seen) = @_; - return () unless exists $depends{$func}; - $seen = {%{$seen||{}}}; - return () if $seen->{$func}++; - my %s; - grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}}; -} - -sub parse_version -{ - my $ver = shift; - - if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) { - return ($1, $2, $3); - } - elsif ($ver !~ /^\d+\.[\d_]+$/) { - die "cannot parse version '$ver'\n"; - } - - $ver =~ s/_//g; - $ver =~ s/$/000000/; - - my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; - - $v = int $v; - $s = int $s; - - if ($r < 5 || ($r == 5 && $v < 6)) { - if ($s % 10) { - die "cannot parse version '$ver'\n"; - } - } - - return ($r, $v, $s); -} - -sub format_version -{ - my $ver = shift; - - $ver =~ s/$/000000/; - my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; - - $v = int $v; - $s = int $s; - - if ($r < 5 || ($r == 5 && $v < 6)) { - if ($s % 10) { - die "invalid version '$ver'\n"; - } - $s /= 10; - - $ver = sprintf "%d.%03d", $r, $v; - $s > 0 and $ver .= sprintf "_%02d", $s; - - return $ver; - } - - return sprintf "%d.%d.%d", $r, $v, $s; -} - -sub info -{ - $opt{quiet} and return; - print @_, "\n"; -} - -sub diag -{ - $opt{quiet} and return; - $opt{diag} and print @_, "\n"; -} - -sub warning -{ - $opt{quiet} and return; - print "*** ", @_, "\n"; -} - -sub error -{ - print "*** ERROR: ", @_, "\n"; -} - -my %given_hints; -my %given_warnings; -sub hint -{ - $opt{quiet} and return; - my $func = shift; - my $rv = 0; - if (exists $warnings{$func} && !$given_warnings{$func}++) { - my $warn = $warnings{$func}; - $warn =~ s!^!*** !mg; - print "*** WARNING: $func\n", $warn; - $rv++; - } - if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) { - my $hint = $hints{$func}; - $hint =~ s/^/ /mg; - print " --- hint for $func ---\n", $hint; - } - $rv; -} - -sub usage -{ - my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms; - my %M = ( 'I' => '*' ); - $usage =~ s/^\s*perl\s+\S+/$^X $0/; - $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g; - - print < }; - my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms; - $copy =~ s/^(?=\S+)/ /gms; - $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms; - $self =~ s/^SKIP.*(?=^__DATA__)/SKIP -if (\@ARGV && \$ARGV[0] eq '--unstrip') { - eval { require Devel::PPPort }; - \$@ and die "Cannot require Devel::PPPort, please install.\\n"; - if (eval \$Devel::PPPort::VERSION < $VERSION) { - die "$0 was originally generated with Devel::PPPort $VERSION.\\n" - . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n" - . "Please install a newer version, or --unstrip will not work.\\n"; - } - Devel::PPPort::WriteFile(\$0); - exit 0; -} -print <$0" or die "cannot strip $0: $!\n"; - print OUT "$pl$c\n"; - - exit 0; -} - -__DATA__ -*/ - -#ifndef _P_P_PORTABILITY_H_ -#define _P_P_PORTABILITY_H_ - -#ifndef DPPP_NAMESPACE -# define DPPP_NAMESPACE DPPP_ -#endif - -#define DPPP_CAT2(x,y) CAT2(x,y) -#define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name) - -#ifndef PERL_REVISION -# if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION)) -# define PERL_PATCHLEVEL_H_IMPLICIT -# include -# endif -# if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL))) -# include -# endif -# ifndef PERL_REVISION -# define PERL_REVISION (5) - /* Replace: 1 */ -# define PERL_VERSION PATCHLEVEL -# define PERL_SUBVERSION SUBVERSION - /* Replace PERL_PATCHLEVEL with PERL_VERSION */ - /* Replace: 0 */ -# endif -#endif - -#define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10)) -#define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION)) - -/* It is very unlikely that anyone will try to use this with Perl 6 - (or greater), but who knows. - */ -#if PERL_REVISION != 5 -# error ppport.h only works with Perl version 5 -#endif /* PERL_REVISION != 5 */ -#ifndef dTHR -# define dTHR dNOOP -#endif -#ifndef dTHX -# define dTHX dNOOP -#endif - -#ifndef dTHXa -# define dTHXa(x) dNOOP -#endif -#ifndef pTHX -# define pTHX void -#endif - -#ifndef pTHX_ -# define pTHX_ -#endif - -#ifndef aTHX -# define aTHX -#endif - -#ifndef aTHX_ -# define aTHX_ -#endif - -#if (PERL_BCDVERSION < 0x5006000) -# ifdef USE_THREADS -# define aTHXR thr -# define aTHXR_ thr, -# else -# define aTHXR -# define aTHXR_ -# endif -# define dTHXR dTHR -#else -# define aTHXR aTHX -# define aTHXR_ aTHX_ -# define dTHXR dTHX -#endif -#ifndef dTHXoa -# define dTHXoa(x) dTHXa(x) -#endif - -#ifdef I_LIMITS -# include -#endif - -#ifndef PERL_UCHAR_MIN -# define PERL_UCHAR_MIN ((unsigned char)0) -#endif - -#ifndef PERL_UCHAR_MAX -# ifdef UCHAR_MAX -# define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX) -# else -# ifdef MAXUCHAR -# define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR) -# else -# define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0) -# endif -# endif -#endif - -#ifndef PERL_USHORT_MIN -# define PERL_USHORT_MIN ((unsigned short)0) -#endif - -#ifndef PERL_USHORT_MAX -# ifdef USHORT_MAX -# define PERL_USHORT_MAX ((unsigned short)USHORT_MAX) -# else -# ifdef MAXUSHORT -# define PERL_USHORT_MAX ((unsigned short)MAXUSHORT) -# else -# ifdef USHRT_MAX -# define PERL_USHORT_MAX ((unsigned short)USHRT_MAX) -# else -# define PERL_USHORT_MAX ((unsigned short)~(unsigned)0) -# endif -# endif -# endif -#endif - -#ifndef PERL_SHORT_MAX -# ifdef SHORT_MAX -# define PERL_SHORT_MAX ((short)SHORT_MAX) -# else -# ifdef MAXSHORT /* Often used in */ -# define PERL_SHORT_MAX ((short)MAXSHORT) -# else -# ifdef SHRT_MAX -# define PERL_SHORT_MAX ((short)SHRT_MAX) -# else -# define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1)) -# endif -# endif -# endif -#endif - -#ifndef PERL_SHORT_MIN -# ifdef SHORT_MIN -# define PERL_SHORT_MIN ((short)SHORT_MIN) -# else -# ifdef MINSHORT -# define PERL_SHORT_MIN ((short)MINSHORT) -# else -# ifdef SHRT_MIN -# define PERL_SHORT_MIN ((short)SHRT_MIN) -# else -# define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3)) -# endif -# endif -# endif -#endif - -#ifndef PERL_UINT_MAX -# ifdef UINT_MAX -# define PERL_UINT_MAX ((unsigned int)UINT_MAX) -# else -# ifdef MAXUINT -# define PERL_UINT_MAX ((unsigned int)MAXUINT) -# else -# define PERL_UINT_MAX (~(unsigned int)0) -# endif -# endif -#endif - -#ifndef PERL_UINT_MIN -# define PERL_UINT_MIN ((unsigned int)0) -#endif - -#ifndef PERL_INT_MAX -# ifdef INT_MAX -# define PERL_INT_MAX ((int)INT_MAX) -# else -# ifdef MAXINT /* Often used in */ -# define PERL_INT_MAX ((int)MAXINT) -# else -# define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1)) -# endif -# endif -#endif - -#ifndef PERL_INT_MIN -# ifdef INT_MIN -# define PERL_INT_MIN ((int)INT_MIN) -# else -# ifdef MININT -# define PERL_INT_MIN ((int)MININT) -# else -# define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3)) -# endif -# endif -#endif - -#ifndef PERL_ULONG_MAX -# ifdef ULONG_MAX -# define PERL_ULONG_MAX ((unsigned long)ULONG_MAX) -# else -# ifdef MAXULONG -# define PERL_ULONG_MAX ((unsigned long)MAXULONG) -# else -# define PERL_ULONG_MAX (~(unsigned long)0) -# endif -# endif -#endif - -#ifndef PERL_ULONG_MIN -# define PERL_ULONG_MIN ((unsigned long)0L) -#endif - -#ifndef PERL_LONG_MAX -# ifdef LONG_MAX -# define PERL_LONG_MAX ((long)LONG_MAX) -# else -# ifdef MAXLONG -# define PERL_LONG_MAX ((long)MAXLONG) -# else -# define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1)) -# endif -# endif -#endif - -#ifndef PERL_LONG_MIN -# ifdef LONG_MIN -# define PERL_LONG_MIN ((long)LONG_MIN) -# else -# ifdef MINLONG -# define PERL_LONG_MIN ((long)MINLONG) -# else -# define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3)) -# endif -# endif -#endif - -#if defined(HAS_QUAD) && (defined(convex) || defined(uts)) -# ifndef PERL_UQUAD_MAX -# ifdef ULONGLONG_MAX -# define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX) -# else -# ifdef MAXULONGLONG -# define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG) -# else -# define PERL_UQUAD_MAX (~(unsigned long long)0) -# endif -# endif -# endif - -# ifndef PERL_UQUAD_MIN -# define PERL_UQUAD_MIN ((unsigned long long)0L) -# endif - -# ifndef PERL_QUAD_MAX -# ifdef LONGLONG_MAX -# define PERL_QUAD_MAX ((long long)LONGLONG_MAX) -# else -# ifdef MAXLONGLONG -# define PERL_QUAD_MAX ((long long)MAXLONGLONG) -# else -# define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1)) -# endif -# endif -# endif - -# ifndef PERL_QUAD_MIN -# ifdef LONGLONG_MIN -# define PERL_QUAD_MIN ((long long)LONGLONG_MIN) -# else -# ifdef MINLONGLONG -# define PERL_QUAD_MIN ((long long)MINLONGLONG) -# else -# define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3)) -# endif -# endif -# endif -#endif - -/* This is based on code from 5.003 perl.h */ -#ifdef HAS_QUAD -# ifdef cray -#ifndef IVTYPE -# define IVTYPE int -#endif - -#ifndef IV_MIN -# define IV_MIN PERL_INT_MIN -#endif - -#ifndef IV_MAX -# define IV_MAX PERL_INT_MAX -#endif - -#ifndef UV_MIN -# define UV_MIN PERL_UINT_MIN -#endif - -#ifndef UV_MAX -# define UV_MAX PERL_UINT_MAX -#endif - -# ifdef INTSIZE -#ifndef IVSIZE -# define IVSIZE INTSIZE -#endif - -# endif -# else -# if defined(convex) || defined(uts) -#ifndef IVTYPE -# define IVTYPE long long -#endif - -#ifndef IV_MIN -# define IV_MIN PERL_QUAD_MIN -#endif - -#ifndef IV_MAX -# define IV_MAX PERL_QUAD_MAX -#endif - -#ifndef UV_MIN -# define UV_MIN PERL_UQUAD_MIN -#endif - -#ifndef UV_MAX -# define UV_MAX PERL_UQUAD_MAX -#endif - -# ifdef LONGLONGSIZE -#ifndef IVSIZE -# define IVSIZE LONGLONGSIZE -#endif - -# endif -# else -#ifndef IVTYPE -# define IVTYPE long -#endif - -#ifndef IV_MIN -# define IV_MIN PERL_LONG_MIN -#endif - -#ifndef IV_MAX -# define IV_MAX PERL_LONG_MAX -#endif - -#ifndef UV_MIN -# define UV_MIN PERL_ULONG_MIN -#endif - -#ifndef UV_MAX -# define UV_MAX PERL_ULONG_MAX -#endif - -# ifdef LONGSIZE -#ifndef IVSIZE -# define IVSIZE LONGSIZE -#endif - -# endif -# endif -# endif -#ifndef IVSIZE -# define IVSIZE 8 -#endif - -#ifndef PERL_QUAD_MIN -# define PERL_QUAD_MIN IV_MIN -#endif - -#ifndef PERL_QUAD_MAX -# define PERL_QUAD_MAX IV_MAX -#endif - -#ifndef PERL_UQUAD_MIN -# define PERL_UQUAD_MIN UV_MIN -#endif - -#ifndef PERL_UQUAD_MAX -# define PERL_UQUAD_MAX UV_MAX -#endif - -#else -#ifndef IVTYPE -# define IVTYPE long -#endif - -#ifndef IV_MIN -# define IV_MIN PERL_LONG_MIN -#endif - -#ifndef IV_MAX -# define IV_MAX PERL_LONG_MAX -#endif - -#ifndef UV_MIN -# define UV_MIN PERL_ULONG_MIN -#endif - -#ifndef UV_MAX -# define UV_MAX PERL_ULONG_MAX -#endif - -#endif - -#ifndef IVSIZE -# ifdef LONGSIZE -# define IVSIZE LONGSIZE -# else -# define IVSIZE 4 /* A bold guess, but the best we can make. */ -# endif -#endif -#ifndef UVTYPE -# define UVTYPE unsigned IVTYPE -#endif - -#ifndef UVSIZE -# define UVSIZE IVSIZE -#endif -#ifndef sv_setuv -# define sv_setuv(sv, uv) \ - STMT_START { \ - UV TeMpUv = uv; \ - if (TeMpUv <= IV_MAX) \ - sv_setiv(sv, TeMpUv); \ - else \ - sv_setnv(sv, (double)TeMpUv); \ - } STMT_END -#endif -#ifndef newSVuv -# define newSVuv(uv) ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv)) -#endif -#ifndef sv_2uv -# define sv_2uv(sv) ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv))) -#endif - -#ifndef SvUVX -# define SvUVX(sv) ((UV)SvIVX(sv)) -#endif - -#ifndef SvUVXx -# define SvUVXx(sv) SvUVX(sv) -#endif - -#ifndef SvUV -# define SvUV(sv) (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv)) -#endif - -#ifndef SvUVx -# define SvUVx(sv) ((PL_Sv = (sv)), SvUV(PL_Sv)) -#endif - -/* Hint: sv_uv - * Always use the SvUVx() macro instead of sv_uv(). - */ -#ifndef sv_uv -# define sv_uv(sv) SvUVx(sv) -#endif - -#if !defined(SvUOK) && defined(SvIOK_UV) -# define SvUOK(sv) SvIOK_UV(sv) -#endif -#ifndef XST_mUV -# define XST_mUV(i,v) (ST(i) = sv_2mortal(newSVuv(v)) ) -#endif - -#ifndef XSRETURN_UV -# define XSRETURN_UV(v) STMT_START { XST_mUV(0,v); XSRETURN(1); } STMT_END -#endif -#ifndef PUSHu -# define PUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG; } STMT_END -#endif - -#ifndef XPUSHu -# define XPUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END -#endif - -#ifdef HAS_MEMCMP -#ifndef memNE -# define memNE(s1,s2,l) (memcmp(s1,s2,l)) -#endif - -#ifndef memEQ -# define memEQ(s1,s2,l) (!memcmp(s1,s2,l)) -#endif - -#else -#ifndef memNE -# define memNE(s1,s2,l) (bcmp(s1,s2,l)) -#endif - -#ifndef memEQ -# define memEQ(s1,s2,l) (!bcmp(s1,s2,l)) -#endif - -#endif -#ifndef MoveD -# define MoveD(s,d,n,t) memmove((char*)(d),(char*)(s), (n) * sizeof(t)) -#endif - -#ifndef CopyD -# define CopyD(s,d,n,t) memcpy((char*)(d),(char*)(s), (n) * sizeof(t)) -#endif - -#ifdef HAS_MEMSET -#ifndef ZeroD -# define ZeroD(d,n,t) memzero((char*)(d), (n) * sizeof(t)) -#endif - -#else -#ifndef ZeroD -# define ZeroD(d,n,t) ((void)memzero((char*)(d), (n) * sizeof(t)), d) -#endif - -#endif -#ifndef PoisonWith -# define PoisonWith(d,n,t,b) (void)memset((char*)(d), (U8)(b), (n) * sizeof(t)) -#endif - -#ifndef PoisonNew -# define PoisonNew(d,n,t) PoisonWith(d,n,t,0xAB) -#endif - -#ifndef PoisonFree -# define PoisonFree(d,n,t) PoisonWith(d,n,t,0xEF) -#endif - -#ifndef Poison -# define Poison(d,n,t) PoisonFree(d,n,t) -#endif -#ifndef Newx -# define Newx(v,n,t) New(0,v,n,t) -#endif - -#ifndef Newxc -# define Newxc(v,n,t,c) Newc(0,v,n,t,c) -#endif - -#ifndef Newxz -# define Newxz(v,n,t) Newz(0,v,n,t) -#endif - -#ifndef PERL_UNUSED_DECL -# ifdef HASATTRIBUTE -# if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER) -# define PERL_UNUSED_DECL -# else -# define PERL_UNUSED_DECL __attribute__((unused)) -# endif -# else -# define PERL_UNUSED_DECL -# endif -#endif - -#ifndef PERL_UNUSED_ARG -# if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */ -# include -# define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x)) -# else -# define PERL_UNUSED_ARG(x) ((void)x) -# endif -#endif - -#ifndef PERL_UNUSED_VAR -# define PERL_UNUSED_VAR(x) ((void)x) -#endif - -#ifndef PERL_UNUSED_CONTEXT -# ifdef USE_ITHREADS -# define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl) -# else -# define PERL_UNUSED_CONTEXT -# endif -#endif -#ifndef NOOP -# define NOOP /*EMPTY*/(void)0 -#endif - -#ifndef dNOOP -# define dNOOP extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL -#endif - -#ifndef NVTYPE -# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) -# define NVTYPE long double -# else -# define NVTYPE double -# endif -typedef NVTYPE NV; -#endif - -#ifndef INT2PTR -# if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE) -# define PTRV UV -# define INT2PTR(any,d) (any)(d) -# else -# if PTRSIZE == LONGSIZE -# define PTRV unsigned long -# else -# define PTRV unsigned -# endif -# define INT2PTR(any,d) (any)(PTRV)(d) -# endif -#endif - -#ifndef PTR2ul -# if PTRSIZE == LONGSIZE -# define PTR2ul(p) (unsigned long)(p) -# else -# define PTR2ul(p) INT2PTR(unsigned long,p) -# endif -#endif -#ifndef PTR2nat -# define PTR2nat(p) (PTRV)(p) -#endif - -#ifndef NUM2PTR -# define NUM2PTR(any,d) (any)PTR2nat(d) -#endif - -#ifndef PTR2IV -# define PTR2IV(p) INT2PTR(IV,p) -#endif - -#ifndef PTR2UV -# define PTR2UV(p) INT2PTR(UV,p) -#endif - -#ifndef PTR2NV -# define PTR2NV(p) NUM2PTR(NV,p) -#endif - -#undef START_EXTERN_C -#undef END_EXTERN_C -#undef EXTERN_C -#ifdef __cplusplus -# define START_EXTERN_C extern "C" { -# define END_EXTERN_C } -# define EXTERN_C extern "C" -#else -# define START_EXTERN_C -# define END_EXTERN_C -# define EXTERN_C extern -#endif - -#if defined(PERL_GCC_PEDANTIC) -# ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN -# define PERL_GCC_BRACE_GROUPS_FORBIDDEN -# endif -#endif - -#if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus) -# ifndef PERL_USE_GCC_BRACE_GROUPS -# define PERL_USE_GCC_BRACE_GROUPS -# endif -#endif - -#undef STMT_START -#undef STMT_END -#ifdef PERL_USE_GCC_BRACE_GROUPS -# define STMT_START (void)( /* gcc supports ``({ STATEMENTS; })'' */ -# define STMT_END ) -#else -# if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__) -# define STMT_START if (1) -# define STMT_END else (void)0 -# else -# define STMT_START do -# define STMT_END while (0) -# endif -#endif -#ifndef boolSV -# define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no) -#endif - -/* DEFSV appears first in 5.004_56 */ -#ifndef DEFSV -# define DEFSV GvSV(PL_defgv) -#endif - -#ifndef SAVE_DEFSV -# define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv)) -#endif - -#ifndef DEFSV_set -# define DEFSV_set(sv) (DEFSV = (sv)) -#endif - -/* Older perls (<=5.003) lack AvFILLp */ -#ifndef AvFILLp -# define AvFILLp AvFILL -#endif -#ifndef ERRSV -# define ERRSV get_sv("@",FALSE) -#endif - -/* Hint: gv_stashpvn - * This function's backport doesn't support the length parameter, but - * rather ignores it. Portability can only be ensured if the length - * parameter is used for speed reasons, but the length can always be - * correctly computed from the string argument. - */ -#ifndef gv_stashpvn -# define gv_stashpvn(str,len,create) gv_stashpv(str,create) -#endif - -/* Replace: 1 */ -#ifndef get_cv -# define get_cv perl_get_cv -#endif - -#ifndef get_sv -# define get_sv perl_get_sv -#endif - -#ifndef get_av -# define get_av perl_get_av -#endif - -#ifndef get_hv -# define get_hv perl_get_hv -#endif - -/* Replace: 0 */ -#ifndef dUNDERBAR -# define dUNDERBAR dNOOP -#endif - -#ifndef UNDERBAR -# define UNDERBAR DEFSV -#endif -#ifndef dAX -# define dAX I32 ax = MARK - PL_stack_base + 1 -#endif - -#ifndef dITEMS -# define dITEMS I32 items = SP - MARK -#endif -#ifndef dXSTARG -# define dXSTARG SV * targ = sv_newmortal() -#endif -#ifndef dAXMARK -# define dAXMARK I32 ax = POPMARK; \ - register SV ** const mark = PL_stack_base + ax++ -#endif -#ifndef XSprePUSH -# define XSprePUSH (sp = PL_stack_base + ax - 1) -#endif - -#if (PERL_BCDVERSION < 0x5005000) -# undef XSRETURN -# define XSRETURN(off) \ - STMT_START { \ - PL_stack_sp = PL_stack_base + ax + ((off) - 1); \ - return; \ - } STMT_END -#endif -#ifndef XSPROTO -# define XSPROTO(name) void name(pTHX_ CV* cv) -#endif - -#ifndef SVfARG -# define SVfARG(p) ((void*)(p)) -#endif -#ifndef PERL_ABS -# define PERL_ABS(x) ((x) < 0 ? -(x) : (x)) -#endif -#ifndef dVAR -# define dVAR dNOOP -#endif -#ifndef SVf -# define SVf "_" -#endif -#ifndef UTF8_MAXBYTES -# define UTF8_MAXBYTES UTF8_MAXLEN -#endif -#ifndef CPERLscope -# define CPERLscope(x) x -#endif -#ifndef PERL_HASH -# define PERL_HASH(hash,str,len) \ - STMT_START { \ - const char *s_PeRlHaSh = str; \ - I32 i_PeRlHaSh = len; \ - U32 hash_PeRlHaSh = 0; \ - while (i_PeRlHaSh--) \ - hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \ - (hash) = hash_PeRlHaSh; \ - } STMT_END -#endif - -#ifndef PERLIO_FUNCS_DECL -# ifdef PERLIO_FUNCS_CONST -# define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs -# define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs) -# else -# define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs -# define PERLIO_FUNCS_CAST(funcs) (funcs) -# endif -#endif - -/* provide these typedefs for older perls */ -#if (PERL_BCDVERSION < 0x5009003) - -# ifdef ARGSproto -typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto); -# else -typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX); -# endif - -typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*); - -#endif -#ifndef isPSXSPC -# define isPSXSPC(c) (isSPACE(c) || (c) == '\v') -#endif - -#ifndef isBLANK -# define isBLANK(c) ((c) == ' ' || (c) == '\t') -#endif - -#ifdef EBCDIC -#ifndef isALNUMC -# define isALNUMC(c) isalnum(c) -#endif - -#ifndef isASCII -# define isASCII(c) isascii(c) -#endif - -#ifndef isCNTRL -# define isCNTRL(c) iscntrl(c) -#endif - -#ifndef isGRAPH -# define isGRAPH(c) isgraph(c) -#endif - -#ifndef isPRINT -# define isPRINT(c) isprint(c) -#endif - -#ifndef isPUNCT -# define isPUNCT(c) ispunct(c) -#endif - -#ifndef isXDIGIT -# define isXDIGIT(c) isxdigit(c) -#endif - -#else -# if (PERL_BCDVERSION < 0x5010000) -/* Hint: isPRINT - * The implementation in older perl versions includes all of the - * isSPACE() characters, which is wrong. The version provided by - * Devel::PPPort always overrides a present buggy version. - */ -# undef isPRINT -# endif -#ifndef isALNUMC -# define isALNUMC(c) (isALPHA(c) || isDIGIT(c)) -#endif - -#ifndef isASCII -# define isASCII(c) ((c) <= 127) -#endif - -#ifndef isCNTRL -# define isCNTRL(c) ((c) < ' ' || (c) == 127) -#endif - -#ifndef isGRAPH -# define isGRAPH(c) (isALNUM(c) || isPUNCT(c)) -#endif - -#ifndef isPRINT -# define isPRINT(c) (((c) >= 32 && (c) < 127)) -#endif - -#ifndef isPUNCT -# define isPUNCT(c) (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64) || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126)) -#endif - -#ifndef isXDIGIT -# define isXDIGIT(c) (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) -#endif - -#endif - -#ifndef PERL_SIGNALS_UNSAFE_FLAG - -#define PERL_SIGNALS_UNSAFE_FLAG 0x0001 - -#if (PERL_BCDVERSION < 0x5008000) -# define D_PPP_PERL_SIGNALS_INIT PERL_SIGNALS_UNSAFE_FLAG -#else -# define D_PPP_PERL_SIGNALS_INIT 0 -#endif - -#if defined(NEED_PL_signals) -static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; -#elif defined(NEED_PL_signals_GLOBAL) -U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; -#else -extern U32 DPPP_(my_PL_signals); -#endif -#define PL_signals DPPP_(my_PL_signals) - -#endif - -/* Hint: PL_ppaddr - * Calling an op via PL_ppaddr requires passing a context argument - * for threaded builds. Since the context argument is different for - * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will - * automatically be defined as the correct argument. - */ - -#if (PERL_BCDVERSION <= 0x5005005) -/* Replace: 1 */ -# define PL_ppaddr ppaddr -# define PL_no_modify no_modify -/* Replace: 0 */ -#endif - -#if (PERL_BCDVERSION <= 0x5004005) -/* Replace: 1 */ -# define PL_DBsignal DBsignal -# define PL_DBsingle DBsingle -# define PL_DBsub DBsub -# define PL_DBtrace DBtrace -# define PL_Sv Sv -# define PL_bufend bufend -# define PL_bufptr bufptr -# define PL_compiling compiling -# define PL_copline copline -# define PL_curcop curcop -# define PL_curstash curstash -# define PL_debstash debstash -# define PL_defgv defgv -# define PL_diehook diehook -# define PL_dirty dirty -# define PL_dowarn dowarn -# define PL_errgv errgv -# define PL_error_count error_count -# define PL_expect expect -# define PL_hexdigit hexdigit -# define PL_hints hints -# define PL_in_my in_my -# define PL_laststatval laststatval -# define PL_lex_state lex_state -# define PL_lex_stuff lex_stuff -# define PL_linestr linestr -# define PL_na na -# define PL_perl_destruct_level perl_destruct_level -# define PL_perldb perldb -# define PL_rsfp_filters rsfp_filters -# define PL_rsfp rsfp -# define PL_stack_base stack_base -# define PL_stack_sp stack_sp -# define PL_statcache statcache -# define PL_stdingv stdingv -# define PL_sv_arenaroot sv_arenaroot -# define PL_sv_no sv_no -# define PL_sv_undef sv_undef -# define PL_sv_yes sv_yes -# define PL_tainted tainted -# define PL_tainting tainting -# define PL_tokenbuf tokenbuf -/* Replace: 0 */ -#endif - -/* Warning: PL_parser - * For perl versions earlier than 5.9.5, this is an always - * non-NULL dummy. Also, it cannot be dereferenced. Don't - * use it if you can avoid is and unless you absolutely know - * what you're doing. - * If you always check that PL_parser is non-NULL, you can - * define DPPP_PL_parser_NO_DUMMY to avoid the creation of - * a dummy parser structure. - */ - -#if (PERL_BCDVERSION >= 0x5009005) -# ifdef DPPP_PL_parser_NO_DUMMY -# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ - (croak("panic: PL_parser == NULL in %s:%d", \ - __FILE__, __LINE__), (yy_parser *) NULL))->var) -# else -# ifdef DPPP_PL_parser_NO_DUMMY_WARNING -# define D_PPP_parser_dummy_warning(var) -# else -# define D_PPP_parser_dummy_warning(var) \ - warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__), -# endif -# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ - (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var) -#if defined(NEED_PL_parser) -static yy_parser DPPP_(dummy_PL_parser); -#elif defined(NEED_PL_parser_GLOBAL) -yy_parser DPPP_(dummy_PL_parser); -#else -extern yy_parser DPPP_(dummy_PL_parser); -#endif - -# endif - -/* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */ -/* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf - * Do not use this variable unless you know exactly what you're - * doint. It is internal to the perl parser and may change or even - * be removed in the future. As of perl 5.9.5, you have to check - * for (PL_parser != NULL) for this variable to have any effect. - * An always non-NULL PL_parser dummy is provided for earlier - * perl versions. - * If PL_parser is NULL when you try to access this variable, a - * dummy is being accessed instead and a warning is issued unless - * you define DPPP_PL_parser_NO_DUMMY_WARNING. - * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access - * this variable will croak with a panic message. - */ - -# define PL_expect D_PPP_my_PL_parser_var(expect) -# define PL_copline D_PPP_my_PL_parser_var(copline) -# define PL_rsfp D_PPP_my_PL_parser_var(rsfp) -# define PL_rsfp_filters D_PPP_my_PL_parser_var(rsfp_filters) -# define PL_linestr D_PPP_my_PL_parser_var(linestr) -# define PL_bufptr D_PPP_my_PL_parser_var(bufptr) -# define PL_bufend D_PPP_my_PL_parser_var(bufend) -# define PL_lex_state D_PPP_my_PL_parser_var(lex_state) -# define PL_lex_stuff D_PPP_my_PL_parser_var(lex_stuff) -# define PL_tokenbuf D_PPP_my_PL_parser_var(tokenbuf) -# define PL_in_my D_PPP_my_PL_parser_var(in_my) -# define PL_in_my_stash D_PPP_my_PL_parser_var(in_my_stash) -# define PL_error_count D_PPP_my_PL_parser_var(error_count) - - -#else - -/* ensure that PL_parser != NULL and cannot be dereferenced */ -# define PL_parser ((void *) 1) - -#endif -#ifndef mPUSHs -# define mPUSHs(s) PUSHs(sv_2mortal(s)) -#endif - -#ifndef PUSHmortal -# define PUSHmortal PUSHs(sv_newmortal()) -#endif - -#ifndef mPUSHp -# define mPUSHp(p,l) sv_setpvn(PUSHmortal, (p), (l)) -#endif - -#ifndef mPUSHn -# define mPUSHn(n) sv_setnv(PUSHmortal, (NV)(n)) -#endif - -#ifndef mPUSHi -# define mPUSHi(i) sv_setiv(PUSHmortal, (IV)(i)) -#endif - -#ifndef mPUSHu -# define mPUSHu(u) sv_setuv(PUSHmortal, (UV)(u)) -#endif -#ifndef mXPUSHs -# define mXPUSHs(s) XPUSHs(sv_2mortal(s)) -#endif - -#ifndef XPUSHmortal -# define XPUSHmortal XPUSHs(sv_newmortal()) -#endif - -#ifndef mXPUSHp -# define mXPUSHp(p,l) STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END -#endif - -#ifndef mXPUSHn -# define mXPUSHn(n) STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END -#endif - -#ifndef mXPUSHi -# define mXPUSHi(i) STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END -#endif - -#ifndef mXPUSHu -# define mXPUSHu(u) STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END -#endif - -/* Replace: 1 */ -#ifndef call_sv -# define call_sv perl_call_sv -#endif - -#ifndef call_pv -# define call_pv perl_call_pv -#endif - -#ifndef call_argv -# define call_argv perl_call_argv -#endif - -#ifndef call_method -# define call_method perl_call_method -#endif -#ifndef eval_sv -# define eval_sv perl_eval_sv -#endif - -/* Replace: 0 */ -#ifndef PERL_LOADMOD_DENY -# define PERL_LOADMOD_DENY 0x1 -#endif - -#ifndef PERL_LOADMOD_NOIMPORT -# define PERL_LOADMOD_NOIMPORT 0x2 -#endif - -#ifndef PERL_LOADMOD_IMPORT_OPS -# define PERL_LOADMOD_IMPORT_OPS 0x4 -#endif - -#ifndef G_METHOD -# define G_METHOD 64 -# ifdef call_sv -# undef call_sv -# endif -# if (PERL_BCDVERSION < 0x5006000) -# define call_sv(sv, flags) ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \ - (flags) & ~G_METHOD) : perl_call_sv(sv, flags)) -# else -# define call_sv(sv, flags) ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \ - (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags)) -# endif -#endif - -/* Replace perl_eval_pv with eval_pv */ - -#ifndef eval_pv -#if defined(NEED_eval_pv) -static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); -static -#else -extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); -#endif - -#ifdef eval_pv -# undef eval_pv -#endif -#define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b) -#define Perl_eval_pv DPPP_(my_eval_pv) - -#if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL) - -SV* -DPPP_(my_eval_pv)(char *p, I32 croak_on_error) -{ - dSP; - SV* sv = newSVpv(p, 0); - - PUSHMARK(sp); - eval_sv(sv, G_SCALAR); - SvREFCNT_dec(sv); - - SPAGAIN; - sv = POPs; - PUTBACK; - - if (croak_on_error && SvTRUE(GvSV(errgv))) - croak(SvPVx(GvSV(errgv), na)); - - return sv; -} - -#endif -#endif - -#ifndef vload_module -#if defined(NEED_vload_module) -static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); -static -#else -extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); -#endif - -#ifdef vload_module -# undef vload_module -#endif -#define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d) -#define Perl_vload_module DPPP_(my_vload_module) - -#if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL) - -void -DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args) -{ - dTHR; - dVAR; - OP *veop, *imop; - - OP * const modname = newSVOP(OP_CONST, 0, name); - /* 5.005 has a somewhat hacky force_normal that doesn't croak on - SvREADONLY() if PL_compling is true. Current perls take care in - ck_require() to correctly turn off SvREADONLY before calling - force_normal_flags(). This seems a better fix than fudging PL_compling - */ - SvREADONLY_off(((SVOP*)modname)->op_sv); - modname->op_private |= OPpCONST_BARE; - if (ver) { - veop = newSVOP(OP_CONST, 0, ver); - } - else - veop = NULL; - if (flags & PERL_LOADMOD_NOIMPORT) { - imop = sawparens(newNULLLIST()); - } - else if (flags & PERL_LOADMOD_IMPORT_OPS) { - imop = va_arg(*args, OP*); - } - else { - SV *sv; - imop = NULL; - sv = va_arg(*args, SV*); - while (sv) { - imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv)); - sv = va_arg(*args, SV*); - } - } - { - const line_t ocopline = PL_copline; - COP * const ocurcop = PL_curcop; - const int oexpect = PL_expect; - -#if (PERL_BCDVERSION >= 0x5004000) - utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0), - veop, modname, imop); -#else - utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(), - modname, imop); -#endif - PL_expect = oexpect; - PL_copline = ocopline; - PL_curcop = ocurcop; - } -} - -#endif -#endif - -#ifndef load_module -#if defined(NEED_load_module) -static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); -static -#else -extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); -#endif - -#ifdef load_module -# undef load_module -#endif -#define load_module DPPP_(my_load_module) -#define Perl_load_module DPPP_(my_load_module) - -#if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL) - -void -DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...) -{ - va_list args; - va_start(args, ver); - vload_module(flags, name, ver, &args); - va_end(args); -} - -#endif -#endif -#ifndef newRV_inc -# define newRV_inc(sv) newRV(sv) /* Replace */ -#endif - -#ifndef newRV_noinc -#if defined(NEED_newRV_noinc) -static SV * DPPP_(my_newRV_noinc)(SV *sv); -static -#else -extern SV * DPPP_(my_newRV_noinc)(SV *sv); -#endif - -#ifdef newRV_noinc -# undef newRV_noinc -#endif -#define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a) -#define Perl_newRV_noinc DPPP_(my_newRV_noinc) - -#if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL) -SV * -DPPP_(my_newRV_noinc)(SV *sv) -{ - SV *rv = (SV *)newRV(sv); - SvREFCNT_dec(sv); - return rv; -} -#endif -#endif - -/* Hint: newCONSTSUB - * Returns a CV* as of perl-5.7.1. This return value is not supported - * by Devel::PPPort. - */ - -/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */ -#if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005) -#if defined(NEED_newCONSTSUB) -static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); -static -#else -extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); -#endif - -#ifdef newCONSTSUB -# undef newCONSTSUB -#endif -#define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c) -#define Perl_newCONSTSUB DPPP_(my_newCONSTSUB) - -#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL) - -/* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */ -/* (There's no PL_parser in perl < 5.005, so this is completely safe) */ -#define D_PPP_PL_copline PL_copline - -void -DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv) -{ - U32 oldhints = PL_hints; - HV *old_cop_stash = PL_curcop->cop_stash; - HV *old_curstash = PL_curstash; - line_t oldline = PL_curcop->cop_line; - PL_curcop->cop_line = D_PPP_PL_copline; - - PL_hints &= ~HINT_BLOCK_SCOPE; - if (stash) - PL_curstash = PL_curcop->cop_stash = stash; - - newSUB( - -#if (PERL_BCDVERSION < 0x5003022) - start_subparse(), -#elif (PERL_BCDVERSION == 0x5003022) - start_subparse(0), -#else /* 5.003_23 onwards */ - start_subparse(FALSE, 0), -#endif - - newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)), - newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */ - newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv)) - ); - - PL_hints = oldhints; - PL_curcop->cop_stash = old_cop_stash; - PL_curstash = old_curstash; - PL_curcop->cop_line = oldline; -} -#endif -#endif - -/* - * Boilerplate macros for initializing and accessing interpreter-local - * data from C. All statics in extensions should be reworked to use - * this, if you want to make the extension thread-safe. See ext/re/re.xs - * for an example of the use of these macros. - * - * Code that uses these macros is responsible for the following: - * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts" - * 2. Declare a typedef named my_cxt_t that is a structure that contains - * all the data that needs to be interpreter-local. - * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t. - * 4. Use the MY_CXT_INIT macro such that it is called exactly once - * (typically put in the BOOT: section). - * 5. Use the members of the my_cxt_t structure everywhere as - * MY_CXT.member. - * 6. Use the dMY_CXT macro (a declaration) in all the functions that - * access MY_CXT. - */ - -#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \ - defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT) - -#ifndef START_MY_CXT - -/* This must appear in all extensions that define a my_cxt_t structure, - * right after the definition (i.e. at file scope). The non-threads - * case below uses it to declare the data as static. */ -#define START_MY_CXT - -#if (PERL_BCDVERSION < 0x5004068) -/* Fetches the SV that keeps the per-interpreter data. */ -#define dMY_CXT_SV \ - SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE) -#else /* >= perl5.004_68 */ -#define dMY_CXT_SV \ - SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \ - sizeof(MY_CXT_KEY)-1, TRUE) -#endif /* < perl5.004_68 */ - -/* This declaration should be used within all functions that use the - * interpreter-local data. */ -#define dMY_CXT \ - dMY_CXT_SV; \ - my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv)) - -/* Creates and zeroes the per-interpreter data. - * (We allocate my_cxtp in a Perl SV so that it will be released when - * the interpreter goes away.) */ -#define MY_CXT_INIT \ - dMY_CXT_SV; \ - /* newSV() allocates one more than needed */ \ - my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ - Zero(my_cxtp, 1, my_cxt_t); \ - sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) - -/* This macro must be used to access members of the my_cxt_t structure. - * e.g. MYCXT.some_data */ -#define MY_CXT (*my_cxtp) - -/* Judicious use of these macros can reduce the number of times dMY_CXT - * is used. Use is similar to pTHX, aTHX etc. */ -#define pMY_CXT my_cxt_t *my_cxtp -#define pMY_CXT_ pMY_CXT, -#define _pMY_CXT ,pMY_CXT -#define aMY_CXT my_cxtp -#define aMY_CXT_ aMY_CXT, -#define _aMY_CXT ,aMY_CXT - -#endif /* START_MY_CXT */ - -#ifndef MY_CXT_CLONE -/* Clones the per-interpreter data. */ -#define MY_CXT_CLONE \ - dMY_CXT_SV; \ - my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ - Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\ - sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) -#endif - -#else /* single interpreter */ - -#ifndef START_MY_CXT - -#define START_MY_CXT static my_cxt_t my_cxt; -#define dMY_CXT_SV dNOOP -#define dMY_CXT dNOOP -#define MY_CXT_INIT NOOP -#define MY_CXT my_cxt - -#define pMY_CXT void -#define pMY_CXT_ -#define _pMY_CXT -#define aMY_CXT -#define aMY_CXT_ -#define _aMY_CXT - -#endif /* START_MY_CXT */ - -#ifndef MY_CXT_CLONE -#define MY_CXT_CLONE NOOP -#endif - -#endif - -#ifndef IVdf -# if IVSIZE == LONGSIZE -# define IVdf "ld" -# define UVuf "lu" -# define UVof "lo" -# define UVxf "lx" -# define UVXf "lX" -# else -# if IVSIZE == INTSIZE -# define IVdf "d" -# define UVuf "u" -# define UVof "o" -# define UVxf "x" -# define UVXf "X" -# endif -# endif -#endif - -#ifndef NVef -# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \ - defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000) - /* Not very likely, but let's try anyway. */ -# define NVef PERL_PRIeldbl -# define NVff PERL_PRIfldbl -# define NVgf PERL_PRIgldbl -# else -# define NVef "e" -# define NVff "f" -# define NVgf "g" -# endif -#endif - -#ifndef SvREFCNT_inc -# ifdef PERL_USE_GCC_BRACE_GROUPS -# define SvREFCNT_inc(sv) \ - ({ \ - SV * const _sv = (SV*)(sv); \ - if (_sv) \ - (SvREFCNT(_sv))++; \ - _sv; \ - }) -# else -# define SvREFCNT_inc(sv) \ - ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL) -# endif -#endif - -#ifndef SvREFCNT_inc_simple -# ifdef PERL_USE_GCC_BRACE_GROUPS -# define SvREFCNT_inc_simple(sv) \ - ({ \ - if (sv) \ - (SvREFCNT(sv))++; \ - (SV *)(sv); \ - }) -# else -# define SvREFCNT_inc_simple(sv) \ - ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL) -# endif -#endif - -#ifndef SvREFCNT_inc_NN -# ifdef PERL_USE_GCC_BRACE_GROUPS -# define SvREFCNT_inc_NN(sv) \ - ({ \ - SV * const _sv = (SV*)(sv); \ - SvREFCNT(_sv)++; \ - _sv; \ - }) -# else -# define SvREFCNT_inc_NN(sv) \ - (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv) -# endif -#endif - -#ifndef SvREFCNT_inc_void -# ifdef PERL_USE_GCC_BRACE_GROUPS -# define SvREFCNT_inc_void(sv) \ - ({ \ - SV * const _sv = (SV*)(sv); \ - if (_sv) \ - (void)(SvREFCNT(_sv)++); \ - }) -# else -# define SvREFCNT_inc_void(sv) \ - (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0) -# endif -#endif -#ifndef SvREFCNT_inc_simple_void -# define SvREFCNT_inc_simple_void(sv) STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END -#endif - -#ifndef SvREFCNT_inc_simple_NN -# define SvREFCNT_inc_simple_NN(sv) (++SvREFCNT(sv), (SV*)(sv)) -#endif - -#ifndef SvREFCNT_inc_void_NN -# define SvREFCNT_inc_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) -#endif - -#ifndef SvREFCNT_inc_simple_void_NN -# define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) -#endif - -#ifndef newSV_type - -#if defined(NEED_newSV_type) -static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); -static -#else -extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); -#endif - -#ifdef newSV_type -# undef newSV_type -#endif -#define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a) -#define Perl_newSV_type DPPP_(my_newSV_type) - -#if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL) - -SV* -DPPP_(my_newSV_type)(pTHX_ svtype const t) -{ - SV* const sv = newSV(0); - sv_upgrade(sv, t); - return sv; -} - -#endif - -#endif - -#if (PERL_BCDVERSION < 0x5006000) -# define D_PPP_CONSTPV_ARG(x) ((char *) (x)) -#else -# define D_PPP_CONSTPV_ARG(x) (x) -#endif -#ifndef newSVpvn -# define newSVpvn(data,len) ((data) \ - ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \ - : newSV(0)) -#endif -#ifndef newSVpvn_utf8 -# define newSVpvn_utf8(s, len, u) newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0) -#endif -#ifndef SVf_UTF8 -# define SVf_UTF8 0 -#endif - -#ifndef newSVpvn_flags - -#if defined(NEED_newSVpvn_flags) -static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); -static -#else -extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); -#endif - -#ifdef newSVpvn_flags -# undef newSVpvn_flags -#endif -#define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c) -#define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags) - -#if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL) - -SV * -DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags) -{ - SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len); - SvFLAGS(sv) |= (flags & SVf_UTF8); - return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv; -} - -#endif - -#endif - -/* Backwards compatibility stuff... :-( */ -#if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen) -# define NEED_sv_2pv_flags -#endif -#if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL) -# define NEED_sv_2pv_flags_GLOBAL -#endif - -/* Hint: sv_2pv_nolen - * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen(). - */ -#ifndef sv_2pv_nolen -# define sv_2pv_nolen(sv) SvPV_nolen(sv) -#endif - -#ifdef SvPVbyte - -/* Hint: SvPVbyte - * Does not work in perl-5.6.1, ppport.h implements a version - * borrowed from perl-5.7.3. - */ - -#if (PERL_BCDVERSION < 0x5007000) - -#if defined(NEED_sv_2pvbyte) -static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); -static -#else -extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); -#endif - -#ifdef sv_2pvbyte -# undef sv_2pvbyte -#endif -#define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b) -#define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte) - -#if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL) - -char * -DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp) -{ - sv_utf8_downgrade(sv,0); - return SvPV(sv,*lp); -} - -#endif - -/* Hint: sv_2pvbyte - * Use the SvPVbyte() macro instead of sv_2pvbyte(). - */ - -#undef SvPVbyte - -#define SvPVbyte(sv, lp) \ - ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \ - ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp)) - -#endif - -#else - -# define SvPVbyte SvPV -# define sv_2pvbyte sv_2pv - -#endif -#ifndef sv_2pvbyte_nolen -# define sv_2pvbyte_nolen(sv) sv_2pv_nolen(sv) -#endif - -/* Hint: sv_pvn - * Always use the SvPV() macro instead of sv_pvn(). - */ - -/* Hint: sv_pvn_force - * Always use the SvPV_force() macro instead of sv_pvn_force(). - */ - -/* If these are undefined, they're not handled by the core anyway */ -#ifndef SV_IMMEDIATE_UNREF -# define SV_IMMEDIATE_UNREF 0 -#endif - -#ifndef SV_GMAGIC -# define SV_GMAGIC 0 -#endif - -#ifndef SV_COW_DROP_PV -# define SV_COW_DROP_PV 0 -#endif - -#ifndef SV_UTF8_NO_ENCODING -# define SV_UTF8_NO_ENCODING 0 -#endif - -#ifndef SV_NOSTEAL -# define SV_NOSTEAL 0 -#endif - -#ifndef SV_CONST_RETURN -# define SV_CONST_RETURN 0 -#endif - -#ifndef SV_MUTABLE_RETURN -# define SV_MUTABLE_RETURN 0 -#endif - -#ifndef SV_SMAGIC -# define SV_SMAGIC 0 -#endif - -#ifndef SV_HAS_TRAILING_NUL -# define SV_HAS_TRAILING_NUL 0 -#endif - -#ifndef SV_COW_SHARED_HASH_KEYS -# define SV_COW_SHARED_HASH_KEYS 0 -#endif - -#if (PERL_BCDVERSION < 0x5007002) - -#if defined(NEED_sv_2pv_flags) -static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); -static -#else -extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); -#endif - -#ifdef sv_2pv_flags -# undef sv_2pv_flags -#endif -#define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c) -#define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags) - -#if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL) - -char * -DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) -{ - STRLEN n_a = (STRLEN) flags; - return sv_2pv(sv, lp ? lp : &n_a); -} - -#endif - -#if defined(NEED_sv_pvn_force_flags) -static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); -static -#else -extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); -#endif - -#ifdef sv_pvn_force_flags -# undef sv_pvn_force_flags -#endif -#define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c) -#define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags) - -#if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL) - -char * -DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) -{ - STRLEN n_a = (STRLEN) flags; - return sv_pvn_force(sv, lp ? lp : &n_a); -} - -#endif - -#endif - -#if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) ) -# define DPPP_SVPV_NOLEN_LP_ARG &PL_na -#else -# define DPPP_SVPV_NOLEN_LP_ARG 0 -#endif -#ifndef SvPV_const -# define SvPV_const(sv, lp) SvPV_flags_const(sv, lp, SV_GMAGIC) -#endif - -#ifndef SvPV_mutable -# define SvPV_mutable(sv, lp) SvPV_flags_mutable(sv, lp, SV_GMAGIC) -#endif -#ifndef SvPV_flags -# define SvPV_flags(sv, lp, flags) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags)) -#endif -#ifndef SvPV_flags_const -# define SvPV_flags_const(sv, lp, flags) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \ - (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN)) -#endif -#ifndef SvPV_flags_const_nolen -# define SvPV_flags_const_nolen(sv, flags) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? SvPVX_const(sv) : \ - (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN)) -#endif -#ifndef SvPV_flags_mutable -# define SvPV_flags_mutable(sv, lp, flags) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \ - sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) -#endif -#ifndef SvPV_force -# define SvPV_force(sv, lp) SvPV_force_flags(sv, lp, SV_GMAGIC) -#endif - -#ifndef SvPV_force_nolen -# define SvPV_force_nolen(sv) SvPV_force_flags_nolen(sv, SV_GMAGIC) -#endif - -#ifndef SvPV_force_mutable -# define SvPV_force_mutable(sv, lp) SvPV_force_flags_mutable(sv, lp, SV_GMAGIC) -#endif - -#ifndef SvPV_force_nomg -# define SvPV_force_nomg(sv, lp) SvPV_force_flags(sv, lp, 0) -#endif - -#ifndef SvPV_force_nomg_nolen -# define SvPV_force_nomg_nolen(sv) SvPV_force_flags_nolen(sv, 0) -#endif -#ifndef SvPV_force_flags -# define SvPV_force_flags(sv, lp, flags) \ - ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ - ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags)) -#endif -#ifndef SvPV_force_flags_nolen -# define SvPV_force_flags_nolen(sv, flags) \ - ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ - ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags)) -#endif -#ifndef SvPV_force_flags_mutable -# define SvPV_force_flags_mutable(sv, lp, flags) \ - ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ - ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \ - : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) -#endif -#ifndef SvPV_nolen -# define SvPV_nolen(sv) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC)) -#endif -#ifndef SvPV_nolen_const -# define SvPV_nolen_const(sv) \ - ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ - ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN)) -#endif -#ifndef SvPV_nomg -# define SvPV_nomg(sv, lp) SvPV_flags(sv, lp, 0) -#endif - -#ifndef SvPV_nomg_const -# define SvPV_nomg_const(sv, lp) SvPV_flags_const(sv, lp, 0) -#endif - -#ifndef SvPV_nomg_const_nolen -# define SvPV_nomg_const_nolen(sv) SvPV_flags_const_nolen(sv, 0) -#endif -#ifndef SvPV_renew -# define SvPV_renew(sv,n) STMT_START { SvLEN_set(sv, n); \ - SvPV_set((sv), (char *) saferealloc( \ - (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \ - } STMT_END -#endif -#ifndef SvMAGIC_set -# define SvMAGIC_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ - (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END -#endif - -#if (PERL_BCDVERSION < 0x5009003) -#ifndef SvPVX_const -# define SvPVX_const(sv) ((const char*) (0 + SvPVX(sv))) -#endif - -#ifndef SvPVX_mutable -# define SvPVX_mutable(sv) (0 + SvPVX(sv)) -#endif -#ifndef SvRV_set -# define SvRV_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ - (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END -#endif - -#else -#ifndef SvPVX_const -# define SvPVX_const(sv) ((const char*)((sv)->sv_u.svu_pv)) -#endif - -#ifndef SvPVX_mutable -# define SvPVX_mutable(sv) ((sv)->sv_u.svu_pv) -#endif -#ifndef SvRV_set -# define SvRV_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ - ((sv)->sv_u.svu_rv = (val)); } STMT_END -#endif - -#endif -#ifndef SvSTASH_set -# define SvSTASH_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ - (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END -#endif - -#if (PERL_BCDVERSION < 0x5004000) -#ifndef SvUV_set -# define SvUV_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ - (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END -#endif - -#else -#ifndef SvUV_set -# define SvUV_set(sv, val) \ - STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ - (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END -#endif - -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf) -#if defined(NEED_vnewSVpvf) -static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); -static -#else -extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); -#endif - -#ifdef vnewSVpvf -# undef vnewSVpvf -#endif -#define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b) -#define Perl_vnewSVpvf DPPP_(my_vnewSVpvf) - -#if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL) - -SV * -DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args) -{ - register SV *sv = newSV(0); - sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); - return sv; -} - -#endif -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf) -# define sv_vcatpvf(sv, pat, args) sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf) -# define sv_vsetpvf(sv, pat, args) sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg) -#if defined(NEED_sv_catpvf_mg) -static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); -static -#else -extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); -#endif - -#define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg) - -#if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL) - -void -DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...) -{ - va_list args; - va_start(args, pat); - sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); - SvSETMAGIC(sv); - va_end(args); -} - -#endif -#endif - -#ifdef PERL_IMPLICIT_CONTEXT -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext) -#if defined(NEED_sv_catpvf_mg_nocontext) -static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); -static -#else -extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); -#endif - -#define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) -#define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) - -#if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL) - -void -DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...) -{ - dTHX; - va_list args; - va_start(args, pat); - sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); - SvSETMAGIC(sv); - va_end(args); -} - -#endif -#endif -#endif - -/* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */ -#ifndef sv_catpvf_mg -# ifdef PERL_IMPLICIT_CONTEXT -# define sv_catpvf_mg Perl_sv_catpvf_mg_nocontext -# else -# define sv_catpvf_mg Perl_sv_catpvf_mg -# endif -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg) -# define sv_vcatpvf_mg(sv, pat, args) \ - STMT_START { \ - sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ - SvSETMAGIC(sv); \ - } STMT_END -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg) -#if defined(NEED_sv_setpvf_mg) -static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); -static -#else -extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); -#endif - -#define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg) - -#if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL) - -void -DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...) -{ - va_list args; - va_start(args, pat); - sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); - SvSETMAGIC(sv); - va_end(args); -} - -#endif -#endif - -#ifdef PERL_IMPLICIT_CONTEXT -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext) -#if defined(NEED_sv_setpvf_mg_nocontext) -static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); -static -#else -extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); -#endif - -#define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) -#define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) - -#if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL) - -void -DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...) -{ - dTHX; - va_list args; - va_start(args, pat); - sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); - SvSETMAGIC(sv); - va_end(args); -} - -#endif -#endif -#endif - -/* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */ -#ifndef sv_setpvf_mg -# ifdef PERL_IMPLICIT_CONTEXT -# define sv_setpvf_mg Perl_sv_setpvf_mg_nocontext -# else -# define sv_setpvf_mg Perl_sv_setpvf_mg -# endif -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg) -# define sv_vsetpvf_mg(sv, pat, args) \ - STMT_START { \ - sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ - SvSETMAGIC(sv); \ - } STMT_END -#endif - -#ifndef newSVpvn_share - -#if defined(NEED_newSVpvn_share) -static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); -static -#else -extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); -#endif - -#ifdef newSVpvn_share -# undef newSVpvn_share -#endif -#define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c) -#define Perl_newSVpvn_share DPPP_(my_newSVpvn_share) - -#if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL) - -SV * -DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash) -{ - SV *sv; - if (len < 0) - len = -len; - if (!hash) - PERL_HASH(hash, (char*) src, len); - sv = newSVpvn((char *) src, len); - sv_upgrade(sv, SVt_PVIV); - SvIVX(sv) = hash; - SvREADONLY_on(sv); - SvPOK_on(sv); - return sv; -} - -#endif - -#endif -#ifndef SvSHARED_HASH -# define SvSHARED_HASH(sv) (0 + SvUVX(sv)) -#endif -#ifndef HvNAME_get -# define HvNAME_get(hv) HvNAME(hv) -#endif -#ifndef HvNAMELEN_get -# define HvNAMELEN_get(hv) (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0) -#endif -#ifndef GvSVn -# define GvSVn(gv) GvSV(gv) -#endif - -#ifndef isGV_with_GP -# define isGV_with_GP(gv) isGV(gv) -#endif -#ifndef WARN_ALL -# define WARN_ALL 0 -#endif - -#ifndef WARN_CLOSURE -# define WARN_CLOSURE 1 -#endif - -#ifndef WARN_DEPRECATED -# define WARN_DEPRECATED 2 -#endif - -#ifndef WARN_EXITING -# define WARN_EXITING 3 -#endif - -#ifndef WARN_GLOB -# define WARN_GLOB 4 -#endif - -#ifndef WARN_IO -# define WARN_IO 5 -#endif - -#ifndef WARN_CLOSED -# define WARN_CLOSED 6 -#endif - -#ifndef WARN_EXEC -# define WARN_EXEC 7 -#endif - -#ifndef WARN_LAYER -# define WARN_LAYER 8 -#endif - -#ifndef WARN_NEWLINE -# define WARN_NEWLINE 9 -#endif - -#ifndef WARN_PIPE -# define WARN_PIPE 10 -#endif - -#ifndef WARN_UNOPENED -# define WARN_UNOPENED 11 -#endif - -#ifndef WARN_MISC -# define WARN_MISC 12 -#endif - -#ifndef WARN_NUMERIC -# define WARN_NUMERIC 13 -#endif - -#ifndef WARN_ONCE -# define WARN_ONCE 14 -#endif - -#ifndef WARN_OVERFLOW -# define WARN_OVERFLOW 15 -#endif - -#ifndef WARN_PACK -# define WARN_PACK 16 -#endif - -#ifndef WARN_PORTABLE -# define WARN_PORTABLE 17 -#endif - -#ifndef WARN_RECURSION -# define WARN_RECURSION 18 -#endif - -#ifndef WARN_REDEFINE -# define WARN_REDEFINE 19 -#endif - -#ifndef WARN_REGEXP -# define WARN_REGEXP 20 -#endif - -#ifndef WARN_SEVERE -# define WARN_SEVERE 21 -#endif - -#ifndef WARN_DEBUGGING -# define WARN_DEBUGGING 22 -#endif - -#ifndef WARN_INPLACE -# define WARN_INPLACE 23 -#endif - -#ifndef WARN_INTERNAL -# define WARN_INTERNAL 24 -#endif - -#ifndef WARN_MALLOC -# define WARN_MALLOC 25 -#endif - -#ifndef WARN_SIGNAL -# define WARN_SIGNAL 26 -#endif - -#ifndef WARN_SUBSTR -# define WARN_SUBSTR 27 -#endif - -#ifndef WARN_SYNTAX -# define WARN_SYNTAX 28 -#endif - -#ifndef WARN_AMBIGUOUS -# define WARN_AMBIGUOUS 29 -#endif - -#ifndef WARN_BAREWORD -# define WARN_BAREWORD 30 -#endif - -#ifndef WARN_DIGIT -# define WARN_DIGIT 31 -#endif - -#ifndef WARN_PARENTHESIS -# define WARN_PARENTHESIS 32 -#endif - -#ifndef WARN_PRECEDENCE -# define WARN_PRECEDENCE 33 -#endif - -#ifndef WARN_PRINTF -# define WARN_PRINTF 34 -#endif - -#ifndef WARN_PROTOTYPE -# define WARN_PROTOTYPE 35 -#endif - -#ifndef WARN_QW -# define WARN_QW 36 -#endif - -#ifndef WARN_RESERVED -# define WARN_RESERVED 37 -#endif - -#ifndef WARN_SEMICOLON -# define WARN_SEMICOLON 38 -#endif - -#ifndef WARN_TAINT -# define WARN_TAINT 39 -#endif - -#ifndef WARN_THREADS -# define WARN_THREADS 40 -#endif - -#ifndef WARN_UNINITIALIZED -# define WARN_UNINITIALIZED 41 -#endif - -#ifndef WARN_UNPACK -# define WARN_UNPACK 42 -#endif - -#ifndef WARN_UNTIE -# define WARN_UNTIE 43 -#endif - -#ifndef WARN_UTF8 -# define WARN_UTF8 44 -#endif - -#ifndef WARN_VOID -# define WARN_VOID 45 -#endif - -#ifndef WARN_ASSERTIONS -# define WARN_ASSERTIONS 46 -#endif -#ifndef packWARN -# define packWARN(a) (a) -#endif - -#ifndef ckWARN -# ifdef G_WARN_ON -# define ckWARN(a) (PL_dowarn & G_WARN_ON) -# else -# define ckWARN(a) PL_dowarn -# endif -#endif - -#if (PERL_BCDVERSION >= 0x5004000) && !defined(warner) -#if defined(NEED_warner) -static void DPPP_(my_warner)(U32 err, const char *pat, ...); -static -#else -extern void DPPP_(my_warner)(U32 err, const char *pat, ...); -#endif - -#define Perl_warner DPPP_(my_warner) - -#if defined(NEED_warner) || defined(NEED_warner_GLOBAL) - -void -DPPP_(my_warner)(U32 err, const char *pat, ...) -{ - SV *sv; - va_list args; - - PERL_UNUSED_ARG(err); - - va_start(args, pat); - sv = vnewSVpvf(pat, &args); - va_end(args); - sv_2mortal(sv); - warn("%s", SvPV_nolen(sv)); -} - -#define warner Perl_warner - -#define Perl_warner_nocontext Perl_warner - -#endif -#endif - -/* concatenating with "" ensures that only literal strings are accepted as argument - * note that STR_WITH_LEN() can't be used as argument to macros or functions that - * under some configurations might be macros - */ -#ifndef STR_WITH_LEN -# define STR_WITH_LEN(s) (s ""), (sizeof(s)-1) -#endif -#ifndef newSVpvs -# define newSVpvs(str) newSVpvn(str "", sizeof(str) - 1) -#endif - -#ifndef newSVpvs_flags -# define newSVpvs_flags(str, flags) newSVpvn_flags(str "", sizeof(str) - 1, flags) -#endif - -#ifndef sv_catpvs -# define sv_catpvs(sv, str) sv_catpvn(sv, str "", sizeof(str) - 1) -#endif - -#ifndef sv_setpvs -# define sv_setpvs(sv, str) sv_setpvn(sv, str "", sizeof(str) - 1) -#endif - -#ifndef hv_fetchs -# define hv_fetchs(hv, key, lval) hv_fetch(hv, key "", sizeof(key) - 1, lval) -#endif - -#ifndef hv_stores -# define hv_stores(hv, key, val) hv_store(hv, key "", sizeof(key) - 1, val, 0) -#endif -#ifndef gv_fetchpvn_flags -# define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt) -#endif - -#ifndef gv_fetchpvs -# define gv_fetchpvs(name, flags, svt) gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt) -#endif - -#ifndef gv_stashpvs -# define gv_stashpvs(name, flags) gv_stashpvn(name "", sizeof(name) - 1, flags) -#endif -#ifndef SvGETMAGIC -# define SvGETMAGIC(x) STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END -#endif -#ifndef PERL_MAGIC_sv -# define PERL_MAGIC_sv '\0' -#endif - -#ifndef PERL_MAGIC_overload -# define PERL_MAGIC_overload 'A' -#endif - -#ifndef PERL_MAGIC_overload_elem -# define PERL_MAGIC_overload_elem 'a' -#endif - -#ifndef PERL_MAGIC_overload_table -# define PERL_MAGIC_overload_table 'c' -#endif - -#ifndef PERL_MAGIC_bm -# define PERL_MAGIC_bm 'B' -#endif - -#ifndef PERL_MAGIC_regdata -# define PERL_MAGIC_regdata 'D' -#endif - -#ifndef PERL_MAGIC_regdatum -# define PERL_MAGIC_regdatum 'd' -#endif - -#ifndef PERL_MAGIC_env -# define PERL_MAGIC_env 'E' -#endif - -#ifndef PERL_MAGIC_envelem -# define PERL_MAGIC_envelem 'e' -#endif - -#ifndef PERL_MAGIC_fm -# define PERL_MAGIC_fm 'f' -#endif - -#ifndef PERL_MAGIC_regex_global -# define PERL_MAGIC_regex_global 'g' -#endif - -#ifndef PERL_MAGIC_isa -# define PERL_MAGIC_isa 'I' -#endif - -#ifndef PERL_MAGIC_isaelem -# define PERL_MAGIC_isaelem 'i' -#endif - -#ifndef PERL_MAGIC_nkeys -# define PERL_MAGIC_nkeys 'k' -#endif - -#ifndef PERL_MAGIC_dbfile -# define PERL_MAGIC_dbfile 'L' -#endif - -#ifndef PERL_MAGIC_dbline -# define PERL_MAGIC_dbline 'l' -#endif - -#ifndef PERL_MAGIC_mutex -# define PERL_MAGIC_mutex 'm' -#endif - -#ifndef PERL_MAGIC_shared -# define PERL_MAGIC_shared 'N' -#endif - -#ifndef PERL_MAGIC_shared_scalar -# define PERL_MAGIC_shared_scalar 'n' -#endif - -#ifndef PERL_MAGIC_collxfrm -# define PERL_MAGIC_collxfrm 'o' -#endif - -#ifndef PERL_MAGIC_tied -# define PERL_MAGIC_tied 'P' -#endif - -#ifndef PERL_MAGIC_tiedelem -# define PERL_MAGIC_tiedelem 'p' -#endif - -#ifndef PERL_MAGIC_tiedscalar -# define PERL_MAGIC_tiedscalar 'q' -#endif - -#ifndef PERL_MAGIC_qr -# define PERL_MAGIC_qr 'r' -#endif - -#ifndef PERL_MAGIC_sig -# define PERL_MAGIC_sig 'S' -#endif - -#ifndef PERL_MAGIC_sigelem -# define PERL_MAGIC_sigelem 's' -#endif - -#ifndef PERL_MAGIC_taint -# define PERL_MAGIC_taint 't' -#endif - -#ifndef PERL_MAGIC_uvar -# define PERL_MAGIC_uvar 'U' -#endif - -#ifndef PERL_MAGIC_uvar_elem -# define PERL_MAGIC_uvar_elem 'u' -#endif - -#ifndef PERL_MAGIC_vstring -# define PERL_MAGIC_vstring 'V' -#endif - -#ifndef PERL_MAGIC_vec -# define PERL_MAGIC_vec 'v' -#endif - -#ifndef PERL_MAGIC_utf8 -# define PERL_MAGIC_utf8 'w' -#endif - -#ifndef PERL_MAGIC_substr -# define PERL_MAGIC_substr 'x' -#endif - -#ifndef PERL_MAGIC_defelem -# define PERL_MAGIC_defelem 'y' -#endif - -#ifndef PERL_MAGIC_glob -# define PERL_MAGIC_glob '*' -#endif - -#ifndef PERL_MAGIC_arylen -# define PERL_MAGIC_arylen '#' -#endif - -#ifndef PERL_MAGIC_pos -# define PERL_MAGIC_pos '.' -#endif - -#ifndef PERL_MAGIC_backref -# define PERL_MAGIC_backref '<' -#endif - -#ifndef PERL_MAGIC_ext -# define PERL_MAGIC_ext '~' -#endif - -/* That's the best we can do... */ -#ifndef sv_catpvn_nomg -# define sv_catpvn_nomg sv_catpvn -#endif - -#ifndef sv_catsv_nomg -# define sv_catsv_nomg sv_catsv -#endif - -#ifndef sv_setsv_nomg -# define sv_setsv_nomg sv_setsv -#endif - -#ifndef sv_pvn_nomg -# define sv_pvn_nomg sv_pvn -#endif - -#ifndef SvIV_nomg -# define SvIV_nomg SvIV -#endif - -#ifndef SvUV_nomg -# define SvUV_nomg SvUV -#endif - -#ifndef sv_catpv_mg -# define sv_catpv_mg(sv, ptr) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_catpv(TeMpSv,ptr); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_catpvn_mg -# define sv_catpvn_mg(sv, ptr, len) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_catpvn(TeMpSv,ptr,len); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_catsv_mg -# define sv_catsv_mg(dsv, ssv) \ - STMT_START { \ - SV *TeMpSv = dsv; \ - sv_catsv(TeMpSv,ssv); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setiv_mg -# define sv_setiv_mg(sv, i) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_setiv(TeMpSv,i); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setnv_mg -# define sv_setnv_mg(sv, num) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_setnv(TeMpSv,num); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setpv_mg -# define sv_setpv_mg(sv, ptr) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_setpv(TeMpSv,ptr); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setpvn_mg -# define sv_setpvn_mg(sv, ptr, len) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_setpvn(TeMpSv,ptr,len); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setsv_mg -# define sv_setsv_mg(dsv, ssv) \ - STMT_START { \ - SV *TeMpSv = dsv; \ - sv_setsv(TeMpSv,ssv); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_setuv_mg -# define sv_setuv_mg(sv, i) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_setuv(TeMpSv,i); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif - -#ifndef sv_usepvn_mg -# define sv_usepvn_mg(sv, ptr, len) \ - STMT_START { \ - SV *TeMpSv = sv; \ - sv_usepvn(TeMpSv,ptr,len); \ - SvSETMAGIC(TeMpSv); \ - } STMT_END -#endif -#ifndef SvVSTRING_mg -# define SvVSTRING_mg(sv) (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL) -#endif - -/* Hint: sv_magic_portable - * This is a compatibility function that is only available with - * Devel::PPPort. It is NOT in the perl core. - * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when - * it is being passed a name pointer with namlen == 0. In that - * case, perl 5.8.0 and later store the pointer, not a copy of it. - * The compatibility can be provided back to perl 5.004. With - * earlier versions, the code will not compile. - */ - -#if (PERL_BCDVERSION < 0x5004000) - - /* code that uses sv_magic_portable will not compile */ - -#elif (PERL_BCDVERSION < 0x5008000) - -# define sv_magic_portable(sv, obj, how, name, namlen) \ - STMT_START { \ - SV *SvMp_sv = (sv); \ - char *SvMp_name = (char *) (name); \ - I32 SvMp_namlen = (namlen); \ - if (SvMp_name && SvMp_namlen == 0) \ - { \ - MAGIC *mg; \ - sv_magic(SvMp_sv, obj, how, 0, 0); \ - mg = SvMAGIC(SvMp_sv); \ - mg->mg_len = -42; /* XXX: this is the tricky part */ \ - mg->mg_ptr = SvMp_name; \ - } \ - else \ - { \ - sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \ - } \ - } STMT_END - -#else - -# define sv_magic_portable(a, b, c, d, e) sv_magic(a, b, c, d, e) - -#endif - -#ifdef USE_ITHREADS -#ifndef CopFILE -# define CopFILE(c) ((c)->cop_file) -#endif - -#ifndef CopFILEGV -# define CopFILEGV(c) (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv) -#endif - -#ifndef CopFILE_set -# define CopFILE_set(c,pv) ((c)->cop_file = savepv(pv)) -#endif - -#ifndef CopFILESV -# define CopFILESV(c) (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv) -#endif - -#ifndef CopFILEAV -# define CopFILEAV(c) (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav) -#endif - -#ifndef CopSTASHPV -# define CopSTASHPV(c) ((c)->cop_stashpv) -#endif - -#ifndef CopSTASHPV_set -# define CopSTASHPV_set(c,pv) ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch)) -#endif - -#ifndef CopSTASH -# define CopSTASH(c) (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv) -#endif - -#ifndef CopSTASH_set -# define CopSTASH_set(c,hv) CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch) -#endif - -#ifndef CopSTASH_eq -# define CopSTASH_eq(c,hv) ((hv) && (CopSTASHPV(c) == HvNAME(hv) \ - || (CopSTASHPV(c) && HvNAME(hv) \ - && strEQ(CopSTASHPV(c), HvNAME(hv))))) -#endif - -#else -#ifndef CopFILEGV -# define CopFILEGV(c) ((c)->cop_filegv) -#endif - -#ifndef CopFILEGV_set -# define CopFILEGV_set(c,gv) ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv)) -#endif - -#ifndef CopFILE_set -# define CopFILE_set(c,pv) CopFILEGV_set((c), gv_fetchfile(pv)) -#endif - -#ifndef CopFILESV -# define CopFILESV(c) (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv) -#endif - -#ifndef CopFILEAV -# define CopFILEAV(c) (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav) -#endif - -#ifndef CopFILE -# define CopFILE(c) (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch) -#endif - -#ifndef CopSTASH -# define CopSTASH(c) ((c)->cop_stash) -#endif - -#ifndef CopSTASH_set -# define CopSTASH_set(c,hv) ((c)->cop_stash = (hv)) -#endif - -#ifndef CopSTASHPV -# define CopSTASHPV(c) (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch) -#endif - -#ifndef CopSTASHPV_set -# define CopSTASHPV_set(c,pv) CopSTASH_set((c), gv_stashpv(pv,GV_ADD)) -#endif - -#ifndef CopSTASH_eq -# define CopSTASH_eq(c,hv) (CopSTASH(c) == (hv)) -#endif - -#endif /* USE_ITHREADS */ -#ifndef IN_PERL_COMPILETIME -# define IN_PERL_COMPILETIME (PL_curcop == &PL_compiling) -#endif - -#ifndef IN_LOCALE_RUNTIME -# define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE) -#endif - -#ifndef IN_LOCALE_COMPILETIME -# define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE) -#endif - -#ifndef IN_LOCALE -# define IN_LOCALE (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME) -#endif -#ifndef IS_NUMBER_IN_UV -# define IS_NUMBER_IN_UV 0x01 -#endif - -#ifndef IS_NUMBER_GREATER_THAN_UV_MAX -# define IS_NUMBER_GREATER_THAN_UV_MAX 0x02 -#endif - -#ifndef IS_NUMBER_NOT_INT -# define IS_NUMBER_NOT_INT 0x04 -#endif - -#ifndef IS_NUMBER_NEG -# define IS_NUMBER_NEG 0x08 -#endif - -#ifndef IS_NUMBER_INFINITY -# define IS_NUMBER_INFINITY 0x10 -#endif - -#ifndef IS_NUMBER_NAN -# define IS_NUMBER_NAN 0x20 -#endif -#ifndef GROK_NUMERIC_RADIX -# define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(sp, send) -#endif -#ifndef PERL_SCAN_GREATER_THAN_UV_MAX -# define PERL_SCAN_GREATER_THAN_UV_MAX 0x02 -#endif - -#ifndef PERL_SCAN_SILENT_ILLDIGIT -# define PERL_SCAN_SILENT_ILLDIGIT 0x04 -#endif - -#ifndef PERL_SCAN_ALLOW_UNDERSCORES -# define PERL_SCAN_ALLOW_UNDERSCORES 0x01 -#endif - -#ifndef PERL_SCAN_DISALLOW_PREFIX -# define PERL_SCAN_DISALLOW_PREFIX 0x02 -#endif - -#ifndef grok_numeric_radix -#if defined(NEED_grok_numeric_radix) -static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); -static -#else -extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); -#endif - -#ifdef grok_numeric_radix -# undef grok_numeric_radix -#endif -#define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b) -#define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix) - -#if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL) -bool -DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send) -{ -#ifdef USE_LOCALE_NUMERIC -#ifdef PL_numeric_radix_sv - if (PL_numeric_radix_sv && IN_LOCALE) { - STRLEN len; - char* radix = SvPV(PL_numeric_radix_sv, len); - if (*sp + len <= send && memEQ(*sp, radix, len)) { - *sp += len; - return TRUE; - } - } -#else - /* older perls don't have PL_numeric_radix_sv so the radix - * must manually be requested from locale.h - */ -#include - dTHR; /* needed for older threaded perls */ - struct lconv *lc = localeconv(); - char *radix = lc->decimal_point; - if (radix && IN_LOCALE) { - STRLEN len = strlen(radix); - if (*sp + len <= send && memEQ(*sp, radix, len)) { - *sp += len; - return TRUE; - } - } -#endif -#endif /* USE_LOCALE_NUMERIC */ - /* always try "." if numeric radix didn't match because - * we may have data from different locales mixed */ - if (*sp < send && **sp == '.') { - ++*sp; - return TRUE; - } - return FALSE; -} -#endif -#endif - -#ifndef grok_number -#if defined(NEED_grok_number) -static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); -static -#else -extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); -#endif - -#ifdef grok_number -# undef grok_number -#endif -#define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c) -#define Perl_grok_number DPPP_(my_grok_number) - -#if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL) -int -DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep) -{ - const char *s = pv; - const char *send = pv + len; - const UV max_div_10 = UV_MAX / 10; - const char max_mod_10 = UV_MAX % 10; - int numtype = 0; - int sawinf = 0; - int sawnan = 0; - - while (s < send && isSPACE(*s)) - s++; - if (s == send) { - return 0; - } else if (*s == '-') { - s++; - numtype = IS_NUMBER_NEG; - } - else if (*s == '+') - s++; - - if (s == send) - return 0; - - /* next must be digit or the radix separator or beginning of infinity */ - if (isDIGIT(*s)) { - /* UVs are at least 32 bits, so the first 9 decimal digits cannot - overflow. */ - UV value = *s - '0'; - /* This construction seems to be more optimiser friendly. - (without it gcc does the isDIGIT test and the *s - '0' separately) - With it gcc on arm is managing 6 instructions (6 cycles) per digit. - In theory the optimiser could deduce how far to unroll the loop - before checking for overflow. */ - if (++s < send) { - int digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - digit = *s - '0'; - if (digit >= 0 && digit <= 9) { - value = value * 10 + digit; - if (++s < send) { - /* Now got 9 digits, so need to check - each time for overflow. */ - digit = *s - '0'; - while (digit >= 0 && digit <= 9 - && (value < max_div_10 - || (value == max_div_10 - && digit <= max_mod_10))) { - value = value * 10 + digit; - if (++s < send) - digit = *s - '0'; - else - break; - } - if (digit >= 0 && digit <= 9 - && (s < send)) { - /* value overflowed. - skip the remaining digits, don't - worry about setting *valuep. */ - do { - s++; - } while (s < send && isDIGIT(*s)); - numtype |= - IS_NUMBER_GREATER_THAN_UV_MAX; - goto skip_value; - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - numtype |= IS_NUMBER_IN_UV; - if (valuep) - *valuep = value; - - skip_value: - if (GROK_NUMERIC_RADIX(&s, send)) { - numtype |= IS_NUMBER_NOT_INT; - while (s < send && isDIGIT(*s)) /* optional digits after the radix */ - s++; - } - } - else if (GROK_NUMERIC_RADIX(&s, send)) { - numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */ - /* no digits before the radix means we need digits after it */ - if (s < send && isDIGIT(*s)) { - do { - s++; - } while (s < send && isDIGIT(*s)); - if (valuep) { - /* integer approximation is valid - it's 0. */ - *valuep = 0; - } - } - else - return 0; - } else if (*s == 'I' || *s == 'i') { - s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; - s++; if (s == send || (*s != 'F' && *s != 'f')) return 0; - s++; if (s < send && (*s == 'I' || *s == 'i')) { - s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; - s++; if (s == send || (*s != 'I' && *s != 'i')) return 0; - s++; if (s == send || (*s != 'T' && *s != 't')) return 0; - s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0; - s++; - } - sawinf = 1; - } else if (*s == 'N' || *s == 'n') { - /* XXX TODO: There are signaling NaNs and quiet NaNs. */ - s++; if (s == send || (*s != 'A' && *s != 'a')) return 0; - s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; - s++; - sawnan = 1; - } else - return 0; - - if (sawinf) { - numtype &= IS_NUMBER_NEG; /* Keep track of sign */ - numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT; - } else if (sawnan) { - numtype &= IS_NUMBER_NEG; /* Keep track of sign */ - numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT; - } else if (s < send) { - /* we can have an optional exponent part */ - if (*s == 'e' || *s == 'E') { - /* The only flag we keep is sign. Blow away any "it's UV" */ - numtype &= IS_NUMBER_NEG; - numtype |= IS_NUMBER_NOT_INT; - s++; - if (s < send && (*s == '-' || *s == '+')) - s++; - if (s < send && isDIGIT(*s)) { - do { - s++; - } while (s < send && isDIGIT(*s)); - } - else - return 0; - } - } - while (s < send && isSPACE(*s)) - s++; - if (s >= send) - return numtype; - if (len == 10 && memEQ(pv, "0 but true", 10)) { - if (valuep) - *valuep = 0; - return IS_NUMBER_IN_UV; - } - return 0; -} -#endif -#endif - -/* - * The grok_* routines have been modified to use warn() instead of - * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit, - * which is why the stack variable has been renamed to 'xdigit'. - */ - -#ifndef grok_bin -#if defined(NEED_grok_bin) -static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -static -#else -extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -#endif - -#ifdef grok_bin -# undef grok_bin -#endif -#define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d) -#define Perl_grok_bin DPPP_(my_grok_bin) - -#if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL) -UV -DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) -{ - const char *s = start; - STRLEN len = *len_p; - UV value = 0; - NV value_nv = 0; - - const UV max_div_2 = UV_MAX / 2; - bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; - bool overflowed = FALSE; - - if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { - /* strip off leading b or 0b. - for compatibility silently suffer "b" and "0b" as valid binary - numbers. */ - if (len >= 1) { - if (s[0] == 'b') { - s++; - len--; - } - else if (len >= 2 && s[0] == '0' && s[1] == 'b') { - s+=2; - len-=2; - } - } - } - - for (; len-- && *s; s++) { - char bit = *s; - if (bit == '0' || bit == '1') { - /* Write it in this wonky order with a goto to attempt to get the - compiler to make the common case integer-only loop pretty tight. - With gcc seems to be much straighter code than old scan_bin. */ - redo: - if (!overflowed) { - if (value <= max_div_2) { - value = (value << 1) | (bit - '0'); - continue; - } - /* Bah. We're just overflowed. */ - warn("Integer overflow in binary number"); - overflowed = TRUE; - value_nv = (NV) value; - } - value_nv *= 2.0; - /* If an NV has not enough bits in its mantissa to - * represent a UV this summing of small low-order numbers - * is a waste of time (because the NV cannot preserve - * the low-order bits anyway): we could just remember when - * did we overflow and in the end just multiply value_nv by the - * right amount. */ - value_nv += (NV)(bit - '0'); - continue; - } - if (bit == '_' && len && allow_underscores && (bit = s[1]) - && (bit == '0' || bit == '1')) - { - --len; - ++s; - goto redo; - } - if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) - warn("Illegal binary digit '%c' ignored", *s); - break; - } - - if ( ( overflowed && value_nv > 4294967295.0) -#if UVSIZE > 4 - || (!overflowed && value > 0xffffffff ) -#endif - ) { - warn("Binary number > 0b11111111111111111111111111111111 non-portable"); - } - *len_p = s - start; - if (!overflowed) { - *flags = 0; - return value; - } - *flags = PERL_SCAN_GREATER_THAN_UV_MAX; - if (result) - *result = value_nv; - return UV_MAX; -} -#endif -#endif - -#ifndef grok_hex -#if defined(NEED_grok_hex) -static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -static -#else -extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -#endif - -#ifdef grok_hex -# undef grok_hex -#endif -#define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d) -#define Perl_grok_hex DPPP_(my_grok_hex) - -#if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL) -UV -DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) -{ - const char *s = start; - STRLEN len = *len_p; - UV value = 0; - NV value_nv = 0; - - const UV max_div_16 = UV_MAX / 16; - bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; - bool overflowed = FALSE; - const char *xdigit; - - if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { - /* strip off leading x or 0x. - for compatibility silently suffer "x" and "0x" as valid hex numbers. - */ - if (len >= 1) { - if (s[0] == 'x') { - s++; - len--; - } - else if (len >= 2 && s[0] == '0' && s[1] == 'x') { - s+=2; - len-=2; - } - } - } - - for (; len-- && *s; s++) { - xdigit = strchr((char *) PL_hexdigit, *s); - if (xdigit) { - /* Write it in this wonky order with a goto to attempt to get the - compiler to make the common case integer-only loop pretty tight. - With gcc seems to be much straighter code than old scan_hex. */ - redo: - if (!overflowed) { - if (value <= max_div_16) { - value = (value << 4) | ((xdigit - PL_hexdigit) & 15); - continue; - } - warn("Integer overflow in hexadecimal number"); - overflowed = TRUE; - value_nv = (NV) value; - } - value_nv *= 16.0; - /* If an NV has not enough bits in its mantissa to - * represent a UV this summing of small low-order numbers - * is a waste of time (because the NV cannot preserve - * the low-order bits anyway): we could just remember when - * did we overflow and in the end just multiply value_nv by the - * right amount of 16-tuples. */ - value_nv += (NV)((xdigit - PL_hexdigit) & 15); - continue; - } - if (*s == '_' && len && allow_underscores && s[1] - && (xdigit = strchr((char *) PL_hexdigit, s[1]))) - { - --len; - ++s; - goto redo; - } - if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) - warn("Illegal hexadecimal digit '%c' ignored", *s); - break; - } - - if ( ( overflowed && value_nv > 4294967295.0) -#if UVSIZE > 4 - || (!overflowed && value > 0xffffffff ) -#endif - ) { - warn("Hexadecimal number > 0xffffffff non-portable"); - } - *len_p = s - start; - if (!overflowed) { - *flags = 0; - return value; - } - *flags = PERL_SCAN_GREATER_THAN_UV_MAX; - if (result) - *result = value_nv; - return UV_MAX; -} -#endif -#endif - -#ifndef grok_oct -#if defined(NEED_grok_oct) -static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -static -#else -extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); -#endif - -#ifdef grok_oct -# undef grok_oct -#endif -#define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d) -#define Perl_grok_oct DPPP_(my_grok_oct) - -#if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL) -UV -DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) -{ - const char *s = start; - STRLEN len = *len_p; - UV value = 0; - NV value_nv = 0; - - const UV max_div_8 = UV_MAX / 8; - bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; - bool overflowed = FALSE; - - for (; len-- && *s; s++) { - /* gcc 2.95 optimiser not smart enough to figure that this subtraction - out front allows slicker code. */ - int digit = *s - '0'; - if (digit >= 0 && digit <= 7) { - /* Write it in this wonky order with a goto to attempt to get the - compiler to make the common case integer-only loop pretty tight. - */ - redo: - if (!overflowed) { - if (value <= max_div_8) { - value = (value << 3) | digit; - continue; - } - /* Bah. We're just overflowed. */ - warn("Integer overflow in octal number"); - overflowed = TRUE; - value_nv = (NV) value; - } - value_nv *= 8.0; - /* If an NV has not enough bits in its mantissa to - * represent a UV this summing of small low-order numbers - * is a waste of time (because the NV cannot preserve - * the low-order bits anyway): we could just remember when - * did we overflow and in the end just multiply value_nv by the - * right amount of 8-tuples. */ - value_nv += (NV)digit; - continue; - } - if (digit == ('_' - '0') && len && allow_underscores - && (digit = s[1] - '0') && (digit >= 0 && digit <= 7)) - { - --len; - ++s; - goto redo; - } - /* Allow \octal to work the DWIM way (that is, stop scanning - * as soon as non-octal characters are seen, complain only iff - * someone seems to want to use the digits eight and nine). */ - if (digit == 8 || digit == 9) { - if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) - warn("Illegal octal digit '%c' ignored", *s); - } - break; - } - - if ( ( overflowed && value_nv > 4294967295.0) -#if UVSIZE > 4 - || (!overflowed && value > 0xffffffff ) -#endif - ) { - warn("Octal number > 037777777777 non-portable"); - } - *len_p = s - start; - if (!overflowed) { - *flags = 0; - return value; - } - *flags = PERL_SCAN_GREATER_THAN_UV_MAX; - if (result) - *result = value_nv; - return UV_MAX; -} -#endif -#endif - -#if !defined(my_snprintf) -#if defined(NEED_my_snprintf) -static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); -static -#else -extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); -#endif - -#define my_snprintf DPPP_(my_my_snprintf) -#define Perl_my_snprintf DPPP_(my_my_snprintf) - -#if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL) - -int -DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...) -{ - dTHX; - int retval; - va_list ap; - va_start(ap, format); -#ifdef HAS_VSNPRINTF - retval = vsnprintf(buffer, len, format, ap); -#else - retval = vsprintf(buffer, format, ap); -#endif - va_end(ap); - if (retval < 0 || (len > 0 && (Size_t)retval >= len)) - Perl_croak(aTHX_ "panic: my_snprintf buffer overflow"); - return retval; -} - -#endif -#endif - -#if !defined(my_sprintf) -#if defined(NEED_my_sprintf) -static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); -static -#else -extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); -#endif - -#define my_sprintf DPPP_(my_my_sprintf) -#define Perl_my_sprintf DPPP_(my_my_sprintf) - -#if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL) - -int -DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...) -{ - va_list args; - va_start(args, pat); - vsprintf(buffer, pat, args); - va_end(args); - return strlen(buffer); -} - -#endif -#endif - -#ifdef NO_XSLOCKS -# ifdef dJMPENV -# define dXCPT dJMPENV; int rEtV = 0 -# define XCPT_TRY_START JMPENV_PUSH(rEtV); if (rEtV == 0) -# define XCPT_TRY_END JMPENV_POP; -# define XCPT_CATCH if (rEtV != 0) -# define XCPT_RETHROW JMPENV_JUMP(rEtV) -# else -# define dXCPT Sigjmp_buf oldTOP; int rEtV = 0 -# define XCPT_TRY_START Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0) -# define XCPT_TRY_END Copy(oldTOP, top_env, 1, Sigjmp_buf); -# define XCPT_CATCH if (rEtV != 0) -# define XCPT_RETHROW Siglongjmp(top_env, rEtV) -# endif -#endif - -#if !defined(my_strlcat) -#if defined(NEED_my_strlcat) -static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); -static -#else -extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); -#endif - -#define my_strlcat DPPP_(my_my_strlcat) -#define Perl_my_strlcat DPPP_(my_my_strlcat) - -#if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL) - -Size_t -DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size) -{ - Size_t used, length, copy; - - used = strlen(dst); - length = strlen(src); - if (size > 0 && used < size - 1) { - copy = (length >= size - used) ? size - used - 1 : length; - memcpy(dst + used, src, copy); - dst[used + copy] = '\0'; - } - return used + length; -} -#endif -#endif - -#if !defined(my_strlcpy) -#if defined(NEED_my_strlcpy) -static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); -static -#else -extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); -#endif - -#define my_strlcpy DPPP_(my_my_strlcpy) -#define Perl_my_strlcpy DPPP_(my_my_strlcpy) - -#if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL) - -Size_t -DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size) -{ - Size_t length, copy; - - length = strlen(src); - if (size > 0) { - copy = (length >= size) ? size - 1 : length; - memcpy(dst, src, copy); - dst[copy] = '\0'; - } - return length; -} - -#endif -#endif -#ifndef PERL_PV_ESCAPE_QUOTE -# define PERL_PV_ESCAPE_QUOTE 0x0001 -#endif - -#ifndef PERL_PV_PRETTY_QUOTE -# define PERL_PV_PRETTY_QUOTE PERL_PV_ESCAPE_QUOTE -#endif - -#ifndef PERL_PV_PRETTY_ELLIPSES -# define PERL_PV_PRETTY_ELLIPSES 0x0002 -#endif - -#ifndef PERL_PV_PRETTY_LTGT -# define PERL_PV_PRETTY_LTGT 0x0004 -#endif - -#ifndef PERL_PV_ESCAPE_FIRSTCHAR -# define PERL_PV_ESCAPE_FIRSTCHAR 0x0008 -#endif - -#ifndef PERL_PV_ESCAPE_UNI -# define PERL_PV_ESCAPE_UNI 0x0100 -#endif - -#ifndef PERL_PV_ESCAPE_UNI_DETECT -# define PERL_PV_ESCAPE_UNI_DETECT 0x0200 -#endif - -#ifndef PERL_PV_ESCAPE_ALL -# define PERL_PV_ESCAPE_ALL 0x1000 -#endif - -#ifndef PERL_PV_ESCAPE_NOBACKSLASH -# define PERL_PV_ESCAPE_NOBACKSLASH 0x2000 -#endif - -#ifndef PERL_PV_ESCAPE_NOCLEAR -# define PERL_PV_ESCAPE_NOCLEAR 0x4000 -#endif - -#ifndef PERL_PV_ESCAPE_RE -# define PERL_PV_ESCAPE_RE 0x8000 -#endif - -#ifndef PERL_PV_PRETTY_NOCLEAR -# define PERL_PV_PRETTY_NOCLEAR PERL_PV_ESCAPE_NOCLEAR -#endif -#ifndef PERL_PV_PRETTY_DUMP -# define PERL_PV_PRETTY_DUMP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE -#endif - -#ifndef PERL_PV_PRETTY_REGPROP -# define PERL_PV_PRETTY_REGPROP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE -#endif - -/* Hint: pv_escape - * Note that unicode functionality is only backported to - * those perl versions that support it. For older perl - * versions, the implementation will fall back to bytes. - */ - -#ifndef pv_escape -#if defined(NEED_pv_escape) -static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); -static -#else -extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); -#endif - -#ifdef pv_escape -# undef pv_escape -#endif -#define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f) -#define Perl_pv_escape DPPP_(my_pv_escape) - -#if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL) - -char * -DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str, - const STRLEN count, const STRLEN max, - STRLEN * const escaped, const U32 flags) -{ - const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\'; - const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc; - char octbuf[32] = "%123456789ABCDF"; - STRLEN wrote = 0; - STRLEN chsize = 0; - STRLEN readsize = 1; -#if defined(is_utf8_string) && defined(utf8_to_uvchr) - bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0; -#endif - const char *pv = str; - const char * const end = pv + count; - octbuf[0] = esc; - - if (!(flags & PERL_PV_ESCAPE_NOCLEAR)) - sv_setpvs(dsv, ""); - -#if defined(is_utf8_string) && defined(utf8_to_uvchr) - if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count)) - isuni = 1; -#endif - - for (; pv < end && (!max || wrote < max) ; pv += readsize) { - const UV u = -#if defined(is_utf8_string) && defined(utf8_to_uvchr) - isuni ? utf8_to_uvchr((U8*)pv, &readsize) : -#endif - (U8)*pv; - const U8 c = (U8)u & 0xFF; - - if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) { - if (flags & PERL_PV_ESCAPE_FIRSTCHAR) - chsize = my_snprintf(octbuf, sizeof octbuf, - "%"UVxf, u); - else - chsize = my_snprintf(octbuf, sizeof octbuf, - "%cx{%"UVxf"}", esc, u); - } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) { - chsize = 1; - } else { - if (c == dq || c == esc || !isPRINT(c)) { - chsize = 2; - switch (c) { - case '\\' : /* fallthrough */ - case '%' : if (c == esc) - octbuf[1] = esc; - else - chsize = 1; - break; - case '\v' : octbuf[1] = 'v'; break; - case '\t' : octbuf[1] = 't'; break; - case '\r' : octbuf[1] = 'r'; break; - case '\n' : octbuf[1] = 'n'; break; - case '\f' : octbuf[1] = 'f'; break; - case '"' : if (dq == '"') - octbuf[1] = '"'; - else - chsize = 1; - break; - default: chsize = my_snprintf(octbuf, sizeof octbuf, - pv < end && isDIGIT((U8)*(pv+readsize)) - ? "%c%03o" : "%c%o", esc, c); - } - } else { - chsize = 1; - } - } - if (max && wrote + chsize > max) { - break; - } else if (chsize > 1) { - sv_catpvn(dsv, octbuf, chsize); - wrote += chsize; - } else { - char tmp[2]; - my_snprintf(tmp, sizeof tmp, "%c", c); - sv_catpvn(dsv, tmp, 1); - wrote++; - } - if (flags & PERL_PV_ESCAPE_FIRSTCHAR) - break; - } - if (escaped != NULL) - *escaped= pv - str; - return SvPVX(dsv); -} - -#endif -#endif - -#ifndef pv_pretty -#if defined(NEED_pv_pretty) -static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); -static -#else -extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); -#endif - -#ifdef pv_pretty -# undef pv_pretty -#endif -#define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g) -#define Perl_pv_pretty DPPP_(my_pv_pretty) - -#if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL) - -char * -DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count, - const STRLEN max, char const * const start_color, char const * const end_color, - const U32 flags) -{ - const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%'; - STRLEN escaped; - - if (!(flags & PERL_PV_PRETTY_NOCLEAR)) - sv_setpvs(dsv, ""); - - if (dq == '"') - sv_catpvs(dsv, "\""); - else if (flags & PERL_PV_PRETTY_LTGT) - sv_catpvs(dsv, "<"); - - if (start_color != NULL) - sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color)); - - pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR); - - if (end_color != NULL) - sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color)); - - if (dq == '"') - sv_catpvs(dsv, "\""); - else if (flags & PERL_PV_PRETTY_LTGT) - sv_catpvs(dsv, ">"); - - if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count) - sv_catpvs(dsv, "..."); - - return SvPVX(dsv); -} - -#endif -#endif - -#ifndef pv_display -#if defined(NEED_pv_display) -static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); -static -#else -extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); -#endif - -#ifdef pv_display -# undef pv_display -#endif -#define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e) -#define Perl_pv_display DPPP_(my_pv_display) - -#if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL) - -char * -DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim) -{ - pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP); - if (len > cur && pv[cur] == '\0') - sv_catpvs(dsv, "\\0"); - return SvPVX(dsv); -} - -#endif -#endif - -#endif /* _P_P_PORTABILITY_H_ */ - -/* End of File ppport.h */ diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h deleted file mode 100644 index dcf56a6..0000000 --- a/xs/src/xsinit.h +++ /dev/null @@ -1,262 +0,0 @@ -#ifndef _xsinit_h_ -#define _xsinit_h_ - -#ifdef _MSC_VER -// Disable some obnoxious warnings given by Visual Studio with the default warning level 4. -#pragma warning(disable: 4100 4127 4189 4244 4267 4700 4702 4800) -#endif - -// undef some macros set by Perl which cause compilation errors on Win32 -#undef read -#undef seekdir -#undef bind -#undef send -#undef connect -#undef wait -#undef accept -#undef close -#undef open -#undef write -#undef socket -#undef listen -#undef shutdown -#undef ioctl -#undef getpeername -#undef rect -#undef setsockopt -#undef getsockopt -#undef getsockname -#undef gethostname -#undef select -#undef socketpair -#undef recvfrom -#undef sendto -#undef pause - -// these need to be included early for Win32 (listing it in Build.PL is not enough) -#include -#include -#include -// #include - -#ifdef SLIC3RXS -// extern "C" { -#include "EXTERN.h" -#include "perl.h" -#include "XSUB.h" -#include "ppport.h" -#undef do_open -#undef do_close -#undef bind -#undef seed -#undef push -#undef pop -#ifdef _MSC_VER - // Undef some of the macros set by Perl , which cause compilation errors on Win32 - #undef connect - #undef link - #undef unlink - #undef seek - #undef send - #undef write - #undef open - #undef close - #undef seekdir - #undef setbuf - #undef fread - #undef fseek - #undef fputc - #undef fwrite - #undef fclose - #undef sleep - #undef snprintf - #undef vsnprintf - #undef strerror - #undef test - #undef times - #undef accept - #undef wait - #undef abort - #undef pause - - // Breaks compilation with Eigen matrices embedded into Slic3r::Point. - #undef malloc - #undef realloc - #undef free - #undef select - - // Because of TBB - #undef _WIN32_WINNT // To avoid compiler warnings - #define _WIN32_WINNT 0x0502 -#endif /* _MSC_VER */ -#undef Zero -#undef Packet -#undef _ -// } -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Slic3r { - -template -struct ClassTraits { - // Name of a Perl alias of a C++ class type, owned by Perl, reference counted. - static const char* name; - // Name of a Perl alias of a C++ class type, owned by the C++ code. - // The references shall be enumerated at the end of XS.pm, where the desctructor is undefined with sub DESTROY {}, - // so Perl will never delete the object instance. - static const char* name_ref; -}; - -// use this for typedefs for which the forward prototype -// in REGISTER_CLASS won't work -#define __REGISTER_CLASS(cname, perlname) \ - template <>const char* ClassTraits::name = "Slic3r::" perlname; \ - template <>const char* ClassTraits::name_ref = "Slic3r::" perlname "::Ref"; - -#define REGISTER_CLASS(cname,perlname) \ - class cname; \ - __REGISTER_CLASS(cname, perlname); - -// Return Perl alias to a C++ class name. -template -const char* perl_class_name(const T*) { return ClassTraits::name; } -// Return Perl alias to a C++ class name, suffixed with ::Ref. -// Such a C++ class instance will not be destroyed by Perl, the instance destruction is left to the C++ code. -template -const char* perl_class_name_ref(const T*) { return ClassTraits::name_ref; } - -// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object reference. -// Perl will never release the C++ instance. -template -SV* perl_to_SV_ref(T &t) { - SV* sv = newSV(0); - sv_setref_pv( sv, perl_class_name_ref(&t), &t ); - return sv; -} - -// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object instance. -// Perl will own the C++ instance, therefore it will also release it. -template -SV* perl_to_SV_clone_ref(const T &t) { - SV* sv = newSV(0); - sv_setref_pv( sv, perl_class_name(&t), new T(t) ); - return sv; -} - -// Reference wrapper to provide a C++ instance to Perl while keeping Perl from destroying the instance. -// The instance is created temporarily by XS.cpp just to provide Perl with a CLASS name and a object instance pointer. -template -class Ref { - T* val; -public: - Ref() : val(NULL) {} - Ref(T* t) : val(t) {} - Ref(const T* t) : val(const_cast(t)) {} - // Called by XS.cpp to convert the referenced object instance to a Perl SV, before it is blessed with the name - // returned by CLASS() - operator T*() const { return val; } - // Name to bless the Perl SV with. The name ends with a "::Ref" suffix to keep Perl from destroying the object instance. - static const char* CLASS() { return ClassTraits::name_ref; } -}; - -// Wrapper to clone a C++ object instance before passing it to Perl for ownership. -// This wrapper instance is created temporarily by XS.cpp to provide Perl with a CLASS name and a object instance pointer. -template -class Clone { - T* val; -public: - Clone() : val(NULL) {} - Clone(T* t) : val(new T(*t)) {} - Clone(const T& t) : val(new T(t)) {} - // Called by XS.cpp to convert the cloned object instance to a Perl SV, before it is blessed with the name - // returned by CLASS() - operator T*() const { return val; } - // Name to bless the Perl SV with. If there is a destructor registered in the XSP file for this class, then Perl will - // call this destructor when the reference counter of this SV drops to zero. - static const char* CLASS() { return ClassTraits::name; } -}; - -SV* ConfigBase__as_hash(ConfigBase* THIS); -SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def); -SV* ConfigBase__get(ConfigBase* THIS, const t_config_option_key &opt_key); -SV* ConfigBase__get_at(ConfigBase* THIS, const t_config_option_key &opt_key, size_t i); -bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value); -bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &opt_key, SV* str); -void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize = false); -bool StaticConfig__set(StaticConfig* THIS, const t_config_option_key &opt_key, SV* value); -SV* to_AV(ExPolygon* expolygon); -SV* to_SV_pureperl(const ExPolygon* expolygon); -void from_SV(SV* expoly_sv, ExPolygon* expolygon); -void from_SV_check(SV* expoly_sv, ExPolygon* expolygon); -void from_SV(SV* line_sv, Line* THIS); -void from_SV_check(SV* line_sv, Line* THIS); -SV* to_AV(Line* THIS); -SV* to_SV_pureperl(const Line* THIS); -void from_SV(SV* poly_sv, MultiPoint* THIS); -void from_SV_check(SV* poly_sv, MultiPoint* THIS); -SV* to_AV(MultiPoint* THIS); -SV* to_SV_pureperl(const MultiPoint* THIS); -void from_SV_check(SV* poly_sv, Polygon* THIS); -void from_SV_check(SV* poly_sv, Polyline* THIS); -SV* to_SV_pureperl(const Point* THIS); -void from_SV(SV* point_sv, Point* point); -void from_SV_check(SV* point_sv, Point* point); -SV* to_SV_pureperl(const Vec2d* point); -bool from_SV(SV* point_sv, Vec2d* point); -bool from_SV_check(SV* point_sv, Vec2d* point); -void from_SV_check(SV* surface_sv, Surface* THIS); -SV* to_SV(TriangleMesh* THIS); - -} - -// Defined in wxPerlIface.cpp -// Return a pointer to the associated wxWidgets object instance given by classname. -extern void* wxPli_sv_2_object( pTHX_ SV* scalar, const char* classname ); - -inline void confess_at(const char *file, int line, const char *func, const char *pat, ...) -{ - #ifdef SLIC3RXS - va_list args; - SV *error_sv = newSVpvf("Error in function %s at %s:%d: ", func, - file, line); - - va_start(args, pat); - sv_vcatpvf(error_sv, pat, &args); - va_end(args); - - sv_catpvn(error_sv, "\n\t", 2); - - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs( sv_2mortal(error_sv) ); - PUTBACK; - call_pv("Carp::confess", G_DISCARD); - FREETMPS; - LEAVE; - #endif -} - -#ifndef CONFESS -/* Implementation of CONFESS("foo"): */ -#ifdef _MSC_VER - #define CONFESS(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) -#else - #define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) -#endif -/* End implementation of CONFESS("foo"): */ -#endif /* CONFESS */ - -using namespace Slic3r; - -#endif diff --git a/xs/t/03_point.t b/xs/t/03_point.t deleted file mode 100644 index 389e120..0000000 --- a/xs/t/03_point.t +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 16; - -my $point = Slic3r::Point->new(10, 15); - -my $point2 = $point->clone; -$point2->scale(2); -is_deeply [ @$point2 ], [20, 30], 'scale'; - -$point2->translate(10, -15); -is_deeply [ @$point2 ], [30, 15], 'translate'; - -{ - my $point3 = Slic3r::Point->new(4300000, -9880845); - is $point->[0], $point->x, 'x accessor'; - is $point->[1], $point->y, 'y accessor'; #,, -} - -{ - my $nearest = $point->nearest_point([ $point2, Slic3r::Point->new(100, 200) ]); - ok $nearest->coincides_with($point2), 'nearest_point'; -} - -{ - my $line = Slic3r::Line->new([0,0], [100,0]); - is +Slic3r::Point->new(0,0) ->distance_to_line($line), 0, 'distance_to_line()'; - is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; - is +Slic3r::Point->new(50,0) ->distance_to_line($line), 0, 'distance_to_line()'; - is +Slic3r::Point->new(150,0)->distance_to_line($line), 50, 'distance_to_line()'; - is +Slic3r::Point->new(0,50) ->distance_to_line($line), 50, 'distance_to_line()'; - is +Slic3r::Point->new(50,50)->distance_to_line($line), 50, 'distance_to_line()'; - is +Slic3r::Point->new(50,50) ->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; - is +Slic3r::Point->new(150,50)->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; -} - -{ - my $line = Slic3r::Line->new([50,50], [125,-25]); - cmp_ok(abs(Slic3r::Point->new(100,0)->distance_to_line($line)), '<=', 4e-15, 'distance_to_line()'); -} - -{ - my $line = Slic3r::Line->new( - [18335846,18335845], - [18335846,1664160], - ); - $point = Slic3r::Point->new(1664161,18335848); - is $point->perp_distance_to_line($line), 16671685, 'perp_distance_to_line() does not overflow'; -} - -{ - my $p0 = Slic3r::Point->new(76975850,89989996); - my $p1 = Slic3r::Point->new(76989990,109989991); - my $p2 = Slic3r::Point->new(76989987,89989994); - ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; -} - -__END__ diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t deleted file mode 100644 index 7da74b9..0000000 --- a/xs/t/09_polyline.t +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 15; - -my $points = [ - [100, 100], - [200, 100], - [200, 200], -]; - -my $polyline = Slic3r::Polyline->new(@$points); - -my $lines = $polyline->lines; -is_deeply [ map $_->pp, @$lines ], [ - [ [100, 100], [200, 100] ], - [ [200, 100], [200, 200] ], -], 'polyline lines'; - -$polyline->append_polyline($polyline->clone); -is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; - -{ - my $len = $polyline->length; - $polyline->clip_end($len/3); - ok abs($polyline->length - ($len-($len/3))) < 1, 'clip_end'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [20,0], [50,0], [80,0], [100,0], - ); - $polyline->simplify(2); - is_deeply $polyline->pp, [ [0,0], [100,0] ], 'Douglas-Peucker'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [50,50], [100,0], [125,-25], [150,50], - ); - $polyline->simplify(25); - is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [100,0], [50,10], - ); - $polyline->simplify(25); - is_deeply $polyline->pp, [ [0,0], [100,0], [50,10] ], 'Douglas-Peucker uses shortest distance instead of perpendicular distance'; -} - -{ - my $polyline = Slic3r::Polyline->new(@$points); - is $polyline->length, 100*2, 'length'; - $polyline->extend_end(50); - is $polyline->length, 100*2 + 50, 'extend_end'; - $polyline->extend_start(50); - is $polyline->length, 100*2 + 50 + 50, 'extend_start'; -} - -{ - my $polyline = Slic3r::Polyline->new(@$points); - my $p1 = Slic3r::Polyline->new; - my $p2 = Slic3r::Polyline->new; - my $point = Slic3r::Point->new(150, 100); - $polyline->split_at($point, $p1, $p2); - is scalar(@$p1), 2, 'split_at'; - is scalar(@$p2), 3, 'split_at'; - ok $p1->last_point->coincides_with($point), 'split_at'; - ok $p2->first_point->coincides_with($point), 'split_at'; -} - -{ - my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]); - my $p1 = Slic3r::Polyline->new; - my $p2 = Slic3r::Polyline->new; - $polyline->split_at($polyline->first_point, $p1, $p2); - is scalar(@$p1), 1, 'split_at'; - is scalar(@$p2), 4, 'split_at'; -} - -__END__ diff --git a/xs/t/10_line.t b/xs/t/10_line.t deleted file mode 100644 index 886573f..0000000 --- a/xs/t/10_line.t +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 35; - -use constant PI => 4 * atan2(1, 1); -use constant EPSILON => 1E-4; - -my $points = [ - [100, 100], - [200, 100], -]; - -my $line = Slic3r::Line->new(@$points); - -{ - my $clone = $line->clone; - $clone->translate(10, -5); - is_deeply $clone->pp, [ - [110, 95], - [210, 95], - ], 'translate'; -} - -{ - ok +Slic3r::Line->new([0,0],[200,0])->parallel_to_line(Slic3r::Line->new([200,200],[0,200])), 'parallel_to'; -} - -foreach my $base_angle (0, PI/4, PI/2, PI) { - my $line = Slic3r::Line->new([0,0], [100,0]); - $line->rotate($base_angle, [0,0]); - my $clone = $line->clone; - ok $line->parallel_to_line($clone), 'line is parallel to self'; - $clone->reverse; - ok $line->parallel_to_line($clone), 'line is parallel to self + PI'; - ok $line->parallel_to($line->direction), 'line is parallel to its direction'; - ok $line->parallel_to($line->direction + PI), 'line is parallel to its direction + PI'; - ok $line->parallel_to($line->direction - PI), 'line is parallel to its direction - PI'; - { - my $line2 = $line->clone; - $line2->reverse; - ok $line->parallel_to_line($line2), 'line is parallel to its opposite'; - } - { - my $line2 = $line->clone; - $line2->rotate(+(EPSILON)/2, [0,0]); - ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; - } - { - my $line2 = $line->clone; - $line2->rotate(-(EPSILON)/2, [0,0]); - ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; - } -} - -{ - my $a = Slic3r::Line->new([100, 0], [200, 0]); - my $b = Slic3r::Line->new([300, 300], [300, 100]); - my $r = $a->intersection_infinite($b); - is_deeply $r->pp, [300, 0], 'intersection_infinite'; -} - -__END__ diff --git a/xs/t/15_config.t b/xs/t/15_config.t deleted file mode 100644 index 4d03201..0000000 --- a/xs/t/15_config.t +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 143; - -foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { - $config->set('layer_height', 0.3); - ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; - is $config->opt_serialize('layer_height'), '0.3', 'serialize float'; - - $config->set('perimeters', 2); - is $config->get('perimeters'), 2, 'set/get int'; - is $config->opt_serialize('perimeters'), '2', 'serialize int'; - - $config->set('extrusion_axis', 'A'); - is $config->get('extrusion_axis'), 'A', 'set/get string'; - is $config->opt_serialize('extrusion_axis'), 'A', 'serialize string'; - - $config->set('notes', "foo\nbar"); - is $config->get('notes'), "foo\nbar", 'set/get string with newline'; - is $config->opt_serialize('notes'), 'foo\nbar', 'serialize string with newline'; - $config->set_deserialize('notes', 'bar\nbaz'); - is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; - - foreach my $test_data ( - { - name => 'empty', - values => [], - serialized => '' - }, - { - name => 'single empty', - values => [''], - serialized => '""' - }, - { - name => 'single noempty, simple', - values => ['RGB'], - serialized => 'RGB' - }, - { - name => 'multiple noempty, simple', - values => ['ABC', 'DEF', '09182745@!#$*(&'], - serialized => 'ABC;DEF;09182745@!#$*(&' - }, - { - name => 'multiple, simple, some empty', - values => ['ABC', 'DEF', '', '09182745@!#$*(&', ''], - serialized => 'ABC;DEF;;09182745@!#$*(&;' - }, - { - name => 'complex', - values => ['some "quoted" notes', "yet\n some notes", "whatever \n notes", ''], - serialized => '"some \"quoted\" notes";"yet\n some notes";"whatever \n notes";' - } - ) - { - $config->set('filament_notes', $test_data->{values}); - is $config->opt_serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; - $config->set_deserialize('filament_notes', ''); - is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; - $config->set_deserialize('filament_notes', $test_data->{serialized}); - is_deeply $config->get('filament_notes'), $test_data->{values}, 'deserialize complex multi-string value ' . $test_data->{name}; - } - - $config->set('first_layer_height', 0.3); - ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; - is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; - -# This is no more supported after first_layer_height was moved from PrintObjectConfig to PrintConfig. -# $config->set('first_layer_height', $config->get('layer_height')); -# $config->get_abs_value('first_layer_height'); -# ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; -# is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; - - # Uh-oh, we have no point option to test at the moment - #ok $config->set('print_center', [50,80]), 'valid point coordinates'; - #is_deeply $config->get('print_center'), [50,80], 'set/get point'; - #is $config->serialize('print_center'), '50,80', 'serialize point'; - #$config->set_deserialize('print_center', '20,10'); - #is_deeply $config->get('print_center'), [20,10], 'deserialize point'; - #ok !$config->set('print_center', ['t',80]), 'invalid point X'; - #ok !$config->set('print_center', [50,'t']), 'invalid point Y'; - - $config->set('use_relative_e_distances', 1); - is $config->get('use_relative_e_distances'), 1, 'set/get bool'; - is $config->opt_serialize('use_relative_e_distances'), '1', 'serialize bool'; - $config->set('gcode_flavor', 'teacup'); - is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; - is $config->opt_serialize('gcode_flavor'), 'teacup', 'serialize enum'; - $config->set_deserialize('gcode_flavor', 'mach3'); - is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; - $config->set_deserialize('gcode_flavor', 'machinekit'); - is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)'; - - $config->set_deserialize('fill_pattern', 'line'); - is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_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'; - $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); - is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; - is $config->opt_serialize('extruder_offset'), '10x20,30x45', 'serialize points'; - $config->set_deserialize('extruder_offset', '20x10'); - is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; - $config->set_deserialize('extruder_offset', '0x0'); - is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[0,0]], 'deserialize points'; - { - my @values = ([10,20]); - $values[2] = [10,20]; # implicitely extend array; this is not the same as explicitely assigning undef to second item - ok !$config->set('extruder_offset', \@values), 'reject undef points'; - } - - # truncate ->get() to first decimal digit - $config->set('nozzle_diameter', [0.2,3]); - is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; - is $config->opt_serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; - $config->set_deserialize('nozzle_diameter', '0.1,0.4'); - is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; - $config->set_deserialize('nozzle_diameter', '3'); - is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [3], 'deserialize a single float'; - { - my @values = (0.4); - $values[2] = 2; # implicitely extend array; this is not the same as explicitely assigning undef to second item - ok !$config->set('nozzle_diameter', \@values), 'reject undef floats'; - } - - $config->set('temperature', [180,210]); - is_deeply $config->get('temperature'), [180,210], 'set/get ints'; - is $config->opt_serialize('temperature'), '180,210', 'serialize ints'; - $config->set_deserialize('temperature', '195,220'); - is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; - { - my @values = (180); - $values[2] = 200; # implicitely extend array; this is not the same as explicitely assigning undef to second item - ok !$config->set('temperature', \@values), 'reject undef ints'; - } - - $config->set('wipe', [1,0]); - is_deeply $config->get('wipe'), [1,0], 'set/get bools'; - is $config->get_at('wipe', 0), 1, 'get_at bools'; - is $config->get_at('wipe', 1), 0, 'get_at bools'; - is $config->get_at('wipe', 9), 1, 'get_at bools'; - is $config->opt_serialize('wipe'), '1,0', 'serialize bools'; - $config->set_deserialize('wipe', '0,1,1'); - is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; - $config->set_deserialize('wipe', ''); - is_deeply $config->get('wipe'), [], 'deserialize bools from empty string'; - $config->set_deserialize('retract_layer_change', 0); - is_deeply $config->get('retract_layer_change'), [0], 'deserialize bools from non-string value'; - { - my @values = (1); - $values[2] = 1; # implicitely extend array; this is not the same as explicitely assigning undef to second item - ok !$config->set('wipe', \@values), 'reject undef bools'; - } - - $config->set('post_process', ['foo','bar']); - is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; - is $config->opt_serialize('post_process'), 'foo;bar', 'serialize strings'; - $config->set_deserialize('post_process', 'bar;baz'); - is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; - { - my @values = ('foo'); - $values[2] = 'bar'; # implicitely extend array; this is not the same as explicitely assigning undef to second item - ok !$config->set('post_process', \@values), 'reject undef strings'; - } - - is_deeply [ sort @{$config->get_keys} ], [ sort keys %{$config->as_hash} ], 'get_keys and as_hash'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('perimeters', 2); - - # test that no crash happens when using set_deserialize() with a key that hasn't been set() yet - $config->set_deserialize('filament_diameter', '3'); - - my $config2 = Slic3r::Config::Static::new_FullPrintConfig; - $config2->apply_dynamic($config); - is $config2->get('perimeters'), 2, 'apply_dynamic'; -} - -{ - my $config = Slic3r::Config::Static::new_FullPrintConfig; - my $config2 = Slic3r::Config->new; - $config2->apply_static($config); - is $config2->get('perimeters'), Slic3r::Config::print_config_def()->{perimeters}{default}, 'apply_static and print_config_def'; - - $config->set('top_solid_infill_speed', 70); - is $config->get_abs_value('top_solid_infill_speed'), 70, 'get_abs_value() works when ratio_over references a floatOrPercent option'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('fill_pattern', 'line'); - - my $config2 = Slic3r::Config->new; - $config2->set('fill_pattern', 'hilbertcurve'); - - is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; -} - -{ - my $config = Slic3r::Config->new; - # the pair [0,0] is part of the test, since it checks whether the 0x0 serialized value is correctly parsed - $config->set('extruder_offset', [ [0,0], [20,0], [0,20] ]); - my $config2 = Slic3r::Config->new; - $config2->apply($config); - is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [ map $_->pp, @{$config2->get('extruder_offset')} ], - 'apply dynamic over dynamic'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('extruder', 2); - $config->set('perimeter_extruder', 3); - $config->normalize_fdm; - ok !$config->has('extruder'), 'extruder option is removed after normalize()'; - is $config->get('infill_extruder'), 2, 'undefined extruder is populated with default extruder'; - is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('infill_extruder', 2); - $config->normalize_fdm; - is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('spiral_vase', 1); - $config->set('retract_layer_change', [1,0]); - $config->normalize_fdm; - is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; -} - -{ - use Cwd qw(abs_path); - use File::Basename qw(dirname); - my $path = abs_path($0); - my $config = Slic3r::Config::load(dirname($path)."/inc/22_config_bad_config_options.ini"); - ok 1, 'did not crash on reading invalid items in config'; -} - -__END__ diff --git a/xs/t/inc/22_config_bad_config_options.ini b/xs/t/inc/22_config_bad_config_options.ini deleted file mode 100644 index b28c624..0000000 --- a/xs/t/inc/22_config_bad_config_options.ini +++ /dev/null @@ -1,7 +0,0 @@ -# generated by Slic3r 1.1.7 on Tue Aug 19 21:49:50 2014 -avoid_crossing_perimeters = 1 -bed_size = 200,180 -g0 = 0 -perimeter_acceleration = 0 -support_material_extruder = 1 -support_material_extrusion_width = 0 diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp deleted file mode 100644 index 3c3ed5b..0000000 --- a/xs/xsp/Config.xsp +++ /dev/null @@ -1,218 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/PrintConfig.hpp" -%} - -%name{Slic3r::Config} class DynamicPrintConfig { - DynamicPrintConfig(); - ~DynamicPrintConfig(); - static DynamicPrintConfig* new_from_defaults() - %code{% RETVAL = DynamicPrintConfig::new_from_defaults_keys(FullPrintConfig::defaults().keys()); %}; - static DynamicPrintConfig* new_from_defaults_keys(std::vector keys); - DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %}; - DynamicPrintConfig* clone_only(std::vector keys) - %code{% RETVAL = new DynamicPrintConfig(); RETVAL->apply_only(*THIS, keys, true); %}; - bool has(t_config_option_key opt_key); - SV* as_hash() - %code{% RETVAL = ConfigBase__as_hash(THIS); %}; - SV* get(t_config_option_key opt_key) - %code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; - SV* get_at(t_config_option_key opt_key, int i) - %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; - SV* get_value(t_config_option_key opt_key) - %code{% - const ConfigOptionDef *def = THIS->def()->get(opt_key); - RETVAL = (def != nullptr && ! def->ratio_over.empty()) ? - newSVnv(THIS->get_abs_value(opt_key)) : - ConfigBase__get(THIS, opt_key); - %}; - bool set(t_config_option_key opt_key, SV* value) - %code{% RETVAL = ConfigBase__set(THIS, opt_key, value); %}; - bool set_deserialize(t_config_option_key opt_key, SV* str) - %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; - void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) - %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string opt_serialize(t_config_option_key opt_key); - double get_abs_value(t_config_option_key opt_key); - %name{get_abs_value_over} - double get_abs_value(t_config_option_key opt_key, double ratio_over); - void apply(DynamicPrintConfig* other) - %code{% THIS->apply(*other, true); %}; - std::vector diff(DynamicPrintConfig* other) - %code{% RETVAL = THIS->diff(*other); %}; - bool equals(DynamicPrintConfig* other) - %code{% RETVAL = THIS->equals(*other); %}; - void apply_static(StaticPrintConfig* other) - %code{% THIS->apply(*other, true); %}; - %name{get_keys} std::vector keys(); - void erase(t_config_option_key opt_key); - void normalize_fdm(); - %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; - static DynamicPrintConfig* load(char *path) - %code%{ - auto config = new DynamicPrintConfig(); - try { - config->load(path, ForwardCompatibilitySubstitutionRule::Disable); - RETVAL = config; - } catch (std::exception& e) { - delete config; - croak("Error extracting configuration from %s:\n%s\n", path, e.what()); - } - %}; - void save(std::string file); - int validate() %code%{ - std::string err = THIS->validate(); - if (! err.empty()) - croak("Configuration is not valid: %s\n", err.c_str()); - RETVAL = 1; - %}; -}; - -%name{Slic3r::Config::Static} class StaticPrintConfig { - static StaticPrintConfig* new_GCodeConfig() - %code{% RETVAL = new GCodeConfig(); %}; - static StaticPrintConfig* new_PrintConfig() - %code{% RETVAL = static_cast(new PrintConfig()); %}; - static StaticPrintConfig* new_FullPrintConfig() - %code{% RETVAL = static_cast(new FullPrintConfig()); %}; - ~StaticPrintConfig(); - bool has(t_config_option_key opt_key); - SV* as_hash() - %code{% RETVAL = ConfigBase__as_hash(THIS); %}; - SV* get(t_config_option_key opt_key) - %code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; - SV* get_at(t_config_option_key opt_key, int i) - %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; - bool set(t_config_option_key opt_key, SV* value) - %code{% RETVAL = StaticConfig__set(THIS, opt_key, value); %}; - bool set_deserialize(t_config_option_key opt_key, SV* str) - %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; - void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) - %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string opt_serialize(t_config_option_key opt_key); - double get_abs_value(t_config_option_key opt_key); - %name{get_abs_value_over} - double get_abs_value(t_config_option_key opt_key, double ratio_over); - void apply_static(StaticPrintConfig* other) - %code{% THIS->apply(*other, true); %}; - void apply_dynamic(DynamicPrintConfig* other) - %code{% THIS->apply(*other, true); %}; - %name{get_keys} std::vector keys(); - std::string get_extrusion_axis() - %code{% - if (GCodeConfig* config = dynamic_cast(THIS)) { - RETVAL = get_extrusion_axis(*config); - } else { - CONFESS("This StaticConfig object does not provide get_extrusion_axis()"); - } - %}; - %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; - static StaticPrintConfig* load(char *path) - %code%{ - auto config = new FullPrintConfig(); - try { - config->load(path, ForwardCompatibilitySubstitutionRule::Disable); - RETVAL = static_cast(config); - } catch (std::exception& e) { - delete config; - croak("Error extracting configuration from %s:\n%s\n", path, e.what()); - } - %}; - - void save(std::string file); -}; - -%package{Slic3r::Config}; - -%{ -PROTOTYPES: DISABLE - -SV* -print_config_def() - CODE: - t_optiondef_map &def = *const_cast(&Slic3r::print_config_def.options); - - HV* options_hv = newHV(); - for (t_optiondef_map::iterator oit = def.begin(); oit != def.end(); ++oit) { - HV* hv = newHV(); - - t_config_option_key opt_key = oit->first; - ConfigOptionDef* optdef = &oit->second; - - const char* opt_type; - if (optdef->type == coFloat || optdef->type == coFloats || optdef->type == coFloatOrPercent || optdef->type == coFloatsOrPercents) { - opt_type = "f"; - } else if (optdef->type == coPercent || optdef->type == coPercents) { - opt_type = "percent"; - } else if (optdef->type == coInt || optdef->type == coInts) { - opt_type = "i"; - } else if (optdef->type == coString) { - opt_type = "s"; - } else if (optdef->type == coStrings) { - opt_type = "s@"; - } else if (optdef->type == coPoint || optdef->type == coPoints) { - opt_type = "point"; - } else if (optdef->type == coPoint3) { - opt_type = "point3"; - } else if (optdef->type == coBool || optdef->type == coBools) { - opt_type = "bool"; - } else if (optdef->type == coEnum) { - opt_type = "select"; - } else { - throw "Unknown option type"; - } - (void)hv_stores( hv, "type", newSVpv(opt_type, 0) ); - (void)hv_stores( hv, "height", newSViv(optdef->height) ); - (void)hv_stores( hv, "width", newSViv(optdef->width) ); - (void)hv_stores( hv, "min", newSViv(optdef->min) ); - (void)hv_stores( hv, "max", newSViv(optdef->max) ); - - // aliases - if (!optdef->aliases.empty()) { - AV* av = newAV(); - av_fill(av, optdef->aliases.size()-1); - for (std::vector::iterator it = optdef->aliases.begin(); it != optdef->aliases.end(); ++it) - av_store(av, it - optdef->aliases.begin(), newSVpvn(it->c_str(), it->length())); - (void)hv_stores( hv, "aliases", newRV_noinc((SV*)av) ); - } - - // shortcut - if (!optdef->shortcut.empty()) { - AV* av = newAV(); - av_fill(av, optdef->shortcut.size()-1); - for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) - av_store(av, it - optdef->shortcut.begin(), newSVpvn(it->c_str(), it->length())); - (void)hv_stores( hv, "shortcut", newRV_noinc((SV*)av) ); - } - - // enum_values - if (optdef->enum_def && !optdef->enum_def->values().empty()) { - AV* av = newAV(); - av_fill(av, optdef->enum_def->values().size()-1); - for (std::vector::const_iterator it = optdef->enum_def->values().begin(); it != optdef->enum_def->values().end(); ++it) - av_store(av, it - optdef->enum_def->values().begin(), newSVpvn(it->c_str(), it->length())); - (void)hv_stores( hv, "values", newRV_noinc((SV*)av) ); - } - - // enum_labels - if (optdef->enum_def && !optdef->enum_def->labels().empty()) { - AV* av = newAV(); - av_fill(av, optdef->enum_def->labels().size()-1); - for (std::vector::const_iterator it = optdef->enum_def->labels().begin(); it != optdef->enum_def->labels().end(); ++it) - av_store(av, it - optdef->enum_def->labels().begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); - (void)hv_stores( hv, "labels", newRV_noinc((SV*)av) ); - } - - if (optdef->default_value) - (void)hv_stores( hv, "default", ConfigOption_to_SV(*optdef->default_value.get(), *optdef) ); - (void)hv_store( options_hv, opt_key.c_str(), opt_key.length(), newRV_noinc((SV*)hv), 0 ); - } - - RETVAL = newRV_noinc((SV*)options_hv); - OUTPUT: - RETVAL -%} diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp deleted file mode 100644 index 50b3254..0000000 --- a/xs/xsp/ExPolygon.xsp +++ /dev/null @@ -1,57 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExPolygon.hpp" -%} - -%name{Slic3r::ExPolygon} class ExPolygon { - ~ExPolygon(); - Clone clone() - %code{% RETVAL = THIS; %}; - SV* arrayref() - %code{% RETVAL = to_AV(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - Ref contour() - %code{% RETVAL = &(THIS->contour); %}; - Polygons* holes() - %code{% RETVAL = &(THIS->holes); %}; - void scale(double factor); - void translate(double x, double y); - double area(); - bool is_valid(); - bool contains_line(Line* line) - %code{% RETVAL = THIS->contains(*line); %}; - bool contains_polyline(Polyline* polyline) - %code{% RETVAL = THIS->contains(*polyline); %}; - bool contains_point(Point* point) - %code{% RETVAL = THIS->contains(*point); %}; - ExPolygons simplify(double tolerance); - Polygons simplify_p(double tolerance); -%{ - -ExPolygon* -ExPolygon::new(...) - CODE: - RETVAL = new ExPolygon (); - // ST(0) is class name, ST(1) is contour and others are holes - from_SV_check(ST(1), &RETVAL->contour); - RETVAL->holes.resize(items-2); - for (unsigned int i = 2; i < items; i++) { - from_SV_check(ST(i), &RETVAL->holes[i-2]); - } - OUTPUT: - RETVAL - -void -ExPolygon::rotate(angle, center_sv) - double angle; - SV* center_sv; - CODE: - Point center; - from_SV_check(center_sv, ¢er); - THIS->rotate(angle, center); - -%} -}; diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp deleted file mode 100644 index fdacb73..0000000 --- a/xs/xsp/Geometry.xsp +++ /dev/null @@ -1,52 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/ShortestPath.hpp" -%} - - -%package{Slic3r::Geometry}; - -%{ - -Clone -convex_hull(points) - Points points - CODE: - RETVAL = Slic3r::Geometry::convex_hull(points); - OUTPUT: - RETVAL - - -double -rad2deg(angle) - double angle - CODE: - RETVAL = Slic3r::Geometry::rad2deg(angle); - OUTPUT: - RETVAL - -double -deg2rad(angle) - double angle - CODE: - RETVAL = Slic3r::Geometry::deg2rad(angle); - OUTPUT: - RETVAL - -IV -_constant() - ALIAS: - X = X - Y = Y - Z = Z - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp deleted file mode 100644 index 6730872..0000000 --- a/xs/xsp/Line.xsp +++ /dev/null @@ -1,78 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Line.hpp" -#include "libslic3r/Polyline.hpp" -%} - -%name{Slic3r::Line} class Line { - ~Line(); - Clone clone() - %code{% RETVAL = THIS; %}; - SV* arrayref() - %code{% RETVAL = to_AV(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - Ref a() - %code{% RETVAL=&THIS->a; %}; - Ref b() - %code{% RETVAL=&THIS->b; %}; - void reverse(); - void scale(double factor); - void translate(double x, double y); - double length(); - double atan2_(); - double orientation(); - double direction(); - bool parallel_to(double angle); - bool parallel_to_line(Line* line) - %code{% RETVAL = THIS->parallel_to(*line); %}; - Clone midpoint(); - Clone intersection_infinite(Line* other) - %code{% - Point p; - bool res = THIS->intersection_infinite(*other, &p); - if (!res) CONFESS("Intersection failed"); - RETVAL = p; - %}; - Polyline* as_polyline() - %code{% RETVAL = new Polyline(THIS->a, THIS->b); %}; - Clone normal(); - Clone vector(); - double ccw(Point* point) - %code{% RETVAL = cross2((THIS->a - *point).cast(), (THIS->b - THIS->a).cast()); %}; -%{ - -Line* -Line::new(...) - CODE: - RETVAL = new Line (); - // ST(0) is class name, ST(1) and ST(2) are endpoints - from_SV_check(ST(1), &RETVAL->a); - from_SV_check(ST(2), &RETVAL->b); - OUTPUT: - RETVAL - -void -Line::rotate(angle, center_sv) - double angle; - SV* center_sv; - CODE: - Point center; - from_SV_check(center_sv, ¢er); - THIS->rotate(angle, center); - -bool -Line::coincides_with(line_sv) - SV* line_sv; - CODE: - Line line; - from_SV_check(line_sv, &line); - RETVAL = (*THIS) == line; - OUTPUT: - RETVAL - -%} -}; - diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp deleted file mode 100644 index 9763c55..0000000 --- a/xs/xsp/Model.xsp +++ /dev/null @@ -1,286 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Model.hpp" -#include "libslic3r/ModelArrange.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/Slicing.hpp" -#include "libslic3r/Format/AMF.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "libslic3r/Format/OBJ.hpp" -#include "libslic3r/Format/STL.hpp" -#include "libslic3r/PresetBundle.hpp" -%} - -%name{Slic3r::Model} class Model { - Model(); - ~Model(); - - %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) - %code%{ - try { - RETVAL = new Model(Model::read_from_file(input_file, nullptr, nullptr, only_if(add_default_instances, Model::LoadAttribute::AddDefaultInstances))); - } catch (std::exception& e) { - croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); - } - %}; - - Clone clone() - %code%{ RETVAL = THIS; %}; - - %name{_add_object} Ref add_object(); - Ref _add_object_clone(ModelObject* other, bool copy_volumes = true) - %code%{ auto ptr = THIS->add_object(*other); if (! copy_volumes) ptr->clear_volumes(); RETVAL = ptr; %}; - void delete_object(size_t idx); - void clear_objects(); - size_t objects_count() - %code%{ RETVAL = THIS->objects.size(); %}; - Ref get_object(int idx) - %code%{ RETVAL = THIS->objects.at(idx); %}; - - Ref get_material(t_model_material_id material_id) - %code%{ - RETVAL = THIS->get_material(material_id); - if (RETVAL == NULL) { - XSRETURN_UNDEF; - } - %}; - - %name{add_material} Ref add_material(t_model_material_id material_id); - Ref add_material_clone(t_model_material_id material_id, ModelMaterial* other) - %code%{ RETVAL = THIS->add_material(material_id, *other); %}; - bool has_material(t_model_material_id material_id) const - %code%{ - RETVAL = (THIS->get_material(material_id) != NULL); - %}; - void delete_material(t_model_material_id material_id); - void clear_materials(); - - std::vector material_names() const - %code%{ - for (ModelMaterialMap::iterator i = THIS->materials.begin(); - i != THIS->materials.end(); ++i) - { - RETVAL.push_back(i->first); - } - %}; - - size_t material_count() const - %code%{ RETVAL = THIS->materials.size(); %}; - - bool add_default_instances(); - void center_instances_around_point(Vec2d* point) - %code%{ THIS->center_instances_around_point(*point); %}; - void translate(double x, double y, double z); - Clone mesh(); - - ModelObjectPtrs* objects() - %code%{ RETVAL = &THIS->objects; %}; - - bool arrange_objects(double dist) %code%{ arrange_objects(*THIS, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(dist) ); %}; - void duplicate(unsigned int copies_num, double dist) %code%{ duplicate(*THIS, copies_num, arr2::InfiniteBed{}, arr2::ArrangeSettings{}.set_distance_from_objects(dist) ); %}; - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); - - bool store_stl(char *path, bool binary) - %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; - -%{ - -Model* -load_stl(CLASS, path, object_name) - char* CLASS; - char* path; - char* object_name; - CODE: - RETVAL = new Model(); - if (! load_stl(path, RETVAL, object_name)) { - delete RETVAL; - RETVAL = NULL; - } - OUTPUT: - RETVAL - -%} -}; - -%name{Slic3r::Model::Material} class ModelMaterial { - Ref model() - %code%{ RETVAL = THIS->get_model(); %}; - - Ref config() - %code%{ RETVAL = &const_cast(THIS->config.get()); %}; - - std::string get_attribute(std::string name) - %code%{ if (THIS->attributes.find(name) != THIS->attributes.end()) RETVAL = THIS->attributes[name]; %}; - - void set_attribute(std::string name, std::string value) - %code%{ THIS->attributes[name] = value; %}; - -%{ - -SV* -ModelMaterial::attributes() - CODE: - HV* hv = newHV(); - for (t_model_material_attributes::const_iterator attr = THIS->attributes.begin(); attr != THIS->attributes.end(); ++attr) { - (void)hv_store( hv, attr->first.c_str(), attr->first.length(), newSVpv(attr->second.c_str(), attr->second.length()), 0 ); - } - RETVAL = (SV*)newRV_noinc((SV*)hv); - OUTPUT: - RETVAL -%} - -}; - - -%name{Slic3r::Model::Object} class ModelObject { - ModelVolumePtrs* volumes() - %code%{ RETVAL = &THIS->volumes; %}; - - ModelInstancePtrs* instances() - %code%{ RETVAL = &THIS->instances; %}; - - void invalidate_bounding_box(); - Clone mesh(); - Clone raw_mesh(); - - %name{_add_volume} Ref add_volume(TriangleMesh* mesh) - %code%{ RETVAL = THIS->add_volume(*mesh); %}; - Ref _add_volume_clone(ModelVolume* other) - %code%{ RETVAL = THIS->add_volume(*other); %}; - - void delete_volume(size_t idx); - void clear_volumes(); - int volumes_count() - %code%{ RETVAL = THIS->volumes.size(); %}; - Ref get_volume(int idx) - %code%{ RETVAL = THIS->volumes.at(idx); %}; - bool move_volume_up(int idx) - %code%{ - if (idx > 0 && idx < int(THIS->volumes.size())) { - std::swap(THIS->volumes[idx-1], THIS->volumes[idx]); - RETVAL = true; - } else - RETVAL = false; - %}; - bool move_volume_down(int idx) - %code%{ - if (idx >= 0 && idx + 1 < int(THIS->volumes.size())) { - std::swap(THIS->volumes[idx+1], THIS->volumes[idx]); - RETVAL = true; - } else - RETVAL = false; - %}; - - %name{_add_instance} Ref add_instance(); - Ref _add_instance_clone(ModelInstance* other) - %code%{ RETVAL = THIS->add_instance(*other); %}; - void delete_last_instance(); - void clear_instances(); - int instances_count() - %code%{ RETVAL = THIS->instances.size(); %}; - - std::string name() - %code%{ RETVAL = THIS->name; %}; - void set_name(std::string value) - %code%{ THIS->name = value; %}; - std::string input_file() - %code%{ RETVAL = THIS->input_file; %}; - void set_input_file(std::string value) - %code%{ THIS->input_file = value; %}; - Ref config() - %code%{ RETVAL = &const_cast(THIS->config.get()); %}; - - Ref model() - %code%{ RETVAL = THIS->get_model(); %}; - - Ref origin_translation() - %code%{ RETVAL = &THIS->origin_translation; %}; - void set_origin_translation(Vec3d* point) - %code%{ THIS->origin_translation = *point; %}; - - void ensure_on_bed(); - int materials_count() const; - int facets_count(); - void center_around_origin(); - void translate(double x, double y, double z); - void scale_xyz(Vec3d* versor) - %code{% THIS->scale(*versor); %}; - void rotate(float angle, Vec3d* axis) - %code{% THIS->rotate(angle, *axis); %}; - void mirror(Axis axis); - -}; - - -%name{Slic3r::Model::Volume} class ModelVolume { - Ref object() - %code%{ RETVAL = THIS->get_object(); %}; - - std::string name() - %code%{ RETVAL = THIS->name; %}; - void set_name(std::string value) - %code%{ THIS->name = value; %}; - t_model_material_id material_id(); - void set_material_id(t_model_material_id material_id) - %code%{ THIS->set_material_id(material_id); %}; - Ref material(); - - Ref config() - %code%{ RETVAL = &const_cast(THIS->config.get()); %}; - Ref mesh() - %code%{ RETVAL = &THIS->mesh(); %}; - - bool modifier() - %code%{ RETVAL = THIS->is_modifier(); %}; - void set_modifier(bool modifier) - %code%{ THIS->set_type(modifier ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); %}; - bool model_part() - %code%{ RETVAL = THIS->is_model_part(); %}; - bool support_enforcer() - %code%{ RETVAL = THIS->is_support_enforcer(); %}; - void set_support_enforcer() - %code%{ THIS->set_type(ModelVolumeType::SUPPORT_ENFORCER); %}; - bool support_blocker() - %code%{ RETVAL = THIS->is_support_blocker(); %}; - void set_support_blocker() - %code%{ THIS->set_type(ModelVolumeType::SUPPORT_BLOCKER); %}; - - size_t split(unsigned int max_extruders); -}; - - -%name{Slic3r::Model::Instance} class ModelInstance { - Ref object() - %code%{ RETVAL = THIS->get_object(); %}; - - Vec3d* rotation() - %code%{ RETVAL = new Vec3d(THIS->get_rotation(X), THIS->get_rotation(Y), THIS->get_rotation(Z)); %}; - - Vec3d* scaling_factor() - %code%{ RETVAL = new Vec3d(THIS->get_scaling_factor(X), THIS->get_scaling_factor(Y), THIS->get_scaling_factor(Z)); %}; - - Vec2d* offset() - %code%{ RETVAL = new Vec2d(THIS->get_offset(X), THIS->get_offset(Y)); %}; - - void set_rotation(double val) - %code%{ THIS->set_rotation(Z, val); THIS->get_object()->invalidate_bounding_box(); %}; - - void set_rotations(Vec3d *rotation) - %code%{ THIS->set_rotation(*rotation); THIS->get_object()->invalidate_bounding_box(); %}; - - void set_scaling_factor(double val) - %code%{ THIS->set_scaling_factor(X, val); THIS->set_scaling_factor(Y, val); THIS->set_scaling_factor(Z, val); THIS->get_object()->invalidate_bounding_box(); %}; - - void set_scaling_factors(Vec3d *scale) - %code%{ THIS->set_scaling_factor(*scale); THIS->get_object()->invalidate_bounding_box(); %}; - - void set_offset(Vec2d *offset) - %code%{ - THIS->set_offset(X, (*offset)(0)); - THIS->set_offset(Y, (*offset)(1)); - %}; -}; diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp deleted file mode 100644 index 0d44ea3..0000000 --- a/xs/xsp/Point.xsp +++ /dev/null @@ -1,129 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Point.hpp" -#include "libslic3r/Line.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/Polyline.hpp" -%} - -%name{Slic3r::Point} class Point { - Point(int _x = 0, int _y = 0); - ~Point(); - Clone clone() - %code{% RETVAL=THIS; %}; - void scale(double factor) - %code{% *THIS *= factor; %}; - void translate(double x, double y) - %code{% *THIS += Point(x, y); %}; - SV* arrayref() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - int x() - %code{% RETVAL = (*THIS)(0); %}; - int y() - %code{% RETVAL = (*THIS)(1); %}; - void set_x(int val) - %code{% (*THIS)(0) = val; %}; - void set_y(int val) - %code{% (*THIS)(1) = val; %}; - Clone nearest_point(Points points) - %code{% RETVAL = nearest_point(points, *THIS).first; %}; - double distance_to(Point* point) - %code{% RETVAL = (*point - *THIS).cast().norm(); %}; - double distance_to_line(Line* line) - %code{% RETVAL = line->distance_to(*THIS); %}; - double perp_distance_to_line(Line* line) - %code{% RETVAL = line->perp_distance_to(*THIS); %}; - double ccw(Point* p1, Point* p2) - %code{% RETVAL = cross2((*p1 - *THIS).cast(), (*p2 - *p1).cast()); %}; - Point* negative() - %code{% RETVAL = new Point(- *THIS); %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %}; - -%{ - -void -Point::rotate(angle, center_sv) - double angle; - SV* center_sv; - CODE: - Point center; - from_SV_check(center_sv, ¢er); - THIS->rotate(angle, center); - -bool -Point::coincides_with(point_sv) - SV* point_sv; - CODE: - Point point; - from_SV_check(point_sv, &point); - RETVAL = (*THIS) == point; - OUTPUT: - RETVAL - -%} - -}; - -%name{Slic3r::Pointf} class Vec2d { - Vec2d(double _x = 0, double _y = 0); - ~Vec2d(); - Clone clone() - %code{% RETVAL = THIS; %}; - SV* arrayref() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - double x() - %code{% RETVAL = (*THIS)(0); %}; - double y() - %code{% RETVAL = (*THIS)(1); %}; - void set_x(double val) - %code{% (*THIS)(0) = val; %}; - void set_y(double val) - %code{% (*THIS)(1) = val; %}; - void translate(double x, double y) - %code{% *THIS += Vec2d(x, y); %}; - void scale(double factor) - %code{% *THIS *= factor; %}; - void rotate(double angle, Vec2d* center) - %code{% *THIS = Eigen::Translation2d(*center) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- *center) * Eigen::Vector2d((*THIS)(0), (*THIS)(1)); %}; - Vec2d* negative() - %code{% RETVAL = new Vec2d(- *THIS); %}; - Vec2d* vector_to(Vec2d* point) - %code{% RETVAL = new Vec2d(*point - *THIS); %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %}; -}; - -%name{Slic3r::Pointf3} class Vec3d { - Vec3d(double _x = 0, double _y = 0, double _z = 0); - ~Vec3d(); - Clone clone() - %code{% RETVAL = THIS; %}; - double x() - %code{% RETVAL = (*THIS)(0); %}; - double y() - %code{% RETVAL = (*THIS)(1); %}; - double z() - %code{% RETVAL = (*THIS)(2); %}; - void set_x(double val) - %code{% (*THIS)(0) = val; %}; - void set_y(double val) - %code{% (*THIS)(1) = val; %}; - void set_z(double val) - %code{% (*THIS)(2) = val; %}; - void translate(double x, double y, double z) - %code{% *THIS += Vec3d(x, y, z); %}; - void scale(double factor) - %code{% *THIS *= factor; %}; - double distance_to(Vec3d* point) - %code{% RETVAL = (*point - *THIS).norm(); %}; - Vec3d* negative() - %code{% RETVAL = new Vec3d(- *THIS); %}; - Vec3d* vector_to(Vec3d* point) - %code{% RETVAL = new Vec3d(*point - *THIS); %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %}; -}; diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp deleted file mode 100644 index 95c1d2d..0000000 --- a/xs/xsp/Polygon.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Polygon.hpp" -%} - -%name{Slic3r::Polygon} class Polygon { - ~Polygon(); - Clone clone() - %code{% RETVAL = THIS; %}; - SV* arrayref() - %code{% RETVAL = to_AV(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - void scale(double factor); - void translate(double x, double y); - void reverse(); - Lines lines(); - Clone split_at_vertex(Point* point) - %code{% RETVAL = THIS->split_at_vertex(*point); %}; - Clone split_at_first_point(); - double length(); - double area(); - bool is_counter_clockwise(); - bool is_clockwise(); - bool make_counter_clockwise(); - bool make_clockwise(); - bool is_valid(); - Clone first_point(); - bool contains_point(Point* point) - %code{% RETVAL = THIS->contains(*point); %}; - Polygons simplify(double tolerance); - Clone centroid(); - Clone first_intersection(Line* line) - %code{% - Point p; - (void)THIS->first_intersection(*line, &p); - RETVAL = p; - %}; -%{ - -Polygon* -Polygon::new(...) - CODE: - RETVAL = new Polygon (); - // ST(0) is class name, ST(1) is first point - RETVAL->points.resize(items-1); - for (unsigned int i = 1; i < items; i++) { - from_SV_check(ST(i), &RETVAL->points[i-1]); - } - OUTPUT: - RETVAL - -void -Polygon::rotate(angle, center_sv) - double angle; - SV* center_sv; - CODE: - Point center; - from_SV_check(center_sv, ¢er); - THIS->rotate(angle, center); - -%} -}; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp deleted file mode 100644 index 595d54e..0000000 --- a/xs/xsp/Polyline.xsp +++ /dev/null @@ -1,74 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Polyline.hpp" -%} - -%name{Slic3r::Polyline} class Polyline { - ~Polyline(); - Clone clone() - %code{% RETVAL = THIS; %}; - SV* arrayref() - %code{% RETVAL = to_AV(THIS); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(THIS); %}; - void scale(double factor); - void translate(double x, double y); - void pop_back() - %code{% THIS->points.pop_back(); %}; - void reverse(); - Lines lines(); - Clone first_point(); - Clone last_point(); - double length(); - bool is_valid(); - void clip_end(double distance); - void clip_start(double distance); - void extend_end(double distance); - void extend_start(double distance); - void simplify(double tolerance); - void split_at(Point* point, Polyline* p1, Polyline* p2) - %code{% THIS->split_at(*point, p1, p2); %}; -%{ - -Polyline* -Polyline::new(...) - CODE: - RETVAL = new Polyline (); - // ST(0) is class name, ST(1) is first point - RETVAL->points.resize(items-1); - for (unsigned int i = 1; i < items; i++) { - from_SV_check(ST(i), &RETVAL->points[i-1]); - } - OUTPUT: - RETVAL - -void -Polyline::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - Point p; - from_SV_check(ST(i), &p); - THIS->points.push_back(p); - } - -void -Polyline::append_polyline(polyline) - Polyline* polyline; - CODE: - for (Points::const_iterator it = polyline->points.begin(); it != polyline->points.end(); ++it) { - THIS->points.push_back((*it)); - } - -void -Polyline::rotate(angle, center_sv) - double angle; - SV* center_sv; - CODE: - Point center; - from_SV_check(center_sv, ¢er); - THIS->rotate(angle, center); - -%} -}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp deleted file mode 100644 index 584a2c1..0000000 --- a/xs/xsp/Print.xsp +++ /dev/null @@ -1,68 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Print.hpp" -%} - -%name{Slic3r::Print} class Print { - Print(); - ~Print(); - - Ref model() - %code%{ RETVAL = const_cast(&THIS->model()); %}; - Ref config() - %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; - double total_used_filament() - %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; - - void auto_assign_extruders(ModelObject* model_object); - std::string output_filepath(std::string path = "") - %code%{ - try { - RETVAL = THIS->output_filepath(path); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - bool apply(Model *model, DynamicPrintConfig* config) - %code%{ - // Touching every config as the Perl bindings does not correctly export ModelConfig, - // therefore the configs have often invalid timestamps. - for (auto obj : model->objects) { - obj->config.touch(); - for (auto vol : obj->volumes) - vol->config.touch(); - } - for (auto mat : model->materials) - mat.second->config.touch(); - RETVAL = THIS->apply(*model, *config); - %}; - std::vector extruders() const; - int validate() %code%{ - std::string err = THIS->validate(); - if (! err.empty()) - croak("Configuration is not valid: %s\n", err.c_str()); - RETVAL = 1; - %}; - - void set_status_silent(); - - void process() %code%{ - try { - THIS->process(); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - void export_gcode(char *path_template) %code%{ - try { - THIS->export_gcode(path_template, nullptr); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - -}; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp deleted file mode 100644 index 5dc0df7..0000000 --- a/xs/xsp/TriangleMesh.xsp +++ /dev/null @@ -1,115 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" -%} - -%name{Slic3r::TriangleMesh} class TriangleMesh { - TriangleMesh(); - ~TriangleMesh(); - Clone clone() - %code{% RETVAL = THIS; %}; - void write_ascii(char* output_file); - void write_binary(char* output_file); - void scale(float factor); - void scale_xyz(Vec3d* versor) - %code{% THIS->scale(versor->cast()); %}; - void translate(float x, float y, float z); - void rotate_x(float angle); - void rotate_y(float angle); - void rotate_z(float angle); - void mirror_x(); - void mirror_y(); - void mirror_z(); - void align_to_origin(); - void rotate(double angle, Point* center); - void merge(TriangleMesh* mesh) - %code{% THIS->merge(*mesh); %}; - Clone convex_hull(); - Clone center() - %code{% RETVAL = THIS->bounding_box().center(); %}; - int facets_count(); - -%{ - -void -TriangleMesh::ReadFromPerl(vertices, facets) - SV* vertices - SV* facets - CODE: - std::vector out_vertices; - { - AV* vertices_av = (AV*)SvRV(vertices); - int number_of_vertices = av_len(vertices_av) + 1; - out_vertices.reserve(number_of_vertices); - for (int i = 0; i < number_of_vertices; ++ i) { - AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, i, 0)); - out_vertices.push_back(Slic3r::Vec3f(SvNV(*av_fetch(vertex_av, 0, 0)), SvNV(*av_fetch(vertex_av, 1, 0)), SvNV(*av_fetch(vertex_av, 2, 0)))); - } - } - std::vector out_indices; - { - AV* facets_av = (AV*)SvRV(facets); - int number_of_facets = av_len(facets_av) + 1; - out_indices.reserve(number_of_facets); - for (int i = 0; i < number_of_facets; ++ i) { - AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); - out_indices.push_back(Slic3r::Vec3i(SvIV(*av_fetch(facet_av, 0, 0)), SvIV(*av_fetch(facet_av, 1, 0)), SvIV(*av_fetch(facet_av, 2, 0)))); - } - } - *THIS = TriangleMesh(std::move(out_vertices), std::move(out_indices)); - -SV* -TriangleMesh::vertices() - CODE: - // vertices - AV* vertices = newAV(); - av_extend(vertices, THIS->its.vertices.size()); - for (size_t i = 0; i < THIS->its.vertices.size(); i++) { - AV* vertex = newAV(); - av_store(vertices, i, newRV_noinc((SV*)vertex)); - av_extend(vertex, 2); - av_store(vertex, 0, newSVnv(THIS->its.vertices[i](0))); - av_store(vertex, 1, newSVnv(THIS->its.vertices[i](1))); - av_store(vertex, 2, newSVnv(THIS->its.vertices[i](2))); - } - - RETVAL = newRV_noinc((SV*)vertices); - OUTPUT: - RETVAL - -SV* -TriangleMesh::facets() - CODE: - // facets - AV* facets = newAV(); - av_extend(facets, THIS->facets_count()); - for (int i = 0; i < THIS->facets_count(); i++) { - AV* facet = newAV(); - av_store(facets, i, newRV_noinc((SV*)facet)); - av_extend(facet, 2); - av_store(facet, 0, newSVnv(THIS->its.indices[i][0])); - av_store(facet, 1, newSVnv(THIS->its.indices[i][1])); - av_store(facet, 2, newSVnv(THIS->its.indices[i][2])); - } - - RETVAL = newRV_noinc((SV*)facets); - OUTPUT: - RETVAL - -SV* -TriangleMesh::size() - CODE: - AV* size = newAV(); - av_extend(size, 2); - av_store(size, 0, newSVnv(THIS->stats().size(0))); - av_store(size, 1, newSVnv(THIS->stats().size(1))); - av_store(size, 2, newSVnv(THIS->stats().size(2))); - RETVAL = newRV_noinc((SV*)size); - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp deleted file mode 100644 index 66a3536..0000000 --- a/xs/xsp/XS.xsp +++ /dev/null @@ -1,38 +0,0 @@ -%module{Slic3r::XS}; -%package{Slic3r::XS}; - -#include -#include "Utils.hpp" - -%{ - -%} - -%package{Slic3r}; -%{ - -SV* -VERSION() - CODE: - RETVAL = newSVpv(SLIC3R_VERSION, 0); - OUTPUT: RETVAL - -SV* -BUILD() - CODE: - RETVAL = newSVpv(SLIC3R_BUILD_ID, 0); - OUTPUT: RETVAL - -SV* -FORK_NAME() - CODE: - RETVAL = newSVpv(SLIC3R_APP_NAME, 0); - OUTPUT: RETVAL - -void -set_logging_level(level) - unsigned int level; - CODE: - Slic3r::set_logging_level(level); - -%} diff --git a/xs/xsp/my.map b/xs/xsp/my.map deleted file mode 100644 index ba5ed6e..0000000 --- a/xs/xsp/my.map +++ /dev/null @@ -1,344 +0,0 @@ -coordf_t T_NV - -std::string T_STD_STRING -t_config_option_key T_STD_STRING -t_model_material_id T_STD_STRING - -std::vector T_STD_VECTOR_STD_STRING - -std::vector T_STD_VECTOR_INT -std::vector T_STD_VECTOR_INT -std::vector T_STD_VECTOR_INT - -std::vector T_STD_VECTOR_UINT - -std::vector T_STD_VECTOR_DOUBLE - -DynamicPrintConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -StaticPrintConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -GCodeConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PrintConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -FullPrintConfig* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -TriangleMesh* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Point* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Point3* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Vec2d* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Vec3d* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Line* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Polyline* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Polygon* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ExPolygon* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Model* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ModelMaterial* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ModelObject* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ModelVolume* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ModelInstance* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Print* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Axis T_UV -ExtrusionLoopRole T_UV -ExtrusionRole T_UV -SurfaceType T_UV - -# we return these types whenever we want the items to be cloned -Points T_ARRAYREF -Pointfs T_ARRAYREF -Lines T_ARRAYREF -Polygons T_ARRAYREF -Polylines T_ARRAYREF -ExPolygons T_ARRAYREF - -# we return these types whenever we want the items to be returned -# by reference and marked ::Ref because they're contained in another -# Perl object -Polygons* T_ARRAYREF_PTR -ModelObjectPtrs* T_PTR_ARRAYREF_PTR -ModelVolumePtrs* T_PTR_ARRAYREF_PTR -ModelInstancePtrs* T_PTR_ARRAYREF_PTR - -# we return these types whenever we want the items to be returned -# by reference and not marked ::Ref because they're newly allocated -# and not referenced by any Perl object - - -INPUT - -T_STD_STRING - { - size_t len; - // const char * c = SvPV($arg, len); - // Always convert strings to UTF-8 before passing them to XS - const char * c = SvPVutf8($arg, len); - $var = std::string(c, len); - } - -T_STD_VECTOR_STD_STRING - if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { - AV* av = (AV*)SvRV($arg); - const unsigned int alen = av_len(av)+1; - $var = std::vector(alen); - STRLEN len; - char* tmp; - SV** elem; - for (unsigned int i = 0; i < alen; i++) { - elem = av_fetch(av, i, 0); - if (elem != NULL) { - tmp = SvPV(*elem, len); - ${var}[i] = std::string(tmp, len); - } - else - ${var}[i] = std::string(\"\"); - } - } - else - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - -T_STD_VECTOR_INT - if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { - AV* av = (AV*)SvRV($arg); - const unsigned int len = av_len(av)+1; - $var = std::vector(len); - SV** elem; - for (unsigned int i = 0; i < len; i++) { - elem = av_fetch(av, i, 0); - if (elem != NULL) - ${var}[i] = SvIV(*elem); - else - ${var}[i] = 0; - } - } - else - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - -T_STD_VECTOR_UINT - if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { - AV* av = (AV*)SvRV($arg); - const unsigned int len = av_len(av)+1; - $var = std::vector(len); - SV** elem; - for (unsigned int i = 0; i < len; i++) { - elem = av_fetch(av, i, 0); - if (elem != NULL) - ${var}[i] = SvUV(*elem); - else - ${var}[i] = 0; - } - } - else - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - -T_STD_VECTOR_DOUBLE - if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { - AV* av = (AV*)SvRV($arg); - const unsigned int len = av_len(av)+1; - $var = std::vector(len); - SV** elem; - for (unsigned int i = 0; i < len; i++) { - elem = av_fetch(av, i, 0); - if (elem != NULL) - ${var}[i] = SvNV(*elem); - else - ${var}[i] = 0.; - } - } - else - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - -O_OBJECT_SLIC3R - if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) { - if ( sv_isa($arg, Slic3r::perl_class_name($var) ) || sv_isa($arg, Slic3r::perl_class_name_ref($var) )) { - $var = ($type)SvIV((SV*)SvRV( $arg )); - } else { - croak(\"$var is not of type %s (got %s)\", Slic3r::perl_class_name($var), HvNAME(SvSTASH(SvRV($arg)))); - XSRETURN_UNDEF; - } - } else { - warn( \"${Package}::$func_name() -- $var is not a blessed SV reference\" ); - XSRETURN_UNDEF; - } - -T_ARRAYREF - if (SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVAV) { - AV* av = (AV*)SvRV($arg); - const unsigned int len = av_len(av)+1; - $var.resize(len); - for (unsigned int i = 0; i < len; i++) { - SV** elem = av_fetch(av, i, 0); - from_SV_check(*elem, &$var\[i]); - } - } else - Perl_croak(aTHX_ \"%s: %s is not an array reference\", - ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, - \"$var\"); - -OUTPUT - -T_STD_STRING - $arg = newSVpvn_utf8( $var.c_str(), $var.length(), true ); - -T_STD_VECTOR_STD_STRING - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len) - av_extend(av, len-1); - for (unsigned int i = 0; i < len; i++) { - const std::string& str = ${var}[i]; - STRLEN len = str.length(); - av_store(av, i, newSVpvn_utf8(str.c_str(), len, true)); - } - -T_STD_VECTOR_INT - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len) - av_extend(av, len-1); - for (unsigned int i = 0; i < len; i++) { - av_store(av, i, newSViv(${var}[i])); - } - -T_STD_VECTOR_UINT - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len) - av_extend(av, len-1); - for (unsigned int i = 0; i < len; i++) { - av_store(av, i, newSVuv(${var}[i])); - } - -T_STD_VECTOR_DOUBLE - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len) - av_extend(av, len-1); - for (unsigned int i = 0; i < len; i++) { - av_store(av, i, newSVnv(${var}[i])); - } - -# return object from pointer -O_OBJECT_SLIC3R - if ($var == NULL) - XSRETURN_UNDEF; - sv_setref_pv( $arg, Slic3r::perl_class_name($var), (void*)$var ); - -# return value handled by template class -O_OBJECT_SLIC3R_T - if ($var == NULL) - XSRETURN_UNDEF; - sv_setref_pv( $arg, $type\::CLASS(), (void*)$var ); - - -T_ARRAYREF - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len > 0) av_extend(av, len-1); - int i = 0; - for (${type}::const_iterator it = $var.begin(); it != $var.end(); ++it) { - av_store(av, i++, perl_to_SV_clone_ref(*it)); - } - -T_ARRAYREF_PTR - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var->size(); - if (len > 0) av_extend(av, len-1); - int i = 0; - for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); - } - -T_PTR_ARRAYREF_PTR - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var->size(); - if (len > 0) av_extend(av, len-1); - int i = 0; - for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { - av_store(av, i++, perl_to_SV_ref(**it)); - } - -T_PTR_ARRAYREF - AV* av = newAV(); - $arg = newRV_noinc((SV*)av); - sv_2mortal($arg); - const unsigned int len = $var.size(); - if (len > 0) av_extend(av, len-1); - int i = 0; - for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { - av_store(av, i++, to_SV(*it)); - } - diff --git a/xs/xsp/mytype.map b/xs/xsp/mytype.map deleted file mode 100644 index e69de29..0000000 diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt deleted file mode 100644 index 7b9c023..0000000 --- a/xs/xsp/typemap.xspt +++ /dev/null @@ -1,99 +0,0 @@ -%typemap{bool}{simple}; -%typemap{size_t}{simple}; -%typemap{coordf_t}{simple}; -%typemap{std::string}; -%typemap{t_config_option_key}; -%typemap{t_model_material_id}; -%typemap{std::vector}; -%typemap{std::vector}; -%typemap{std::vector*}; -%typemap{std::vector}; -%typemap{std::vector*}; -%typemap{std::vector}; -%typemap{std::vector*}; -%typemap{std::vector}; -%typemap{void*}; -%typemap{SV*}; -%typemap{AV*}; -%typemap{Point*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Point3*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Vec2d*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Vec3d*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{DynamicPrintConfig*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{StaticPrintConfig*}; -%typemap{Ref}{simple}; -%typemap{GCodeConfig*}; -%typemap{Ref}{simple}; -%typemap{PrintConfig*}; -%typemap{Ref}{simple}; -%typemap{FullPrintConfig*}; -%typemap{Ref}{simple}; -%typemap{ExPolygon*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Line*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Polyline*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{Polygon*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{TriangleMesh*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{Print*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{Points}; -%typemap{Pointfs}; -%typemap{Lines}; -%typemap{Polygons}; -%typemap{Polylines}; -%typemap{ExPolygons}; -%typemap{Polygons*}; -%typemap{TriangleMesh*}; -%typemap{Model*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelMaterial*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelObject*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelObjectPtrs*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelVolume*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelVolumePtrs*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelInstance*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ModelInstancePtrs*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{Axis}{parsed}{ - %cpp_type{Axis}; - %precall_code{% - $CVar = (Axis)SvUV($PerlVar); - %}; -};