Compare commits
69 Commits
Version_1.
...
V1.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d884ef371d | ||
|
|
2f86351eab | ||
|
|
eeeb2a5437 | ||
|
|
8153dcc1b1 | ||
|
|
65a85dae84 | ||
|
|
2b269ea194 | ||
|
|
63f899f4a5 | ||
|
|
4d6feb71b9 | ||
|
|
d783651751 | ||
|
|
9b04886c3a | ||
|
|
3a99562743 | ||
|
|
5183107d79 | ||
|
|
8c672c53c7 | ||
|
|
3039c76417 | ||
|
|
f33a08f704 | ||
|
|
5ccb55ff98 | ||
|
|
764ce01063 | ||
|
|
58e2343a2e | ||
|
|
6485825ad8 | ||
|
|
2387bc9cdb | ||
|
|
63daf0c087 | ||
|
|
516d3a3313 | ||
|
|
470b3a19ed | ||
|
|
759c6732b8 | ||
|
|
c31585e5ba | ||
|
|
8cca851e84 | ||
|
|
00718edfa7 | ||
|
|
f82a8a4ca1 | ||
|
|
4407c9eb62 | ||
|
|
74f7fce027 | ||
|
|
354224679e | ||
|
|
92119cf9f5 | ||
|
|
0401c46f99 | ||
|
|
9d43e47a54 | ||
|
|
354ae2e282 | ||
|
|
4ffa594874 | ||
|
|
e795f88c09 | ||
|
|
e7028531e0 | ||
|
|
0d1dd29341 | ||
|
|
c56cfdb727 | ||
|
|
bfc036deae | ||
|
|
87ab8bd8dd | ||
|
|
ce3ad490b7 | ||
|
|
74d0317dd0 | ||
|
|
1c9ce7ec05 | ||
|
|
2a8cdc3414 | ||
|
|
cea37f9f7d | ||
|
|
48ed13e6f1 | ||
|
|
7a7fb7da00 | ||
|
|
02857e3fd8 | ||
|
|
8cb30a7329 | ||
|
|
1fc08d237c | ||
|
|
ee616bbbb6 | ||
|
|
30e9b00baf | ||
|
|
172b1893e2 | ||
|
|
04b1c22cc6 | ||
|
|
2046b2f21f | ||
|
|
1eb7e979b0 | ||
|
|
82f75eb146 | ||
|
|
7c6e614b3b | ||
|
|
76aaf50055 | ||
|
|
d76b009f40 | ||
|
|
ca4f336d62 | ||
|
|
9856ad7031 | ||
|
|
f6271b0c90 | ||
|
|
bc68a07f47 | ||
|
|
ec446edb36 | ||
|
|
4c3e3eea6d | ||
|
|
4bdbc32044 |
1
.gitignore
vendored
@@ -12,7 +12,6 @@ xs/MANIFEST.bak
|
||||
xs/assertlib*
|
||||
.init_bundle.ini
|
||||
.vs/*
|
||||
local-lib
|
||||
/src/TAGS
|
||||
/.vscode/
|
||||
build-linux/*
|
||||
|
||||
132
Build.PL
@@ -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__
|
||||
@@ -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("$<$<CONFIG:DEBUG>:-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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ See the [QIDI's homepage](https://qidi3d.com) for more information.
|
||||
|
||||
You can find the printer's firmware here:
|
||||
|
||||
[Q1 Pro](https://github.com/QIDITECH/QIDI_Q1_Pro)
|
||||
|
||||
[X-MAX 3](https://github.com/QIDITECH/QIDI_MAX3)
|
||||
|
||||
[X-Plus 3](https://github.com/QIDITECH/QIDI_PLUS3)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
5
deps/+LibBGCode/LibBGCode.cmake
vendored
@@ -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")
|
||||
|
||||
2
deps/+OpenVDB/OpenVDB.cmake
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,8 +0,0 @@
|
||||
package Slic3r::Line;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# a line is a two-points line
|
||||
use parent 'Slic3r::Polyline';
|
||||
|
||||
1;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,8 +0,0 @@
|
||||
package Slic3r::Polygon;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# a polygon is a closed polyline.
|
||||
use parent 'Slic3r::Polyline';
|
||||
|
||||
1;
|
||||
@@ -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;
|
||||
BIN
resources/icons/Q1 Pro_thumbnail.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
17
resources/icons/cost_weight.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<path fill="#4479FB" d="M14.6,13.1l-1.4-7.3c-0.1-0.6-0.7-1.1-1.4-1.1H9.5C9.9,4.3,10,3.9,10,3.4c0-1.1-0.9-2-2-2s-2,0.9-2,2
|
||||
c0,0.5,0.2,1,0.5,1.3H4.1c-0.7,0-1.2,0.5-1.4,1.1l-1.4,7.3c-0.2,0.8,0.5,1.6,1.4,1.6h10.5C14.1,14.7,14.8,13.9,14.6,13.1z M8,2.3
|
||||
c0.6,0,1,0.5,1,1s-0.5,1-1,1s-1-0.5-1-1S7.4,2.3,8,2.3z M13.6,13.5c0,0-0.1,0.1-0.3,0.1H2.7c-0.2,0-0.3-0.1-0.3-0.1
|
||||
c0-0.1-0.1-0.1-0.1-0.2L3.8,6c0-0.2,0.2-0.3,0.4-0.3h7.7c0.2,0,0.4,0.1,0.4,0.3l1.4,7.3C13.7,13.4,13.6,13.5,13.6,13.5z"/>
|
||||
<g>
|
||||
<path fill="#4479FB" d="M6.9,12c-0.2-0.4-0.4-0.7-0.6-0.9c-0.2-0.2-0.4-0.4-0.6-0.6H5.4v1.6H4.5V8.2h0.9v1.6h0.2l1.1-1.6h1
|
||||
l-1.3,1.9c0.3,0.3,0.6,0.6,0.8,0.8c0.2,0.3,0.5,0.7,0.7,1.1H6.9z"/>
|
||||
<path fill="#4479FB" d="M11.5,11.9C11.3,12,11.1,12,11,12.1c-0.2,0-0.4,0.1-0.7,0.1c-0.4,0-0.8-0.1-1.1-0.2
|
||||
c-0.3-0.2-0.6-0.4-0.8-0.7c-0.2-0.3-0.3-0.6-0.3-1c0-0.4,0.1-0.8,0.3-1.1c0.2-0.3,0.4-0.6,0.8-0.7c0.3-0.2,0.7-0.3,1.1-0.3
|
||||
c0.2,0,0.4,0,0.6,0.1c0.2,0,0.4,0.1,0.5,0.1V9c-0.4-0.1-0.7-0.2-1-0.2c-0.4,0-0.7,0.1-1,0.3C9.1,9.4,9,9.7,9,10.2
|
||||
c0,0.3,0.1,0.5,0.2,0.7c0.1,0.2,0.3,0.3,0.5,0.4c0.2,0.1,0.4,0.1,0.7,0.1c0.2,0,0.3,0,0.4,0v-0.9h-0.4V9.8h1.2V11.9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
resources/icons/print_time.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<path fill="#4479FB" d="M8.5,9.2L8.3,9.1c-0.2,0-0.4,0-0.5,0L7.5,9.2c-1.3,0.4-2.3,1.6-2.3,3v0.5h5.5v-0.5
|
||||
C10.8,10.8,9.8,9.6,8.5,9.2z"/>
|
||||
<path fill="#4479FB" d="M7.9,6.8l-2-1.4c0,0,0-0.1,0-0.1h4.2c0,0,0.1,0.1,0,0.1l-2,1.4C8,6.9,7.9,6.9,7.9,6.8z"/>
|
||||
<path fill="#4479FB" d="M13.3,14h-0.4v-1.8c0-1.7-0.9-3.3-2.3-4.2c1.4-0.9,2.3-2.5,2.3-4.2V2h0.4C13.7,2,14,1.7,14,1.4
|
||||
s-0.3-0.7-0.6-0.7H2.7C2.3,0.7,2,1,2,1.4S2.3,2,2.7,2h0.4v1.8c0,1.7,0.9,3.3,2.3,4.2c-1.4,0.9-2.3,2.5-2.3,4.2V14H2.7
|
||||
C2.3,14,2,14.3,2,14.6c0,0.4,0.3,0.7,0.6,0.7h10.7c0.4,0,0.6-0.3,0.6-0.7C14,14.3,13.7,14,13.3,14z M4.1,12.2c0-1.4,0.8-2.7,2-3.4
|
||||
C6.3,8.6,6.5,8.3,6.5,8c0-0.3-0.2-0.6-0.4-0.7c-1.2-0.7-2-2-2-3.4V2h7.9v1.8c0,1.4-0.8,2.7-2,3.4C9.6,7.4,9.5,7.7,9.5,8
|
||||
c0,0.3,0.2,0.6,0.4,0.7c1.2,0.7,2,2,2,3.4V14H4.1V12.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +1,7 @@
|
||||
min_slic3r_version = 1.1.3
|
||||
1.1.3 Optimize parameters
|
||||
min_slic3r_version = 1.1.2
|
||||
1.1.2 Optimize parameters
|
||||
min_slic3r_version = 1.1.1
|
||||
1.1.1 Optimize parameters
|
||||
min_slic3r_version = 1.1.0
|
||||
|
||||
60
resources/profiles/QIDITechnology/Q1 Pro.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="694.5px" height="694.5px" viewBox="0 0 694.5 694.5" enable-background="new 0 0 694.5 694.5" xml:space="preserve">
|
||||
<rect fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="693.1" y="15.7" fill="#FFFFFF" width="1.4" height="678.8"/>
|
||||
<rect x="0" y="693.1" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="559.1" fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="417.4" fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="275.7" y="15.7" fill="#FFFFFF" width="1.4" height="678.8"/>
|
||||
<rect x="133.9" y="14.2" fill="#FFFFFF" width="1.4" height="680.3"/>
|
||||
<rect x="21.1" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="49.4" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="77.8" y="14.2" fill="#FFFFFF" width="0.4" height="680.3"/>
|
||||
<rect x="106.1" y="14.2" fill="#FFFFFF" width="0.4" height="680.3"/>
|
||||
<rect x="162.8" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="191.1" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="219.5" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="247.8" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="304.5" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="332.9" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="361.2" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="389.6" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="446.3" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="474.6" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="502.9" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="531.3" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="588" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="616.3" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="644.7" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="673" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="0" y="559.1" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="417.4" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="275.7" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="133.9" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="21.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="49.4" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="77.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="106.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="162.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="191.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="219.5" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="247.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="304.5" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="332.9" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="361.2" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="389.6" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="446.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="474.6" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="502.9" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="531.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="588" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="616.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="644.7" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="673" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<polygon fill="#FFFFFF" points="694.5,15.7 588.1,15.7 588.1,0 589.6,0 589.6,14 694.5,14 "/>
|
||||
<polygon fill="#FFFFFF" points="327.5,15.7 69.4,15.7 69.4,0 70.9,0 70.9,14 326,14 326,0 327.5,0 "/>
|
||||
<rect x="327.5" y="0" fill="#FFFFFF" width="260.9" height="1.4"/>
|
||||
<rect x="0" y="0" fill="#FFFFFF" width="69.4" height="1.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/profiles/QIDITechnology/Q1 Pro_bed.STL
Normal file
BIN
resources/profiles/QIDITechnology/Q1 Pro_thumbnail.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
60
resources/profiles/QIDITechnology/Q1.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="694.5px" height="694.5px" viewBox="0 0 694.5 694.5" enable-background="new 0 0 694.5 694.5" xml:space="preserve">
|
||||
<rect fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="693.1" y="15.7" fill="#FFFFFF" width="1.4" height="678.8"/>
|
||||
<rect x="0" y="693.1" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="559.1" fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="417.4" fill="#FFFFFF" width="1.4" height="694.5"/>
|
||||
<rect x="275.7" y="15.7" fill="#FFFFFF" width="1.4" height="678.8"/>
|
||||
<rect x="133.9" y="14.2" fill="#FFFFFF" width="1.4" height="680.3"/>
|
||||
<rect x="21.1" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="49.4" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="77.8" y="14.2" fill="#FFFFFF" width="0.4" height="680.3"/>
|
||||
<rect x="106.1" y="14.2" fill="#FFFFFF" width="0.4" height="680.3"/>
|
||||
<rect x="162.8" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="191.1" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="219.5" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="247.8" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="304.5" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="332.9" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="361.2" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="389.6" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="446.3" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="474.6" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="502.9" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="531.3" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="588" y="0" fill="#FFFFFF" width="0.4" height="694.5"/>
|
||||
<rect x="616.3" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="644.7" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="673" y="15.7" fill="#FFFFFF" width="0.4" height="678.8"/>
|
||||
<rect x="0" y="559.1" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="417.4" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="275.7" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="133.9" fill="#FFFFFF" width="694.5" height="1.4"/>
|
||||
<rect x="0" y="21.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="49.4" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="77.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="106.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="162.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="191.1" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="219.5" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="247.8" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="304.5" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="332.9" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="361.2" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="389.6" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="446.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="474.6" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="502.9" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="531.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="588" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="616.3" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="644.7" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<rect x="0" y="673" fill="#FFFFFF" width="694.5" height="0.4"/>
|
||||
<polygon fill="#FFFFFF" points="694.5,15.7 588.1,15.7 588.1,0 589.6,0 589.6,14 694.5,14 "/>
|
||||
<polygon fill="#FFFFFF" points="327.5,15.7 69.4,15.7 69.4,0 70.9,0 70.9,14 326,14 326,0 327.5,0 "/>
|
||||
<rect x="327.5" y="0" fill="#FFFFFF" width="260.9" height="1.4"/>
|
||||
<rect x="0" y="0" fill="#FFFFFF" width="69.4" height="1.4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/profiles/QIDITechnology/Q1_bed.STL
Normal file
BIN
resources/profiles/QIDITechnology/Q1_thumbnail.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
@@ -18,6 +18,7 @@ var LangText={
|
||||
"t16": "Calibration",
|
||||
"t17": "Pressure Advance",
|
||||
"t18": "Exclude Objects",
|
||||
"t19": "Max Volumetric Speed",
|
||||
|
||||
"l0": "Learn more:",
|
||||
"l1": "You can download the 3D model from the following link.",
|
||||
@@ -62,6 +63,9 @@ var LangText={
|
||||
"l41": "4.PA calibration does not work with PETG.",
|
||||
"l42": "When multiple models are printed at the same time, you can stop printing a model in the middle of printing.",
|
||||
"l43": "Note: Models with the same file name are treated as the same object.",
|
||||
"l44": "Different filaments have different maximum volume speed.",
|
||||
"l45": "Nozzle material, caliber, printing temperature, etc., will affect the maximum volume speed.",
|
||||
"l46": "During the test, the printing speed will increase layer by layer. When there is a hole or missing wire on the surface of the model, it indicates that the layer has reached the maximum volume speed, and the maximum volume speed is calculated according to the ratio of height.",
|
||||
},
|
||||
"zh_CN": {
|
||||
"t1": "用户指南",
|
||||
@@ -82,6 +86,7 @@ var LangText={
|
||||
"t16": "校准",
|
||||
"t17": "压力提前",
|
||||
"t18": "排除对象",
|
||||
"t19": "最大体积速度",
|
||||
|
||||
"l0": "了解更多:",
|
||||
"l1": "您可以从以下链接下载3D模型。",
|
||||
@@ -126,6 +131,9 @@ var LangText={
|
||||
"l41": "4.PA校准不适用于PETG。",
|
||||
"l42": "当多个模型同时打印时,你可以在打印中途停止某个模型的打印。",
|
||||
"l43": "注:相同文件名的模型会被视为同一对象。",
|
||||
"l44": "不同线材的最大体积速度各不相同。",
|
||||
"l45": "喷嘴材质、口径、打印温度、等都会影响最大体积速度。",
|
||||
"l46": "测试时打印速度会逐层增加,当模型表面出现空洞或缺丝时,说明该层已达到最大体积速度,根据高度比例算出最大体积速度。",
|
||||
},
|
||||
"ja": {
|
||||
"t1": "ユーザーガイド",
|
||||
@@ -146,6 +154,7 @@ var LangText={
|
||||
"t16": "較正",
|
||||
"t17": "圧力前進",
|
||||
"t18": "対象をはずします",
|
||||
"t19": "最大体積速度です",
|
||||
|
||||
"l0": "もっと詳しく知る:",
|
||||
"l1": "3Dモデルは以下のWebサイトからダウンロードできます。",
|
||||
@@ -190,6 +199,9 @@ var LangText={
|
||||
"l41": "4.PA キャリブレーションは PETG では機能しません。",
|
||||
"l42": "複数のモデルが同時に印刷されている場合は、あるモデルの印刷を途中でやめることができます。",
|
||||
"l43": "注:同じファイル名のモデルは、同じ対象として扱われます。",
|
||||
"l44": "最大体積速度は線材によって異なります。",
|
||||
"l45": "ノズルの材質、口径、印刷温度などが最大体積速度に影響します。",
|
||||
"l46": "テスト時の印刷速度は層ごとに増加します。模型の表面に穴ができたり、糸が欠けたりした場合、その層が最大体積速度に達したことを示し、高さの割合から最大体積速度を算出します。",
|
||||
},
|
||||
"fr": {
|
||||
"t1": "Guide de l'utilisateur",
|
||||
@@ -210,6 +222,7 @@ var LangText={
|
||||
"t16": "Étalonnage",
|
||||
"t17": "Avance de pression",
|
||||
"t18": "Exclure des objets",
|
||||
"t19": "Vitesse volumétrique Max",
|
||||
|
||||
"l0": "Apprendre encore plus:",
|
||||
"l1": "Vous pouvez télécharger des modèles 3D à partir des sites Web suivants.",
|
||||
@@ -254,6 +267,9 @@ var LangText={
|
||||
"l41": "4.L'étalonnage PA ne fonctionne pas avec le PETG.",
|
||||
"l42": "Lorsque plusieurs modèles sont imprimés en même temps, vous pouvez arrêter l’impression d’un modèle au milieu de l’impression.",
|
||||
"l43": "Note: les modèles avec le même nom de fichier sont traités comme le même objet.",
|
||||
"l44": "Différents filaments ont une vitesse de volume maximale différente.",
|
||||
"l45": "Le matériau de la buse, le calibre, la température d’impression, etc., affecteront la vitesse de volume maximum.",
|
||||
"l46": "Pendant l’essai, la vitesse d’impression augmentera couche par couche. Quand il y a un trou ou un fil manquant sur la surface du modèle, cela indique que la couche a atteint la vitesse volumique maximale, et la vitesse volumique maximale est calculée selon le rapport de la hauteur.",
|
||||
},
|
||||
"de": {
|
||||
"t1": "Benutzerhandbuch",
|
||||
@@ -274,6 +290,7 @@ var LangText={
|
||||
"t16": "Kalibrierung",
|
||||
"t17": "Druckvorschub",
|
||||
"t18": "Subjekt wird eliminiert",
|
||||
"t19": "Maximale geschwindigkeit",
|
||||
|
||||
"l0": "Erfahren Sie mehr:",
|
||||
"l1": "Sie können 3D-Modelle von den folgenden Websites herunterladen.",
|
||||
@@ -318,6 +335,9 @@ var LangText={
|
||||
"l41": "4.Die PA-Kalibrierung funktioniert nicht mit PETG.",
|
||||
"l42": "Wenn sie mehrere modelle gleichzeitig drucken, können sie den druckvorgang beenden.",
|
||||
"l43": "Anmerkung: ein modell mit dem gleichen namen wird als gleiches objekt betrachtet.",
|
||||
"l44": "Es gibt eine vielzahl Von lichtern mit maximale geschwindigkeit.",
|
||||
"l45": "Die düsen, der durchmesser, die drucktemperatur all das beeinflusst die maximale geschwindigkeit.",
|
||||
"l46": "Bei den tests wird die druckgeschwindigkeit etage für etage erhöht Wenn eine öffnung Oder keine linie in der oberfläche eines modells angezeigt wird, zeigt sie an, dass sie die höchstgeschwindigkeit erreicht hat und dass die höchste geschwindigkeit nach höhe angegeben wird.",
|
||||
},
|
||||
"be": {
|
||||
"t1": "Кіраўніцтва карыстальніка",
|
||||
@@ -338,6 +358,7 @@ var LangText={
|
||||
"t16": "Каліброўка",
|
||||
"t17": "Увеличение давления",
|
||||
"t18": "Исключить объекты из списка",
|
||||
"t19": "Максимальная объемная частота вращения",
|
||||
|
||||
"l0": "даведацца больш:",
|
||||
"l1": "Вы можаце загрузіць 3D-мадэлі з наступных сайтаў.",
|
||||
@@ -382,6 +403,9 @@ var LangText={
|
||||
"l41": "4.Калибровка PA не работает с PETG.",
|
||||
"l42": "Когда одновременно печатаются несколько моделей, можно прекратить печатание модели в процессе печати.",
|
||||
"l43": "Примечание: модели с одним и тем же именем файла рассматриваются как Один и тот же объект.",
|
||||
"l44": "Различные нити накала имеют разную максимальную объемную скорость.",
|
||||
"l45": "Материал сопла, калибр, температура печати и т.д., повлияет на максимальную объемную скорость.",
|
||||
"l46": "Во время теста скорость печати будет увеличиваться слой за слоем. Если на поверхности модели имеется отверстие или недостающая проволока, это указывает на то, что слой достиг максимальной объемной скорости, а максимальная объемная скорость рассчитывается в соответствии с отношением высоты.",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
BIN
resources/web/guide/img/MaxVolumetricSpeed.gif
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
resources/web/guide/img/Q1 Pro.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
resources/web/guide/img/Q1 ProPoster.png
Normal file
|
After Width: | Height: | Size: 517 KiB |
@@ -83,6 +83,10 @@
|
||||
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
|
||||
<div class="trans" tid="t17"></div>
|
||||
</li>
|
||||
<li menu="MaxVolumetricSpeed" class="MenuBtn" onClick="GotoMenu('MaxVolumetricSpeed')">
|
||||
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
|
||||
<div class="trans" tid="t19"></div>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
|
||||
@@ -93,6 +97,10 @@
|
||||
<i class="fa fa-caret-right"></i>
|
||||
</label>
|
||||
<ol>
|
||||
<li menu="Q1 Pro" class="MenuBtn" onClick="GotoMenu('Q1 Pro')">
|
||||
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
|
||||
<div>Q1 Pro</div>
|
||||
</li>
|
||||
<li menu="X-MAX 3" class="MenuBtn" onClick="GotoMenu('X-MAX 3')">
|
||||
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
|
||||
<div>X-MAX 3</div>
|
||||
@@ -174,6 +182,10 @@
|
||||
<div class="AutozoomImage"><img src="img/PressureAdvance.gif"/></div>
|
||||
<div class="ThumbnailTitle trans" tid="t17"></div>
|
||||
</div>
|
||||
<div class="Thumbnail" onClick="GotoMenu('MaxVolumetricSpeed')">
|
||||
<div class="AutozoomImage"><img src="img/MaxVolumetricSpeed.gif"/></div>
|
||||
<div class="ThumbnailTitle trans" tid="t19"></div>
|
||||
</div>
|
||||
<div class="Thumbnail" onClick="GotoMenu('IssueReport')">
|
||||
<div class="AutozoomImage"><img src="img/IssueReport.png"/></div>
|
||||
<div class="ThumbnailTitle trans" tid="t6"></div>
|
||||
@@ -294,6 +306,20 @@
|
||||
<div class="CenterImage"><img src="img/PressureAdvanceTower.png"/></div>
|
||||
</div>
|
||||
|
||||
<div class="IntroduceBoard" board="MaxVolumetricSpeed">
|
||||
<div class="IntroduceTitle trans" tid="t19"></div>
|
||||
<div class="IntroduceText trans" tid="l44"></div>
|
||||
<div class="IntroduceText trans" tid="l45"></div>
|
||||
<div class="AutozoomImage"><img src="img/MaxVolumetricSpeed.gif"/></div>
|
||||
<div class="IntroduceText trans" tid="l46"></div>
|
||||
</div>
|
||||
|
||||
<div class="IntroduceBoard" board="Q1 Pro">
|
||||
<div class="AutozoomImage"><img src="img/Q1 ProPoster.png"/></div>
|
||||
<div class="IntroduceTextBold trans" tid="l0"></div>
|
||||
<div class="IntroduceText">https://qidi3d.com</div>
|
||||
</div>
|
||||
|
||||
<div class="IntroduceBoard" board="X-MAX 3">
|
||||
<div class="AutozoomImage"><img src="img/X-MAX3Poster.png"/></div>
|
||||
<div class="IntroduceTextBold trans" tid="l0"></div>
|
||||
@@ -526,6 +552,22 @@
|
||||
<td>2200 ± 200</td>
|
||||
<td>7.5 ± 1.5</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in 1" :key="index">
|
||||
<td>PLA-CF</td>
|
||||
<td>≤30%</td>
|
||||
<td>/</td>
|
||||
<td>+</td>
|
||||
<td>+</td>
|
||||
<td>++</td>
|
||||
<td>55.2℃</td>
|
||||
<td>52.6℃</td>
|
||||
<td>54.92 ± 0.22</td>
|
||||
<td>4020.55 ± 99.11</td>
|
||||
<td>4.90 ± 0.97</td>
|
||||
<td>91.57 ± 0.31</td>
|
||||
<td>4197.66 ± 279.96</td>
|
||||
<td>6.65 ± 0.38</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in 1" :key="index">
|
||||
<td>UltraPA</td>
|
||||
<td>≤15%</td>
|
||||
@@ -554,6 +596,14 @@
|
||||
<div class="IntroduceText trans" tid="l2"></div>
|
||||
<div class="IntroduceText trans" tid="l3"></div>
|
||||
<div class="EmailBlock">
|
||||
<div class="PrinterBlock">
|
||||
<div class="CenterImage"><img src="img/Q1 Pro.png"/></div>
|
||||
<div class="ThumbnailTitle">Q1 Pro</div>
|
||||
<div class="IntroduceText">
|
||||
<b>E-mail:</b><br/>Q1support@qidi3d.com<br>Q1AMS@qidi3d.com<br/><br>
|
||||
<b>Skype:</b><br/>Q1support@qidi3d.com
|
||||
</div>
|
||||
</div>
|
||||
<div class="PrinterBlock">
|
||||
<div class="CenterImage"><img src="img/X-MAX 3.png"/></div>
|
||||
<div class="ThumbnailTitle">X-MAX 3</div>
|
||||
|
||||
70
src/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"typeinfo": "cpp",
|
||||
"any": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"complex": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"csignal": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"list": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"map": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"regex": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"fstream": "cpp",
|
||||
"future": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"thread": "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<int, int> 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<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
||||
const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("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<int, int> 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 = 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<std::string> 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<ConfigOptionInt>("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();
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -153,7 +153,7 @@ void AppConfig::set_defaults()
|
||||
set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save"
|
||||
|
||||
if (get("color_mapinulation_panel").empty())
|
||||
set("color_mapinulation_panel", "0");
|
||||
set("color_mapinulation_panel", "1");
|
||||
|
||||
if (get("order_volumes").empty())
|
||||
set("order_volumes", "1");
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <functional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#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<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
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<Segment>& 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<Segment> &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<Segment>& segments)
|
||||
Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(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<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
|
||||
Point start = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
|
||||
Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
|
||||
|
||||
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<int64_t>().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<Segment>& 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<Segment> &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<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) ||
|
||||
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::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<int64_t>())
|
||||
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<int64_t>()) {
|
||||
if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
|
||||
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<int64_t>() || !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<int64_t>() || !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<Segment>& 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<int64_t>() && v1 == from.cast<int64_t>() ));
|
||||
if (v0 == to.cast<int64_t>() && !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<int64_t>() && (!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<SkeletalTrapezoidation::Segment> &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<int64_t>() && v1 == from.cast<int64_t>()));
|
||||
if (v0 == to.cast<int64_t>() && !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<int64_t>() && (!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<VoronoiUtils::Segment> &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<double>();
|
||||
const Vec2d source_segment_vec = source_segment.to().cast<double>() - 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<SkeletalTrapezoidation::Segment> &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<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
|
||||
Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const Polygons &polys,
|
||||
Polygons &polys_rotated,
|
||||
std::vector<SkeletalTrapezoidation::Segment> &segments,
|
||||
const std::vector<double> &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<double> 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<Point> 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<coord_t>(*starting_voronoi_edge));
|
||||
edge_t* prev_edge = nullptr;
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast<coord_t>(), *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<coord_t>(), *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<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
|
||||
|
||||
Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast<coord_t>();
|
||||
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>();
|
||||
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
|
||||
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
|
||||
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<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast<coord_t>(), 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<coord_t>(), 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));
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <Arachne/utils/VoronoiUtils.hpp>
|
||||
|
||||
#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<pos_t>;
|
||||
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<Point, Point, PointHash>;
|
||||
using NodeSet = ankerl::unordered_dense::set<node_t*>;
|
||||
|
||||
/*!
|
||||
@@ -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_t::edge_type*, edge_t*> vd_edge_to_he_edge;
|
||||
ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> 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<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
|
||||
ankerl::unordered_dense::map<const VD::vertex_type *, node_t *> 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<Segment>& 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<Segment> &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<Segment>& segments);
|
||||
Points discretize(const VD::edge_type& segment, const std::vector<Segment>& 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<Segment>& 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<Segment>& 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<Segment> &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<VoronoiUtils::Segment> &segments);
|
||||
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
@@ -156,7 +156,6 @@ struct PathsPointIndexLocator
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
|
||||
|
||||
@@ -27,5 +27,24 @@ public:
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
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
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <stack>
|
||||
#include <optional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#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<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
|
||||
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::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<Segment>& 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<Segment>& 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<Segment> &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<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const Point ap = p - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(p, &pxx);
|
||||
const Point ppxx = pxx - p;
|
||||
const coord_t d = ppxx.cast<int64_t>().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<coord_t>::max());
|
||||
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(mex <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::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<coord_t>(static_cast<float>(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<int64_t>::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<int64_t>::max()));
|
||||
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::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<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::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
|
||||
@@ -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 <vector>
|
||||
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include "PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
*/
|
||||
class VoronoiUtils
|
||||
{
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using voronoi_data_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
|
||||
|
||||
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &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
|
||||
@@ -58,7 +58,7 @@ class DefaultArrangerCtl : public Arranger<ArrItem>::Ctl {
|
||||
public:
|
||||
DefaultArrangerCtl() = default;
|
||||
|
||||
explicit DefaultArrangerCtl(ArrangeTaskBase::Ctl &ctl) : taskctl{&ctl} {}
|
||||
explicit DefaultArrangerCtl(ArrangeTaskCtl &ctl) : taskctl{&ctl} {}
|
||||
|
||||
void update_status(int st) override
|
||||
{
|
||||
|
||||
@@ -325,7 +325,7 @@ class DefaultArranger: public Arranger<ArrItem> {
|
||||
// a pure RectangleBed with inner-fit polygon calculation.
|
||||
if (!with_wipe_tower &&
|
||||
m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto &&
|
||||
std::is_convertible_v<Bed, RectangleBed>) {
|
||||
IsRectangular<Bed>) {
|
||||
PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond};
|
||||
|
||||
RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)};
|
||||
|
||||
@@ -181,6 +181,11 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed)
|
||||
|
||||
ArrangeBed to_arrange_bed(const Points &bedpts);
|
||||
|
||||
template<class Bed, class En = void> struct IsRectangular_ : public std::false_type {};
|
||||
template<> struct IsRectangular_<RectangleBed>: public std::true_type {};
|
||||
template<> struct IsRectangular_<BoundingBox>: public std::true_type {};
|
||||
|
||||
template<class Bed> static constexpr bool IsRectangular = IsRectangular_<Bed>::value;
|
||||
} // namespace arr2
|
||||
|
||||
inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; }
|
||||
|
||||
@@ -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<double, 5> dists;
|
||||
auto cc = fullbb.center(); // The gravity center
|
||||
dists[0] = (minc - cc).cast<double>().norm();
|
||||
dists[1] = (maxc - cc).cast<double>().norm();
|
||||
dists[2] = (itmcntr - cc).template cast<double>().norm();
|
||||
dists[3] = (top_left - cc).cast<double>().norm();
|
||||
dists[4] = (bottom_right - cc).cast<double>().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<double>().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<double>().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<double>().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<double>(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);
|
||||
|
||||
@@ -101,6 +101,9 @@ ExPolygons to_expolygons(const SegmentedRectangleBed<Args...> &bed)
|
||||
return to_expolygons(RectangleBed{bed.bb});
|
||||
}
|
||||
|
||||
template<class SegB>
|
||||
struct IsRectangular_<SegB, std::enable_if_t<IsSegmentedBed<SegB>, void>> : public std::true_type
|
||||
{};
|
||||
}} // namespace Slic3r::arr2
|
||||
|
||||
#endif // SEGMENTEDRECTANGLEBED_HPP
|
||||
|
||||
@@ -115,21 +115,13 @@ ArrangeTask<ArrItem>::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<int> 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
|
||||
|
||||
@@ -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<PointType>& 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
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BuildVolume::BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_print_height) : m_bed_shape(bed_shape), m_max_print_height(max_print_height)
|
||||
//B52
|
||||
BuildVolume::BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_print_height, const std::vector<Vec2d> &exclude_bed_shape)
|
||||
: m_bed_shape(bed_shape), m_max_print_height(max_print_height),m_exclude_bed_shape(exclude_bed_shape)
|
||||
{
|
||||
assert(max_print_height >= 0);
|
||||
|
||||
@@ -22,11 +24,16 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_p
|
||||
BoundingBoxf bboxf = get_extents(bed_shape);
|
||||
m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, max_print_height) };
|
||||
|
||||
//B52
|
||||
if (bed_shape.size() >= 4 && std::abs((m_area - double(m_bbox.size().x()) * double(m_bbox.size().y()))) < sqr(SCALED_EPSILON)) {
|
||||
// Square print bed, use the bounding box for collision detection.
|
||||
m_type = Type::Rectangle;
|
||||
m_circle.center = 0.5 * (m_bbox.min.cast<double>() + m_bbox.max.cast<double>());
|
||||
m_circle.radius = 0.5 * m_bbox.size().cast<double>().norm();
|
||||
} else if (exclude_bed_shape.size()>3) {
|
||||
m_type = Type::Rectangle;
|
||||
m_circle.center = 0.5 * (m_bbox.min.cast<double>() + m_bbox.max.cast<double>());
|
||||
m_circle.radius = 0.5 * m_bbox.size().cast<double>().norm();
|
||||
} else if (bed_shape.size() > 3) {
|
||||
// Circle was discretized, formatted into text with limited accuracy, thus the circle was deformed.
|
||||
// RANSAC is slightly more accurate than the iterative Taubin / Newton method with such an input.
|
||||
@@ -304,19 +311,49 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i
|
||||
}
|
||||
}
|
||||
|
||||
//B52
|
||||
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(m_type == Type::Rectangle);
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
std::vector<BoundingBox3Base<Vec3d>> exclude_build_volume;
|
||||
for (int i = 1; i < m_exclude_bed_shape.size(); i += 7) {
|
||||
std::vector<Vec2d> tem_exclude_bed_shap;
|
||||
for (int j = 1; j < 6; j++)
|
||||
tem_exclude_bed_shap.push_back(m_exclude_bed_shape[i + j]);
|
||||
BoundingBoxf tem_bboxf = get_extents(tem_exclude_bed_shap);
|
||||
auto tem_exclude_bboxf = BoundingBoxf3{to_3d(tem_bboxf.min, 0.), to_3d(tem_bboxf.max, m_max_print_height)};
|
||||
BoundingBox3Base<Vec3d> tem_build_volume = tem_exclude_bboxf.inflated(SceneEpsilon);
|
||||
exclude_build_volume.push_back(tem_build_volume);
|
||||
}
|
||||
|
||||
bool is_contain = false;
|
||||
bool is_intersect = false;
|
||||
|
||||
for (const auto &tem_build_volume : exclude_build_volume) {
|
||||
if (tem_build_volume.contains(volume_bbox)) {
|
||||
is_contain=true;
|
||||
is_intersect = false;
|
||||
break;
|
||||
}
|
||||
else if (tem_build_volume.intersects(volume_bbox)) {
|
||||
is_contain = false;
|
||||
is_intersect = true;
|
||||
}
|
||||
}
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
|
||||
is_contain ? ObjectState::Outside :
|
||||
is_intersect ? ObjectState::Outside :
|
||||
build_volume.contains(volume_bbox) ? ObjectState::Inside :
|
||||
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
||||
build_volume.intersects(volume_bbox) ? ObjectState::Colliding :
|
||||
ObjectState::Outside;
|
||||
}
|
||||
|
||||
//B52
|
||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
||||
@@ -332,7 +369,32 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox);
|
||||
std::vector<BoundingBox3Base<Vec3d>> exclude_build_volume;
|
||||
for (int i = 1; i < m_exclude_bed_shape.size(); i += 7) {
|
||||
std::vector<Vec2d> tem_exclude_bed_shap;
|
||||
for (int j = 1; j < 5; j++)
|
||||
tem_exclude_bed_shap.push_back(m_exclude_bed_shape[i + j]);
|
||||
BoundingBoxf tem_bboxf = get_extents(tem_exclude_bed_shap);
|
||||
auto tem_exclude_bboxf = BoundingBoxf3{to_3d(tem_bboxf.min, 0.), to_3d(tem_bboxf.max, m_max_print_height)};
|
||||
BoundingBox3Base<Vec3d> tem_build_volume = tem_exclude_bboxf.inflated(SceneEpsilon);
|
||||
exclude_build_volume.push_back(tem_build_volume);
|
||||
}
|
||||
|
||||
bool is_contain = false;
|
||||
bool is_intersect = false;
|
||||
|
||||
for (const auto &tem_build_volume : exclude_build_volume) {
|
||||
if (tem_build_volume.contains(paths_bbox)) {
|
||||
is_contain = true;
|
||||
is_intersect = false;
|
||||
break;
|
||||
} else if (tem_build_volume.intersects(paths_bbox)) {
|
||||
is_contain = false;
|
||||
is_intersect = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (build_volume.contains(paths_bbox) && !is_contain && !is_intersect);
|
||||
}
|
||||
case Type::Circle:
|
||||
{
|
||||
|
||||
@@ -34,10 +34,13 @@ public:
|
||||
// Initialized to empty, all zeros, Invalid.
|
||||
BuildVolume() {}
|
||||
// Initialize from PrintConfig::bed_shape and PrintConfig::max_print_height
|
||||
BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_print_height);
|
||||
//B52
|
||||
BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_print_height, const std::vector<Vec2d> &exclude_bed_shape);
|
||||
|
||||
// Source data, unscaled coordinates.
|
||||
const std::vector<Vec2d>& bed_shape() const { return m_bed_shape; }
|
||||
//B52
|
||||
const std::vector<Vec2d>& exclude_bed_shape() const { return m_exclude_bed_shape; }
|
||||
double max_print_height() const { return m_max_print_height; }
|
||||
|
||||
// Derived data
|
||||
@@ -101,6 +104,8 @@ public:
|
||||
private:
|
||||
// Source definition of the print bed geometry (PrintConfig::bed_shape)
|
||||
std::vector<Vec2d> m_bed_shape;
|
||||
//B52
|
||||
std::vector<Vec2d> m_exclude_bed_shape;
|
||||
// Source definition of the print volume height (PrintConfig::max_print_height)
|
||||
double m_max_print_height;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -212,6 +215,7 @@ set(SLIC3R_SOURCES
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
Layer.hpp
|
||||
LayerRegion.hpp
|
||||
LayerRegion.cpp
|
||||
libslic3r.h
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||
@@ -486,8 +490,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
|
||||
|
||||
@@ -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<std::string>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<bool>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<double>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<int>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<bool>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<int>)
|
||||
@@ -1417,9 +1424,11 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<std::string>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
|
||||
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<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<bool>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<double>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<int>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<bool>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<int>)
|
||||
@@ -1453,9 +1465,11 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::Con
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable<double>, Slic3r::ConfigOptionFloatNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloats)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable<int>, Slic3r::ConfigOptionIntNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionInts)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
@@ -39,9 +40,9 @@ namespace Slic3r {
|
||||
template<class Archive> 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<class T, class En = void> struct NilValueTempl
|
||||
{
|
||||
using NilType = T;
|
||||
static_assert(always_false<T>::value, "Type has no well defined nil value");
|
||||
};
|
||||
|
||||
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_integral_v<T>, void>> {
|
||||
using NilType = T;
|
||||
static constexpr auto value = std::numeric_limits<T>::max();
|
||||
};
|
||||
|
||||
template<> struct NilValueTempl<bool> : public NilValueTempl<int>{};
|
||||
|
||||
// For enums the nil is the max value of the underlying type.
|
||||
template<class T>
|
||||
struct NilValueTempl<T, std::enable_if_t<std::is_enum_v<T>, void>>
|
||||
{
|
||||
using NilType = T;
|
||||
static constexpr auto value = static_cast<T>(std::numeric_limits<std::underlying_type_t<T>>::max());
|
||||
};
|
||||
|
||||
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_floating_point_v<T>, void>> {
|
||||
using NilType = T;
|
||||
static constexpr auto value = std::numeric_limits<T>::quiet_NaN();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct NilValueTempl<FloatOrPercent> : public NilValueTempl<double> {};
|
||||
|
||||
template<> struct NilValueTempl<std::string> {
|
||||
using NilType = const char *;
|
||||
|
||||
static constexpr const char* value = "";
|
||||
};
|
||||
|
||||
template<int N, class T> struct NilValueTempl<Vec<N, T>> {
|
||||
using NilType = Vec<N, T>;
|
||||
// No constexpr for Vec<N, T>
|
||||
static inline const Vec<N, T> value = Vec<N, T>::Ones() * NilValueTempl<remove_cvref_t<T>>::value;
|
||||
};
|
||||
|
||||
template<class T> using NilType = typename NilValueTempl<remove_cvref_t<T>>::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<class T> static constexpr NilType<T> NilValue() noexcept { return NilValueTempl<remove_cvref_t<T>>::value; }
|
||||
// Value of a single valued option (bool, int, float, string, point, enum)
|
||||
template <class T>
|
||||
template <class T, bool NULLABLE = false>
|
||||
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<const ConfigOptionSingle<T>*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
assert(dynamic_cast<const ConfigOptionSingle*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle*>(rhs)->value;
|
||||
}
|
||||
|
||||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
|
||||
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
|
||||
assert(dynamic_cast<const ConfigOptionSingle*>(&rhs));
|
||||
if (this->is_nil() && rhs.is_nil())
|
||||
return true;
|
||||
return this->value == static_cast<const ConfigOptionSingle*>(&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<T>{}(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<const ConfigOptionSingle*>(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<const ConfigOptionSingle*>(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<T> nil_value() { return NilValue<T>(); }
|
||||
|
||||
// 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<class Archive> void serialize(Archive & ar) { ar(this->value); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
using ConfigOptionSingleNullable = ConfigOptionSingle<T, true>;
|
||||
// Value of a vector valued option (bools, ints, floats, strings, points)
|
||||
class ConfigOptionVectorBase : public ConfigOption {
|
||||
public:
|
||||
@@ -551,23 +655,34 @@ private:
|
||||
template<class Archive> void serialize(Archive & ar) { ar(this->values); }
|
||||
};
|
||||
|
||||
class ConfigOptionFloat : public ConfigOptionSingle<double>
|
||||
template<bool NULLABLE = false>
|
||||
class ConfigOptionFloatTempl : public ConfigOptionSingle<double, NULLABLE>
|
||||
{
|
||||
public:
|
||||
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}
|
||||
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}
|
||||
ConfigOptionFloatTempl() : ConfigOptionSingle<double, NULLABLE>(0) {}
|
||||
explicit ConfigOptionFloatTempl(double _value) : ConfigOptionSingle<double, NULLABLE>(_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<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double>>(this)); }
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double, NULLABLE>>(this)); }
|
||||
};
|
||||
|
||||
template<bool NULLABLE>
|
||||
@@ -713,26 +839,35 @@ private:
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<double>>(this)); }
|
||||
};
|
||||
|
||||
using ConfigOptionFloat = ConfigOptionFloatTempl<false>;
|
||||
using ConfigOptionFloatNullable = ConfigOptionFloatTempl<true>;
|
||||
using ConfigOptionFloats = ConfigOptionFloatsTempl<false>;
|
||||
using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl<true>;
|
||||
|
||||
class ConfigOptionInt : public ConfigOptionSingle<int>
|
||||
template<bool NULLABLE = false>
|
||||
class ConfigOptionIntTempl : public ConfigOptionSingle<int, NULLABLE>
|
||||
{
|
||||
public:
|
||||
ConfigOptionInt() : ConfigOptionSingle<int>(0) {}
|
||||
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}
|
||||
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}
|
||||
ConfigOptionIntTempl() : ConfigOptionSingle<int, NULLABLE>(0) {}
|
||||
explicit ConfigOptionIntTempl(int value) : ConfigOptionSingle<int, NULLABLE>(value) {}
|
||||
explicit ConfigOptionIntTempl(double _value) : ConfigOptionSingle<int, NULLABLE>(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<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int>>(this)); }
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int, NULLABLE>>(this)); }
|
||||
};
|
||||
|
||||
using ConfigOptionInt = ConfigOptionIntTempl<false>;
|
||||
using ConfigOptionIntNullable = ConfigOptionIntTempl<true>;
|
||||
template<bool NULLABLE>
|
||||
class ConfigOptionIntsTempl : public ConfigOptionVector<int>
|
||||
{
|
||||
@@ -1838,6 +1982,8 @@ public:
|
||||
template<class Archive> 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<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
|
||||
if (this->nullable) {
|
||||
switch (this->type) {
|
||||
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
|
||||
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
|
||||
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
|
||||
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
|
||||
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
|
||||
|
||||
@@ -68,6 +68,21 @@ std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& cus
|
||||
return custom_tool_changes;
|
||||
}
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> 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<std::pair<double, unsigned int>> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders)
|
||||
{
|
||||
std::vector<std::pair<double, unsigned int>> 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<unsigned int>(custom_gcode.extruder));
|
||||
}
|
||||
}
|
||||
return custom_color_changes;
|
||||
}
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -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<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> 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<std::pair<double, unsigned int>> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -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<class Archive> void load(Archive &ar) {
|
||||
|
||||
@@ -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<BoundingBox> 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);
|
||||
|
||||
@@ -184,6 +184,10 @@ public:
|
||||
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
|
||||
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
|
||||
double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
|
||||
//w21
|
||||
void set_width(float set_val) { m_attributes.width = set_val; }
|
||||
void set_height(float set_val) { m_attributes.height = set_val; }
|
||||
void set_mm3_per_mm(float set_val) { m_attributes.mm3_per_mm = set_val; }
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
||||
@@ -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<float, float> &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;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,14 @@
|
||||
#include "FillConcentric.hpp"
|
||||
#include "FillEnsuring.hpp"
|
||||
#include "Polygon.hpp"
|
||||
//w21
|
||||
#include "../ShortestPath.hpp"
|
||||
//w11
|
||||
|
||||
#include "LayerRegion.hpp"
|
||||
|
||||
#define NARROW_INFILL_AREA_THRESHOLD 3
|
||||
#define NARROW_INFILL_AREA_THRESHOLD_MIN 0.5
|
||||
namespace Slic3r {
|
||||
|
||||
//static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
|
||||
@@ -112,12 +118,16 @@ struct SurfaceFill {
|
||||
Surface surface;
|
||||
ExPolygons expolygons;
|
||||
SurfaceFillParams params;
|
||||
//w21
|
||||
std::vector<size_t> region_id_group;
|
||||
ExPolygons no_overlap_expolygons;
|
||||
};
|
||||
//w11
|
||||
static bool is_narrow_infill_area(const ExPolygon &expolygon)
|
||||
{
|
||||
ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD));
|
||||
if (offsets.empty())
|
||||
ExPolygons offsets_min = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD_MIN));
|
||||
if (offsets.empty() && !offsets_min.empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -219,8 +229,20 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
fill.region_id = region_id;
|
||||
fill.surface = surface;
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
} else
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
//w21
|
||||
fill.region_id_group.push_back(region_id);
|
||||
//w21
|
||||
fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
|
||||
} else {
|
||||
//w21
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id);
|
||||
if (t == fill.region_id_group.end()) {
|
||||
fill.region_id_group.push_back(region_id);
|
||||
//w21
|
||||
fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,47 +338,36 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
}
|
||||
|
||||
// Use ipEnsuring pattern for all internal Solids.
|
||||
{
|
||||
for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
fill.params.pattern = ipEnsuring;
|
||||
}
|
||||
}
|
||||
//w11
|
||||
if (layer.object()->config().detect_narrow_internal_solid_infill) {
|
||||
size_t surface_fills_size = surface_fills.size();
|
||||
for (size_t i = 0; i < surface_fills_size; i++) {
|
||||
for (size_t i = 0; i < surface_fills.size(); i++) {
|
||||
if (surface_fills[i].surface.surface_type != stInternalSolid)
|
||||
continue;
|
||||
|
||||
size_t expolygons_size = surface_fills[i].expolygons.size();
|
||||
size_t expolygons_size = surface_fills[i].expolygons.size();
|
||||
std::vector<size_t> narrow_expolygons_index;
|
||||
narrow_expolygons_index.reserve(expolygons_size);
|
||||
|
||||
for (size_t j = 0; j < expolygons_size; j++)
|
||||
if (is_narrow_infill_area(surface_fills[i].expolygons[j]))
|
||||
narrow_expolygons_index.push_back(j);
|
||||
|
||||
if (narrow_expolygons_index.size() == 0) {
|
||||
continue;
|
||||
} else if (narrow_expolygons_index.size() == expolygons_size) {
|
||||
// w11
|
||||
// w14
|
||||
if (narrow_expolygons_index.size() == expolygons_size) {
|
||||
surface_fills[i].params.pattern = ipConcentricInternal;
|
||||
} else {
|
||||
params = surface_fills[i].params;
|
||||
params.pattern = ipConcentricInternal;
|
||||
surface_fills.emplace_back(params);
|
||||
surface_fills.back().region_id = surface_fills[i].region_id;
|
||||
surface_fills.back().surface.surface_type = stInternalSolid;
|
||||
surface_fills.back().surface.thickness = surface_fills[i].surface.thickness;
|
||||
for (size_t j = 0; j < narrow_expolygons_index.size(); j++) {
|
||||
surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]]));
|
||||
}
|
||||
for (int j = narrow_expolygons_index.size() - 1; j >= 0; j--) {
|
||||
surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + narrow_expolygons_index[j]);
|
||||
}
|
||||
surface_fills[i].params.pattern = ipEnsuring;
|
||||
}
|
||||
//w21
|
||||
if (narrow_expolygons_index.size() != expolygons_size && narrow_expolygons_index.size() != expolygons_size) {
|
||||
surface_fills.back().region_id_group = surface_fills[i].region_id_group;
|
||||
surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
fill.params.pattern = ipEnsuring;
|
||||
}
|
||||
}
|
||||
return surface_fills;
|
||||
}
|
||||
@@ -489,6 +500,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const auto resolution = this->object()->print()->config().gcode_resolution.value;
|
||||
const auto perimeter_generator = this->object()->config().perimeter_generator;
|
||||
//w21
|
||||
float filter_gap_infill_value = this->object()->config().filter_top_gap_infill;
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@@ -549,37 +562,34 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
//w14
|
||||
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentricInternal) || surface_fill.params.pattern == ipEnsuring;
|
||||
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring || surface_fill.params.pattern == ipConcentricInternal;
|
||||
params.layer_height = layerm.layer()->height;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
//w21
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
Polylines polylines;
|
||||
ThickPolylines thick_polylines;
|
||||
try {
|
||||
if (params.use_arachne) {
|
||||
//w14
|
||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||
layerm.region().config().infill_overlap.percent ?
|
||||
f->overlap = layerm.region().config().perimeter_extrusion_width * layerm.region().config().infill_overlap.value / 100 *(-1):
|
||||
f->overlap = float(layerm.region().config().infill_overlap.value);
|
||||
|
||||
} else
|
||||
f->overlap = 0;
|
||||
thick_polylines = f->fill_surface_arachne(&surface_fill.surface, params);
|
||||
}
|
||||
//w14
|
||||
else {
|
||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||
layerm.region().config().infill_overlap.percent ?
|
||||
f->overlap = layerm.region().config().perimeter_extrusion_width *
|
||||
layerm.region().config().infill_overlap.value / 100 * (-1) :
|
||||
f->overlap = float(layerm.region().config().infill_overlap.value);
|
||||
//w14
|
||||
if (this->object()->config().detect_narrow_internal_solid_infill &&
|
||||
(surface_fill.params.pattern == ipConcentricInternal || surface_fill.params.pattern == ipEnsuring)) {
|
||||
layerm.region().config().infill_overlap.percent ?
|
||||
f->overlap = layerm.region().config().perimeter_extrusion_width * layerm.region().config().infill_overlap.value / 100 * (-1) :
|
||||
f->overlap = float(layerm.region().config().infill_overlap.value);
|
||||
} else
|
||||
f->overlap = 0;
|
||||
|
||||
} else
|
||||
f->overlap = 0;
|
||||
try {
|
||||
if (params.use_arachne) {
|
||||
thick_polylines = f->fill_surface_arachne(&surface_fill.surface, params);
|
||||
//w21
|
||||
//if (f->layer_id % 2 == 0 && surface_fill.params.pattern == ipConcentricInternal)
|
||||
// std::reverse(thick_polylines.begin(), thick_polylines.end());
|
||||
}
|
||||
else {
|
||||
polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
}
|
||||
} catch (InfillFailedException &) {
|
||||
@@ -628,6 +638,48 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
ExtrusionAttributes{ surface_fill.params.extrusion_role,
|
||||
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() }
|
||||
});
|
||||
//w21
|
||||
if (surface_fill.params.pattern == ipMonotonicLines && surface_fill.surface.surface_type == stTop) {
|
||||
ExPolygons unextruded_areas = diff_ex(f->no_overlap_expolygons, union_ex(eec->polygons_covered_by_spacing(10)));
|
||||
ExPolygons gapfill_areas = union_ex(unextruded_areas);
|
||||
if (!f->no_overlap_expolygons.empty())
|
||||
gapfill_areas = intersection_ex(gapfill_areas, f->no_overlap_expolygons);
|
||||
if (gapfill_areas.size() > 0 && params.density >= 1) {
|
||||
Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing));
|
||||
double min = 0.2 * new_flow.scaled_spacing() * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
double max = 2. * new_flow.scaled_spacing();
|
||||
ExPolygons gaps_ex = diff_ex(opening_ex(gapfill_areas, float(min / 2.)),
|
||||
offset2_ex(gapfill_areas, -float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
||||
Points ordering_points;
|
||||
ordering_points.reserve(gaps_ex.size());
|
||||
ExPolygons gaps_ex_sorted;
|
||||
gaps_ex_sorted.reserve(gaps_ex.size());
|
||||
for (const ExPolygon &ex : gaps_ex)
|
||||
ordering_points.push_back(ex.contour.first_point());
|
||||
std::vector<Points::size_type> order = chain_points(ordering_points);
|
||||
for (size_t i : order)
|
||||
gaps_ex_sorted.emplace_back(std::move(gaps_ex[i]));
|
||||
|
||||
ThickPolylines polylines;
|
||||
for (ExPolygon &ex : gaps_ex_sorted) {
|
||||
ex.douglas_peucker(0.0125 / 0.000001 * 0.1);
|
||||
ex.medial_axis(min, max, &polylines);
|
||||
}
|
||||
|
||||
if (!polylines.empty() && !surface_fill.params.extrusion_role.is_bridge()) {
|
||||
ExtrusionEntityCollection gap_fill;
|
||||
polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
|
||||
[&](const ThickPolyline &p) {
|
||||
return p.length() < 0; // scale_(params.filter_out_gap_fill);
|
||||
}),
|
||||
polylines.end());
|
||||
|
||||
variable_width_gap(polylines, ExtrusionRole::GapFill, surface_fill.params.flow, gap_fill.entities,filter_gap_infill_value);
|
||||
|
||||
eec->append(std::move(gap_fill.entities));
|
||||
}
|
||||
}
|
||||
}
|
||||
layerm.m_fills.entities.push_back(eec);
|
||||
}
|
||||
insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size()));
|
||||
@@ -673,6 +725,153 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
assert(dynamic_cast<const ExtrusionEntityCollection*>(e) != nullptr);
|
||||
#endif
|
||||
}
|
||||
//w21
|
||||
void Layer::variable_width_gap(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity *> &out,const float filter_gap_infill_value)
|
||||
{
|
||||
const float tolerance = float(scale_(0.05));
|
||||
for (const ThickPolyline &p : polylines) {
|
||||
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
|
||||
if (!paths.empty()) {
|
||||
if (paths.front().first_point() == paths.back().last_point()) {
|
||||
out.emplace_back(new ExtrusionLoop(std::move(paths)));
|
||||
}
|
||||
else {
|
||||
for (ExtrusionPath &path : paths) {
|
||||
if (filter_gap_infill_value != 0) {
|
||||
if (path.length() >= scale_(filter_gap_infill_value) || path.width() >= scale_(filter_gap_infill_value))
|
||||
out.emplace_back(new ExtrusionPath(std::move(path)));
|
||||
}
|
||||
else
|
||||
out.emplace_back(new ExtrusionPath(std::move(path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//w21
|
||||
ExtrusionPaths Layer::thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline,
|
||||
ExtrusionRole role,
|
||||
const Flow & flow,
|
||||
const float tolerance)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
ExtrusionPath path(role);
|
||||
ThickLines lines = thick_polyline.thicklines();
|
||||
|
||||
size_t start_index = 0;
|
||||
double max_width, min_width;
|
||||
|
||||
for (int i = 0; i < (int) lines.size(); ++i) {
|
||||
const ThickLine &line = lines[i];
|
||||
|
||||
if (i == 0) {
|
||||
max_width = line.a_width;
|
||||
min_width = line.a_width;
|
||||
}
|
||||
|
||||
const coordf_t line_len = line.length();
|
||||
if (line_len < SCALED_EPSILON)
|
||||
continue;
|
||||
|
||||
double thickness_delta = std::max(fabs(max_width - line.b_width), fabs(min_width - line.b_width));
|
||||
if (thickness_delta > tolerance) {
|
||||
if (start_index != i) {
|
||||
path = ExtrusionPath(role);
|
||||
double length = lines[start_index].length();
|
||||
double sum = lines[start_index].length() * 0.5 * (lines[start_index].a_width + lines[start_index].b_width);
|
||||
path.polyline.append(lines[start_index].a);
|
||||
for (int idx = start_index + 1; idx < i; idx++) {
|
||||
length += lines[idx].length();
|
||||
sum += lines[idx].length() * 0.5 * (lines[idx].a_width + lines[idx].b_width);
|
||||
path.polyline.append(lines[idx].a);
|
||||
}
|
||||
path.polyline.append(lines[i].a);
|
||||
if (length > SCALED_EPSILON) {
|
||||
double w = sum / length;
|
||||
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
|
||||
|
||||
//path.mm3_per_mm = new_flow.mm3_per_mm();
|
||||
path.set_mm3_per_mm(new_flow.mm3_per_mm());
|
||||
//path.width = new_flow.width();
|
||||
path.set_width(new_flow.width());
|
||||
//path.height = new_flow.height();
|
||||
path.set_height(new_flow.height());
|
||||
paths.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
|
||||
start_index = i;
|
||||
max_width = line.a_width;
|
||||
min_width = line.a_width;
|
||||
thickness_delta = fabs(line.a_width - line.b_width);
|
||||
if (thickness_delta > tolerance) {
|
||||
const unsigned int segments = (unsigned int) ceil(thickness_delta / tolerance);
|
||||
const coordf_t seg_len = line_len / segments;
|
||||
Points pp;
|
||||
std::vector<coordf_t> width;
|
||||
{
|
||||
pp.push_back(line.a);
|
||||
width.push_back(line.a_width);
|
||||
for (size_t j = 1; j < segments; ++j) {
|
||||
pp.push_back(
|
||||
(line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
|
||||
|
||||
coordf_t w = line.a_width + (j * seg_len) * (line.b_width - line.a_width) / line_len;
|
||||
width.push_back(w);
|
||||
width.push_back(w);
|
||||
}
|
||||
pp.push_back(line.b);
|
||||
width.push_back(line.b_width);
|
||||
|
||||
assert(pp.size() == segments + 1u);
|
||||
assert(width.size() == segments * 2);
|
||||
}
|
||||
|
||||
lines.erase(lines.begin() + i);
|
||||
for (size_t j = 0; j < segments; ++j) {
|
||||
ThickLine new_line(pp[j], pp[j + 1]);
|
||||
new_line.a_width = width[2 * j];
|
||||
new_line.b_width = width[2 * j + 1];
|
||||
lines.insert(lines.begin() + i + j, new_line);
|
||||
}
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
max_width = std::max(max_width, std::max(line.a_width, line.b_width));
|
||||
min_width = std::min(min_width, std::min(line.a_width, line.b_width));
|
||||
}
|
||||
}
|
||||
size_t final_size = lines.size();
|
||||
if (start_index < final_size) {
|
||||
path = ExtrusionPath(role);
|
||||
double length = lines[start_index].length();
|
||||
double sum = lines[start_index].length() * lines[start_index].a_width;
|
||||
path.polyline.append(lines[start_index].a);
|
||||
for (int idx = start_index + 1; idx < final_size; idx++) {
|
||||
length += lines[idx].length();
|
||||
sum += lines[idx].length() * lines[idx].a_width;
|
||||
path.polyline.append(lines[idx].a);
|
||||
}
|
||||
path.polyline.append(lines[final_size - 1].b);
|
||||
if (length > SCALED_EPSILON) {
|
||||
double w = sum / length;
|
||||
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
|
||||
//path.mm3_per_mm = new_flow.mm3_per_mm();
|
||||
path.set_mm3_per_mm(new_flow.mm3_per_mm());
|
||||
//path.width = new_flow.width();
|
||||
path.set_width(new_flow.width());
|
||||
//path.height = new_flow.height();
|
||||
path.set_height(new_flow.height());
|
||||
paths.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
|
||||
{
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef slic3r_Fill_hpp_
|
||||
#define slic3r_Fill_hpp_
|
||||
|
||||
#include <memory.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_
|
||||
@@ -94,6 +94,8 @@ public:
|
||||
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
//w21
|
||||
ExPolygons no_overlap_expolygons;
|
||||
|
||||
public:
|
||||
virtual ~Fill() {}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "FillConcentric.hpp"
|
||||
|
||||
#include <libslic3r/ShortestPath.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillConcentric::_fill_surface_single(
|
||||
@@ -116,6 +118,7 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms,
|
||||
}
|
||||
if (j < thick_polylines_out.size())
|
||||
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
|
||||
//reorder_by_shortest_traverse(thick_polylines_out);
|
||||
} else {
|
||||
Polylines polylines;
|
||||
this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines);
|
||||
|
||||
@@ -86,6 +86,11 @@ const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt
|
||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/QIDI_Slicer_custom_gcode_per_print_z.xml";
|
||||
const std::string CUT_INFORMATION_FILE = "Metadata/QIDI_Slicer_cut_information.xml";
|
||||
|
||||
static constexpr const char *RELATIONSHIP_TAG = "Relationship";
|
||||
|
||||
static constexpr const char* TARGET_ATTR = "Target";
|
||||
static constexpr const char* RELS_TYPE_ATTR = "Type";
|
||||
|
||||
static constexpr const char* MODEL_TAG = "model";
|
||||
static constexpr const char* RESOURCES_TAG = "resources";
|
||||
static constexpr const char* OBJECT_TAG = "object";
|
||||
@@ -113,13 +118,14 @@ static constexpr const char* Z_ATTR = "z";
|
||||
static constexpr const char* V1_ATTR = "v1";
|
||||
static constexpr const char* V2_ATTR = "v2";
|
||||
static constexpr const char* V3_ATTR = "v3";
|
||||
static constexpr const char* PPATH_ATTR = "p:path";
|
||||
static constexpr const char* OBJECTID_ATTR = "objectid";
|
||||
static constexpr const char* TRANSFORM_ATTR = "transform";
|
||||
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 +179,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 +189,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[] =
|
||||
{
|
||||
@@ -327,18 +335,20 @@ namespace Slic3r {
|
||||
|
||||
class _3MF_Importer : public _3MF_Base
|
||||
{
|
||||
typedef std::pair<std::string, int> PathId;
|
||||
struct Component
|
||||
{
|
||||
int object_id;
|
||||
PathId object_id;
|
||||
std::string path;
|
||||
Transform3d transform;
|
||||
|
||||
explicit Component(int object_id)
|
||||
explicit Component(PathId object_id)
|
||||
: object_id(object_id)
|
||||
, transform(Transform3d::Identity())
|
||||
{
|
||||
}
|
||||
|
||||
Component(int object_id, const Transform3d& transform)
|
||||
Component(PathId object_id, const Transform3d &transform)
|
||||
: object_id(object_id)
|
||||
, transform(transform)
|
||||
{
|
||||
@@ -353,7 +363,7 @@ namespace Slic3r {
|
||||
std::vector<Vec3i> triangles;
|
||||
std::vector<std::string> custom_supports;
|
||||
std::vector<std::string> custom_seam;
|
||||
std::vector<std::string> mmu_segmentation;
|
||||
std::vector<std::string> mm_segmentation;
|
||||
|
||||
bool empty() { return vertices.empty() || triangles.empty(); }
|
||||
|
||||
@@ -362,7 +372,7 @@ namespace Slic3r {
|
||||
triangles.clear();
|
||||
custom_supports.clear();
|
||||
custom_seam.clear();
|
||||
mmu_segmentation.clear();
|
||||
mm_segmentation.clear();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -456,11 +466,11 @@ namespace Slic3r {
|
||||
};
|
||||
|
||||
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
|
||||
typedef std::map<int, int> IdToModelObjectMap;
|
||||
typedef std::map<int, ComponentsList> IdToAliasesMap;
|
||||
typedef std::map<PathId, int> IdToModelObjectMap;
|
||||
typedef std::map<PathId, ComponentsList> IdToAliasesMap;
|
||||
typedef std::vector<Instance> InstancesList;
|
||||
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
|
||||
typedef std::map<int, Geometry> IdToGeometryMap;
|
||||
typedef std::map<PathId, Geometry> IdToGeometryMap;
|
||||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
|
||||
@@ -501,6 +511,8 @@ namespace Slic3r {
|
||||
std::string m_curr_metadata_name;
|
||||
std::string m_curr_characters;
|
||||
std::string m_name;
|
||||
std::string m_start_part_path;
|
||||
std::string m_model_path;
|
||||
|
||||
public:
|
||||
_3MF_Importer();
|
||||
@@ -524,8 +536,9 @@ namespace Slic3r {
|
||||
}
|
||||
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
|
||||
bool _extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
bool _extract_model_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
bool _is_svg_shape_file(const std::string &filename) const;
|
||||
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
@@ -538,6 +551,9 @@ namespace Slic3r {
|
||||
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
||||
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
|
||||
// handlers to parse the .rels file
|
||||
void _handle_start_relationships_element(const char* name, const char** attributes);
|
||||
bool _handle_start_relationship(const char **attributes, unsigned int num_attributes);
|
||||
// handlers to parse the .model file
|
||||
void _handle_start_model_xml_element(const char* name, const char** attributes);
|
||||
void _handle_end_model_xml_element(const char* name);
|
||||
@@ -589,7 +605,7 @@ namespace Slic3r {
|
||||
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
|
||||
|
||||
bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
bool _create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
|
||||
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
|
||||
|
||||
@@ -609,6 +625,8 @@ namespace Slic3r {
|
||||
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
||||
|
||||
// callbacks to parse the .rels file
|
||||
static void XMLCALL _handle_start_relationships_element(void *userData, const char *name, const char **attributes);
|
||||
// callbacks to parse the .model file
|
||||
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
|
||||
static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
|
||||
@@ -658,6 +676,7 @@ namespace Slic3r {
|
||||
m_sla_support_points.clear();
|
||||
m_curr_metadata_name.clear();
|
||||
m_curr_characters.clear();
|
||||
m_start_part_path = MODEL_FILE; // set default value for invalid .rel file
|
||||
clear_errors();
|
||||
|
||||
return _load_model_from_file(filename, model, config, config_substitutions);
|
||||
@@ -697,16 +716,28 @@ 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
|
||||
int index = mz_zip_reader_locate_file(&archive, RELATIONSHIPS_FILE.c_str(), nullptr, 0);
|
||||
if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat))
|
||||
return false;
|
||||
|
||||
mz_zip_archive_file_stat start_part_stat{std::numeric_limits<mz_uint32>::max()};
|
||||
m_model_path = MODEL_FILE;
|
||||
_extract_relationships_from_archive(archive, stat);
|
||||
bool found_model = false;
|
||||
// we first loop the entries to read from the .model files which are not root
|
||||
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)) {
|
||||
try
|
||||
{
|
||||
// valid model name -> extract model
|
||||
if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) {
|
||||
// valid model name -> extract model
|
||||
m_model_path = "/" + name;
|
||||
if (m_model_path == m_start_part_path) {
|
||||
start_part_stat = stat;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (!_extract_model_from_archive(archive, stat)) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Archive does not contain a valid model");
|
||||
@@ -719,9 +750,31 @@ namespace Slic3r {
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
found_model = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read root model file
|
||||
if (start_part_stat.m_file_index < num_entries) {
|
||||
try {
|
||||
m_model_path.clear();
|
||||
if (!_extract_model_from_archive(archive, start_part_stat)) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Archive does not contain a valid model");
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
found_model = true;
|
||||
}
|
||||
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) {
|
||||
@@ -855,7 +908,7 @@ namespace Slic3r {
|
||||
ObjectMetadata::VolumeMetadataList volumes;
|
||||
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
||||
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
|
||||
if (obj_metadata != m_objects_metadata.end()) {
|
||||
// config data has been found, this model was saved using slic3r pe
|
||||
|
||||
@@ -936,8 +989,55 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
// // fixes the min z of the model if negative
|
||||
// model.adjust_min_z();
|
||||
// We support our 3mf contains only configuration without mesh,
|
||||
// others MUST contain mesh (triangles and vertices).
|
||||
if (!m_qidislicer_generator_version.has_value() && model.objects.empty()) {
|
||||
const std::string msg = (boost::format(_u8L("The 3MF file does not contain a valid mesh.\n\n\"%1%\"")) % filename).str();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
if (stat.m_uncomp_size == 0 ||
|
||||
stat.m_uncomp_size > 10000000 // Prevent overloading by big Relations file(>10MB). there is no reason to be soo big
|
||||
) {
|
||||
add_error("Found invalid size");
|
||||
return false;
|
||||
}
|
||||
|
||||
_destroy_xml_parser();
|
||||
|
||||
m_xml_parser = XML_ParserCreate(nullptr);
|
||||
if (m_xml_parser == nullptr) {
|
||||
add_error("Unable to create parser");
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(m_xml_parser, (void *) this);
|
||||
XML_SetStartElementHandler(m_xml_parser, _handle_start_relationships_element);
|
||||
|
||||
void *parser_buffer = XML_GetBuffer(m_xml_parser, (int) stat.m_uncomp_size);
|
||||
if (parser_buffer == nullptr) {
|
||||
add_error("Unable to create buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading config data to buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!XML_ParseBuffer(m_xml_parser, (int) stat.m_uncomp_size, 1)) {
|
||||
char error_buf[1024];
|
||||
::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)),
|
||||
(int) XML_GetCurrentLineNumber(m_xml_parser));
|
||||
add_error(error_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1501,6 +1601,39 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes)
|
||||
{
|
||||
_3MF_Importer *importer = (_3MF_Importer *) userData;
|
||||
if (importer != nullptr)
|
||||
importer->_handle_start_relationships_element(name, attributes);
|
||||
}
|
||||
|
||||
void _3MF_Importer::_handle_start_relationships_element(const char *name, const char **attributes)
|
||||
{
|
||||
if (m_xml_parser == nullptr)
|
||||
return;
|
||||
|
||||
bool res = true;
|
||||
unsigned int num_attributes = (unsigned int) XML_GetSpecifiedAttributeCount(m_xml_parser);
|
||||
|
||||
if (::strcmp(RELATIONSHIP_TAG, name) == 0)
|
||||
res = _handle_start_relationship(attributes, num_attributes);
|
||||
|
||||
m_curr_characters.clear();
|
||||
if (!res)
|
||||
_stop_xml_parser();
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_handle_start_relationship(const char **attributes, unsigned int num_attributes)
|
||||
{
|
||||
std::string type = get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR);
|
||||
// only exactly that string type mean root model file
|
||||
if (type == "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") {
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, TARGET_ATTR);
|
||||
m_start_part_path = path;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
|
||||
{
|
||||
if (m_xml_parser == nullptr)
|
||||
@@ -1640,6 +1773,8 @@ namespace Slic3r {
|
||||
|
||||
bool _3MF_Importer::_handle_end_model()
|
||||
{
|
||||
if (!m_model_path.empty())
|
||||
return true;
|
||||
// deletes all non-built or non-instanced objects
|
||||
for (const IdToModelObjectMap::value_type& object : m_objects) {
|
||||
if (object.second >= int(m_model->objects.size())) {
|
||||
@@ -1708,6 +1843,7 @@ namespace Slic3r {
|
||||
bool _3MF_Importer::_handle_end_object()
|
||||
{
|
||||
if (m_curr_object.object != nullptr) {
|
||||
PathId object_id{m_model_path, m_curr_object.id};
|
||||
if (m_curr_object.geometry.empty()) {
|
||||
// no geometry defined
|
||||
// remove the object from the model
|
||||
@@ -1715,26 +1851,26 @@ namespace Slic3r {
|
||||
|
||||
if (m_curr_object.components.empty()) {
|
||||
// no components defined -> invalid object, delete it
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id);
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
|
||||
if (object_item != m_objects.end())
|
||||
m_objects.erase(object_item);
|
||||
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id);
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
|
||||
if (alias_item != m_objects_aliases.end())
|
||||
m_objects_aliases.erase(alias_item);
|
||||
}
|
||||
else
|
||||
// adds components to aliases
|
||||
m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components });
|
||||
m_objects_aliases.insert({ object_id, m_curr_object.components });
|
||||
}
|
||||
else {
|
||||
// geometry defined, store it for later use
|
||||
m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) });
|
||||
m_geometries.insert({ object_id, std::move(m_curr_object.geometry) });
|
||||
|
||||
// stores the object for later use
|
||||
if (m_objects.find(m_curr_object.id) == m_objects.end()) {
|
||||
m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx });
|
||||
m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself
|
||||
if (m_objects.find(object_id) == m_objects.end()) {
|
||||
m_objects.insert({ object_id, m_curr_object.model_object_idx });
|
||||
m_objects_aliases.insert({object_id, {1, Component(object_id)}}); // aliases itself
|
||||
}
|
||||
else {
|
||||
add_error("Found object with duplicate id");
|
||||
@@ -1820,7 +1956,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;
|
||||
}
|
||||
|
||||
@@ -1845,19 +1981,22 @@ namespace Slic3r {
|
||||
|
||||
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
if (path.empty()) path = m_model_path;
|
||||
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
|
||||
PathId path_id { path, object_id };
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(path_id);
|
||||
if (object_item == m_objects.end()) {
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(path_id);
|
||||
if (alias_item == m_objects_aliases.end()) {
|
||||
add_error("Found component with invalid object id");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_curr_object.components.emplace_back(object_id, transform);
|
||||
m_curr_object.components.emplace_back(path_id, transform);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1891,9 +2030,11 @@ namespace Slic3r {
|
||||
|
||||
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
if (path.empty()) path = m_model_path;
|
||||
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
||||
|
||||
return _create_object_instance(object_id, transform, printable, 1);
|
||||
return _create_object_instance({path, object_id}, transform, printable, 1);
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_handle_end_item()
|
||||
@@ -2049,7 +2190,7 @@ namespace Slic3r {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
bool _3MF_Importer::_create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
{
|
||||
static const unsigned int MAX_RECURSIONS = 10;
|
||||
|
||||
@@ -2310,41 +2451,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; i<triangles_count; ++i) {
|
||||
size_t index = volume_data.first_triangle_id + i;
|
||||
assert(index < geometry.custom_supports.size());
|
||||
assert(index < geometry.custom_seam.size());
|
||||
assert(index < geometry.mmu_segmentation.size());
|
||||
assert(index < geometry.mm_segmentation.size());
|
||||
if (! geometry.custom_supports[index].empty())
|
||||
volume->supported_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 +3133,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 +3554,6 @@ namespace Slic3r {
|
||||
if (const std::optional<EmbossShape> &es = volume->emboss_shape;
|
||||
es.has_value())
|
||||
to_xml(stream, *es, *volume, archive);
|
||||
// stores volume's text data
|
||||
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
|
||||
tc.has_value())
|
||||
TextConfigurationSerialization::to_xml(stream, *tc);
|
||||
@@ -3575,11 +3704,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 +3827,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 +3843,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 +3861,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<TextConfiguration> TextConfigurationSerialization::read(const char **attributes, unsigned int num_attributes)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Original implementation of STEP format import created by Bambulab.
|
||||
// https://github.com/bambulab/BambuStudio
|
||||
// Forked off commit 1555904, modified by QIDI Research.
|
||||
|
||||
#ifndef slic3r_Format_STEP_hpp_
|
||||
#define slic3r_Format_STEP_hpp_
|
||||
|
||||
@@ -135,7 +135,7 @@ std::pair<DynamicPrintConfig, ConfigSubstitutions> 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)};
|
||||
}
|
||||
|
||||
@@ -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 <memory>
|
||||
@@ -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<coord_t>::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<double> distances{0.5, 1.5};
|
||||
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
|
||||
* // produces
|
||||
* {{0, 0}, {0, 0.5}, {1, 0}}
|
||||
* // notice that 1.5 is omitted
|
||||
* @endcode
|
||||
*/
|
||||
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> 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<const Point> xy_path,
|
||||
const std::vector<double>& ensure_points_at_distances,
|
||||
const double initial_elevation,
|
||||
const std::function<double(double)>& 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<Linef> 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<double> get_first_crossed_line_distance(
|
||||
tcb::span<const Line> xy_path,
|
||||
const AABBTreeLines::LinesDistancer<Linef>& 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<Vec2d>& shape, const double padding);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Bed shape with inner padding.
|
||||
*/
|
||||
Bed(const std::vector<Vec2d>& 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<coord_t>::max(), std::numeric_limits<coord_t>::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<typename Derived>
|
||||
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector");
|
||||
Eigen::Matrix<double, Derived::SizeAtCompileTime, 1, Eigen::DontAlign> point_to_gcode(const Eigen::MatrixBase<Derived> &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<double>(point.x()), unscaled<double>(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<typename Derived>
|
||||
@@ -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 <key, value> items
|
||||
static void encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& 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<ObjectLayerToPrint>;
|
||||
using ObjectLayerToPrint = GCode::ObjectLayerToPrint;
|
||||
using ObjectsLayerToPrint = std::vector<GCode::ObjectLayerToPrint>;
|
||||
|
||||
std::optional<Point> last_position;
|
||||
|
||||
private:
|
||||
class GCodeOutputStream {
|
||||
@@ -309,6 +210,15 @@ private:
|
||||
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
|
||||
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
|
||||
|
||||
Polyline get_layer_change_xy_path(const Vec3d &from, const Vec3d &to);
|
||||
|
||||
std::string get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id);
|
||||
/** @brief Generates ramping travel gcode for layer change. */
|
||||
std::string generate_ramping_layer_change_gcode(
|
||||
const Polyline &xy_path,
|
||||
const double initial_elevation,
|
||||
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
|
||||
) const;
|
||||
LayerResult process_layer(
|
||||
const Print &print,
|
||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||
@@ -342,19 +252,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<unsigned int> &extruder_ids);
|
||||
std::string preamble();
|
||||
std::optional<std::string> 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.);
|
||||
@@ -406,7 +309,8 @@ private:
|
||||
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
|
||||
std::string generate_travel_gcode(
|
||||
const Points3& travel,
|
||||
const std::string& comment
|
||||
const std::string& comment,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
);
|
||||
Polyline generate_travel_xy_path(
|
||||
const Point& start,
|
||||
@@ -414,8 +318,15 @@ 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,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
);
|
||||
|
||||
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, const ExtrusionRole role, const std::function<std::string()>& insert_gcode);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
|
||||
//B41
|
||||
@@ -426,7 +337,7 @@ private:
|
||||
int unique_id;
|
||||
};
|
||||
std::unordered_map<const PrintInstance *, LabelData> m_label_data;
|
||||
std::string retract_and_wipe(bool toolchange = false);
|
||||
std::string retract_and_wipe(bool toolchange = false, bool reset_e = true);
|
||||
std::string unretract() { return m_writer.unretract(); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z);
|
||||
bool line_distancer_is_required(const std::vector<unsigned int>& extruder_ids);
|
||||
@@ -446,7 +357,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 +390,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 +410,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<AABBTreeLines::LinesDistancer<Linef>> 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 +423,15 @@ private:
|
||||
double m_last_mm3_per_mm;
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
Point m_last_pos;
|
||||
bool m_last_pos_defined;
|
||||
std::optional<Vec3d> m_previous_layer_last_position;
|
||||
std::optional<Vec3d> m_previous_layer_last_position_before_wipe;
|
||||
// This needs to be populated during the layer processing!
|
||||
std::optional<Vec3d> m_current_layer_first_position;
|
||||
std::optional<unsigned> m_layer_change_extruder_id;
|
||||
bool m_layer_change_used_external_mp{false};
|
||||
const Layer* m_layer_change_layer{nullptr};
|
||||
std::optional<Vec2d> m_layer_change_origin;
|
||||
bool m_already_unretracted{false};
|
||||
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
@@ -527,19 +445,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<const PrintObject*, Point> 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; }
|
||||
|
||||
@@ -1171,9 +1171,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons
|
||||
{
|
||||
// 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.
|
||||
bool use_external = m_use_external_mp || m_use_external_mp_once;
|
||||
bool use_external = m_use_external_mp || 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.
|
||||
|
||||
@@ -17,11 +17,10 @@ class AvoidCrossingPerimeters
|
||||
public:
|
||||
// Routing around the objects vs. inside a single object.
|
||||
void use_external_mp(bool use = true) { m_use_external_mp = use; };
|
||||
void use_external_mp_once() { m_use_external_mp_once = true; }
|
||||
bool used_external_mp_once() { return m_use_external_mp_once; }
|
||||
bool used_external_mp_once() { return use_external_mp_once; }
|
||||
void disable_once() { m_disabled_once = true; }
|
||||
bool disabled_once() const { return m_disabled_once; }
|
||||
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
|
||||
void reset_once_modifiers() { use_external_mp_once = false; m_disabled_once = false; }
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
@@ -50,10 +49,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// just for the next travel move
|
||||
bool use_external_mp_once { false };
|
||||
private:
|
||||
bool m_use_external_mp { false };
|
||||
// just for the next travel move
|
||||
bool m_use_external_mp_once { false };
|
||||
// this flag disables avoid_crossing_perimeters just for the next travel move
|
||||
// we enable it by default for the first travel move in print
|
||||
bool m_disabled_once { true };
|
||||
|
||||
@@ -143,6 +143,14 @@ std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attri
|
||||
float external_perim_reference_speed,
|
||||
float default_speed)
|
||||
{
|
||||
//w19
|
||||
bool is_overhang = attributes.overhang_attributes->start_distance_from_prev_layer >= 0.25 * attributes.width &&
|
||||
attributes.overhang_attributes->end_distance_from_prev_layer >= 0.25 * attributes.width;//&&
|
||||
//attributes.overhang_attributes->proximity_to_curled_lines > 0.05 ;
|
||||
|
||||
if (!is_overhang) {
|
||||
return {default_speed, 0};
|
||||
}
|
||||
assert(attributes.overhang_attributes.has_value());
|
||||
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
@@ -197,9 +205,11 @@ std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attri
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
//w19
|
||||
float curled_base_speed = interpolate_speed(speed_sections,
|
||||
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines/tan(67.5));
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
|
||||
|
||||
@@ -54,6 +54,9 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
||||
"HEIGHT:",
|
||||
"WIDTH:",
|
||||
"LAYER_CHANGE",
|
||||
"LAYER_CHANGE_TRAVEL",
|
||||
"LAYER_CHANGE_RETRACTION_START",
|
||||
"LAYER_CHANGE_RETRACTION_END",
|
||||
"COLOR_CHANGE",
|
||||
"PAUSE_PRINT",
|
||||
"CUSTOM_GCODE",
|
||||
@@ -3803,7 +3806,13 @@ void GCodeProcessor::post_process()
|
||||
struct LineData
|
||||
{
|
||||
std::string line;
|
||||
float time;
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> times{ 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
enum ETimeMode
|
||||
{
|
||||
Normal = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Normal),
|
||||
Stealth = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Stealth)
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -3833,10 +3842,10 @@ void GCodeProcessor::post_process()
|
||||
#endif // NDEBUG
|
||||
|
||||
EWriteType m_write_type{ EWriteType::BySize };
|
||||
// Time machine containing g1 times cache
|
||||
TimeMachine& m_machine;
|
||||
// Time machines containing g1 times cache
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
|
||||
// Current time
|
||||
float m_time{ 0.0f };
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{ 0.0f, 0.0f };
|
||||
// Current size in bytes
|
||||
size_t m_size{ 0 };
|
||||
|
||||
@@ -3852,11 +3861,12 @@ void GCodeProcessor::post_process()
|
||||
|
||||
bgcode::binarize::Binarizer& m_binarizer;
|
||||
public:
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type,
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#else
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#endif // NDEBUG
|
||||
|
||||
// return: number of internal G1 lines (from G2/G3 splitting) processed
|
||||
@@ -3873,9 +3883,9 @@ void GCodeProcessor::post_process()
|
||||
else
|
||||
return ret;
|
||||
|
||||
auto init_it = m_machine.g1_times_cache.begin() + m_times_cache_id;
|
||||
auto init_it = m_machines[Normal].g1_times_cache.begin() + m_times_cache_id;
|
||||
auto it = init_it;
|
||||
while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
}
|
||||
@@ -3885,7 +3895,7 @@ void GCodeProcessor::post_process()
|
||||
|
||||
// search for internal G1 lines
|
||||
if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) {
|
||||
while (it != m_machine.g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
++g1_lines_counter;
|
||||
@@ -3893,14 +3903,17 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
}
|
||||
|
||||
if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter)
|
||||
m_time = it->elapsed_time;
|
||||
if (it != m_machines[Normal].g1_times_cache.end() && it->id == g1_lines_counter) {
|
||||
m_times[Normal] = it->elapsed_time;
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
m_times[Stealth] = (m_machines[Stealth].g1_times_cache.begin() + std::distance(m_machines[Normal].g1_times_cache.begin(), it))->elapsed_time;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// add the given gcode line to the cache
|
||||
void append_line(const std::string& line) {
|
||||
m_lines.push_back({ line, m_time });
|
||||
m_lines.push_back({ line, m_times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3911,7 +3924,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
|
||||
// Insert the gcode lines required by the command cmd by backtracing into the cache
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd, std::function<std::string(unsigned int, float, float)> line_inserter,
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd,
|
||||
std::function<std::string(unsigned int, const std::vector<float>&)> line_inserter,
|
||||
std::function<std::string(const std::string&)> line_replacer) {
|
||||
assert(!m_lines.empty());
|
||||
const float time_step = backtrace.time_step();
|
||||
@@ -3919,13 +3933,13 @@ void GCodeProcessor::post_process()
|
||||
float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time
|
||||
for (unsigned int i = 0; i < backtrace.steps; ++i) {
|
||||
const float backtrace_time_i = (i + 1) * time_step;
|
||||
const float time_threshold_i = m_time - backtrace_time_i;
|
||||
const float time_threshold_i = m_times[Normal] - backtrace_time_i;
|
||||
auto rev_it = m_lines.rbegin() + rev_it_dist;
|
||||
auto start_rev_it = rev_it;
|
||||
|
||||
std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
|
||||
// backtrace into the cache to find the place where to insert the line
|
||||
while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
while (rev_it != m_lines.rend() && rev_it->times[Normal] > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
rev_it->line = line_replacer(rev_it->line);
|
||||
++rev_it;
|
||||
if (rev_it != m_lines.rend())
|
||||
@@ -3937,11 +3951,15 @@ void GCodeProcessor::post_process()
|
||||
break;
|
||||
|
||||
// insert the line for the current step
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->time != last_time_insertion) {
|
||||
last_time_insertion = rev_it->time;
|
||||
const std::string out_line = line_inserter(i + 1, last_time_insertion, m_time - last_time_insertion);
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) {
|
||||
last_time_insertion = rev_it->times[Normal];
|
||||
std::vector<float> time_diffs;
|
||||
time_diffs.push_back(m_times[Normal] - last_time_insertion);
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
time_diffs.push_back(m_times[Stealth] - rev_it->times[Stealth]);
|
||||
const std::string out_line = line_inserter(i + 1, time_diffs);
|
||||
rev_it_dist = std::distance(m_lines.rbegin(), rev_it) + 1;
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->time });
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(out_line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3967,7 +3985,7 @@ void GCodeProcessor::post_process()
|
||||
std::string out_string;
|
||||
if (!m_lines.empty()) {
|
||||
if (m_write_type == EWriteType::ByTime) {
|
||||
while (m_lines.front().time < m_time - backtrace_time) {
|
||||
while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
|
||||
const LineData& data = m_lines.front();
|
||||
out_string += data.line;
|
||||
m_size -= data.line.length();
|
||||
@@ -4052,7 +4070,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
};
|
||||
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize,
|
||||
m_time_processor.machines);
|
||||
|
||||
// replace placeholder lines with the proper final value
|
||||
// gcode_line is in/out parameter, to reduce expensive memory allocation
|
||||
@@ -4256,9 +4275,14 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
export_lines.insert_lines(backtrace, cmd,
|
||||
// line inserter
|
||||
[tool_number, this](unsigned int id, float time, float time_diff) {
|
||||
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n";
|
||||
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
|
||||
const int temperature = int(m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
std::string out = "M104.1 T" + std::to_string(tool_number);
|
||||
if (time_diffs.size() > 0)
|
||||
out += " P" + std::to_string(int(std::round(time_diffs[0])));
|
||||
if (time_diffs.size() > 1)
|
||||
out += " Q" + std::to_string(int(std::round(time_diffs[1])));
|
||||
out += " S" + std::to_string(temperature) + "\n";
|
||||
return out;
|
||||
},
|
||||
// line replacer
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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);
|
||||
@@ -187,6 +198,8 @@ public:
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec2d quantize(const Vec2f &pt)
|
||||
{ return { quantize(double(pt.x()), XYZF_EXPORT_DIGITS), quantize(double(pt.y()), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
@@ -224,7 +237,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "LabelObjects.hpp"
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
|
||||
#include "boost/algorithm/string/replace.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
@@ -39,10 +41,10 @@ Polygon instance_outline(const PrintInstance* pi)
|
||||
}; // anonymous namespace
|
||||
|
||||
|
||||
void LabelObjects::init(const Print& print)
|
||||
void LabelObjects::init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor)
|
||||
{
|
||||
m_label_objects_style = print.config().gcode_label_objects;
|
||||
m_flavor = print.config().gcode_flavor;
|
||||
m_label_objects_style = label_object_style;
|
||||
m_flavor = gcode_flavor;
|
||||
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return;
|
||||
@@ -51,7 +53,7 @@ void LabelObjects::init(const Print& print)
|
||||
|
||||
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
|
||||
// belong to the same ModelObject.
|
||||
for (const PrintObject* po : print.objects())
|
||||
for (const PrintObject* po : objects)
|
||||
for (const PrintInstance& pi : po->instances())
|
||||
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi);
|
||||
|
||||
@@ -81,18 +83,75 @@ 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; }, '_');
|
||||
}
|
||||
}
|
||||
|
||||
m_label_data.emplace(pi, LabelData{name, unique_id});
|
||||
// Now calculate the polygon and center for Cancel Object (this is not always used).
|
||||
Polygon outline = instance_outline(pi);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
char buffer[64];
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
std::string center_str(buffer);
|
||||
std::string polygon_str = std::string("[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
polygon_str += buffer;
|
||||
}
|
||||
polygon_str.pop_back();
|
||||
polygon_str += "]";
|
||||
|
||||
m_label_data.emplace_back(LabelData{pi, name, center_str, polygon_str, unique_id});
|
||||
++unique_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelObjects::update(const PrintInstance *instance) {
|
||||
if (this->last_operation_instance == instance) {
|
||||
return false;
|
||||
}
|
||||
this->last_operation_instance = instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_start_instance(GCodeWriter& writer) {
|
||||
if (current_instance == nullptr && last_operation_instance != nullptr) {
|
||||
current_instance = last_operation_instance;
|
||||
|
||||
std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)};
|
||||
result += writer.reset_e(true);
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_stop_instance() {
|
||||
if (current_instance != nullptr) {
|
||||
const std::string result{this->stop_object(*current_instance)};
|
||||
current_instance = nullptr;
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_change_instance(GCodeWriter& writer) {
|
||||
if (last_operation_instance != current_instance) {
|
||||
const std::string stop_instance_gcode{this->maybe_stop_instance()};
|
||||
// Be carefull with refactoring: this->maybe_stop_instance() + this->maybe_start_instance()
|
||||
// may not be evaluated in order. The order is indeed undefined!
|
||||
return stop_instance_gcode + this->maybe_start_instance(writer);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool LabelObjects::has_active_instance() {
|
||||
return this->current_instance != nullptr;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header() const
|
||||
{
|
||||
@@ -101,38 +160,34 @@ std::string LabelObjects::all_objects_header() const
|
||||
|
||||
std::string out;
|
||||
|
||||
// Let's sort the values according to unique_id so they are in the same order in which they were added.
|
||||
std::vector<std::pair<const PrintInstance*, LabelData>> label_data_sorted;
|
||||
for (const auto& pi_and_label : m_label_data)
|
||||
label_data_sorted.emplace_back(pi_and_label);
|
||||
std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; });
|
||||
|
||||
out += "\n";
|
||||
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;
|
||||
Polygon outline = instance_outline(print_instance);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
out += buffer + std::string(" POLYGON=[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
out += buffer;
|
||||
}
|
||||
out.pop_back();
|
||||
out += "]\n";
|
||||
} else {
|
||||
out += start_object(*print_instance, IncludeName::Yes);
|
||||
out += stop_object(*print_instance);
|
||||
for (const LabelData& label : m_label_data) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper)
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "' CENTER=" + label.center + " POLYGON=" + label.polygon + "\n";
|
||||
else {
|
||||
out += start_object(*label.pi, IncludeName::Yes);
|
||||
out += stop_object(*label.pi);
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header_singleline_json() const
|
||||
{
|
||||
std::string out;
|
||||
out = "{\"objects\":[";
|
||||
for (size_t i=0; i<m_label_data.size(); ++i) {
|
||||
const LabelData& label = m_label_data[i];
|
||||
out += std::string("{\"name\":\"") + label.name + "\",";
|
||||
out += "\"polygon\":" + label.polygon + "}";
|
||||
if (i != m_label_data.size() - 1)
|
||||
out += ",";
|
||||
}
|
||||
out += "]}";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const
|
||||
@@ -140,7 +195,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
@@ -154,7 +209,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.
|
||||
}
|
||||
@@ -169,7 +224,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
@@ -178,7 +233,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.
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define slic3r_GCode_LabelObjects_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -11,30 +13,49 @@ enum class LabelObjectsStyle;
|
||||
struct PrintInstance;
|
||||
class Print;
|
||||
|
||||
class GCodeWriter;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
|
||||
class LabelObjects {
|
||||
class LabelObjects
|
||||
{
|
||||
public:
|
||||
void init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor);
|
||||
std::string all_objects_header() const;
|
||||
std::string all_objects_header_singleline_json() const;
|
||||
|
||||
bool update(const PrintInstance *instance);
|
||||
|
||||
std::string maybe_start_instance(GCodeWriter& writer);
|
||||
|
||||
std::string maybe_stop_instance();
|
||||
|
||||
std::string maybe_change_instance(GCodeWriter& writer);
|
||||
|
||||
bool has_active_instance();
|
||||
|
||||
private:
|
||||
struct LabelData
|
||||
{
|
||||
const PrintInstance* pi;
|
||||
std::string name;
|
||||
std::string center;
|
||||
std::string polygon;
|
||||
int unique_id;
|
||||
};
|
||||
enum class IncludeName {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
void init(const Print& print);
|
||||
std::string all_objects_header() const;
|
||||
std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const;
|
||||
std::string stop_object(const PrintInstance& print_instance) const;
|
||||
|
||||
private:
|
||||
struct LabelData {
|
||||
std::string name;
|
||||
int unique_id;
|
||||
};
|
||||
const PrintInstance* current_instance{nullptr};
|
||||
const PrintInstance* last_operation_instance{nullptr};
|
||||
|
||||
LabelObjectsStyle m_label_objects_style;
|
||||
GCodeFlavor m_flavor;
|
||||
std::unordered_map<const PrintInstance*, LabelData> m_label_data;
|
||||
std::vector<LabelData> m_label_data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <memory.h>
|
||||
#include <cstring>
|
||||
#include <cfloat>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
@@ -27,6 +28,11 @@ static constexpr float max_segment_length = 5.f;
|
||||
// affect how distant will be propagated a flow rate adjustment.
|
||||
static constexpr int max_look_back_limit = 128;
|
||||
|
||||
// Max non-extruding XY distance (travel move) in mm between two continuous extrusions where we pretend
|
||||
// it's all one continuous extrusion line. Above this distance, we assume extruder pressure hits 0
|
||||
// This exists because often there are tiny travel moves between stuff like infill.
|
||||
// Lines where some extruder pressure will remain (so we should equalize between these small travels).
|
||||
static constexpr double max_ignored_gap_between_extruding_segments = 3.;
|
||||
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value)
|
||||
{
|
||||
// Preallocate some data, so that output_buffer.data() will return an empty string.
|
||||
@@ -59,8 +65,8 @@ PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_
|
||||
extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive;
|
||||
}
|
||||
|
||||
// Don't regulate the pressure before and after gap-fill and ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::GapFill, GCodeExtrusionRole::Ironing}) {
|
||||
// Don't regulate the pressure before and after ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::Ironing}) {
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].negative = 0;
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 0;
|
||||
}
|
||||
@@ -97,6 +103,72 @@ void PressureEqualizer::process_layer(const std::string &gcode)
|
||||
}
|
||||
assert(!this->opened_extrude_set_speed_block);
|
||||
}
|
||||
// At this point, we have an entire layer of gcode lines loaded into m_gcode_lines.
|
||||
// Now, we will split the mix of travels and extrusions into segments of continuous extrusions and process them.
|
||||
// We skip over large travels, and pretend that small ones are part of a continuous extrusion segment.
|
||||
for (auto current_extrusion_end_it = m_gcode_lines.cbegin(); current_extrusion_end_it != m_gcode_lines.cend();) {
|
||||
// Find beginning of next extrusion segment from current position.
|
||||
const auto current_extrusion_begin_it = std::find_if(current_extrusion_end_it, m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return line.extruding();
|
||||
});
|
||||
|
||||
// We start with extrusion length of zero.
|
||||
current_extrusion_end_it = current_extrusion_begin_it;
|
||||
|
||||
// Inner loop extends the extrusion segment over small travel moves.
|
||||
while (current_extrusion_end_it != m_gcode_lines.cend()) {
|
||||
// Find the end of the current extrusion segment.
|
||||
const auto travel_begin_it = std::find_if(std::next(current_extrusion_end_it), m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return !line.extruding();
|
||||
});
|
||||
|
||||
current_extrusion_end_it = std::prev(travel_begin_it);
|
||||
|
||||
const auto next_extrusion_segment_it = advance_segment_beyond_small_gap(current_extrusion_end_it);
|
||||
if (std::distance(current_extrusion_end_it, next_extrusion_segment_it) > 0) {
|
||||
// Extend the continuous line over the small gap.
|
||||
current_extrusion_end_it = next_extrusion_segment_it;
|
||||
continue; // Keep going, loop again to find the new end of extrusion segment.
|
||||
} else {
|
||||
break; // Gap to next extrude is too big, stop looking forward. We've found the end of this segment.
|
||||
}
|
||||
}
|
||||
|
||||
// Now, run the pressure equalizer across the segment like a streamroller.
|
||||
// It operates on a sliding window that moves forward across gcode line by line.
|
||||
const std::ptrdiff_t current_extrusion_begin_idx = std::distance(m_gcode_lines.cbegin(), current_extrusion_begin_it);
|
||||
for (auto current_line_it = current_extrusion_begin_it; current_line_it != current_extrusion_end_it; ++current_line_it) {
|
||||
const std::ptrdiff_t current_line_idx = std::distance(m_gcode_lines.cbegin(), current_line_it);
|
||||
|
||||
// Feed pressure equalizer past lines, going back to max_look_back_limit (or start of segment).
|
||||
const size_t start_idx = size_t(std::max<std::ptrdiff_t>(current_extrusion_begin_idx, current_line_idx - max_look_back_limit));
|
||||
adjust_volumetric_rate(start_idx, size_t(current_line_idx));
|
||||
}
|
||||
|
||||
// Current extrusion is all done processing so advance beyond it for the next loop.
|
||||
if (current_extrusion_end_it != m_gcode_lines.cend())
|
||||
++current_extrusion_end_it;
|
||||
}
|
||||
}
|
||||
|
||||
PressureEqualizer::GCodeLinesConstIt PressureEqualizer::advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const {
|
||||
// This should only be run on the last extruding line before a gap.
|
||||
assert(last_extruding_line_it != m_gcode_lines.cend() && last_extruding_line_it->extruding());
|
||||
double travel_distance = 0.;
|
||||
// Start at the beginning of a gap, advance till extrusion found or gap too big.
|
||||
for (auto current_line_it = std::next(last_extruding_line_it); current_line_it != m_gcode_lines.cend(); ++current_line_it) {
|
||||
// Started extruding again! Return segment extension.
|
||||
if (current_line_it->extruding())
|
||||
return current_line_it;
|
||||
|
||||
travel_distance += current_line_it->dist_xy();
|
||||
// Gap too big, don't extend segment.
|
||||
if (travel_distance > max_ignored_gap_between_extruding_segments)
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
// Looped until the end of the layer and couldn't extend extrusion.
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
LayerResult PressureEqualizer::process_layer(LayerResult &&input)
|
||||
@@ -391,7 +463,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
|
||||
buf.extruder_id = m_current_extruder;
|
||||
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
|
||||
|
||||
adjust_volumetric_rate();
|
||||
#ifdef PRESSURE_EQUALIZER_DEBUG
|
||||
++line_idx;
|
||||
#endif
|
||||
@@ -506,16 +577,14 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
|
||||
}
|
||||
}
|
||||
|
||||
void PressureEqualizer::adjust_volumetric_rate()
|
||||
void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, const size_t last_line_idx)
|
||||
{
|
||||
if (m_gcode_lines.size() < 2)
|
||||
// Don't bother adjusting volumetric rate if there's no gcode to adjust.
|
||||
if (last_line_idx <= first_line_idx || last_line_idx - first_line_idx < 2)
|
||||
return;
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
size_t fist_line_idx = size_t(std::max<int>(0, int(m_gcode_lines.size()) - max_look_back_limit));
|
||||
const size_t last_line_idx = m_gcode_lines.size() - 1;
|
||||
size_t line_idx = last_line_idx;
|
||||
if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
// Nothing to do, the last move is not extruding.
|
||||
return;
|
||||
|
||||
@@ -523,13 +592,13 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
|
||||
feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
|
||||
|
||||
while (line_idx != fist_line_idx) {
|
||||
while (line_idx != first_line_idx) {
|
||||
size_t idx_prev = line_idx - 1;
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev);
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != first_line_idx; --idx_prev);
|
||||
if (!m_gcode_lines[idx_prev].extruding())
|
||||
break;
|
||||
// Don't decelerate before ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't decelerate before ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_prev;
|
||||
continue;
|
||||
}
|
||||
@@ -549,7 +618,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
// Limit by the succeeding volumetric flow rate.
|
||||
rate_end = rate_succ;
|
||||
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_end = line.volumetric_extrusion_rate_end;
|
||||
} else if (line.volumetric_extrusion_rate_end > rate_end) {
|
||||
line.volumetric_extrusion_rate_end = rate_end;
|
||||
@@ -571,9 +641,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start;
|
||||
}
|
||||
}
|
||||
@@ -587,8 +656,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next);
|
||||
if (!m_gcode_lines[idx_next].extruding())
|
||||
break;
|
||||
// Don't accelerate after ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't accelerate after ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_next;
|
||||
continue;
|
||||
}
|
||||
@@ -603,7 +672,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited.
|
||||
|
||||
float rate_start = feedrate_per_extrusion_role[iRole];
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_start = line.volumetric_extrusion_rate_start;
|
||||
} else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start)
|
||||
rate_start = rate_prec;
|
||||
@@ -627,9 +697,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,8 @@ private:
|
||||
bool extrude_end_tag = false;
|
||||
};
|
||||
|
||||
using GCodeLines = std::vector<GCodeLine>;
|
||||
using GCodeLinesConstIt = GCodeLines::const_iterator;
|
||||
// Output buffer will only grow. It will not be reallocated over and over.
|
||||
std::vector<char> output_buffer;
|
||||
size_t output_buffer_length;
|
||||
@@ -182,9 +184,10 @@ private:
|
||||
bool process_line(const char *line, const char *line_end, GCodeLine &buf);
|
||||
void output_gcode_line(size_t line_idx);
|
||||
|
||||
GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const;
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx);
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
inline void push_to_output(GCodeG1Formatter &formatter);
|
||||
|
||||
@@ -133,6 +133,11 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr
|
||||
return distance;
|
||||
}
|
||||
|
||||
void reverse(SmoothPath &path) {
|
||||
std::reverse(path.begin(), path.end());
|
||||
for (SmoothPathElement &path_element : path)
|
||||
Geometry::ArcWelder::reverse(path_element.path);
|
||||
}
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
|
||||
@@ -33,6 +33,7 @@ std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &pa
|
||||
// rather discard such a degenerate segment.
|
||||
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold);
|
||||
|
||||
void reverse(SmoothPath &path);
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
static AABBTreeLines::LinesDistancer<Linef> get_layer_distancer(const std::vector<Vec2f> &layer_points)
|
||||
{
|
||||
Linesf lines;
|
||||
for (size_t idx = 1; idx < layer_points.size(); ++idx)
|
||||
lines.emplace_back(layer_points[idx - 1].cast<double>(), layer_points[idx].cast<double>());
|
||||
|
||||
return AABBTreeLines::LinesDistancer{std::move(lines)};
|
||||
}
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
|
||||
{
|
||||
/* This post-processor relies on several assumptions:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
@@ -22,8 +33,8 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
}
|
||||
|
||||
// Get total XY length for this layer by summing all extrusion moves.
|
||||
float total_layer_length = 0;
|
||||
float layer_height = 0;
|
||||
float total_layer_length = 0.f;
|
||||
float layer_height = 0.f;
|
||||
float z = 0.f;
|
||||
|
||||
{
|
||||
@@ -49,15 +60,21 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
const bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
const bool smooth_spiral = m_smooth_spiral && m_config.use_relative_e_distances.value;
|
||||
|
||||
const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer);
|
||||
Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back();
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
std::string new_gcode, transition_gcode;
|
||||
std::vector<Vec2f> current_layer;
|
||||
m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing,
|
||||
&len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
@@ -66,16 +83,52 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position.
|
||||
if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
|
||||
len += dist_XY;
|
||||
const float factor = len / total_layer_length;
|
||||
if (transition_in)
|
||||
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.e() * factor, 5);
|
||||
else if (transition_out) {
|
||||
// We want the last layer to ramp down extrusion, but without changing z height!
|
||||
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
|
||||
// We add this new layer at the very end
|
||||
GCodeReader::GCodeLine transition_line(line);
|
||||
transition_line.set(reader, E, line.e() * (1.f - factor), 5);
|
||||
transition_gcode += transition_line.raw() + '\n';
|
||||
}
|
||||
|
||||
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
|
||||
line.set(reader, Z, z + factor * layer_height);
|
||||
|
||||
bool emit_gcode_line = true;
|
||||
if (smooth_spiral) {
|
||||
// Now we also need to try to interpolate X and Y
|
||||
Vec2f p(line.x(), line.y()); // Get current x/y coordinates
|
||||
current_layer.emplace_back(p); // Store that point for later use on the next layer
|
||||
|
||||
auto [nearest_distance, idx, nearest_pt] = previous_layer_distancer.distance_from_lines_extra<false>(p.cast<double>());
|
||||
if (nearest_distance < max_xy_smoothing) {
|
||||
// Interpolate between the point on this layer and the point on the previous layer
|
||||
Vec2f target = nearest_pt.cast<float>() * (1.f - factor) + p * factor;
|
||||
|
||||
// We will emit a new g-code line only when XYZ positions differ from the previous g-code line.
|
||||
emit_gcode_line = GCodeFormatter::quantize(last_point) != GCodeFormatter::quantize(target);
|
||||
|
||||
line.set(reader, X, target.x());
|
||||
line.set(reader, Y, target.y());
|
||||
// We need to figure out the distance of this new line!
|
||||
float modified_dist_XY = (last_point - target).norm();
|
||||
// Scale the extrusion amount according to change in length
|
||||
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5);
|
||||
last_point = target;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
len += dist_XY;
|
||||
line.set(reader, Z, z + len * layer_height_factor);
|
||||
if (transition && line.has(E))
|
||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
||||
last_point = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (emit_gcode_line)
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
@@ -84,14 +137,18 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
cause a visible seam when loops are not aligned in XY; by skipping
|
||||
it we blend the first loop move in the XY plane (although the smoothness
|
||||
of such blend depend on how long the first segment is; maybe we should
|
||||
enforce some minimum length?). */
|
||||
enforce some minimum length?).
|
||||
When smooth_spiral is enabled, we're gonna end up exactly where the next layer should
|
||||
start anyway, so we don't need the travel move */
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
if (transition_out)
|
||||
transition_gcode += line.raw() + '\n';
|
||||
});
|
||||
|
||||
return new_gcode;
|
||||
m_previous_layer = std::move(current_layer);
|
||||
return new_gcode + transition_gcode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,28 +6,38 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
class SpiralVase
|
||||
{
|
||||
public:
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
SpiralVase() = delete;
|
||||
|
||||
explicit SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.apply_config(m_config);
|
||||
const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end());
|
||||
m_max_xy_smoothing = float(2. * max_nozzle_diameter);
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
m_transition_layer = en && ! m_enabled;
|
||||
m_enabled = en;
|
||||
void enable(bool enable)
|
||||
{
|
||||
m_transition_layer = enable && !m_enabled;
|
||||
m_enabled = enable;
|
||||
}
|
||||
|
||||
std::string process_layer(const std::string &gcode);
|
||||
std::string process_layer(const std::string &gcode, bool last_layer);
|
||||
|
||||
private:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
float m_max_xy_smoothing = 0.f;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
bool m_transition_layer = false;
|
||||
// Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes
|
||||
bool m_smooth_spiral = true;
|
||||
std::vector<Vec2f> m_previous_layer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb,
|
||||
int count = 0;
|
||||
for (const auto& [format, size] : thumbnails_list) {
|
||||
static constexpr const size_t max_row_length = 78;
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{{size}, true, false, false, true});
|
||||
//B54
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{{size}, true, false, false, false});
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
if (data.is_valid()) {
|
||||
switch (format) {
|
||||
|
||||
@@ -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<std::pair<double, unsigned int>>());
|
||||
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>(), std::vector<std::pair<double, unsigned int>>());
|
||||
|
||||
// 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<std::pair<double, unsigned int>> 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<std::pair<double, unsigned int>> 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<coordf_t> &zs)
|
||||
}
|
||||
|
||||
// Collect extruders reuqired to print layers.
|
||||
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
|
||||
{
|
||||
void ToolOrdering::collect_extruders(
|
||||
const PrintObject &object,
|
||||
const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches,
|
||||
const std::vector<std::pair<double, unsigned int>> &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<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
|
||||
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
|
||||
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin();
|
||||
unsigned int extruder_override = 0;
|
||||
|
||||
std::vector<std::pair<double, unsigned int>>::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();
|
||||
|
||||
@@ -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<coordf_t> &zs);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches, const std::vector<std::pair<double, unsigned int>> &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();
|
||||
|
||||
444
src/libslic3r/GCode/Travels.cpp
Normal file
@@ -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<const ExtrusionPath *>(&e_entity)) {
|
||||
return to_lines(path->as_polyline());
|
||||
} else if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(&e_entity)) {
|
||||
return to_lines(multipath->as_polyline());
|
||||
} else if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(&e_entity)) {
|
||||
return to_lines(loop->polygon());
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Invalid argument supplied to TODO()");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> get_previous_layer_distancer(
|
||||
const GCodeGenerator::ObjectsLayerToPrint &objects_to_print, const ExPolygons &slices
|
||||
) {
|
||||
std::vector<ObjectOrExtrusionLinef> 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<AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef>, size_t> get_current_layer_distancer(const ObjectsLayerToPrint &objects_to_print)
|
||||
{
|
||||
std::vector<ObjectOrExtrusionLinef> 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<const ExtrusionEntityCollection *>(layerm.perimeters().entities[perimeter_id]));
|
||||
const auto *eec = static_cast<const ExtrusionEntityCollection *>(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<const Point> 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<DistancedPoint> slice_xy_path(
|
||||
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
|
||||
) {
|
||||
assert(xy_path.size() >= 2);
|
||||
std::vector<DistancedPoint> 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<const Point> xy_path,
|
||||
const std::vector<double> &ensure_points_at_distances,
|
||||
const double initial_elevation,
|
||||
const std::function<double(double)> &elevation
|
||||
) {
|
||||
Points3 result{};
|
||||
|
||||
std::vector<DistancedPoint> 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<const Line> xy_path,
|
||||
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
|
||||
const ObjectsLayerToPrint &objects_to_print,
|
||||
const std::function<bool(const ObjectOrExtrusionLinef &)> &predicate,
|
||||
const bool ignore_starting_object_intersection
|
||||
) {
|
||||
assert(!xy_path.empty());
|
||||
if (xy_path.empty())
|
||||
return std::numeric_limits<double>::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<std::pair<Vec2d, size_t>> intersections = distancer.intersections_with_line<true>(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<double>::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<double> linspace(const double from, const double to, const unsigned count) {
|
||||
if (count == 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<double> 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<double> 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
|
||||
240
src/libslic3r/GCode/Travels.hpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Utility functions for travel gcode generation.
|
||||
*/
|
||||
|
||||
#ifndef slic3r_GCode_Travels_hpp_
|
||||
#define slic3r_GCode_Travels_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <tcbspan/span.hpp>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/math/special_functions/pow.hpp>
|
||||
|
||||
#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<ObjectLayerToPrint>;
|
||||
|
||||
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<const ExtrusionEntity *>{}(eee.extrusion_entity);
|
||||
boost::hash_combine(seed, std::hash<int>{}(eee.object_layer_idx));
|
||||
boost::hash_combine(seed, std::hash<int>{}(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<ObjectOrExtrusionLinef> &previous_layer_distancer() const { return m_previous_layer_distancer; }
|
||||
|
||||
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> ¤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<ObjectOrExtrusionLinef> m_previous_layer_distancer;
|
||||
|
||||
AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> m_current_layer_distancer;
|
||||
std::unordered_set<ExtrudedExtrusionEntity, ExtrudedExtrusionEntityHash> 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<double> distances{0.5, 1.5};
|
||||
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
|
||||
* // produces
|
||||
* {{0, 0}, {0, 0.5}, {1, 0}}
|
||||
* // notice that 1.5 is omitted
|
||||
* @endcode
|
||||
*/
|
||||
std::vector<DistancedPoint> slice_xy_path(
|
||||
tcb::span<const Point> xy_path, tcb::span<const double> 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<double> 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<const Point> 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<const Point> xy_path,
|
||||
const std::vector<double> &ensure_points_at_distances,
|
||||
const double initial_elevation,
|
||||
const std::function<double(double)> &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<const Line> xy_path,
|
||||
const AABBTreeLines::LinesDistancer<ObjectOrExtrusionLinef> &distancer,
|
||||
const ObjectsLayerToPrint &objects_to_print = {},
|
||||
const std::function<bool(const ObjectOrExtrusionLinef &)> &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_
|
||||
@@ -21,9 +21,9 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &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)
|
||||
@@ -32,30 +32,14 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
void Wipe::set_path(SmoothPath &&path) {
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (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) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max);
|
||||
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);
|
||||
@@ -67,7 +51,6 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
@@ -185,7 +168,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 +199,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void set_path(SmoothPath &&path);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
static float volume_to_length(float volume, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
static float length_to_volume(float length, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)));
|
||||
}
|
||||
class WipeTowerWriter
|
||||
{
|
||||
public:
|
||||
@@ -102,6 +112,10 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
WipeTowerWriter& switch_filament_monitoring(bool enable) {
|
||||
m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n";
|
||||
return *this;
|
||||
}
|
||||
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
|
||||
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
|
||||
// is asked to suppres output of some lines, which look like extrusions.
|
||||
@@ -284,6 +298,24 @@ public:
|
||||
return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false);
|
||||
}
|
||||
|
||||
// Loads filament while also moving towards given point in x-axis. Unlike the previous function, this one respects
|
||||
// both the loading_speed and x_speed. Can shorten the move.
|
||||
WipeTowerWriter& load_move_x_advanced_there_and_back(float farthest_x, float e_dist, float e_speed, float x_speed)
|
||||
{
|
||||
float old_x = x();
|
||||
float time = std::abs(e_dist / e_speed); // time that the whole move must take
|
||||
float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel
|
||||
float x_dist = x_speed * time; // totel x-distance to travel during the move
|
||||
int n = int(x_dist / (2*x_max_dist) + 1.f); // how many there and back moves should we do
|
||||
float r = 2*n*x_max_dist / x_dist; // actual/required dist if the move is not shortened
|
||||
|
||||
float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_max_dist / r;
|
||||
for (int i=0; i<n; ++i) {
|
||||
extrude_explicit(end_point, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
extrude_explicit(old_x, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// Elevate the extruder head above the current print_z position.
|
||||
WipeTowerWriter& z_hop(float hop, float f = 0.f)
|
||||
{
|
||||
@@ -326,6 +358,7 @@ public:
|
||||
// Set extruder temperature, don't wait by default.
|
||||
WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false)
|
||||
{
|
||||
m_gcode += "G4 S0\n"; // to flush planner queue
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
return *this;
|
||||
}
|
||||
@@ -523,14 +556,15 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)),
|
||||
m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)),
|
||||
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)),
|
||||
m_extra_spacing(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_extra_flow(float(config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_y_shift(0.f),
|
||||
m_z_pos(0.f),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
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),
|
||||
@@ -561,6 +595,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_set_extruder_trimpot = config.high_current_on_filament_swap;
|
||||
}
|
||||
|
||||
m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU");
|
||||
// Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
|
||||
const std::vector<Vec2d>& bed_points = config.bed_shape.values;
|
||||
BoundingBoxf bb(bed_points);
|
||||
@@ -595,6 +630,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_extruder - 1));
|
||||
m_filpar[idx].temperature = config.temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
@@ -607,6 +643,8 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx);
|
||||
m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx));
|
||||
m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx));
|
||||
}
|
||||
|
||||
m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point
|
||||
@@ -720,7 +758,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f);
|
||||
box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
cleaning_box.translate(prime_section_width, 0.f);
|
||||
writer.travel(cleaning_box.ld, 7200);
|
||||
}
|
||||
@@ -767,7 +805,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
for (const auto &b : m_layer_info->tool_changes)
|
||||
if ( b.new_tool == tool ) {
|
||||
wipe_volume = b.wipe_volume;
|
||||
wipe_area = b.required_depth * m_layer_info->extra_spacing;
|
||||
wipe_area = b.required_depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -808,14 +846,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||
is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
|
||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||
(is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature));
|
||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
|
||||
toolchange_Load(writer, cleaning_box);
|
||||
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
++ m_num_tool_changes;
|
||||
} else
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature);
|
||||
|
||||
m_depth_traversed += wipe_area;
|
||||
|
||||
@@ -842,13 +881,14 @@ void WipeTower::toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature)
|
||||
{
|
||||
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
|
||||
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
|
||||
|
||||
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm
|
||||
|
||||
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
|
||||
|
||||
@@ -861,10 +901,14 @@ void WipeTower::toolchange_Unload(
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming;
|
||||
const bool cold_ramming = m_is_mk4mmu3;
|
||||
|
||||
if (do_ramming) {
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
if (! m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
if (cold_ramming)
|
||||
writer.set_extruder_temp(old_temperature - 20);
|
||||
}
|
||||
else
|
||||
writer.set_position(ramming_start_pos);
|
||||
@@ -885,7 +929,7 @@ void WipeTower::toolchange_Unload(
|
||||
if (tch.old_tool == m_current_tool) {
|
||||
sum_of_depths += tch.ramming_depth;
|
||||
float ramming_end_y = sum_of_depths;
|
||||
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
|
||||
if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
|
||||
(m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
|
||||
@@ -899,6 +943,10 @@ void WipeTower::toolchange_Unload(
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_mk4mmu3) {
|
||||
writer.switch_filament_monitoring(false);
|
||||
writer.wait(1.5f);
|
||||
}
|
||||
|
||||
// now the ramming itself:
|
||||
while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||
@@ -939,31 +987,66 @@ void WipeTower::toolchange_Unload(
|
||||
.retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
|
||||
.resume_preview();
|
||||
}
|
||||
const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
const bool cooling_will_happen = m_semm && number_of_cooling_moves > 0;
|
||||
bool change_temp_later = false;
|
||||
// Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should
|
||||
// be already set and there is no need to change anything. Also, the temperature could be changed
|
||||
// for wrong extruder.
|
||||
if (m_semm) {
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait.
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer() || cold_ramming) ) { // Set the extruder temperature, but don't wait.
|
||||
// If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
|
||||
// However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
|
||||
if (cold_ramming && cooling_will_happen)
|
||||
change_temp_later = true;
|
||||
else
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
m_old_temperature = new_temperature;
|
||||
}
|
||||
}
|
||||
|
||||
// Cooling:
|
||||
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
if (m_semm && number_of_moves > 0) {
|
||||
if (cooling_will_happen) {
|
||||
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
|
||||
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
||||
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_cooling_moves - 1.f);
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
|
||||
writer.suppress_preview()
|
||||
.travel(writer.x(), writer.y() + y_step);
|
||||
old_x = writer.x();
|
||||
turning_point = xr-old_x > old_x-xl ? xr : xl;
|
||||
for (int i=0; i<number_of_moves; ++i) {
|
||||
float stamping_dist_e = m_filpar[m_current_tool].filament_stamping_distance + m_cooling_tube_length / 2.f;
|
||||
|
||||
for (int i=0; i<number_of_cooling_moves; ++i) {
|
||||
|
||||
// Stamping - happens after every cooling move except for the last one.
|
||||
if (i>0 && m_filpar[m_current_tool].filament_stamping_distance != 0) {
|
||||
|
||||
// Stamping turning point shall be no farther than 20mm from the current nozzle position:
|
||||
float stamping_turning_point = std::clamp(old_x + 20.f * (turning_point - old_x > 0.f ? 1.f : -1.f), xl, xr);
|
||||
|
||||
// Only last 5mm will be done with the fast x travel. The point is to spread possible blobs
|
||||
// along the whole wipe tower.
|
||||
if (stamping_dist_e > 5) {
|
||||
float cent = writer.x();
|
||||
writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), m_filpar[m_current_tool].filament_stamping_loading_speed, 200);
|
||||
writer.load_move_x_advanced(cent, 5, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
writer.travel(cent, writer.y());
|
||||
} else
|
||||
writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
|
||||
// Retract while the print head is stationary, so if there is a blob, it is not dragged along.
|
||||
writer.retract(stamping_dist_e, m_filpar[m_current_tool].unloading_speed * 60.f);
|
||||
}
|
||||
|
||||
if (i == number_of_cooling_moves - 1 && change_temp_later) {
|
||||
// If cold_ramming, the temperature change should be done before the last cooling move.
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
}
|
||||
float speed = initial_speed + speed_inc * 2*i;
|
||||
writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
|
||||
speed += speed_inc;
|
||||
@@ -980,7 +1063,7 @@ void WipeTower::toolchange_Unload(
|
||||
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
@@ -1005,6 +1088,8 @@ void WipeTower::toolchange_Change(
|
||||
//writer.append("[end_filament_gcode]\n");
|
||||
writer.append("[toolchange_gcode_from_wipe_tower_generator]\n");
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.switch_filament_monitoring(true);
|
||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
||||
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
|
||||
@@ -1065,20 +1150,23 @@ void WipeTower::toolchange_Wipe(
|
||||
const float& xl = cleaning_box.ld.x();
|
||||
const float& xr = cleaning_box.rd.x();
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||
const float line_width = m_perimeter_width * m_extra_flow;
|
||||
writer.change_analyzer_line_width(line_width);
|
||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||
// the ordered volume, even if it means violating the box. This can later be removed and simply
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f);
|
||||
float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer.
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
|
||||
// if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) {
|
||||
writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy);
|
||||
// if there is less than 2.5*line_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) {
|
||||
writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1093,21 +1181,21 @@ void WipeTower::toolchange_Wipe(
|
||||
|
||||
float traversed_x = writer.x();
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*m_perimeter_width)
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||
break; // in case next line would not fit
|
||||
|
||||
traversed_x -= writer.x();
|
||||
x_to_wipe -= std::abs(traversed_x);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200);
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
// stepping to the next line:
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*m_perimeter_width, writer.y() + dy);
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*line_width, writer.y() + dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1121,6 +1209,7 @@ void WipeTower::toolchange_Wipe(
|
||||
m_left_to_right = !m_left_to_right;
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
|
||||
writer.change_analyzer_line_width(m_perimeter_width);
|
||||
}
|
||||
|
||||
|
||||
@@ -1400,9 +1489,19 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
// Extract purging volumes for each extruder pair:
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (size_t i = 0; i<number_of_extruders; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
|
||||
|
||||
// For SEMM printers, the project can be configured to use defaults from configuration,
|
||||
// in which case the custom matrix shall be ignored. We will overwrite the values.
|
||||
if (config.single_extruder_multi_material && ! config.wiping_volumes_use_custom_matrix) {
|
||||
for (size_t i = 0; i < number_of_extruders; ++i) {
|
||||
for (size_t j = 0; j < number_of_extruders; ++j) {
|
||||
if (i != j)
|
||||
wipe_volumes[i][j] = (i == j ? 0.f : config.multimaterial_purging.value * config.filament_purge_multiplier.get_at(j) / 100.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
||||
@@ -1411,6 +1510,13 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
return wipe_volumes;
|
||||
}
|
||||
|
||||
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
|
||||
{
|
||||
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
|
||||
return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing;
|
||||
}
|
||||
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
|
||||
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
|
||||
unsigned int new_tool, float wipe_volume)
|
||||
@@ -1427,22 +1533,17 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float depth = 0.f;
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator);
|
||||
float ramming_depth = depth;
|
||||
length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
|
||||
float first_wipe_line = -length_to_extrude;
|
||||
length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
float ramming_depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
|
||||
depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
|
||||
depth *= m_extra_spacing;
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
}
|
||||
|
||||
|
||||
@@ -1492,14 +1593,14 @@ void WipeTower::save_on_last_wipe()
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
float length_to_save = finish_layer().total_extrusion_length_in_plane();
|
||||
float length_to_wipe = volume_to_length(toolchange.wipe_volume,
|
||||
m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save;
|
||||
|
||||
length_to_wipe = std::max(length_to_wipe,0.f);
|
||||
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) );
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
toolchange.required_depth = (toolchange.ramming_depth + depth_to_wipe) * m_extra_spacing;
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1560,8 +1661,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
}
|
||||
}
|
||||
|
||||
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 +1706,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
|
||||
#ifndef slic3r_GCode_WipeTower_hpp_
|
||||
#define slic3r_GCode_WipeTower_hpp_
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -219,7 +218,7 @@ public:
|
||||
return m_current_layer_finished;
|
||||
}
|
||||
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
std::vector<std::pair<float, std::vector<float>>> 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 {
|
||||
@@ -232,6 +231,8 @@ public:
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
float filament_stamping_loading_speed = 0.f;
|
||||
float filament_stamping_distance = 0.f;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
@@ -243,6 +244,7 @@ public:
|
||||
float filament_area;
|
||||
bool multitool_ramming;
|
||||
float multitool_ramming_time = 0.f;
|
||||
float filament_minimal_purge_on_wipe_tower = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -260,6 +262,7 @@ private:
|
||||
|
||||
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_is_mk4mmu3 = false;
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -275,7 +278,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;
|
||||
@@ -310,8 +312,6 @@ private:
|
||||
// State of the wipe tower generator.
|
||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
|
||||
bool m_print_brim = true;
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
@@ -320,7 +320,9 @@ private:
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_extra_flow = 1.f;
|
||||
float m_extra_spacing_wipe = 1.f;
|
||||
float m_extra_spacing_ramming = 1.f;
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
@@ -332,16 +334,10 @@ private:
|
||||
return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area();
|
||||
}
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
@@ -356,19 +352,19 @@ private:
|
||||
float ramming_depth;
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_volume_total;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv}, wipe_volume_total{wv} {}
|
||||
};
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
WipeTowerInfo(float z_par, float layer_height_par)
|
||||
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
|
||||
: z{z_par}, height{layer_height_par}, depth{0} {}
|
||||
};
|
||||
|
||||
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
|
||||
@@ -380,6 +376,7 @@ private:
|
||||
|
||||
// Stores information about used filament length per extruder:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> 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.
|
||||
@@ -390,6 +387,7 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature);
|
||||
|
||||
void toolchange_Change(
|
||||
@@ -412,4 +410,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerQIDIMM_hpp_
|
||||
#endif // slic3r_GCode_WipeTower_hpp_
|
||||
|
||||
@@ -57,12 +57,24 @@ 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.m_label_objects.maybe_stop_instance();
|
||||
gcode += gcodegen.retract_and_wipe();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
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, [](){return "";}
|
||||
);
|
||||
} 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, ExtrusionRole::Mixed, [](){return "";});
|
||||
}
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
@@ -81,10 +93,14 @@ 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)
|
||||
if (gcodegen.config().wipe_tower) {
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z");
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
position.z() = z;
|
||||
gcodegen.writer().update_position(position);
|
||||
deretraction_str += gcodegen.unretract();
|
||||
|
||||
}
|
||||
}
|
||||
assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n');
|
||||
assert(deretraction_str.empty() || deretraction_str.back() == '\n');
|
||||
@@ -94,11 +110,14 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
|
||||
std::string tcr_gcode;
|
||||
unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode);
|
||||
if (gcodegen.config().default_acceleration > 0)
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().wipe_tower_acceleration.value));
|
||||
gcode += tcr_gcode;
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().default_acceleration.value));
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
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.");
|
||||
@@ -119,7 +138,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
return gcode;
|
||||
}
|
||||
|
||||
@@ -245,10 +264,11 @@ std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extr
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
const double purge_z{m_final_purge.print_z + gcodegen.config().z_offset.value};
|
||||
if (std::abs(gcodegen.writer().get_position().z() - purge_z) > EPSILON)
|
||||
gcode += gcodegen.generate_travel_gcode(
|
||||
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
|
||||
"move to safe place for purging"
|
||||
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(purge_z)}},
|
||||
"move to safe place for purging", [](){return "";}
|
||||
);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
|
||||