Compare commits

..

69 Commits

Author SHA1 Message Date
sunsets
d884ef371d version 1.1.3 2024-04-05 20:04:00 +08:00
QIDI TECH
2f86351eab Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-04-05 16:36:13 +08:00
QIDI TECH
eeeb2a5437 Add Q1 2024-04-05 16:36:07 +08:00
sunsets
8153dcc1b1 Update MsgDialog.cpp 2024-04-05 15:11:47 +08:00
sunsets
65a85dae84 Update PrintHostDialogs.cpp 2024-04-05 09:47:57 +08:00
sunsets
2b269ea194 Update .clang-format 2024-04-02 14:10:58 +08:00
Wang YB
63f899f4a5 fix island 2024-04-01 20:03:38 +08:00
sunsets
4d6feb71b9 icons 2024-04-01 17:00:05 +08:00
sunsets
d783651751 Optimized the interface for sending files 2024-04-01 16:47:52 +08:00
sunsets
9b04886c3a Fixed a bug where the device interface failed to render by pressing alt button 2024-04-01 15:16:47 +08:00
sunsets
3a99562743 Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-04-01 15:10:34 +08:00
sunsets
5183107d79 Fixed a bug that would cause sending to fail 2024-04-01 15:10:24 +08:00
QIDI TECH
8c672c53c7 Add wipe device 2024-04-01 15:06:28 +08:00
sunsets
3039c76417 Update Preset.cpp 2024-03-30 16:36:21 +08:00
Wang YB
f33a08f704 update top_one_wall & top_gap_fills 2024-03-30 15:14:10 +08:00
sunsets
5ccb55ff98 Prusa 2.7.3 2024-03-30 10:22:25 +08:00
sunsets
764ce01063 Update PrinterWebView.cpp 2024-03-28 15:57:09 +08:00
sunsets
58e2343a2e Fixed a bug where the device list button could not be added properly when certain language characters were included 2024-03-28 13:51:08 +08:00
sunsets
6485825ad8 Changing the physical printer ip and host_type can immediately change the button in the devicelist 2024-03-27 16:08:25 +08:00
sunsets
2387bc9cdb Prusa 2.7.2 2024-03-27 14:38:03 +08:00
sunsets
63daf0c087 Fixed a bug where calibration part of the parameters were read incorrectly 2024-03-22 13:52:42 +08:00
sunsets
516d3a3313 QIDI Prusa 2024-03-21 11:29:09 +08:00
QIDI TECH
470b3a19ed Update AboutDialog.cpp 2024-03-21 11:15:06 +08:00
sunsets
759c6732b8 print_host 2024-03-20 11:16:37 +08:00
sunsets
c31585e5ba print_host 2024-03-20 11:11:53 +08:00
sunsets
8cca851e84 print_host 2024-03-20 10:29:12 +08:00
sunsets
00718edfa7 print_host 2024-03-20 10:08:22 +08:00
QIDI TECH
f82a8a4ca1 Update README.md 2024-03-19 15:15:09 +08:00
sunsets
4407c9eb62 V1.1.2 2024-03-19 11:25:07 +08:00
sunsets
74f7fce027 Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-03-19 10:37:05 +08:00
sunsets
354224679e Fixed a bug where retract override failed 2024-03-19 10:37:02 +08:00
QIDI TECH
92119cf9f5 Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-03-18 14:23:42 +08:00
sunsets
0401c46f99 reset exclude_area 2024-03-18 14:11:10 +08:00
sunsets
9d43e47a54 Update GUI_App.cpp 2024-03-18 10:41:14 +08:00
sunsets
354ae2e282 reupload 2024-03-18 10:16:46 +08:00
sunsets
4ffa594874 reupload 2024-03-18 10:16:03 +08:00
sunsets
e795f88c09 Linux 2024-03-18 10:11:37 +08:00
QIDI TECH
e7028531e0 Add Q1 Pro 2024-03-18 09:41:25 +08:00
Wang YB
0d1dd29341 update gap infill 2024-03-16 10:51:48 +08:00
sunsets
c56cfdb727 Fix a bug in exclude_object 2024-03-15 13:43:09 +08:00
QIDI TECH
bfc036deae Update Max Volumetric Speed 2024-03-14 15:47:02 +08:00
Wang YB
87ab8bd8dd update gapinfill 2024-03-12 09:40:32 +08:00
sunsets
ce3ad490b7 thumb_transparent_background 2024-03-09 13:56:03 +08:00
sunsets
74d0317dd0 Update Moonraker.cpp 2024-03-08 16:03:18 +08:00
sunsets
1c9ce7ec05 macOS 10.13 2024-03-08 14:18:08 +08:00
Wang YB
2a8cdc3414 add top surface gap_infill 2024-03-08 09:11:53 +08:00
sunsets
cea37f9f7d Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-03-05 14:36:05 +08:00
Wang YB
48ed13e6f1 update upper_slices_cache 2024-03-05 10:30:39 +08:00
QIDI TECH
7a7fb7da00 Update Fill.cpp 2024-03-04 18:28:52 +08:00
Wang YB
02857e3fd8 update fill.cpp 2024-03-01 13:18:53 +08:00
Wang YB
8cb30a7329 debug top_infill areas 2024-03-01 13:15:40 +08:00
sunsets
1fc08d237c exclude_area and host_type 2024-03-01 08:54:32 +08:00
QIDI TECH
ee616bbbb6 Select all printers for QIDI on the first run 2024-02-29 14:08:32 +08:00
QIDI TECH
30e9b00baf Revert "add mix_wall revert infill_overlap"
This reverts commit 04b1c22cc6.
2024-02-28 20:01:53 +08:00
QIDI TECH
172b1893e2 Add the Max Volumetric Speed in the guide 2024-02-28 18:29:12 +08:00
Wang YB
04b1c22cc6 add mix_wall revert infill_overlap 2024-02-27 13:51:25 +08:00
QIDI TECH
2046b2f21f Optimized configuration update notification 2024-02-23 16:04:36 +08:00
sunsets
1eb7e979b0 Update PrintHostDialogs.cpp 2024-02-23 13:54:47 +08:00
sunsets
82f75eb146 add device_button without regex 2024-02-23 13:53:30 +08:00
QIDI TECH
7c6e614b3b Merge branch 'master' of https://github.com/QIDITECH/QIDISlicer 2024-02-23 09:57:00 +08:00
QIDI TECH
76aaf50055 Update Preset.cpp 2024-02-23 09:56:55 +08:00
Wang YB
d76b009f40 debug classic wall 2024-02-23 09:30:08 +08:00
QIDI TECH
ca4f336d62 Add filament shrink 2024-02-22 16:53:56 +08:00
QIDI TECH
9856ad7031 Update AppConfig.cpp 2024-02-03 13:40:16 +08:00
Wang YB
f6271b0c90 update overhang_attributes 2024-02-03 11:30:39 +08:00
sunsets
bc68a07f47 Exclude_area 2024-02-03 10:37:29 +08:00
QIDI TECH
ec446edb36 Fixed abnormal digital board height during PA calibration 2024-02-01 14:27:38 +08:00
QIDI TECH
4c3e3eea6d Merge pull request #34 from clarkjc/bugfix/bad-reference
BedShapePanel::update_exclude_area() returns a reference to a std::vector<Vec2d> that has already been destroyed
2024-01-29 14:08:45 +08:00
John Clark
4bdbc32044 Fixed return of bad reference to local variable from BedShapePanel::update_exclude_area() 2024-01-28 23:27:44 -05:00
280 changed files with 10731 additions and 16811 deletions

1
.gitignore vendored
View File

@@ -12,7 +12,6 @@ xs/MANIFEST.bak
xs/assertlib* xs/assertlib*
.init_bundle.ini .init_bundle.ini
.vs/* .vs/*
local-lib
/src/TAGS /src/TAGS
/.vscode/ /.vscode/
build-linux/* build-linux/*

132
Build.PL
View File

@@ -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__

View File

@@ -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_PCH "Use precompiled headers" 1)
option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 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_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_ASAN "Enable ASan on Clang and GCC" 0)
option(SLIC3R_UBSAN "Enable UBSan 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) 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) if (IS_CROSS_COMPILE)
message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!") 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) set(SLIC3R_BUILD_TESTS OFF CACHE BOOL "" FORCE)
endif () endif ()
@@ -165,9 +163,6 @@ if(NOT WIN32)
add_compile_options("$<$<CONFIG:DEBUG>:-DDEBUG>") add_compile_options("$<$<CONFIG:DEBUG>:-DDEBUG>")
endif() 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. # 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 # 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) 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) if(SLIC3R_BUILD_SANDBOXES)
add_subdirectory(sandboxes) add_subdirectory(sandboxes)

View File

@@ -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
}
}

View File

@@ -15,6 +15,8 @@ See the [QIDI's homepage](https://qidi3d.com) for more information.
You can find the printer's firmware here: 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-MAX 3](https://github.com/QIDITECH/QIDI_MAX3)
[X-Plus 3](https://github.com/QIDITECH/QIDI_PLUS3) [X-Plus 3](https://github.com/QIDITECH/QIDI_PLUS3)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,9 +1,8 @@
set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory") set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory")
set(_source_dir_line set(_source_dir_line
URL https://github.com/prusa3d/libbgcode/archive/bc390aab4427589a6402b4c7f65cf4d0a8f987ec.zip URL https://github.com/prusa3d/libbgcode/archive/6f43cb004ef3d3bda37dde49f6235e24d2717629.zip
URL_HASH SHA256=0c86cb67232089728233014f937e2a07d133a61e31dd8811a9c905e563a49f24 URL_HASH SHA256=eb5198caecb6a693a294af6a56c37b0adb1eb159a34a9c3116970b80659ee9f9)
)
if (LibBGCode_SOURCE_DIR) if (LibBGCode_SOURCE_DIR)
set(_source_dir_line "SOURCE_DIR;${LibBGCode_SOURCE_DIR};BUILD_ALWAYS;ON") set(_source_dir_line "SOURCE_DIR;${LibBGCode_SOURCE_DIR};BUILD_ALWAYS;ON")

View File

@@ -15,7 +15,7 @@ endif ()
add_cmake_project(OpenVDB add_cmake_project(OpenVDB
# 8.2 patched # 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 URL_HASH SHA256=f353e7b99bd0cbfc27ac9082de51acf32a8bc0b3e21ff9661ecca6f205ec1d81
CMAKE_ARGS CMAKE_ARGS
-DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,8 +0,0 @@
package Slic3r::Line;
use strict;
use warnings;
# a line is a two-points line
use parent 'Slic3r::Polyline';
1;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,8 +0,0 @@
package Slic3r::Polygon;
use strict;
use warnings;
# a polygon is a closed polyline.
use parent 'Slic3r::Polyline';
1;

View File

@@ -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;

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View 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

View 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

View File

@@ -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 min_slic3r_version = 1.1.1
1.1.1 Optimize parameters 1.1.1 Optimize parameters
min_slic3r_version = 1.1.0 min_slic3r_version = 1.1.0

File diff suppressed because it is too large Load Diff

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -18,6 +18,7 @@ var LangText={
"t16": "Calibration", "t16": "Calibration",
"t17": "Pressure Advance", "t17": "Pressure Advance",
"t18": "Exclude Objects", "t18": "Exclude Objects",
"t19": "Max Volumetric Speed",
"l0": "Learn more:", "l0": "Learn more:",
"l1": "You can download the 3D model from the following link.", "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.", "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.", "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.", "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": { "zh_CN": {
"t1": "用户指南", "t1": "用户指南",
@@ -82,6 +86,7 @@ var LangText={
"t16": "校准", "t16": "校准",
"t17": "压力提前", "t17": "压力提前",
"t18": "排除对象", "t18": "排除对象",
"t19": "最大体积速度",
"l0": "了解更多:", "l0": "了解更多:",
"l1": "您可以从以下链接下载3D模型。", "l1": "您可以从以下链接下载3D模型。",
@@ -126,6 +131,9 @@ var LangText={
"l41": "4.PA校准不适用于PETG。", "l41": "4.PA校准不适用于PETG。",
"l42": "当多个模型同时打印时,你可以在打印中途停止某个模型的打印。", "l42": "当多个模型同时打印时,你可以在打印中途停止某个模型的打印。",
"l43": "注:相同文件名的模型会被视为同一对象。", "l43": "注:相同文件名的模型会被视为同一对象。",
"l44": "不同线材的最大体积速度各不相同。",
"l45": "喷嘴材质、口径、打印温度、等都会影响最大体积速度。",
"l46": "测试时打印速度会逐层增加,当模型表面出现空洞或缺丝时,说明该层已达到最大体积速度,根据高度比例算出最大体积速度。",
}, },
"ja": { "ja": {
"t1": "ユーザーガイド", "t1": "ユーザーガイド",
@@ -146,6 +154,7 @@ var LangText={
"t16": "較正", "t16": "較正",
"t17": "圧力前進", "t17": "圧力前進",
"t18": "対象をはずします", "t18": "対象をはずします",
"t19": "最大体積速度です",
"l0": "もっと詳しく知る:", "l0": "もっと詳しく知る:",
"l1": "3Dモデルは以下のWebサイトからダウンロードできます。", "l1": "3Dモデルは以下のWebサイトからダウンロードできます。",
@@ -190,6 +199,9 @@ var LangText={
"l41": "4.PA キャリブレーションは PETG では機能しません。", "l41": "4.PA キャリブレーションは PETG では機能しません。",
"l42": "複数のモデルが同時に印刷されている場合は、あるモデルの印刷を途中でやめることができます。", "l42": "複数のモデルが同時に印刷されている場合は、あるモデルの印刷を途中でやめることができます。",
"l43": "注:同じファイル名のモデルは、同じ対象として扱われます。", "l43": "注:同じファイル名のモデルは、同じ対象として扱われます。",
"l44": "最大体積速度は線材によって異なります。",
"l45": "ノズルの材質、口径、印刷温度などが最大体積速度に影響します。",
"l46": "テスト時の印刷速度は層ごとに増加します。模型の表面に穴ができたり、糸が欠けたりした場合、その層が最大体積速度に達したことを示し、高さの割合から最大体積速度を算出します。",
}, },
"fr": { "fr": {
"t1": "Guide de l'utilisateur", "t1": "Guide de l'utilisateur",
@@ -210,6 +222,7 @@ var LangText={
"t16": "Étalonnage", "t16": "Étalonnage",
"t17": "Avance de pression", "t17": "Avance de pression",
"t18": "Exclure des objets", "t18": "Exclure des objets",
"t19": "Vitesse volumétrique Max",
"l0": "Apprendre encore plus:", "l0": "Apprendre encore plus:",
"l1": "Vous pouvez télécharger des modèles 3D à partir des sites Web suivants.", "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.", "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 limpression dun modèle au milieu de limpression.", "l42": "Lorsque plusieurs modèles sont imprimés en même temps, vous pouvez arrêter limpression dun modèle au milieu de limpression.",
"l43": "Note: les modèles avec le même nom de fichier sont traités comme le même objet.", "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 dimpression, etc., affecteront la vitesse de volume maximum.",
"l46": "Pendant lessai, la vitesse dimpression 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": { "de": {
"t1": "Benutzerhandbuch", "t1": "Benutzerhandbuch",
@@ -274,6 +290,7 @@ var LangText={
"t16": "Kalibrierung", "t16": "Kalibrierung",
"t17": "Druckvorschub", "t17": "Druckvorschub",
"t18": "Subjekt wird eliminiert", "t18": "Subjekt wird eliminiert",
"t19": "Maximale geschwindigkeit",
"l0": "Erfahren Sie mehr:", "l0": "Erfahren Sie mehr:",
"l1": "Sie können 3D-Modelle von den folgenden Websites herunterladen.", "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.", "l41": "4.Die PA-Kalibrierung funktioniert nicht mit PETG.",
"l42": "Wenn sie mehrere modelle gleichzeitig drucken, können sie den druckvorgang beenden.", "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.", "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": { "be": {
"t1": "Кіраўніцтва карыстальніка", "t1": "Кіраўніцтва карыстальніка",
@@ -338,6 +358,7 @@ var LangText={
"t16": "Каліброўка", "t16": "Каліброўка",
"t17": "Увеличение давления", "t17": "Увеличение давления",
"t18": "Исключить объекты из списка", "t18": "Исключить объекты из списка",
"t19": "Максимальная объемная частота вращения",
"l0": "даведацца больш:", "l0": "даведацца больш:",
"l1": "Вы можаце загрузіць 3D-мадэлі з наступных сайтаў.", "l1": "Вы можаце загрузіць 3D-мадэлі з наступных сайтаў.",
@@ -382,6 +403,9 @@ var LangText={
"l41": "4.Калибровка PA не работает с PETG.", "l41": "4.Калибровка PA не работает с PETG.",
"l42": "Когда одновременно печатаются несколько моделей, можно прекратить печатание модели в процессе печати.", "l42": "Когда одновременно печатаются несколько моделей, можно прекратить печатание модели в процессе печати.",
"l43": "Примечание: модели с одним и тем же именем файла рассматриваются как Один и тот же объект.", "l43": "Примечание: модели с одним и тем же именем файла рассматриваются как Один и тот же объект.",
"l44": "Различные нити накала имеют разную максимальную объемную скорость.",
"l45": "Материал сопла, калибр, температура печати и т.д., повлияет на максимальную объемную скорость.",
"l46": "Во время теста скорость печати будет увеличиваться слой за слоем. Если на поверхности модели имеется отверстие или недостающая проволока, это указывает на то, что слой достиг максимальной объемной скорости, а максимальная объемная скорость рассчитывается в соответствии с отношением высоты.",
}, },
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

View File

@@ -83,6 +83,10 @@
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div> <div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
<div class="trans" tid="t17"></div> <div class="trans" tid="t17"></div>
</li> </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> </ol>
</li> </li>
@@ -93,6 +97,10 @@
<i class="fa fa-caret-right"></i> <i class="fa fa-caret-right"></i>
</label> </label>
<ol> <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')"> <li menu="X-MAX 3" class="MenuBtn" onClick="GotoMenu('X-MAX 3')">
<div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div> <div class="MenuBtnIcon"><img src="img/MenuBtnIcon.svg"/></div>
<div>X-MAX 3</div> <div>X-MAX 3</div>
@@ -174,6 +182,10 @@
<div class="AutozoomImage"><img src="img/PressureAdvance.gif"/></div> <div class="AutozoomImage"><img src="img/PressureAdvance.gif"/></div>
<div class="ThumbnailTitle trans" tid="t17"></div> <div class="ThumbnailTitle trans" tid="t17"></div>
</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="Thumbnail" onClick="GotoMenu('IssueReport')">
<div class="AutozoomImage"><img src="img/IssueReport.png"/></div> <div class="AutozoomImage"><img src="img/IssueReport.png"/></div>
<div class="ThumbnailTitle trans" tid="t6"></div> <div class="ThumbnailTitle trans" tid="t6"></div>
@@ -294,6 +306,20 @@
<div class="CenterImage"><img src="img/PressureAdvanceTower.png"/></div> <div class="CenterImage"><img src="img/PressureAdvanceTower.png"/></div>
</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="IntroduceBoard" board="X-MAX 3">
<div class="AutozoomImage"><img src="img/X-MAX3Poster.png"/></div> <div class="AutozoomImage"><img src="img/X-MAX3Poster.png"/></div>
<div class="IntroduceTextBold trans" tid="l0"></div> <div class="IntroduceTextBold trans" tid="l0"></div>
@@ -526,6 +552,22 @@
<td>2200 ± 200</td> <td>2200 ± 200</td>
<td>7.5 ± 1.5</td> <td>7.5 ± 1.5</td>
</tr> </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"> <tr v-for="(item, index) in 1" :key="index">
<td>UltraPA</td> <td>UltraPA</td>
<td>≤15</td> <td>≤15</td>
@@ -554,6 +596,14 @@
<div class="IntroduceText trans" tid="l2"></div> <div class="IntroduceText trans" tid="l2"></div>
<div class="IntroduceText trans" tid="l3"></div> <div class="IntroduceText trans" tid="l3"></div>
<div class="EmailBlock"> <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="PrinterBlock">
<div class="CenterImage"><img src="img/X-MAX 3.png"/></div> <div class="CenterImage"><img src="img/X-MAX 3.png"/></div>
<div class="ThumbnailTitle">X-MAX 3</div> <div class="ThumbnailTitle">X-MAX 3</div>

70
src/.vscode/settings.json vendored Normal file
View 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"
}
}

View File

@@ -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. // 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"); boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
#endif // _WIN32 #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 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; 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); m_print_config.apply(config);
} }
#ifdef SLIC3R_GUI
#if ENABLE_GL_CORE_PROFILE #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 // search for special keys into command line parameters
auto it = std::find(m_actions.begin(), m_actions.end(), "gcodeviewer"); auto it = std::find(m_actions.begin(), m_actions.end(), "gcodeviewer");
if (it != m_actions.end()) { 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"); it = std::find(m_actions.begin(), m_actions.end(), "opengl-version");
if (it != m_actions.end()) { if (it != m_actions.end()) {
std::string opengl_version_str = m_config.opt_string("opengl-version"); const Semver opengl_minimum = Semver(3,2,0);
if (std::find(Slic3r::GUI::OpenGLVersions::core_str.begin(), Slic3r::GUI::OpenGLVersions::core_str.end(), opengl_version_str) == Slic3r::GUI::OpenGLVersions::core_str.end()) { const std::string opengl_version_str = m_config.opt_string("opengl-version");
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::optional<Semver> semver = Semver::parse(opengl_version_str);
boost::nowide::cerr << "Found invalid OpenGL version: " << opengl_version_str << std::endl; if (semver.has_value() && (*semver) >= opengl_minimum ) {
opengl_version_str.clear(); 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()) { it = std::find(m_actions.begin(), m_actions.end(), "opengl-compatibility");
std::vector<std::string> tokens; if (it != m_actions.end()) {
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());
}
start_gui = true; 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); m_actions.erase(it);
} }
it = std::find(m_actions.begin(), m_actions.end(), "opengl-debug"); it = std::find(m_actions.begin(), m_actions.end(), "opengl-debug");
if (it != m_actions.end()) { if (it != m_actions.end()) {
start_gui = true; start_gui = true;
#if ENABLE_OPENGL_DEBUG_OPTION
opengl_debug = true; opengl_debug = true;
#endif // ENABLE_OPENGL_DEBUG_OPTION
m_actions.erase(it); m_actions.erase(it);
} }
#else #else
@@ -222,6 +227,16 @@ int CLI::run(int argc, char **argv)
} }
} }
#endif // ENABLE_GL_CORE_PROFILE #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. // Read input file(s) if any.
for (const std::string& file : m_input_files) 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.download_url = download_url;
params.delete_after_load = delete_after_load; params.delete_after_load = delete_after_load;
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
#if ENABLE_OPENGL_DEBUG_OPTION
params.opengl_version = opengl_version; params.opengl_version = opengl_version;
params.opengl_debug = opengl_debug; params.opengl_debug = opengl_debug;
#endif // ENABLE_OPENGL_DEBUG_OPTION params.opengl_compatibiity_profile = opengl_compatibility_profile;
#endif // ENABLE_GL_CORE_PROFILE #endif // ENABLE_GL_CORE_PROFILE
return Slic3r::GUI::GUI_Run(params); return Slic3r::GUI::GUI_Run(params);
#else // SLIC3R_GUI #else // SLIC3R_GUI
@@ -781,6 +795,7 @@ bool CLI::setup(int argc, char **argv)
set_var_dir((path_resources / "icons").string()); set_var_dir((path_resources / "icons").string());
set_local_dir((path_resources / "localization").string()); set_local_dir((path_resources / "localization").string());
set_sys_shapes_dir((path_resources / "shapes").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. // Parse all command line options into a DynamicConfig.
// If any option is unsupported, print usage and abort immediately. // 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); 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. //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(); std::string validity = m_config.validate();

View File

@@ -268,7 +268,7 @@ int wmain(int argc, wchar_t **argv)
// In that case, use Mesa. // In that case, use Mesa.
(::GetSystemMetrics(SM_REMOTESESSION) && !force_hw) || (::GetSystemMetrics(SM_REMOTESESSION) && !force_hw) ||
// Try to load the default OpenGL driver and test its context version. // 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 */ #endif /* SLIC3R_GUI */
wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; wchar_t path_to_exe[MAX_PATH + 1] = { 0 };

View File

@@ -153,7 +153,7 @@ void AppConfig::set_defaults()
set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save" set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save"
if (get("color_mapinulation_panel").empty()) if (get("color_mapinulation_panel").empty())
set("color_mapinulation_panel", "0"); set("color_mapinulation_panel", "1");
if (get("order_volumes").empty()) if (get("order_volumes").empty())
set("order_volumes", "1"); set("order_volumes", "1");

View File

@@ -10,7 +10,6 @@
#include <functional> #include <functional>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "utils/VoronoiUtils.hpp"
#include "utils/linearAlg2D.hpp" #include "utils/linearAlg2D.hpp"
#include "Utils.hpp" #include "Utils.hpp"
@@ -19,26 +18,9 @@
#include "Geometry/VoronoiUtilsCgal.hpp" #include "Geometry/VoronoiUtilsCgal.hpp"
#include "../EdgeGrid.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). #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 namespace Slic3r::Arachne
{ {
@@ -108,8 +90,7 @@ static void export_graph_to_svg(const std::string
} }
#endif #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); auto he_node_it = vd_node_to_he_node.find(&vd_node);
if (he_node_it == vd_node_to_he_node.end()) 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()); auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
if (he_edge_it != vd_edge_to_he_edge.end()) if (he_edge_it != vd_edge_to_he_edge.end())
{ // Twin segment(s) have already been made { // 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 /*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 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.*/ 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::cell_type *left_cell = vd_edge.cell();
const vd_t::cell_type* right_cell = vd_edge.twin()->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()); Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
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()); Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
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>();
bool point_left = left_cell->contains_point(); bool point_left = left_cell->contains_point();
bool point_right = right_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. 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); Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
} }
else //This is a straight edge between two points. else //This is a straight edge between two points.
{ {
/*While the edge is straight, it is still discretized since the part /*While the edge is straight, it is still discretized since the part
becomes narrower between the two points. As such it may need different becomes narrower between the two points. As such it may need different
beadings along the way.*/ beadings along the way.*/
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); 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(); coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2; Point middle = (left_point + right_point) / 2;
Point x_axis_dir = perp(Point(right_point - left_point)); 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()) if (cell.incident_edge()->is_infinite())
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. 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 // 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 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.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())) 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 return false; // Don't copy any part of this cell
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
if (some_point == source_point.cast<int64_t>()) 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. //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. //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)) 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 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 { do {
assert(vd_edge->is_finite()); 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; start_source_point = source_point;
end_source_point = source_point; end_source_point = source_point;
starting_vd_edge = vd_edge->next(); starting_vd_edge = vd_edge->next();
ending_vd_edge = vd_edge; ending_vd_edge = vd_edge;
} else { } 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()); 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; 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, SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
double transitioning_angle, coord_t discretization_step_size, double transitioning_angle, coord_t discretization_step_size,
@@ -450,194 +385,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys); 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) void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{ {
@@ -670,8 +417,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
Geometry::VoronoiDiagram voronoi_diagram; VD voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI #ifdef ARACHNE_DEBUG_VORONOI
{ {
@@ -680,45 +427,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #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()); 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()) if (!cell.incident_edge())
continue; // There is no spoon continue; // There is no spoon
Point start_source_point; Point start_source_point;
Point end_source_point; Point end_source_point;
vd_t::edge_type* starting_voronoi_edge = nullptr; const VD::edge_type *starting_voronoi_edge = nullptr;
vd_t::edge_type* ending_voronoi_edge = nullptr; const VD::edge_type *ending_voronoi_edge = nullptr;
// Compute and store result in above variables // Compute and store result in above variables
if (cell.contains_point()) { if (cell.contains_point()) {
@@ -727,7 +444,12 @@ process_voronoi_diagram:
continue; continue;
} else { } else {
assert(cell.contains_segment()); 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) { if (!starting_voronoi_edge || !ending_voronoi_edge) {
@@ -736,68 +458,30 @@ process_voronoi_diagram:
} }
// Copy start to end edge to graph // Copy start to end edge to graph
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
edge_t* prev_edge = nullptr; 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()); 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);
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);
node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0; starting_node->data.distance_to_boundary = 0;
constexpr bool is_next_to_start_or_end = true; 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); 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(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(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
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 v1 = VoronoiUtils::p(vd_edge->vertex0()).cast<coord_t>(); Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).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); 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); 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()); 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);
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);
prev_edge->to->data.distance_to_boundary = 0; 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 #ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));

View File

@@ -11,7 +11,6 @@
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp" #include "utils/PolygonsSegmentIndex.hpp"
@@ -26,8 +25,9 @@
//#define ARACHNE_DEBUG //#define ARACHNE_DEBUG
//#define ARACHNE_DEBUG_VORONOI //#define ARACHNE_DEBUG_VORONOI
namespace Slic3r::Arachne namespace Slic3r::Arachne {
{
using VD = Slic3r::Geometry::VoronoiDiagram;
/*! /*!
* Main class of the dynamic beading strategies. * Main class of the dynamic beading strategies.
@@ -50,8 +50,6 @@ deposition modeling" by Kuipers et al.
*/ */
class SkeletalTrapezoidation class SkeletalTrapezoidation
{ {
using pos_t = double;
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
using graph_t = SkeletalTrapezoidationGraph; using graph_t = SkeletalTrapezoidationGraph;
using edge_t = STHalfEdge; using edge_t = STHalfEdge;
using node_t = STHalfEdgeNode; using node_t = STHalfEdgeNode;
@@ -83,7 +81,6 @@ class SkeletalTrapezoidation
public: public:
using Segment = PolygonsSegmentIndex; using Segment = PolygonsSegmentIndex;
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
using NodeSet = ankerl::unordered_dense::set<node_t*>; using NodeSet = ankerl::unordered_dense::set<node_t*>;
/*! /*!
@@ -168,9 +165,9 @@ protected:
* mapping each voronoi VD edge to the corresponding halfedge HE edge * 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 * 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<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> vd_node_to_he_node; ankerl::unordered_dense::map<const VD::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. 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): * (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) * 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. * \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- * 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 * \return A number of coordinates along the edge where the edge is broken
* up into discrete pieces. * 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 * 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 * /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether. * 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); 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);
/*!
* 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);
/*! /*!
* 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 * 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(); 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 } // namespace Slic3r::Arachne

View File

@@ -156,7 +156,6 @@ struct PathsPointIndexLocator
} }
}; };
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
}//namespace Slic3r::Arachne }//namespace Slic3r::Arachne

View File

@@ -27,5 +27,24 @@ public:
} // namespace Slic3r::Arachne } // 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 #endif//UTILS_POLYGONS_SEGMENT_INDEX_H

View File

@@ -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

View File

@@ -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

View File

@@ -58,7 +58,7 @@ class DefaultArrangerCtl : public Arranger<ArrItem>::Ctl {
public: public:
DefaultArrangerCtl() = default; DefaultArrangerCtl() = default;
explicit DefaultArrangerCtl(ArrangeTaskBase::Ctl &ctl) : taskctl{&ctl} {} explicit DefaultArrangerCtl(ArrangeTaskCtl &ctl) : taskctl{&ctl} {}
void update_status(int st) override void update_status(int st) override
{ {

View File

@@ -325,7 +325,7 @@ class DefaultArranger: public Arranger<ArrItem> {
// a pure RectangleBed with inner-fit polygon calculation. // a pure RectangleBed with inner-fit polygon calculation.
if (!with_wipe_tower && if (!with_wipe_tower &&
m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto && 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}; PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond};
RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)}; RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)};

View File

@@ -181,6 +181,11 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed)
ArrangeBed to_arrange_bed(const Points &bedpts); 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 } // namespace arr2
inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; } inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; }

View File

@@ -51,9 +51,9 @@ protected:
public: public:
TMArrangeKernel() = default; TMArrangeKernel() = default;
TMArrangeKernel(Vec2crd gravity_center, size_t itm_cnt, double bedarea = NaNd) 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} , m_item_cnt{itm_cnt}
, sink{gravity_center}
{} {}
TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd) TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd)
@@ -87,8 +87,6 @@ public:
// Will hold the resulting score // Will hold the resulting score
double score = 0; double score = 0;
// Density is the pack density: how big is the arranged pile
double density = 0;
// Distinction of cases for the arrangement scene // Distinction of cases for the arrangement scene
enum e_cases { enum e_cases {
@@ -96,8 +94,6 @@ public:
// OR for all items in a small-only scene. // OR for all items in a small-only scene.
BIG_ITEM, BIG_ITEM,
// This branch is for the last big item in a mixed scene
LAST_BIG_ITEM,
// For small items in a mixed scene. // For small items in a mixed scene.
SMALL_ITEM, SMALL_ITEM,
@@ -109,10 +105,8 @@ public:
bool bigitems = is_big(envelope_area(item)) || m_rtree.empty(); bool bigitems = is_big(envelope_area(item)) || m_rtree.empty();
if (is_wt) if (is_wt)
compute_case = WIPE_TOWER; compute_case = WIPE_TOWER;
else if (bigitems && m_rem_cnt > 0) else if (bigitems)
compute_case = BIG_ITEM; compute_case = BIG_ITEM;
else if (bigitems && m_rem_cnt == 0)
compute_case = LAST_BIG_ITEM;
else else
compute_case = SMALL_ITEM; compute_case = SMALL_ITEM;
@@ -129,20 +123,8 @@ public:
Point top_left{minc.x(), maxc.y()}; Point top_left{minc.x(), maxc.y()};
Point bottom_right{maxc.x(), minc.y()}; Point bottom_right{maxc.x(), minc.y()};
// Now the distance of the gravity center will be calculated to the // The smallest distance from the arranged pile center:
// five anchor points and the smallest will be chosen. double dist = norm((itmcntr - m_pilebb.center()).template cast<double>().norm());
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;
// Prepare a variable for the alignment score. // Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item // This will indicate: how well is the candidate item
@@ -150,7 +132,7 @@ public:
// with all neighbors and return the score for the best // with all neighbors and return the score for the best
// alignment. So it is enough for the candidate to be // alignment. So it is enough for the candidate to be
// aligned with only one item. // aligned with only one item.
auto alignment_score = 1.0; auto alignment_score = 1.;
auto query = bgi::intersects(ibb); auto query = bgi::intersects(ibb);
auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree; auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree;
@@ -170,33 +152,24 @@ public:
auto bb = p.bb; auto bb = p.bb;
bb.merge(ibb); bb.merge(ibb);
auto bbarea = area(bb); 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) if(ascore < alignment_score)
alignment_score = ascore; 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); 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 // The final mix of the score is the balance between the
// distance from the full pile center, the pack density and // distance from the full pile center, the pack density and
// the alignment with the neighbors // 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 // Let the density matter more when fewer objects remain
score = 0.50 * dist + (1.0 - R) * 0.20 * density + score = 0.6 * dist + 0.1 * alignment_score + (1.0 - R) * (0.3 * dist) + R * 0.3 * alignment_score;
0.30 * alignment_score;
break; break;
} }
case LAST_BIG_ITEM: {
score = norm((itmcntr - m_pilebb.center()).template cast<double>().norm());
break;
}
case SMALL_ITEM: { case SMALL_ITEM: {
// Here there are the small items that should be placed around the // Here there are the small items that should be placed around the
// already processed bigger items. // already processed bigger items.
@@ -236,8 +209,11 @@ public:
if (m_item_cnt == 0) if (m_item_cnt == 0)
m_item_cnt = m_rem_cnt + fixed.size() + 1; m_item_cnt = m_rem_cnt + fixed.size() + 1;
if (std::isnan(m_bin_area)) if (std::isnan(m_bin_area)) {
m_bin_area = area(bed); auto sz = bounding_box(bed).size();
m_bin_area = scaled<double>(unscaled(sz.x()) * unscaled(sz.y()));
}
m_norm = std::sqrt(m_bin_area); m_norm = std::sqrt(m_bin_area);
@@ -245,7 +221,7 @@ public:
m_itemstats.reserve(fixed.size()); m_itemstats.reserve(fixed.size());
m_rtree.clear(); m_rtree.clear();
m_smallsrtree.clear(); m_smallsrtree.clear();
m_pilebb = {}; m_pilebb = {active_sink, active_sink};
unsigned idx = 0; unsigned idx = 0;
for (auto &fixitem : fixed) { for (auto &fixitem : fixed) {
auto fixitmbb = fixed_bounding_box(fixitem); auto fixitmbb = fixed_bounding_box(fixitem);

View File

@@ -101,6 +101,9 @@ ExPolygons to_expolygons(const SegmentedRectangleBed<Args...> &bed)
return to_expolygons(RectangleBed{bed.bb}); 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 }} // namespace Slic3r::arr2
#endif // SEGMENTEDRECTANGLEBED_HPP #endif // SEGMENTEDRECTANGLEBED_HPP

View File

@@ -115,21 +115,13 @@ ArrangeTask<ArrItem>::process_native(Ctl &ctl)
} subctl{ctl, *this}; } subctl{ctl, *this};
auto fixed_items = printable.unselected; arranger->arrange(printable.selected, printable.unselected, bed, subctl);
// 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);
std::vector<int> printable_bed_indices = std::vector<int> printable_bed_indices =
get_bed_indices(crange(printable.selected), crange(printable.unselected)); get_bed_indices(crange(printable.selected), crange(printable.unselected));
// If there are no printables, leave the physical bed empty // 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 // Unprintable items should go to the first logical (!) bed not containing
// any printable items // any printable items

View File

@@ -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() && 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(); 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 // Will prevent warnings caused by non existing definition of template in hpp

View File

@@ -8,7 +8,9 @@
namespace Slic3r { 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); 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); BoundingBoxf bboxf = get_extents(bed_shape);
m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, max_print_height) }; 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)) { 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. // Square print bed, use the bounding box for collision detection.
m_type = Type::Rectangle; m_type = Type::Rectangle;
m_circle.center = 0.5 * (m_bbox.min.cast<double>() + m_bbox.max.cast<double>()); 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(); 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) { } else if (bed_shape.size() > 3) {
// Circle was discretized, formatted into text with limited accuracy, thus the circle was deformed. // 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. // 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 BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
{ {
assert(m_type == Type::Rectangle); assert(m_type == Type::Rectangle);
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon); 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) if (m_max_print_height == 0.0)
build_volume.max.z() = std::numeric_limits<double>::max(); build_volume.max.z() = std::numeric_limits<double>::max();
if (ignore_bottom) if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::max(); build_volume.min.z() = -std::numeric_limits<double>::max();
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below : 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.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 bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const
{ {
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) { 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(); build_volume.max.z() = std::numeric_limits<double>::max();
if (ignore_bottom) if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::max(); 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: case Type::Circle:
{ {

View File

@@ -34,10 +34,13 @@ public:
// Initialized to empty, all zeros, Invalid. // Initialized to empty, all zeros, Invalid.
BuildVolume() {} BuildVolume() {}
// Initialize from PrintConfig::bed_shape and PrintConfig::max_print_height // 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. // Source data, unscaled coordinates.
const std::vector<Vec2d>& bed_shape() const { return m_bed_shape; } 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; } double max_print_height() const { return m_max_print_height; }
// Derived data // Derived data
@@ -101,6 +104,8 @@ public:
private: private:
// Source definition of the print bed geometry (PrintConfig::bed_shape) // Source definition of the print bed geometry (PrintConfig::bed_shape)
std::vector<Vec2d> m_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) // Source definition of the print volume height (PrintConfig::max_print_height)
double m_max_print_height; double m_max_print_height;

View File

@@ -80,7 +80,6 @@ set(SLIC3R_SOURCES
ExtrusionSimulator.hpp ExtrusionSimulator.hpp
FileParserError.hpp FileParserError.hpp
Fill/Fill.cpp Fill/Fill.cpp
Fill/Fill.hpp
Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp Fill/Fill3DHoneycomb.hpp
Fill/FillAdaptive.cpp Fill/FillAdaptive.cpp
@@ -184,6 +183,8 @@ set(SLIC3R_SOURCES
GCode/GCodeProcessor.hpp GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp GCode/AvoidCrossingPerimeters.hpp
GCode/Travels.cpp
GCode/Travels.hpp
GCode.cpp GCode.cpp
GCode.hpp GCode.hpp
GCodeReader.cpp GCodeReader.cpp
@@ -205,6 +206,8 @@ set(SLIC3R_SOURCES
Geometry/Voronoi.hpp Geometry/Voronoi.hpp
Geometry/VoronoiOffset.cpp Geometry/VoronoiOffset.cpp
Geometry/VoronoiOffset.hpp Geometry/VoronoiOffset.hpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Geometry/VoronoiVisualUtils.hpp Geometry/VoronoiVisualUtils.hpp
Int128.hpp Int128.hpp
JumpPointSearch.cpp JumpPointSearch.cpp
@@ -212,6 +215,7 @@ set(SLIC3R_SOURCES
KDTreeIndirect.hpp KDTreeIndirect.hpp
Layer.cpp Layer.cpp
Layer.hpp Layer.hpp
LayerRegion.hpp
LayerRegion.cpp LayerRegion.cpp
libslic3r.h libslic3r.h
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h" "${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
@@ -486,8 +490,9 @@ set(SLIC3R_SOURCES
Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp Geometry/Voronoi.cpp
Arachne/utils/VoronoiUtils.cpp Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.hpp
Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidation.cpp
Arachne/SkeletalTrapezoidationEdge.hpp Arachne/SkeletalTrapezoidationEdge.hpp

View File

@@ -245,6 +245,8 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
{ {
if (this->nullable) { if (this->nullable) {
switch (this->type) { switch (this->type) {
case coFloat: return new ConfigOptionFloatNullable();
case coInt: return new ConfigOptionIntNullable();
case coFloats: return new ConfigOptionFloatsNullable(); case coFloats: return new ConfigOptionFloatsNullable();
case coInts: return new ConfigOptionIntsNullable(); case coInts: return new ConfigOptionIntsNullable();
case coPercents: return new ConfigOptionPercentsNullable(); case coPercents: return new ConfigOptionPercentsNullable();
@@ -883,6 +885,8 @@ size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase& config, const char*
end = start; 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(); config.handle_legacy_composite();
return num_key_value_pairs; 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::Vec2d>)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<bool>) 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::ConfigOptionVectorBase)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<int>) 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<Slic3r::Vec2d>)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatNullable)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntNullable)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable)
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) 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::Vec2d>)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec3d>) 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::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::ConfigOption, Slic3r::ConfigOptionVectorBase)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<int>) 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<Slic3r::Vec2d>)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat) 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::ConfigOptionFloats)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt) 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::ConfigOptionInts)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable)
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)

View File

@@ -4,6 +4,7 @@
#include <assert.h> #include <assert.h>
#include <map> #include <map>
#include <climits> #include <climits>
#include <limits>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
@@ -39,9 +40,9 @@ namespace Slic3r {
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); } 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) noexcept { 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) noexcept { 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.value == r.value && int(l.percent) < int(r.percent)); }
} }
namespace std { namespace std {
@@ -298,8 +299,55 @@ public:
typedef ConfigOption* ConfigOptionPtr; typedef ConfigOption* ConfigOptionPtr;
typedef const ConfigOption* ConfigOptionConstPtr; 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) // 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 { class ConfigOptionSingle : public ConfigOption {
public: public:
T value; T value;
@@ -310,16 +358,18 @@ public:
{ {
if (rhs->type() != this->type()) if (rhs->type() != this->type())
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type"); throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs)); assert(dynamic_cast<const ConfigOptionSingle*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; this->value = static_cast<const ConfigOptionSingle*>(rhs)->value;
} }
bool operator==(const ConfigOption &rhs) const override bool operator==(const ConfigOption &rhs) const override
{ {
if (rhs.type() != this->type()) if (rhs.type() != this->type())
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types"); throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs)); assert(dynamic_cast<const ConfigOptionSingle*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value; 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; } 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); } 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: private:
friend class cereal::access; friend class cereal::access;
template<class Archive> void serialize(Archive & ar) { ar(this->value); } 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) // Value of a vector valued option (bools, ints, floats, strings, points)
class ConfigOptionVectorBase : public ConfigOption { class ConfigOptionVectorBase : public ConfigOption {
public: public:
@@ -551,23 +655,34 @@ private:
template<class Archive> void serialize(Archive & ar) { ar(this->values); } 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: public:
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {} ConfigOptionFloatTempl() : ConfigOptionSingle<double, NULLABLE>(0) {}
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {} explicit ConfigOptionFloatTempl(double _value) : ConfigOptionSingle<double, NULLABLE>(_value) {}
static ConfigOptionType static_type() { return coFloat; } static ConfigOptionType static_type() { return coFloat; }
ConfigOptionType type() const override { return static_type(); } ConfigOptionType type() const override { return static_type(); }
double getFloat() const override { return this->value; } double getFloat() const override { return this->value; }
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } ConfigOption* clone() const override { return new ConfigOptionFloatTempl(*this); }
bool operator==(const ConfigOptionFloat &rhs) const throw() { return this->value == rhs.value; } bool operator==(const ConfigOptionFloatTempl &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionFloat &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::string serialize() const override
{ {
std::ostringstream ss; 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(); return ss.str();
} }
@@ -575,19 +690,30 @@ public:
{ {
UNUSED(append); UNUSED(append);
std::istringstream iss(str); 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; iss >> this->value;
}
return !iss.fail(); return !iss.fail();
} }
ConfigOptionFloat& operator=(const ConfigOption *opt) ConfigOptionFloatTempl& operator=(const ConfigOption *opt)
{ {
this->set(opt); this->set(opt);
return *this; return *this;
} }
bool is_nil() const override
{
return std::isnan(this->value);
}
private: private:
friend class cereal::access; 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> template<bool NULLABLE>
@@ -713,26 +839,35 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<double>>(this)); } 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 ConfigOptionFloats = ConfigOptionFloatsTempl<false>;
using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl<true>; using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl<true>;
class ConfigOptionInt : public ConfigOptionSingle<int> template<bool NULLABLE = false>
class ConfigOptionIntTempl : public ConfigOptionSingle<int, NULLABLE>
{ {
public: public:
ConfigOptionInt() : ConfigOptionSingle<int>(0) {} ConfigOptionIntTempl() : ConfigOptionSingle<int, NULLABLE>(0) {}
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {} explicit ConfigOptionIntTempl(int value) : ConfigOptionSingle<int, NULLABLE>(value) {}
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {} explicit ConfigOptionIntTempl(double _value) : ConfigOptionSingle<int, NULLABLE>(int(floor(_value + 0.5))) {}
static ConfigOptionType static_type() { return coInt; } static ConfigOptionType static_type() { return coInt; }
ConfigOptionType type() const override { return static_type(); } ConfigOptionType type() const override { return static_type(); }
int getInt() const override { return this->value; } int getInt() const override { return this->value; }
void setInt(int val) override { this->value = val; } void setInt(int val) override { this->value = val; }
ConfigOption* clone() const override { return new ConfigOptionInt(*this); } ConfigOption* clone() const override { return new ConfigOptionIntTempl(*this); }
bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; } bool operator==(const ConfigOptionIntTempl &rhs) const throw() { return this->value == rhs.value; }
std::string serialize() const override std::string serialize() const override
{ {
std::ostringstream ss; std::ostringstream ss;
if (this->value == this->nil_value()) {
if (NULLABLE)
ss << "nil";
else
throw ConfigurationError("Serializing NaN");
} else
ss << this->value; ss << this->value;
return ss.str(); return ss.str();
} }
@@ -741,11 +876,18 @@ public:
{ {
UNUSED(append); UNUSED(append);
std::istringstream iss(str); 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; iss >> this->value;
}
return !iss.fail(); return !iss.fail();
} }
ConfigOptionInt& operator=(const ConfigOption *opt) ConfigOptionIntTempl& operator=(const ConfigOption *opt)
{ {
this->set(opt); this->set(opt);
return *this; return *this;
@@ -753,9 +895,11 @@ public:
private: private:
friend class cereal::access; 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> template<bool NULLABLE>
class ConfigOptionIntsTempl : public ConfigOptionVector<int> class ConfigOptionIntsTempl : public ConfigOptionVector<int>
{ {
@@ -1838,6 +1982,8 @@ public:
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const { template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
if (this->nullable) { if (this->nullable) {
switch (this->type) { 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 coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; }
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();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 { template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
if (this->nullable) { if (this->nullable) {
switch (this->type) { 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 coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break; case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break; case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;

View File

@@ -68,6 +68,21 @@ std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& cus
return custom_tool_changes; 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 CustomGCode
} // namespace Slic3r } // namespace Slic3r

View File

@@ -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. // 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); 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 CustomGCode
} // namespace Slic3r } // namespace Slic3r

View File

@@ -105,7 +105,7 @@ struct EmbossShape
// Note: image is only cache it is not neccessary to store // Note: image is only cache it is not neccessary to store
// Store file data as plain string // 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("")); ar(path, path_in_3mf, (file_data != nullptr) ? *file_data : std::string(""));
} }
template<class Archive> void load(Archive &ar) { template<class Archive> void load(Archive &ar) {

View File

@@ -434,7 +434,7 @@ inline void expolygons_rotate(ExPolygons &expolys, double angle)
expoly.rotate(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) for (const ExPolygon &expoly : expolys)
if (expoly.contains(pt, border_result)) 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 ExPolygon &expoly);
bool has_duplicate_points(const ExPolygons &expolys); bool has_duplicate_points(const ExPolygons &expolys);
// Return True when erase some otherwise False.
bool remove_same_neighbor(ExPolygons &expolys); bool remove_same_neighbor(ExPolygons &expolys);
bool remove_sticks(ExPolygon &poly); bool remove_sticks(ExPolygon &poly);
void keep_largest_contour_only(ExPolygons &polygons); void keep_largest_contour_only(ExPolygons &polygons);

View File

@@ -184,6 +184,10 @@ public:
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } 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); } 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()); } 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: private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;

View File

@@ -697,11 +697,12 @@ void gcode_spread_points(
*/ */
float area_total = 0; float area_total = 0;
float volume_total = 0; float volume_total = 0;
size_t n_cells = 0;
#if 0
float volume_excess = 0; float volume_excess = 0;
float volume_deficit = 0; float volume_deficit = 0;
size_t n_cells = 0;
float area_circle_total = 0; float area_circle_total = 0;
#if 0
// The intermediate lines. // The intermediate lines.
for (int j = row_first; j < row_last; ++ j) { for (int j = row_first; j < row_last; ++ j) {
const std::pair<float, float> &span1 = spans[j]; const std::pair<float, float> &span1 = spans[j];
@@ -755,7 +756,9 @@ void gcode_spread_points(
cell.volume = acc[j][i]; cell.volume = acc[j][i];
cell.area = mask[j][i]; cell.area = mask[j][i];
assert(cell.area >= 0.f && cell.area <= 1.000001f); assert(cell.area >= 0.f && cell.area <= 1.000001f);
#if 0
area_circle_total += area; area_circle_total += area;
#endif
if (cell.area < area) if (cell.area < area)
cell.area = area; cell.area = area;
cell.fraction_covered = std::clamp((cell.area > 0) ? (area / cell.area) : 0, 0.f, 1.f); 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; float cell_height = cell.volume / cell.area;
cell.excess_height = cell_height - height_target; cell.excess_height = cell_height - height_target;
#if 0
area_circle_total += area;
if (cell.excess_height > 0.f) if (cell.excess_height > 0.f)
volume_excess += cell.excess_height * cell.area * cell.fraction_covered; volume_excess += cell.excess_height * cell.area * cell.fraction_covered;
else else
volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered; volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered;
#endif
volume_total += cell.volume * cell.fraction_covered; volume_total += cell.volume * cell.fraction_covered;
area_total += cell.area * cell.fraction_covered; area_total += cell.area * cell.fraction_covered;
} }

View File

@@ -17,8 +17,14 @@
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include "FillEnsuring.hpp" #include "FillEnsuring.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
//w21
#include "../ShortestPath.hpp"
//w11 //w11
#include "LayerRegion.hpp"
#define NARROW_INFILL_AREA_THRESHOLD 3 #define NARROW_INFILL_AREA_THRESHOLD 3
#define NARROW_INFILL_AREA_THRESHOLD_MIN 0.5
namespace Slic3r { namespace Slic3r {
//static constexpr const float NarrowInfillAreaThresholdMM = 3.f; //static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
@@ -112,12 +118,16 @@ struct SurfaceFill {
Surface surface; Surface surface;
ExPolygons expolygons; ExPolygons expolygons;
SurfaceFillParams params; SurfaceFillParams params;
//w21
std::vector<size_t> region_id_group;
ExPolygons no_overlap_expolygons;
}; };
//w11 //w11
static bool is_narrow_infill_area(const ExPolygon &expolygon) static bool is_narrow_infill_area(const ExPolygon &expolygon)
{ {
ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD)); 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 true;
return false; return false;
@@ -219,8 +229,20 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
fill.region_id = region_id; fill.region_id = region_id;
fill.surface = surface; fill.surface = surface;
fill.expolygons.emplace_back(std::move(fill.surface.expolygon)); fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
} else //w21
fill.expolygons.emplace_back(surface.expolygon); 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. // 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 //w11
if (layer.object()->config().detect_narrow_internal_solid_infill) { 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) if (surface_fills[i].surface.surface_type != stInternalSolid)
continue; 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; std::vector<size_t> narrow_expolygons_index;
narrow_expolygons_index.reserve(expolygons_size); narrow_expolygons_index.reserve(expolygons_size);
for (size_t j = 0; j < expolygons_size; j++) for (size_t j = 0; j < expolygons_size; j++)
if (is_narrow_infill_area(surface_fills[i].expolygons[j])) if (is_narrow_infill_area(surface_fills[i].expolygons[j]))
narrow_expolygons_index.push_back(j); narrow_expolygons_index.push_back(j);
if (narrow_expolygons_index.size() == 0) { if (narrow_expolygons_index.size() == expolygons_size) {
continue;
} else if (narrow_expolygons_index.size() == expolygons_size) {
// w11
// w14
surface_fills[i].params.pattern = ipConcentricInternal; surface_fills[i].params.pattern = ipConcentricInternal;
} else { } else {
params = surface_fills[i].params; surface_fills[i].params.pattern = ipEnsuring;
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]);
}
} }
//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; 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 Slic3r::BoundingBox bbox = this->object()->bounding_box();
const auto resolution = this->object()->print()->config().gcode_resolution.value; const auto resolution = this->object()->print()->config().gcode_resolution.value;
const auto perimeter_generator = this->object()->config().perimeter_generator; 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 #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.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution; params.resolution = resolution;
//w14 //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; params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) { for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing; 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); surface_fill.surface.expolygon = std::move(expoly);
Polylines polylines; Polylines polylines;
ThickPolylines thick_polylines; ThickPolylines thick_polylines;
try { //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;
try {
if (params.use_arachne) { 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); 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());
} }
//w14 else {
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);
} else
f->overlap = 0;
polylines = f->fill_surface(&surface_fill.surface, params); polylines = f->fill_surface(&surface_fill.surface, params);
} }
} catch (InfillFailedException &) { } catch (InfillFailedException &) {
@@ -628,6 +638,48 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
ExtrusionAttributes{ surface_fill.params.extrusion_role, ExtrusionAttributes{ surface_fill.params.extrusion_role,
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() } 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); 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())); 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); assert(dynamic_cast<const ExtrusionEntityCollection*>(e) != nullptr);
#endif #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 Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
{ {

View File

@@ -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_

View File

@@ -94,6 +94,8 @@ public:
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring). // PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
const PrintConfig *print_config = nullptr; const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr; const PrintObjectConfig *print_object_config = nullptr;
//w21
ExPolygons no_overlap_expolygons;
public: public:
virtual ~Fill() {} virtual ~Fill() {}

View File

@@ -5,6 +5,8 @@
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include <libslic3r/ShortestPath.hpp>
namespace Slic3r { namespace Slic3r {
void FillConcentric::_fill_surface_single( void FillConcentric::_fill_surface_single(
@@ -116,6 +118,7 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
} }
if (j < thick_polylines_out.size()) if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
//reorder_by_shortest_traverse(thick_polylines_out);
} else { } else {
Polylines polylines; Polylines polylines;
this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines); this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines);

View File

@@ -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 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"; 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* MODEL_TAG = "model";
static constexpr const char* RESOURCES_TAG = "resources"; static constexpr const char* RESOURCES_TAG = "resources";
static constexpr const char* OBJECT_TAG = "object"; 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* V1_ATTR = "v1";
static constexpr const char* V2_ATTR = "v2"; static constexpr const char* V2_ATTR = "v2";
static constexpr const char* V3_ATTR = "v3"; 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* OBJECTID_ATTR = "objectid";
static constexpr const char* TRANSFORM_ATTR = "transform"; static constexpr const char* TRANSFORM_ATTR = "transform";
static constexpr const char* PRINTABLE_ATTR = "printable"; static constexpr const char* PRINTABLE_ATTR = "printable";
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; 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* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value"; 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_STYLE_ATTR = "style";
static constexpr const char *FONT_WEIGHT_ATTR = "weight"; 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_TAG = "slic3rpe:shape";
static constexpr const char *SHAPE_SCALE_ATTR = "scale"; static constexpr const char *SHAPE_SCALE_ATTR = "scale";
static constexpr const char *UNHEALED_ATTR = "unhealed"; static constexpr const char *UNHEALED_ATTR = "unhealed";
@@ -182,6 +189,7 @@ static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf";
// EmbossProjection // EmbossProjection
static constexpr const char *DEPTH_ATTR = "depth"; static constexpr const char *DEPTH_ATTR = "depth";
static constexpr const char *USE_SURFACE_ATTR = "use_surface"; 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 unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] = const char* VALID_OBJECT_TYPES[] =
{ {
@@ -327,18 +335,20 @@ namespace Slic3r {
class _3MF_Importer : public _3MF_Base class _3MF_Importer : public _3MF_Base
{ {
typedef std::pair<std::string, int> PathId;
struct Component struct Component
{ {
int object_id; PathId object_id;
std::string path;
Transform3d transform; Transform3d transform;
explicit Component(int object_id) explicit Component(PathId object_id)
: object_id(object_id) : object_id(object_id)
, transform(Transform3d::Identity()) , transform(Transform3d::Identity())
{ {
} }
Component(int object_id, const Transform3d& transform) Component(PathId object_id, const Transform3d &transform)
: object_id(object_id) : object_id(object_id)
, transform(transform) , transform(transform)
{ {
@@ -353,7 +363,7 @@ namespace Slic3r {
std::vector<Vec3i> triangles; std::vector<Vec3i> triangles;
std::vector<std::string> custom_supports; std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam; 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(); } bool empty() { return vertices.empty() || triangles.empty(); }
@@ -362,7 +372,7 @@ namespace Slic3r {
triangles.clear(); triangles.clear();
custom_supports.clear(); custom_supports.clear();
custom_seam.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. // 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<PathId, int> IdToModelObjectMap;
typedef std::map<int, ComponentsList> IdToAliasesMap; typedef std::map<PathId, ComponentsList> IdToAliasesMap;
typedef std::vector<Instance> InstancesList; typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap; 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, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap; typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap; typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
@@ -501,6 +511,8 @@ namespace Slic3r {
std::string m_curr_metadata_name; std::string m_curr_metadata_name;
std::string m_curr_characters; std::string m_curr_characters;
std::string m_name; std::string m_name;
std::string m_start_part_path;
std::string m_model_path;
public: public:
_3MF_Importer(); _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 _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 _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_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_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); 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); 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); 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 // handlers to parse the .model file
void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_start_model_xml_element(const char* name, const char** attributes);
void _handle_end_model_xml_element(const char* name); 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_text_configuration(const char** attributes, unsigned int num_attributes);
bool _handle_start_shape_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); 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); 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 // 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_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); 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_sla_support_points.clear();
m_curr_metadata_name.clear(); m_curr_metadata_name.clear();
m_curr_characters.clear(); m_curr_characters.clear();
m_start_part_path = MODEL_FILE; // set default value for invalid .rel file
clear_errors(); clear_errors();
return _load_model_from_file(filename, model, config, config_substitutions); return _load_model_from_file(filename, model, config, config_substitutions);
@@ -697,16 +716,28 @@ namespace Slic3r {
m_name = boost::filesystem::path(filename).stem().string(); 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) { for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) { if (mz_zip_reader_file_stat(&archive, i, &stat)) {
std::string name(stat.m_filename); std::string name(stat.m_filename);
std::replace(name.begin(), name.end(), '\\', '/'); std::replace(name.begin(), name.end(), '\\', '/');
if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) {
try // valid model name -> extract model
{ m_model_path = "/" + name;
// valid model name -> extract model if (m_model_path == m_start_part_path) {
start_part_stat = stat;
continue;
}
try {
if (!_extract_model_from_archive(archive, stat)) { if (!_extract_model_from_archive(archive, stat)) {
close_zip_reader(&archive); close_zip_reader(&archive);
add_error("Archive does not contain a valid model"); add_error("Archive does not contain a valid model");
@@ -719,9 +750,31 @@ namespace Slic3r {
close_zip_reader(&archive); close_zip_reader(&archive);
throw Slic3r::FileIOError(e.what()); 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 // we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i) { for (mz_uint i = 0; i < num_entries; ++i) {
@@ -855,7 +908,7 @@ namespace Slic3r {
ObjectMetadata::VolumeMetadataList volumes; ObjectMetadata::VolumeMetadataList volumes;
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; 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()) { if (obj_metadata != m_objects_metadata.end()) {
// config data has been found, this model was saved using slic3r pe // 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 // We support our 3mf contains only configuration without mesh,
// model.adjust_min_z(); // 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; 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) void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
{ {
if (m_xml_parser == nullptr) if (m_xml_parser == nullptr)
@@ -1640,6 +1773,8 @@ namespace Slic3r {
bool _3MF_Importer::_handle_end_model() bool _3MF_Importer::_handle_end_model()
{ {
if (!m_model_path.empty())
return true;
// deletes all non-built or non-instanced objects // deletes all non-built or non-instanced objects
for (const IdToModelObjectMap::value_type& object : m_objects) { for (const IdToModelObjectMap::value_type& object : m_objects) {
if (object.second >= int(m_model->objects.size())) { if (object.second >= int(m_model->objects.size())) {
@@ -1708,6 +1843,7 @@ namespace Slic3r {
bool _3MF_Importer::_handle_end_object() bool _3MF_Importer::_handle_end_object()
{ {
if (m_curr_object.object != nullptr) { if (m_curr_object.object != nullptr) {
PathId object_id{m_model_path, m_curr_object.id};
if (m_curr_object.geometry.empty()) { if (m_curr_object.geometry.empty()) {
// no geometry defined // no geometry defined
// remove the object from the model // remove the object from the model
@@ -1715,26 +1851,26 @@ namespace Slic3r {
if (m_curr_object.components.empty()) { if (m_curr_object.components.empty()) {
// no components defined -> invalid object, delete it // 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()) if (object_item != m_objects.end())
m_objects.erase(object_item); 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()) if (alias_item != m_objects_aliases.end())
m_objects_aliases.erase(alias_item); m_objects_aliases.erase(alias_item);
} }
else else
// adds components to aliases // 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 { else {
// geometry defined, store it for later use // 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 // stores the object for later use
if (m_objects.find(m_curr_object.id) == m_objects.end()) { if (m_objects.find(object_id) == m_objects.end()) {
m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); m_objects.insert({ object_id, m_curr_object.model_object_idx });
m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself m_objects_aliases.insert({object_id, {1, Component(object_id)}}); // aliases itself
} }
else { else {
add_error("Found object with duplicate id"); 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_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.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; return true;
} }
@@ -1845,19 +1981,22 @@ namespace Slic3r {
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) 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); 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)); 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()) { 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()) { if (alias_item == m_objects_aliases.end()) {
add_error("Found component with invalid object id"); add_error("Found component with invalid object id");
return false; return false;
} }
} }
m_curr_object.components.emplace_back(object_id, transform); m_curr_object.components.emplace_back(path_id, transform);
return true; return true;
} }
@@ -1891,9 +2030,11 @@ namespace Slic3r {
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); 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)); 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); 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() bool _3MF_Importer::_handle_end_item()
@@ -2049,7 +2190,7 @@ namespace Slic3r {
return true; 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; static const unsigned int MAX_RECURSIONS = 10;
@@ -2310,41 +2451,30 @@ namespace Slic3r {
if (has_transform) if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); 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->supported_facets.reserve(triangles_count);
volume->seam_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) { for (size_t i=0; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i; size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size()); assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.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()) if (! geometry.custom_supports[index].empty())
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty()) if (! geometry.custom_seam[index].empty())
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
if (! geometry.mmu_segmentation[index].empty()) if (! geometry.mm_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]);
} }
volume->supported_facets.shrink_to_fit(); volume->supported_facets.shrink_to_fit();
volume->seam_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()) if (auto &es = volume_data.shape_configuration; es.has_value())
volume->emboss_shape = std::move(es); volume->emboss_shape = std::move(es);
if (auto &tc = volume_data.text_configuration; tc.has_value()) if (auto &tc = volume_data.text_configuration; tc.has_value())
volume->text_configuration = std::move(tc); 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 // apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata) { for (const Metadata& metadata : volume_data.metadata) {
@@ -3003,12 +3133,12 @@ namespace Slic3r {
output_buffer += "\""; output_buffer += "\"";
} }
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i);
if (! mmu_painting_data_string.empty()) { if (! mm_painting_data_string.empty()) {
output_buffer += " "; output_buffer += " ";
output_buffer += MMU_SEGMENTATION_ATTR; output_buffer += MM_SEGMENTATION_ATTR;
output_buffer += "=\""; output_buffer += "=\"";
output_buffer += mmu_painting_data_string; output_buffer += mm_painting_data_string;
output_buffer += "\""; output_buffer += "\"";
} }
@@ -3424,7 +3554,6 @@ namespace Slic3r {
if (const std::optional<EmbossShape> &es = volume->emboss_shape; if (const std::optional<EmbossShape> &es = volume->emboss_shape;
es.has_value()) es.has_value())
to_xml(stream, *es, *volume, archive); to_xml(stream, *es, *volume, archive);
// stores volume's text data
if (const std::optional<TextConfiguration> &tc = volume->text_configuration; if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
tc.has_value()) tc.has_value())
TextConfigurationSerialization::to_xml(stream, *tc); 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. // All import should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter; CNumericLocalesSetter locales_setter;
_3MF_Importer importer; _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(); importer.log_errors();
handle_legacy_project_loaded(importer.version(), config, importer.qidislicer_generator_version()); 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) 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){ 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); 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()) if (horizontal_align_str.empty())
return FontProp::HorizontalAlign::center; 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) { if (horizontal_align_str.length() == 1) {
int horizontal_align_int = 0; 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)) 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); 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){ 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); 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()) if (vertical_align_str.empty())
return FontProp::VerticalAlign::center; 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); 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) std::optional<TextConfiguration> TextConfigurationSerialization::read(const char **attributes, unsigned int num_attributes)
{ {

View File

@@ -41,7 +41,8 @@ namespace {
std::string to_ini(const ConfMap &m) std::string to_ini(const ConfMap &m)
{ {
std::string ret; std::string ret;
for (auto &param : m) ret += param.first + " = " + param.second + "\n"; for (auto &param : m)
ret += param.first + " = " + param.second + "\n";
return ret; return ret;
} }

View File

@@ -1,6 +1,5 @@
// Original implementation of STEP format import created by Bambulab. // Original implementation of STEP format import created by Bambulab.
// https://github.com/bambulab/BambuStudio // https://github.com/bambulab/BambuStudio
// Forked off commit 1555904, modified by QIDI Research.
#ifndef slic3r_Format_STEP_hpp_ #ifndef slic3r_Format_STEP_hpp_
#define slic3r_Format_STEP_hpp_ #define slic3r_Format_STEP_hpp_

View File

@@ -135,7 +135,7 @@ std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
// as output argument then replace it with the readed profile to report // as output argument then replace it with the readed profile to report
// that it was empty. // that it was empty.
profile_use = profile_in.empty() ? profile_out : profile_in; 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)}; return {profile_use, std::move(config_substitutions)};
} }

File diff suppressed because it is too large Load Diff

View File

@@ -24,8 +24,9 @@
#include "GCode/WipeTowerIntegration.hpp" #include "GCode/WipeTowerIntegration.hpp"
#include "GCode/SeamPlacer.hpp" #include "GCode/SeamPlacer.hpp"
#include "GCode/GCodeProcessor.hpp" #include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp"
#include "GCode/ThumbnailData.hpp" #include "GCode/ThumbnailData.hpp"
#include "GCode/Travels.hpp"
#include "EdgeGrid.hpp"
#include "tcbspan/span.hpp" #include "tcbspan/span.hpp"
#include <memory> #include <memory>
@@ -38,6 +39,7 @@ namespace Slic3r {
// Forward declarations. // Forward declarations.
class GCodeGenerator; class GCodeGenerator;
struct WipeTowerData;
namespace { struct Item; } namespace { struct Item; }
struct PrintInstance; struct PrintInstance;
@@ -75,136 +77,33 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; } static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
}; };
namespace GCode::Impl { namespace GCode {
struct DistancedPoint { // Object and support extrusions of the same PrintObject at the same print_z.
Point point; // public, so that it could be accessed by free helper functions from GCode.cpp
double distance_from_start; 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; }
}; };
/** struct PrintObjectInstance
* @brief Takes a path described as a list of points and adds points to it. {
* const PrintObject *print_object = nullptr;
* @param xy_path A list of points describing a path in xy. int instance_idx = -1;
* @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);
/** bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; }
* @brief Take xy_path and genrate a travel acording to elevation. bool operator!=(const PrintObjectInstance &other) const { return !(*this == other); }
*
* @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;
}; };
}
} // namespace GCode
class GCodeGenerator { class GCodeGenerator {
public: public:
GCodeGenerator() : GCodeGenerator(const Print* print = nullptr); // The default value is only used in unit tests.
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() = default; ~GCodeGenerator() = default;
// throws std::runtime_exception on error, // throws std::runtime_exception on error,
@@ -215,13 +114,25 @@ public:
const Vec2d& origin() const { return m_origin; } const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf); void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } 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. // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
template<typename Derived> template<typename Derived>
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const { Eigen::Matrix<double, Derived::SizeAtCompileTime, 1, Eigen::DontAlign> 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"); 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 return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
- m_config.extruder_offset.get_at(m_writer.extruder()->id()); - 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. // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
template<typename Derived> 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); 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; } 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 set_layer_count(unsigned int value) { m_layer_count = value; }
void apply_print_config(const PrintConfig &print_config); void apply_print_config(const PrintConfig &print_config);
@@ -252,18 +161,10 @@ public:
// translate full config into a list of <key, value> items // 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); 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. using ObjectLayerToPrint = GCode::ObjectLayerToPrint;
// public, so that it could be accessed by free helper functions from GCode.cpp using ObjectsLayerToPrint = std::vector<GCode::ObjectLayerToPrint>;
struct ObjectLayerToPrint
{ std::optional<Point> last_position;
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>;
private: private:
class GCodeOutputStream { class GCodeOutputStream {
@@ -309,6 +210,15 @@ private:
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print); 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( LayerResult process_layer(
const Print &print, const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z. // 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, const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream); 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); void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble(); 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( std::string change_layer(
coordf_t previous_layer_z, coordf_t previous_layer_z,
coordf_t print_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_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.); 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 extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
std::string generate_travel_gcode( std::string generate_travel_gcode(
const Points3& travel, const Points3& travel,
const std::string& comment const std::string& comment,
const std::function<std::string()>& insert_gcode
); );
Polyline generate_travel_xy_path( Polyline generate_travel_xy_path(
const Point& start, const Point& start,
@@ -414,8 +318,15 @@ private:
const bool needs_retraction, const bool needs_retraction,
bool& could_be_wipe_disabled 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); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
//B41 //B41
@@ -426,7 +337,7 @@ private:
int unique_id; int unique_id;
}; };
std::unordered_map<const PrintInstance *, LabelData> m_label_data; 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 unretract() { return m_writer.unretract(); }
std::string set_extruder(unsigned int extruder_id, double print_z); std::string set_extruder(unsigned int extruder_id, double print_z);
bool line_distancer_is_required(const std::vector<unsigned int>& extruder_ids); bool line_distancer_is_required(const std::vector<unsigned int>& extruder_ids);
@@ -446,7 +357,7 @@ private:
struct PlaceholderParserIntegration { struct PlaceholderParserIntegration {
void reset(); void reset();
void init(const GCodeWriter &config); 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(); void validate_output_vector_variables();
PlaceholderParser parser; PlaceholderParser parser;
@@ -479,6 +390,7 @@ private:
AvoidCrossingPerimeters m_avoid_crossing_perimeters; AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs; JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
GCode::TravelObstacleTracker m_travel_obstacle_tracker;
bool m_enable_loop_clipping; bool m_enable_loop_clipping;
// If enabled, the G-code generator will put following comments at the ends // 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 // 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. // In non-sequential mode, all its copies will be printed.
const Layer* m_layer; const Layer* m_layer;
// m_layer is an object layer and it is being printed over raft surface. // 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; bool m_object_layer_over_raft;
double m_volumetric_speed; double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active? // Support for the extrusion role markers. Which marker is active?
@@ -512,8 +423,15 @@ private:
double m_last_mm3_per_mm; double m_last_mm3_per_mm;
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
Point m_last_pos; std::optional<Vec3d> m_previous_layer_last_position;
bool m_last_pos_defined; 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<CoolingBuffer> m_cooling_buffer;
std::unique_ptr<SpiralVase> m_spiral_vase; std::unique_ptr<SpiralVase> m_spiral_vase;
@@ -527,19 +445,23 @@ private:
bool m_brim_done; bool m_brim_done;
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
bool m_second_layer_things_done; bool m_second_layer_things_done;
// Index of a last object copy extruded. // G-code that is due to be written before the next extrusion
std::pair<const PrintObject*, Point> m_last_obj_copy; 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; bool m_silent_time_estimator_enabled;
// Processor // Processor
GCodeProcessor m_processor; GCodeProcessor m_processor;
// Back-pointer to Print (const).
const Print* m_print;
std::string _extrude( std::string _extrude(
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1); 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_machine_envelope(GCodeOutputStream &file, const 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_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, 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. // On the first printing layer. This flag triggers first layer speeds.
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }

View File

@@ -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). // 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. // 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); 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 Point end = point + scaled_origin;
const Line travel(start, end); 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. // 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). // 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. // Otherwise perform the path planning in the coordinate system of the active object.

View File

@@ -17,11 +17,10 @@ class AvoidCrossingPerimeters
public: public:
// Routing around the objects vs. inside a single object. // 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(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 use_external_mp_once; }
bool used_external_mp_once() { return m_use_external_mp_once; }
void disable_once() { m_disabled_once = true; } void disable_once() { m_disabled_once = true; }
bool disabled_once() const { return m_disabled_once; } 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); void init_layer(const Layer &layer);
@@ -50,10 +49,10 @@ public:
} }
}; };
// just for the next travel move
bool use_external_mp_once { false };
private: private:
bool m_use_external_mp { false }; 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 // this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print // we enable it by default for the first travel move in print
bool m_disabled_once { true }; bool m_disabled_once { true };

View File

@@ -143,6 +143,14 @@ std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attri
float external_perim_reference_speed, float external_perim_reference_speed,
float default_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()); assert(attributes.overhang_attributes.has_value());
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = { std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
{100, ConfigOptionFloatOrPercent{default_speed, false}}}; {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), 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)); interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
//w19
float curled_base_speed = interpolate_speed(speed_sections, float curled_base_speed = interpolate_speed(speed_sections,
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines); attributes.width * attributes.overhang_attributes->proximity_to_curled_lines/tan(67.5));
float final_speed = std::min(curled_base_speed, extrusion_speed); 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), 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)); interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));

View File

@@ -54,6 +54,9 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
"HEIGHT:", "HEIGHT:",
"WIDTH:", "WIDTH:",
"LAYER_CHANGE", "LAYER_CHANGE",
"LAYER_CHANGE_TRAVEL",
"LAYER_CHANGE_RETRACTION_START",
"LAYER_CHANGE_RETRACTION_END",
"COLOR_CHANGE", "COLOR_CHANGE",
"PAUSE_PRINT", "PAUSE_PRINT",
"CUSTOM_GCODE", "CUSTOM_GCODE",
@@ -3803,7 +3806,13 @@ void GCodeProcessor::post_process()
struct LineData struct LineData
{ {
std::string line; 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 #ifndef NDEBUG
@@ -3833,10 +3842,10 @@ void GCodeProcessor::post_process()
#endif // NDEBUG #endif // NDEBUG
EWriteType m_write_type{ EWriteType::BySize }; EWriteType m_write_type{ EWriteType::BySize };
// Time machine containing g1 times cache // Time machines containing g1 times cache
TimeMachine& m_machine; const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
// Current time // 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 // Current size in bytes
size_t m_size{ 0 }; size_t m_size{ 0 };
@@ -3852,11 +3861,12 @@ void GCodeProcessor::post_process()
bgcode::binarize::Binarizer& m_binarizer; bgcode::binarize::Binarizer& m_binarizer;
public: 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 #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 #else
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {} : m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
#endif // NDEBUG #endif // NDEBUG
// return: number of internal G1 lines (from G2/G3 splitting) processed // return: number of internal G1 lines (from G2/G3 splitting) processed
@@ -3873,9 +3883,9 @@ void GCodeProcessor::post_process()
else else
return ret; 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; 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; ++it;
++m_times_cache_id; ++m_times_cache_id;
} }
@@ -3885,7 +3895,7 @@ void GCodeProcessor::post_process()
// search for internal G1 lines // search for internal G1 lines
if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) { 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; ++it;
++m_times_cache_id; ++m_times_cache_id;
++g1_lines_counter; ++g1_lines_counter;
@@ -3893,14 +3903,17 @@ void GCodeProcessor::post_process()
} }
} }
if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter) if (it != m_machines[Normal].g1_times_cache.end() && it->id == g1_lines_counter) {
m_time = it->elapsed_time; 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; return ret;
} }
// add the given gcode line to the cache // add the given gcode line to the cache
void append_line(const std::string& line) { void append_line(const std::string& line) {
m_lines.push_back({ line, m_time }); m_lines.push_back({ line, m_times });
#ifndef NDEBUG #ifndef NDEBUG
m_statistics.add_line(line.length()); m_statistics.add_line(line.length());
#endif // NDEBUG #endif // NDEBUG
@@ -3911,7 +3924,8 @@ void GCodeProcessor::post_process()
} }
// Insert the gcode lines required by the command cmd by backtracing into the cache // 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) { std::function<std::string(const std::string&)> line_replacer) {
assert(!m_lines.empty()); assert(!m_lines.empty());
const float time_step = backtrace.time_step(); 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 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) { for (unsigned int i = 0; i < backtrace.steps; ++i) {
const float backtrace_time_i = (i + 1) * time_step; 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 rev_it = m_lines.rbegin() + rev_it_dist;
auto start_rev_it = rev_it; auto start_rev_it = rev_it;
std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line); std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
// backtrace into the cache to find the place where to insert the 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->line = line_replacer(rev_it->line);
++rev_it; ++rev_it;
if (rev_it != m_lines.rend()) if (rev_it != m_lines.rend())
@@ -3937,11 +3951,15 @@ void GCodeProcessor::post_process()
break; break;
// insert the line for the current step // insert the line for the current step
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->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->time; last_time_insertion = rev_it->times[Normal];
const std::string out_line = line_inserter(i + 1, last_time_insertion, m_time - last_time_insertion); 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; 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 #ifndef NDEBUG
m_statistics.add_line(out_line.length()); m_statistics.add_line(out_line.length());
#endif // NDEBUG #endif // NDEBUG
@@ -3967,7 +3985,7 @@ void GCodeProcessor::post_process()
std::string out_string; std::string out_string;
if (!m_lines.empty()) { if (!m_lines.empty()) {
if (m_write_type == EWriteType::ByTime) { 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(); const LineData& data = m_lines.front();
out_string += data.line; out_string += data.line;
m_size -= data.line.length(); 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 // replace placeholder lines with the proper final value
// gcode_line is in/out parameter, to reduce expensive memory allocation // 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, export_lines.insert_lines(backtrace, cmd,
// line inserter // line inserter
[tool_number, this](unsigned int id, float time, float time_diff) { [tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]); const 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"; 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; return out;
}, },
// line replacer // line replacer

View File

@@ -185,6 +185,9 @@ namespace Slic3r {
Height, Height,
Width, Width,
Layer_Change, Layer_Change,
Layer_Change_Travel,
Layer_Change_Retraction_Start,
Layer_Change_Retraction_End,
Color_Change, Color_Change,
Pause_Print, Pause_Print,
Custom_Code, Custom_Code,

View File

@@ -316,9 +316,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con
return w.string(); 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; GCodeG1Formatter w;
w.emit_xy(point); w.emit_xy(point);
@@ -330,6 +329,11 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view
return w.string(); 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) 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.); 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(); 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) { if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) {
return this->travel_to_z(point.z(), comment); return this->travel_to_z(to.z(), comment);
} else if (std::abs(point.z() - m_pos.z()) < EPSILON) { } else if (std::abs(to.z() - m_pos.z()) < EPSILON) {
return this->travel_to_xy(point.head<2>(), comment); return this->travel_to_xy(to.head<2>(), comment);
} else { } 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; GCodeG1Formatter w;
w.emit_xyz(point); w.emit_xyz(to);
Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
w.emit_f(speed.norm() * 60.0); 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); w.emit_comment(this->config.gcode_comments, comment);
return w.string(); return w.string();
} }
}
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) 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; double speed = this->config.travel_speed_z.value;
if (speed == 0.) if (speed == 0.)

View File

@@ -66,10 +66,21 @@ public:
std::string toolchange_prefix() const; std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id); 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 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(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_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 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(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); 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) }; } { return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
static Vec3d quantize(const Vec3d &pt) 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) }; } { 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); 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) { 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(); ptr_err.ptr += s.size();
} }

View File

@@ -1,10 +1,12 @@
#include "LabelObjects.hpp" #include "LabelObjects.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "GCode/GCodeWriter.hpp"
#include "Model.hpp" #include "Model.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "TriangleMeshSlicer.hpp" #include "TriangleMeshSlicer.hpp"
#include "boost/algorithm/string/replace.hpp"
namespace Slic3r::GCode { namespace Slic3r::GCode {
@@ -39,10 +41,10 @@ Polygon instance_outline(const PrintInstance* pi)
}; // anonymous namespace }; // 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_label_objects_style = label_object_style;
m_flavor = print.config().gcode_flavor; m_flavor = gcode_flavor;
if (m_label_objects_style == LabelObjectsStyle::Disabled) if (m_label_objects_style == LabelObjectsStyle::Disabled)
return; return;
@@ -51,7 +53,7 @@ void LabelObjects::init(const Print& print)
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which // Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
// belong to the same ModelObject. // belong to the same ModelObject.
for (const PrintObject* po : print.objects()) for (const PrintObject* po : objects)
for (const PrintInstance& pi : po->instances()) for (const PrintInstance& pi : po->instances())
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi); 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) if (object_has_more_instances)
name += " (Instance " + std::to_string(instance_id) + ")"; name += " (Instance " + std::to_string(instance_id) + ")";
if (m_flavor == gcfKlipper) { 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; }, '_'); 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; ++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 std::string LabelObjects::all_objects_header() const
{ {
@@ -101,38 +160,34 @@ std::string LabelObjects::all_objects_header() const
std::string out; 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"; out += "\n";
for (const auto& [print_instance, label] : label_data_sorted) { for (const LabelData& label : m_label_data) {
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) { if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper)
char buffer[64]; out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "' CENTER=" + label.center + " POLYGON=" + label.polygon + "\n";
out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name; else {
Polygon outline = instance_outline(print_instance); out += start_object(*label.pi, IncludeName::Yes);
assert(! outline.empty()); out += stop_object(*label.pi);
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);
} }
} }
out += "\n"; out += "\n";
return out; 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 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) if (m_label_objects_style == LabelObjectsStyle::Disabled)
return std::string(); 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; std::string out;
if (m_label_objects_style == LabelObjectsStyle::Octoprint) if (m_label_objects_style == LabelObjectsStyle::Octoprint)
@@ -154,7 +209,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
} }
out += "\n"; out += "\n";
} else if (m_flavor == gcfKlipper) } else if (m_flavor == gcfKlipper)
out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n"; out += "EXCLUDE_OBJECT_START NAME='" + label.name + "'\n";
else { else {
// Not supported by / implemented for the other firmware flavors. // 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) if (m_label_objects_style == LabelObjectsStyle::Disabled)
return std::string(); 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; std::string out;
if (m_label_objects_style == LabelObjectsStyle::Octoprint) 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) if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware)
out += std::string("M486 S-1\n"); out += std::string("M486 S-1\n");
else if (m_flavor ==gcfKlipper) else if (m_flavor ==gcfKlipper)
out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n"; out += "EXCLUDE_OBJECT_END NAME='" + label.name + "'\n";
else { else {
// Not supported by / implemented for the other firmware flavors. // Not supported by / implemented for the other firmware flavors.
} }

View File

@@ -2,7 +2,9 @@
#define slic3r_GCode_LabelObjects_hpp_ #define slic3r_GCode_LabelObjects_hpp_
#include <string> #include <string>
#include <unordered_map> #include <vector>
#include "libslic3r/Print.hpp"
namespace Slic3r { namespace Slic3r {
@@ -11,30 +13,49 @@ enum class LabelObjectsStyle;
struct PrintInstance; struct PrintInstance;
class Print; class Print;
class GCodeWriter;
namespace GCode { namespace GCode {
class LabelObjects
class LabelObjects { {
public: 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 { enum class IncludeName {
No, No,
Yes 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 start_object(const PrintInstance& print_instance, IncludeName include_name) const;
std::string stop_object(const PrintInstance& print_instance) const; std::string stop_object(const PrintInstance& print_instance) const;
private: const PrintInstance* current_instance{nullptr};
struct LabelData { const PrintInstance* last_operation_instance{nullptr};
std::string name;
int unique_id;
};
LabelObjectsStyle m_label_objects_style; LabelObjectsStyle m_label_objects_style;
GCodeFlavor m_flavor; GCodeFlavor m_flavor;
std::unordered_map<const PrintInstance*, LabelData> m_label_data; std::vector<LabelData> m_label_data;
}; };

View File

@@ -1,6 +1,7 @@
#include <memory.h> #include <memory.h>
#include <cstring> #include <cstring>
#include <cfloat> #include <cfloat>
#include <algorithm>
#include "../libslic3r.h" #include "../libslic3r.h"
#include "../PrintConfig.hpp" #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. // affect how distant will be propagated a flow rate adjustment.
static constexpr int max_look_back_limit = 128; 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) 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. // 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; extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive;
} }
// Don't regulate the pressure before and after gap-fill and ironing. // Don't regulate the pressure before and after ironing.
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::GapFill, GCodeExtrusionRole::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)].negative = 0;
m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 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); 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) 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; buf.extruder_id = m_current_extruder;
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
adjust_volumetric_rate();
#ifdef PRESSURE_EQUALIZER_DEBUG #ifdef PRESSURE_EQUALIZER_DEBUG
++line_idx; ++line_idx;
#endif #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; 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; 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. // Nothing to do, the last move is not extruding.
return; return;
@@ -523,13 +592,13 @@ void PressureEqualizer::adjust_volumetric_rate()
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max()); 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; 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; 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()) if (!m_gcode_lines[idx_prev].extruding())
break; break;
// Don't decelerate before ironing and gap-fill. // Don't decelerate before ironing.
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) { if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
line_idx = idx_prev; line_idx = idx_prev;
continue; continue;
} }
@@ -549,7 +618,8 @@ void PressureEqualizer::adjust_volumetric_rate()
// Limit by the succeeding volumetric flow rate. // Limit by the succeeding volumetric flow rate.
rate_end = rate_succ; 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; rate_end = line.volumetric_extrusion_rate_end;
} else if (line.volumetric_extrusion_rate_end > rate_end) { } else if (line.volumetric_extrusion_rate_end > rate_end) {
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; 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.
// Don't store feed rate for ironing and gap-fill. if (line.extrusion_role != GCodeExtrusionRole::Ironing)
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start; 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); for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next);
if (!m_gcode_lines[idx_next].extruding()) if (!m_gcode_lines[idx_next].extruding())
break; break;
// Don't accelerate after ironing and gap-fill. // Don't accelerate after ironing.
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) { if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
line_idx = idx_next; line_idx = idx_next;
continue; continue;
} }
@@ -603,7 +672,8 @@ void PressureEqualizer::adjust_volumetric_rate()
continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited. continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited.
float rate_start = feedrate_per_extrusion_role[iRole]; 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; rate_start = line.volumetric_extrusion_rate_start;
} else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start) } else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start)
rate_start = rate_prec; rate_start = rate_prec;
@@ -627,9 +697,8 @@ void PressureEqualizer::adjust_volumetric_rate()
line.modified = true; 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
// Don't store feed rate for ironing and gap-fill. if (line.extrusion_role != GCodeExtrusionRole::Ironing)
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end; feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end;
} }
} }

View File

@@ -169,6 +169,8 @@ private:
bool extrude_end_tag = false; 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. // Output buffer will only grow. It will not be reallocated over and over.
std::vector<char> output_buffer; std::vector<char> output_buffer;
size_t output_buffer_length; size_t output_buffer_length;
@@ -182,9 +184,10 @@ private:
bool process_line(const char *line, const char *line_end, GCodeLine &buf); bool process_line(const char *line, const char *line_end, GCodeLine &buf);
void output_gcode_line(size_t line_idx); 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. // 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. // 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. // Push the text to the end of the output_buffer.
inline void push_to_output(GCodeG1Formatter &formatter); inline void push_to_output(GCodeG1Formatter &formatter);

View File

@@ -133,6 +133,11 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr
return distance; 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 &params) void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters &params)
{ {
double tolerance = params.tolerance; double tolerance = params.tolerance;

View File

@@ -33,6 +33,7 @@ std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &pa
// rather discard such a degenerate segment. // rather discard such a degenerate segment.
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold); double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold);
void reverse(SmoothPath &path);
class SmoothPathCache class SmoothPathCache
{ {
public: public:

View File

@@ -1,10 +1,21 @@
#include "SpiralVase.hpp" #include "SpiralVase.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include <sstream> #include <sstream>
#include <cmath>
#include <limits>
namespace Slic3r { 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: /* This post-processor relies on several assumptions:
- all layers are processed through it, including those that are not supposed - 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. // Get total XY length for this layer by summing all extrusion moves.
float total_layer_length = 0; float total_layer_length = 0.f;
float layer_height = 0; float layer_height = 0.f;
float z = 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. // Remove layer height from initial Z.
z -= layer_height; z -= layer_height;
std::string new_gcode; // FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances.
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
// For absolute extruder distances it will be switched off. // For absolute extruder distances it will be switched off.
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition // Tapering the absolute extruder distances requires to process every extrusion value after the first transition
// layer. // layer.
bool transition = m_transition_layer && m_config.use_relative_e_distances.value; const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
float layer_height_factor = layer_height / total_layer_length; 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; 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, &current_layer, &previous_layer_distancer]
(GCodeReader &reader, GCodeReader::GCodeLine line) { (GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) { if (line.cmd_is("G1")) {
if (line.has_z()) { if (line.has_z()) {
@@ -66,16 +83,52 @@ std::string SpiralVase::process_layer(const std::string &gcode)
line.set(reader, Z, z); line.set(reader, Z, z);
new_gcode += line.raw() + '\n'; new_gcode += line.raw() + '\n';
return; 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 { } else {
float dist_XY = line.dist_XY(reader); last_point = p;
if (dist_XY > 0) { }
// horizontal move }
if (line.extruding(reader)) {
len += dist_XY; if (emit_gcode_line)
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);
new_gcode += line.raw() + '\n'; new_gcode += line.raw() + '\n';
} }
return; 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 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 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 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'; 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;
} }
} }

View File

@@ -6,28 +6,38 @@
namespace Slic3r { namespace Slic3r {
class SpiralVase { class SpiralVase
{
public: 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.z() = (float)m_config.z_offset;
m_reader.apply_config(m_config); 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) { void enable(bool enable)
m_transition_layer = en && ! m_enabled; {
m_enabled = en; 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: private:
const PrintConfig &m_config; const PrintConfig &m_config;
GCodeReader m_reader; GCodeReader m_reader;
float m_max_xy_smoothing = 0.f;
bool m_enabled = false; bool m_enabled = false;
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height. // First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
bool m_transition_layer = false; 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;
}; };
} }

View File

@@ -81,7 +81,8 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb,
int count = 0; int count = 0;
for (const auto& [format, size] : thumbnails_list) { for (const auto& [format, size] : thumbnails_list) {
static constexpr const size_t max_row_length = 78; 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) for (const ThumbnailData& data : thumbnails)
if (data.is_valid()) { if (data.is_valid()) {
switch (format) { switch (format) {

View File

@@ -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); double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
// Collect extruders required to print the layers. // 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. // Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder); 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. // 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. // 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; std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
if (auto num_extruders = unsigned(print.config().nozzle_diameter.size()); 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 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) { 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). // 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. // 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); 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()) 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. // Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder); this->reorder_extruders(first_extruder);
@@ -214,8 +221,11 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
} }
// Collect extruders reuqired to print layers. // 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. // Collect the support extruders.
for (auto support_layer : object.support_layers()) { for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); 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. // Extruder overrides are ordered by print_z.
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override; std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin();
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
unsigned int extruder_override = 0; 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. // Collect the object extruders.
for (auto layer : object.layers()) { for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z); 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. // 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; 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? // What extruders are required to print this object layer?
for (const LayerRegion *layerm : layer->regions()) { for (const LayerRegion *layerm : layer->regions()) {
const PrintRegion &region = layerm->region(); const PrintRegion &region = layerm->region();
@@ -365,6 +383,11 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
std::swap(lt.extruders[i], lt.extruders.front()); std::swap(lt.extruders[i], lt.extruders.front());
break; 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(); last_extruder_id = lt.extruders.back();

View File

@@ -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 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. // If not overriden, it is set to 0.
unsigned int extruder_override = 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? // 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. // Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
bool has_skirt = false; bool has_skirt = false;
@@ -165,7 +168,7 @@ public:
private: private:
void initialize_layers(std::vector<coordf_t> &zs); 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 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); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
bool insert_wipe_tower_extruder(); bool insert_wipe_tower_extruder();

View 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 &params)
: 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 &current_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

View 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> &current_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 &params);
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_

View File

@@ -21,9 +21,9 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
if (config.wipe.get_at(id)) { if (config.wipe.get_at(id)) {
// Wipe length to extrusion ratio. // Wipe length to extrusion ratio.
const double xy_to_e = this->calc_xy_to_e_ratio(config, id); 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) 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) if (wipe_xy == 0)
@@ -32,30 +32,14 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
this->enable(wipe_xy); this->enable(wipe_xy);
} }
void Wipe::set_path(SmoothPath &&path, bool reversed) void Wipe::set_path(SmoothPath &&path) {
{
this->reset_path(); this->reset_path();
if (this->enabled() && ! path.empty()) { if (this->enabled() && ! path.empty()) {
if (reversed) { const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max);
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 {
m_path = std::move(path.front().path); m_path = std::move(path.front().path);
int64_t len = Geometry::ArcWelder::estimate_path_length(m_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()) if (it->path_attributes.role.is_bridge())
break; // Do not perform a wipe on bridges. break; // Do not perform a wipe on bridges.
assert(it->path.size() >= 2); 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()); m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
} }
} }
}
assert(m_path.empty() || m_path.size() > 1); assert(m_path.empty() || m_path.size() > 1);
} }
@@ -185,7 +168,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
return done; return done;
}; };
// Start with the current position, which may be different from the wipe path start in case of loop clipping. // 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(); auto it = this->path().begin();
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset); Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
++ it; ++ it;
@@ -216,7 +199,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
// add tag for processor // add tag for processor
assert(p == GCodeFormatter::quantize(p)); assert(p == GCodeFormatter::quantize(p));
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; 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);
} }
} }

View File

@@ -42,7 +42,7 @@ public:
if (this->enabled() && path.size() > 1) if (this->enabled() && path.size() > 1)
m_path = std::move(path); 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; } void offset_path(const Point &v) { m_offset += v; }
std::string wipe(GCodeGenerator &gcodegen, bool toolchange); std::string wipe(GCodeGenerator &gcodegen, bool toolchange);

View File

@@ -22,6 +22,16 @@
namespace Slic3r 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 class WipeTowerWriter
{ {
public: public:
@@ -102,6 +112,10 @@ public:
return *this; 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 // 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 // filament loading and cooling moves from normal extrusion moves. Therefore the writer
// is asked to suppres output of some lines, which look like extrusions. // 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); 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. // Elevate the extruder head above the current print_z position.
WipeTowerWriter& z_hop(float hop, float f = 0.f) WipeTowerWriter& z_hop(float hop, float f = 0.f)
{ {
@@ -326,6 +358,7 @@ public:
// Set extruder temperature, don't wait by default. // Set extruder temperature, don't wait by default.
WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false) 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"; m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
return *this; 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_rotation_angle(float(config.wipe_tower_rotation_angle)),
m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)), m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)),
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)), 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_y_shift(0.f),
m_z_pos(0.f), m_z_pos(0.f),
m_bridging(float(config.wipe_tower_bridging)), m_bridging(float(config.wipe_tower_bridging)),
m_no_sparse_layers(config.wipe_tower_no_sparse_layers), m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
m_gcode_flavor(config.gcode_flavor), m_gcode_flavor(config.gcode_flavor),
m_travel_speed(config.travel_speed), m_travel_speed(config.travel_speed),
m_travel_speed_z(config.travel_speed_z),
m_infill_speed(default_region_config.infill_speed), m_infill_speed(default_region_config.infill_speed),
m_perimeter_speed(default_region_config.perimeter_speed), m_perimeter_speed(default_region_config.perimeter_speed),
m_current_tool(initial_tool), 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_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. // Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
const std::vector<Vec2d>& bed_points = config.bed_shape.values; const std::vector<Vec2d>& bed_points = config.bed_shape.values;
BoundingBoxf bb(bed_points); 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].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].temperature = config.temperature.get_at(idx);
m_filpar[idx].first_layer_temperature = config.first_layer_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. // 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. // 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_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_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].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 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); toolchange_Wipe(writer, cleaning_box , 20.f);
box_coordinates box = cleaning_box; box_coordinates box = cleaning_box;
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); 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); cleaning_box.translate(prime_section_width, 0.f);
writer.travel(cleaning_box.ld, 7200); 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) for (const auto &b : m_layer_info->tool_changes)
if ( b.new_tool == tool ) { if ( b.new_tool == tool ) {
wipe_volume = b.wipe_volume; wipe_volume = b.wipe_volume;
wipe_area = b.required_depth * m_layer_info->extra_spacing; wipe_area = b.required_depth;
break; 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. // 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. if (tool != (unsigned int)-1){ // This is not the last change.
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, 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_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
toolchange_Load(writer, cleaning_box); toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road 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. toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
++ m_num_tool_changes; ++ m_num_tool_changes;
} else } 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; m_depth_traversed += wipe_area;
@@ -842,13 +881,14 @@ void WipeTower::toolchange_Unload(
WipeTowerWriter &writer, WipeTowerWriter &writer,
const box_coordinates &cleaning_box, const box_coordinates &cleaning_box,
const std::string& current_material, const std::string& current_material,
const int old_temperature,
const int new_temperature) const int new_temperature)
{ {
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
float xr = cleaning_box.rd.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 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); 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 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 do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming;
const bool cold_ramming = m_is_mk4mmu3;
if (do_ramming) { if (do_ramming) {
writer.travel(ramming_start_pos); // move to starting position writer.travel(ramming_start_pos); // move to starting position
if (! m_is_mk4mmu3)
writer.disable_linear_advance(); writer.disable_linear_advance();
if (cold_ramming)
writer.set_extruder_temp(old_temperature - 20);
} }
else else
writer.set_position(ramming_start_pos); writer.set_position(ramming_start_pos);
@@ -885,7 +929,7 @@ void WipeTower::toolchange_Unload(
if (tch.old_tool == m_current_tool) { if (tch.old_tool == m_current_tool) {
sum_of_depths += tch.ramming_depth; sum_of_depths += tch.ramming_depth;
float ramming_end_y = sum_of_depths; 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 ) || 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 ) ) (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: // now the ramming itself:
while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) 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) .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
.resume_preview(); .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 // 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 // be already set and there is no need to change anything. Also, the temperature could be changed
// for wrong extruder. // for wrong extruder.
if (m_semm) { 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) // 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). // 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); writer.set_extruder_temp(new_temperature, false);
m_old_temperature = new_temperature; m_old_temperature = new_temperature;
} }
} }
// Cooling: // Cooling:
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves; if (cooling_will_happen) {
if (m_semm && number_of_moves > 0) {
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed; const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
const float& final_speed = m_filpar[m_current_tool].cooling_final_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() writer.suppress_preview()
.travel(writer.x(), writer.y() + y_step); .travel(writer.x(), writer.y() + y_step);
old_x = writer.x(); old_x = writer.x();
turning_point = xr-old_x > old_x-xl ? xr : xl; 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; float speed = initial_speed + speed_inc * 2*i;
writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed); writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
speed += speed_inc; 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: // 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 // 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) if (do_ramming)
writer.travel(pos, 2400.f); writer.travel(pos, 2400.f);
else else
@@ -1005,6 +1088,8 @@ void WipeTower::toolchange_Change(
//writer.append("[end_filament_gcode]\n"); //writer.append("[end_filament_gcode]\n");
writer.append("[toolchange_gcode_from_wipe_tower_generator]\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) // 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 // 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. // 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& xl = cleaning_box.ld.x();
const float& xr = cleaning_box.rd.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 // 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 // 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. // 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 x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer. 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. // 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; 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; 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 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*m_perimeter_width) { if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) {
writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy); writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy);
m_left_to_right = !m_left_to_right; m_left_to_right = !m_left_to_right;
} }
@@ -1093,21 +1181,21 @@ void WipeTower::toolchange_Wipe(
float traversed_x = writer.x(); float traversed_x = writer.x();
if (m_left_to_right) 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 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 break; // in case next line would not fit
traversed_x -= writer.x(); traversed_x -= writer.x();
x_to_wipe -= std::abs(traversed_x); x_to_wipe -= std::abs(traversed_x);
if (x_to_wipe < WT_EPSILON) { 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; break;
} }
// stepping to the next line: // 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; m_left_to_right = !m_left_to_right;
} }
@@ -1121,6 +1209,7 @@ void WipeTower::toolchange_Wipe(
m_left_to_right = !m_left_to_right; m_left_to_right = !m_left_to_right;
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. 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: // Extract purging volumes for each extruder pair:
std::vector<std::vector<float>> wipe_volumes; std::vector<std::vector<float>> wipe_volumes;
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON); 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)); 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. // 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 i = 0; i<number_of_extruders; ++i)
for (unsigned int j = 0; j<number_of_extruders; ++j) 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; 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 // 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, void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
unsigned int new_tool, float wipe_volume) 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; return;
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower // 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 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), 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, m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
layer_height_par); 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 = (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 ramming_depth = depth; float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
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);
depth += (int(length_to_extrude / width) + 1) * m_perimeter_width; float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
depth *= m_extra_spacing; 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) { if (i == idx) {
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into 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 volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ); 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 m_used_filament_length.assign(m_used_filament_length.size(), 0.f); // reset used filament stats
used = 0.f; 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 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)); 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);
} }
} }

View File

@@ -1,6 +1,5 @@
#ifndef WipeTower_ #ifndef slic3r_GCode_WipeTower_hpp_
#define WipeTower_ #define slic3r_GCode_WipeTower_hpp_
#include <cmath> #include <cmath>
#include <string> #include <string>
#include <sstream> #include <sstream>
@@ -219,7 +218,7 @@ public:
return m_current_layer_finished; 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; } int get_number_of_toolchanges() const { return m_num_tool_changes; }
struct FilamentParameters { struct FilamentParameters {
@@ -232,6 +231,8 @@ public:
float unloading_speed = 0.f; float unloading_speed = 0.f;
float unloading_speed_start = 0.f; float unloading_speed_start = 0.f;
float delay = 0.f ; float delay = 0.f ;
float filament_stamping_loading_speed = 0.f;
float filament_stamping_distance = 0.f;
int cooling_moves = 0; int cooling_moves = 0;
float cooling_initial_speed = 0.f; float cooling_initial_speed = 0.f;
float cooling_final_speed = 0.f; float cooling_final_speed = 0.f;
@@ -243,6 +244,7 @@ public:
float filament_area; float filament_area;
bool multitool_ramming; bool multitool_ramming;
float multitool_ramming_time = 0.f; float multitool_ramming_time = 0.f;
float filament_minimal_purge_on_wipe_tower = 0.f;
}; };
private: private:
@@ -260,6 +262,7 @@ private:
bool m_semm = true; // Are we using a single extruder multimaterial printer? 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. 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_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth 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. 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) 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 = 0.f;
float m_travel_speed_z = 0.f;
float m_infill_speed = 0.f; float m_infill_speed = 0.f;
float m_perimeter_speed = 0.f; float m_perimeter_speed = 0.f;
float m_first_layer_speed = 0.f; float m_first_layer_speed = 0.f;
@@ -310,8 +312,6 @@ private:
// State of the wipe tower generator. // State of the wipe tower generator.
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. 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_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. // A fill-in direction (positive Y, negative Y) alternates with each layer.
wipe_shape m_current_shape = SHAPE_NORMAL; wipe_shape m_current_shape = SHAPE_NORMAL;
size_t m_current_tool = 0; size_t m_current_tool = 0;
@@ -320,7 +320,9 @@ private:
float m_depth_traversed = 0.f; // Current y position at the wipe tower. float m_depth_traversed = 0.f; // Current y position at the wipe tower.
bool m_current_layer_finished = false; bool m_current_layer_finished = false;
bool m_left_to_right = true; 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; } 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(); 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 // Calculates depth for all layers and propagates them downwards
void plan_tower(); 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 // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
void save_on_last_wipe(); void save_on_last_wipe();
@@ -356,19 +352,19 @@ private:
float ramming_depth; float ramming_depth;
float first_wipe_line; float first_wipe_line;
float wipe_volume; 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) 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 z; // z position of the layer
float height; // layer height float height; // layer height
float depth; // depth of the layer based on all layers above 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; } 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; std::vector<ToolChange> tool_changes;
WipeTowerInfo(float z_par, float layer_height_par) 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(...)) 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: // Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length; 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 // Return index of first toolchange that switches to non-soluble extruder
// ot -1 if there is no such toolchange. // ot -1 if there is no such toolchange.
@@ -390,6 +387,7 @@ private:
WipeTowerWriter &writer, WipeTowerWriter &writer,
const box_coordinates &cleaning_box, const box_coordinates &cleaning_box,
const std::string& current_material, const std::string& current_material,
const int old_temperature,
const int new_temperature); const int new_temperature);
void toolchange_Change( void toolchange_Change(
@@ -412,4 +410,4 @@ private:
} // namespace Slic3r } // namespace Slic3r
#endif // WipeTowerQIDIMM_hpp_ #endif // slic3r_GCode_WipeTower_hpp_

View File

@@ -57,12 +57,24 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|| is_ramming || is_ramming
|| will_go_down); // don't dig into the print || will_go_down); // don't dig into the print
if (should_travel_to_tower) { 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(); 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( gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos), *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";}
ExtrusionRole::Mixed, );
"Travel to a Wipe Tower"); } 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(); gcode += gcodegen.unretract();
} else { } else {
// When this is multiextruder printer without any ramming, we can just change // 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) if (is_ramming)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. 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 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"); 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(); deretraction_str += gcodegen.unretract();
}
} }
assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n'); assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n');
assert(deretraction_str.empty() || deretraction_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); boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
std::string tcr_gcode; std::string tcr_gcode;
unescape_string_cstyle(tcr_rotated_gcode, 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 += 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. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>()); 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)) { if (!is_approx(z, current_z)) {
gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); 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. // 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; return gcode;
} }
@@ -245,10 +264,11 @@ std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extr
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
{ {
std::string gcode; 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( gcode += gcodegen.generate_travel_gcode(
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}}, {{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(purge_z)}},
"move to safe place for purging" "move to safe place for purging", [](){return "";}
); );
gcode += append_tcr(gcodegen, m_final_purge, -1); gcode += append_tcr(gcodegen, m_final_purge, -1);
return gcode; return gcode;

Some files were not shown because too many files have changed in this diff Show More