Merge branch 'master' into fs_svg_SPE-1517

This commit is contained in:
Filip Sykala - NTB T15p 2023-09-13 21:53:59 +02:00
commit 255081e342
142 changed files with 10418 additions and 3244 deletions

9
deps/CMakeLists.txt vendored
View File

@ -24,8 +24,8 @@
# therefore, unfortunatelly, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
#
cmake_minimum_required(VERSION 3.10)
project(PrusaSlicer-deps)
cmake_minimum_required(VERSION 3.2)
include(ExternalProject)
include(ProcessorCount)
@ -66,6 +66,10 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE)
message(STATUS "Forcing CMAKE_BUILD_TYPE to Release as it was not specified.")
endif ()
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
cmake_policy(SET CMP0135 NEW)
endif ()
function(prusaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
@ -199,6 +203,8 @@ include(NanoSVG/NanoSVG.cmake)
include(wxWidgets/wxWidgets.cmake)
include(OCCT/OCCT.cmake)
include(LibBGCode/LibBGCode.cmake)
set(_dep_list
dep_Boost
dep_TBB
@ -214,6 +220,7 @@ set(_dep_list
${PNG_PKG}
${ZLIB_PKG}
${EXPAT_PKG}
dep_LibBGCode
)
# if (NOT MSVC)

34
deps/LibBGCode/LibBGCode.cmake vendored Normal file
View File

@ -0,0 +1,34 @@
set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory")
set(_source_dir_line
URL https://github.com/prusa3d/libbgcode/archive/50bedae2ae0c7fc83dd350a8be99ddc8f1749005.zip
URL_HASH SHA256=3958c93a325d6d7ed1c97aabb37cc09a08f8e981e3a7917312d568071e462162
)
if (LibBGCode_SOURCE_DIR)
set(_source_dir_line "SOURCE_DIR;${LibBGCode_SOURCE_DIR};BUILD_ALWAYS;ON")
endif ()
prusaslicer_add_cmake_project(LibBGCode_deps
${_source_dir_line}
SOURCE_SUBDIR deps
DEPENDS dep_Boost ${ZLIB_PKG}
CMAKE_ARGS
-DDEP_DOWNLOAD_DIR:PATH=${DEP_DOWNLOAD_DIR}
-DDEP_CMAKE_OPTS:STRING=-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
-DLibBGCode_Deps_SELECT_ALL:BOOL=OFF
-DLibBGCode_Deps_SELECT_heatshrink:BOOL=ON
-DDESTDIR=${DESTDIR}
)
prusaslicer_add_cmake_project(LibBGCode
${_source_dir_line}
DEPENDS dep_LibBGCode_deps
CMAKE_ARGS
-DLibBGCode_BUILD_TESTS:BOOL=OFF
-DLibBGCode_BUILD_CMD_TOOL:BOOL=OFF
)
if (MSVC)
add_debug_dep(dep_LibBGCode)
endif ()

View File

@ -25,7 +25,6 @@ our @EXPORT_OK = qw(
X Y Z
convex_hull
chained_path_from
deg2rad
rad2deg
);

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 128 128"
enable-background="new 0 0 128 128"
xml:space="preserve"
sodipodi:docname="convert_file.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1064">
</defs><sodipodi:namedview
id="namedview1062"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="4.8782082"
inkscape:cx="62.830447"
inkscape:cy="67.750287"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="3191"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<g
id="g1046"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1">
<path
fill="#ed6b21"
d="M 115.76,51.2 107.7,43.14 C 105.23,40.67 100.73,38.8 97.23,38.8 h -50.8 c -4.2,0 -7.62,3.42 -7.62,7.62 v 66.04 c 0,4.2 3.42,7.62 7.62,7.62 h 66.04 c 4.2,0 7.62,-3.42 7.62,-7.62 v -50.8 c 0,-3.49 -1.86,-7.99 -4.33,-10.46 z m -4.34,2.84 h -6.57 v -6.57 z m 3.59,58.43 c 0,1.4 -1.14,2.54 -2.54,2.54 H 46.43 c -1.4,0 -2.54,-1.14 -2.54,-2.54 V 46.42 c 0,-1.4 1.14,-2.54 2.54,-2.54 h 50.8 c 0.74,0 1.63,0.18 2.54,0.46 v 12.24 c 0,1.4 1.14,2.54 2.54,2.54 h 12.24 c 0.28,0.91 0.46,1.8 0.46,2.54 z"
id="path1036"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1" />
<path
fill="#ed6b21"
d="m 53.97,59.13 h 35.72 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 H 53.97 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.13,2.54 2.54,2.54 z"
id="path1038"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1" />
<path
fill="#ed6b21"
d="M 104.93,69.29 H 53.97 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 50.96 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1040"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1" />
<path
fill="#ed6b21"
d="M 104.93,84.53 H 53.97 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 50.96 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1042"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1" />
<path
fill="#ed6b21"
d="M 104.93,99.77 H 53.97 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 50.96 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1044"
style="stroke:none;stroke-opacity:1;fill:#808080;fill-opacity:1" />
</g><g
id="g1058">
<path
fill="#808080"
d="M 85.27,20.71 77.21,12.65 C 74.74,10.18 70.24,8.31 66.74,8.31 h -50.8 c -4.2,0 -7.62,3.42 -7.62,7.62 v 66.04 c 0,4.2 3.42,7.62 7.62,7.62 h 17.78 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 H 15.94 c -1.4,0 -2.54,-1.14 -2.54,-2.54 V 15.94 c 0,-1.4 1.14,-2.54 2.54,-2.54 h 50.8 c 0.74,0 1.63,0.18 2.54,0.46 V 26.1 c 0,1.4 1.14,2.54 2.54,2.54 h 12.45 c 0.16,0.49 0.25,0.93 0.25,1.27 v 3.81 c 0,1.4 1.14,2.54 2.54,2.54 1.4,0 2.54,-1.14 2.54,-2.54 v -3.81 c 0.01,-2.77 -1.85,-6.72 -4.33,-9.2 z m -10.9,-3.72 6.57,6.57 h -6.57 z"
id="path1048" />
<path
fill="#808080"
d="M 59.21,23.56 H 23.48 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 H 59.2 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.13,-2.54 -2.53,-2.54 z"
id="path1050" />
<path
fill="#808080"
d="m 28.73,38.8 h -5.24 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 5.24 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1052" />
<path
fill="#808080"
d="m 28.73,54.04 h -5.24 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 5.24 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1054" />
<path
fill="#808080"
d="m 28.73,69.29 h -5.24 c -1.4,0 -2.54,1.14 -2.54,2.54 0,1.4 1.14,2.54 2.54,2.54 h 5.24 c 1.4,0 2.54,-1.14 2.54,-2.54 0,-1.4 -1.14,-2.54 -2.54,-2.54 z"
id="path1056" />
</g>
<path
d="M 104.85,54.04 82.71545,76.15908 60.58864,54.03226 66.08342,48.53747 78.78283,61.23685 V 43.51678 C 78.76993,34.93742 71.81821,27.98568 63.23888,27.97284 H 47.69494 v -7.77197 h 15.54394 c 12.87167,0.0129 23.30304,10.44422 23.31591,23.31591 V 61.33793 L 99.34746,48.54524 Z"
id="path1427"
style="stroke-width:0.233159;fill:#ed6b21;fill-opacity:1" /></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="number_x5F_of_x5F_copies">
<g>
<path fill="#808080" d="M13,2c0.55,0,1,0.45,1,1V13c0,0.55-0.45,1-1,1H3c-0.55,0-1-0.45-1-1V3c0-0.55,0.45-1,1-1H13 M13,1H3
c-1.1,0-2,0.89-2,2V13c0,1.1,0.89,2,2,2H13c1.1,0,2-0.89,2-2V3C15,1.89,14.11,1,13,1L13,1z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,4.5H4C3.72,4.5,3.5,4.28,3.5,4S3.72,3.5,4,3.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,4.5,6,4.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,12.5H4c-0.28,0-0.5-0.22-0.5-0.5s0.22-0.5,0.5-0.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,12.5,6,12.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M5,12.5c-0.28,0-0.5-0.22-0.5-0.5V4c0-0.28,0.22-0.5,0.5-0.5S5.5,3.72,5.5,4v8C5.5,12.28,5.28,12.5,5,12.5
z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="number_x5F_of_x5F_copies">
<g>
<path fill="#808080" d="M13,2c0.55,0,1,0.45,1,1V13c0,0.55-0.45,1-1,1H3c-0.55,0-1-0.45-1-1V3c0-0.55,0.45-1,1-1H13 M13,1H3
c-1.1,0-2,0.89-2,2V13c0,1.1,0.89,2,2,2H13c1.1,0,2-0.89,2-2V3C15,1.89,14.11,1,13,1L13,1z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,4.5H4C3.72,4.5,3.5,4.28,3.5,4S3.72,3.5,4,3.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,4.5,6,4.5z"/>
</g>
<g>
<path fill="#808080" d="M12,8.5H8C7.72,8.5,7.5,8.28,7.5,8S7.72,7.5,8,7.5h4c0.28,0,0.5,0.22,0.5,0.5S12.28,8.5,12,8.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,12.5H4c-0.28,0-0.5-0.22-0.5-0.5s0.22-0.5,0.5-0.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,12.5,6,12.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M5,12.5c-0.28,0-0.5-0.22-0.5-0.5V4c0-0.28,0.22-0.5,0.5-0.5S5.5,3.72,5.5,4v8C5.5,12.28,5.28,12.5,5,12.5
z"/>
</g>
<g>
<path fill="#808080" d="M10,10.5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5s0.5,0.22,0.5,0.5v4
C10.5,10.28,10.28,10.5,10,10.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="number_x5F_of_x5F_copies">
<g>
<path fill="#808080" d="M13,2c0.55,0,1,0.45,1,1V13c0,0.55-0.45,1-1,1H3c-0.55,0-1-0.45-1-1V3c0-0.55,0.45-1,1-1H13 M13,1H3
c-1.1,0-2,0.89-2,2V13c0,1.1,0.89,2,2,2H13c1.1,0,2-0.89,2-2V3C15,1.89,14.11,1,13,1L13,1z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,4.5H4C3.72,4.5,3.5,4.28,3.5,4S3.72,3.5,4,3.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,4.5,6,4.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M12,8.5H8C7.72,8.5,7.5,8.28,7.5,8S7.72,7.5,8,7.5h4c0.28,0,0.5,0.22,0.5,0.5S12.28,8.5,12,8.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M6,12.5H4c-0.28,0-0.5-0.22-0.5-0.5s0.22-0.5,0.5-0.5h2c0.28,0,0.5,0.22,0.5,0.5S6.28,12.5,6,12.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M5,12.5c-0.28,0-0.5-0.22-0.5-0.5V4c0-0.28,0.22-0.5,0.5-0.5S5.5,3.72,5.5,4v8C5.5,12.28,5.28,12.5,5,12.5
z"/>
</g>
<g>
<path fill="#ED6B21" d="M10,10.5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5s0.5,0.22,0.5,0.5v4
C10.5,10.28,10.28,10.5,10,10.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -33,6 +33,7 @@ src/slic3r/GUI/DesktopIntegrationDialog.cpp
src/slic3r/GUI/DoubleSlider.cpp
src/slic3r/GUI/Downloader.cpp
src/slic3r/GUI/DownloaderFileGet.cpp
src/slic3r/GUI/EditGCodeDialog.cpp
src/slic3r/GUI/ExtraRenderers.cpp
src/slic3r/GUI/ExtruderSequenceDialog.cpp
src/slic3r/GUI/Field.cpp

View File

@ -1,3 +1,6 @@
min_slic3r_version = 2.6.2-alpha0
1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format.
1.11.0-alpha0 Binary g-code, arc fitting, QOI/PNG thumbnails, 90degree XL tower, XL specific filament variants.
min_slic3r_version = 2.6.0-beta2
1.9.8 FW version notification (MK2.5/3 family). Minor update of MK4IS profiles. Updated MK4IS thumbnail.
1.9.7 MK4 Input Shaper RC firmware notification.

File diff suppressed because one or more lines are too long

View File

@ -2,8 +2,12 @@
// Why?
#define _WIN32_WINNT 0x0502
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX
#include <Windows.h>
#include <wchar.h>
#ifdef SLIC3R_GUI
@ -763,6 +767,7 @@ bool CLI::setup(int argc, char **argv)
set_var_dir((path_resources / "icons").string());
set_local_dir((path_resources / "localization").string());
set_sys_shapes_dir((path_resources / "shapes").string());
set_custom_gcodes_dir((path_resources / "custom_gcodes").string());
// Parse all command line options into a DynamicConfig.
// If any option is unsupported, print usage and abort immediately.

View File

@ -1,14 +1,16 @@
// Why?
#define _WIN32_WINNT 0x0502
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX
#include <Windows.h>
#include <shellapi.h>
#include <wchar.h>
#ifdef SLIC3R_GUI
extern "C"
{

View File

@ -84,15 +84,24 @@ inline IntPoint IntPoint2d(cInt x, cInt y)
);
}
inline cInt Round(double val)
// Fast rounding upwards.
inline double FRound(double a)
{
double v = val < 0 ? val - 0.5 : val + 0.5;
// Why does Java Math.round(0.49999999999999994) return 1?
// https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1
return a == 0.49999999999999994 ? 0 : floor(a + 0.5);
}
template<typename IType>
inline IType Round(double val)
{
double v = FRound(val);
#if defined(CLIPPERLIB_INT32) && ! defined(NDEBUG)
static constexpr const double hi = 65536 * 16383;
if (v > hi || -v > hi)
throw clipperException("Coordinate outside allowed range");
#endif
return static_cast<cInt>(v);
return static_cast<IType>(v);
}
// Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional.
@ -340,7 +349,7 @@ inline cInt TopX(TEdge &edge, const cInt currentY)
{
return (currentY == edge.Top.y()) ?
edge.Top.x() :
edge.Bot.x() + Round(edge.Dx *(currentY - edge.Bot.y()));
edge.Bot.x() + Round<cInt>(edge.Dx *(currentY - edge.Bot.y()));
}
//------------------------------------------------------------------------------
@ -350,65 +359,53 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
ip.z() = 0;
#endif
double b1, b2;
if (Edge1.Dx == Edge2.Dx)
{
ip.y() = Edge1.Curr.y();
ip.x() = TopX(Edge1, ip.y());
return;
}
else if (Edge1.Delta.x() == 0)
int64_t y;
if (Edge1.Delta.x() == 0)
{
ip.x() = Edge1.Bot.x();
if (IsHorizontal(Edge2))
ip.y() = Edge2.Bot.y();
else
{
b2 = Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx);
ip.y() = Round(ip.x() / Edge2.Dx + b2);
}
y = IsHorizontal(Edge2) ?
Edge2.Bot.y() :
Round<int64_t>(ip.x() / Edge2.Dx + Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx));
}
else if (Edge2.Delta.x() == 0)
{
ip.x() = Edge2.Bot.x();
if (IsHorizontal(Edge1))
ip.y() = Edge1.Bot.y();
else
{
b1 = Edge1.Bot.y() - (Edge1.Bot.x() / Edge1.Dx);
ip.y() = Round(ip.x() / Edge1.Dx + b1);
}
}
else
y = IsHorizontal(Edge1) ?
Edge1.Bot.y() :
Round<int64_t>(ip.x() / Edge1.Dx + Edge1.Bot.y() - (Edge1.Bot.x() / Edge1.Dx));
}
else
{
b1 = double(Edge1.Bot.x()) - double(Edge1.Bot.y()) * Edge1.Dx;
b2 = double(Edge2.Bot.x()) - double(Edge2.Bot.y()) * Edge2.Dx;
double b1 = double(Edge1.Bot.x()) - double(Edge1.Bot.y()) * Edge1.Dx;
double b2 = double(Edge2.Bot.x()) - double(Edge2.Bot.y()) * Edge2.Dx;
double q = (b2-b1) / (Edge1.Dx - Edge2.Dx);
ip.y() = Round(q);
ip.x() = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ?
Round(Edge1.Dx * q + b1) :
Round(Edge2.Dx * q + b2);
y = Round<int64_t>(q);
ip.x() = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ?
Round<cInt>(Edge1.Dx * q + b1) :
Round<cInt>(Edge2.Dx * q + b2);
}
if (ip.y() < Edge1.Top.y() || ip.y() < Edge2.Top.y())
ip.y() = cInt(y);
if (y < Edge1.Top.y() || y < Edge2.Top.y())
{
if (Edge1.Top.y() > Edge2.Top.y())
ip.y() = Edge1.Top.y();
else
ip.y() = Edge2.Top.y();
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.x() = TopX(Edge1, ip.y());
else
ip.x() = TopX(Edge2, ip.y());
}
ip.y() = (Edge1.Top.y() > Edge2.Top.y() ? Edge1 : Edge2).Top.y();
y = ip.y();
ip.x() = TopX(std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx) ? Edge1 : Edge2, ip.y());
}
//finally, don't allow 'ip' to be BELOW curr.y() (ie bottom of scanbeam) ...
if (ip.y() > Edge1.Curr.y())
if (y > Edge1.Curr.y())
{
ip.y() = Edge1.Curr.y();
//use the more vertical edge to derive X ...
if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
ip.x() = TopX(Edge2, ip.y()); else
ip.x() = TopX(Edge1, ip.y());
ip.x() = TopX(std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx) ? Edge2 : Edge1, ip.y());
}
}
//------------------------------------------------------------------------------
@ -3539,8 +3536,8 @@ void ClipperOffset::DoOffset(double delta)
for (cInt j = 1; j <= steps; j++)
{
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[0].x() + X * delta),
Round(m_srcPoly[0].y() + Y * delta)));
Round<cInt>(m_srcPoly[0].x() + X * delta),
Round<cInt>(m_srcPoly[0].y() + Y * delta)));
double X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
@ -3552,8 +3549,8 @@ void ClipperOffset::DoOffset(double delta)
for (int j = 0; j < 4; ++j)
{
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[0].x() + X * delta),
Round(m_srcPoly[0].y() + Y * delta)));
Round<cInt>(m_srcPoly[0].x() + X * delta),
Round<cInt>(m_srcPoly[0].y() + Y * delta)));
if (X < 0) X = 1;
else if (Y < 0) Y = 1;
else X = -1;
@ -3606,9 +3603,9 @@ void ClipperOffset::DoOffset(double delta)
if (node.m_endtype == etOpenButt)
{
int j = len - 1;
pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta));
pt1 = IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * delta), Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * delta));
m_destPoly.emplace_back(pt1);
pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta));
pt1 = IntPoint2d(Round<cInt>(m_srcPoly[j].x() - m_normals[j].x() * delta), Round<cInt>(m_srcPoly[j].y() - m_normals[j].y() * delta));
m_destPoly.emplace_back(pt1);
}
else
@ -3633,9 +3630,9 @@ void ClipperOffset::DoOffset(double delta)
if (node.m_endtype == etOpenButt)
{
pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta));
pt1 = IntPoint2d(Round<cInt>(m_srcPoly[0].x() - m_normals[0].x() * delta), Round<cInt>(m_srcPoly[0].y() - m_normals[0].y() * delta));
m_destPoly.emplace_back(pt1);
pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta));
pt1 = IntPoint2d(Round<cInt>(m_srcPoly[0].x() + m_normals[0].x() * delta), Round<cInt>(m_srcPoly[0].y() + m_normals[0].y() * delta));
m_destPoly.emplace_back(pt1);
}
else
@ -3663,8 +3660,8 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() );
if (cosA > 0) // angle => 0 degrees
{
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round<cInt>(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
return;
}
//else angle => 180 degrees
@ -3674,11 +3671,11 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
if (m_sinA * m_delta < 0)
{
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round<cInt>(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
m_destPoly.emplace_back(m_srcPoly[j]);
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
}
else
switch (jointype)
@ -3702,19 +3699,19 @@ void ClipperOffset::DoSquare(int j, int k)
double dx = std::tan(std::atan2(m_sinA,
m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4);
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)),
Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx))));
Round<cInt>(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)),
Round<cInt>(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx))));
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)),
Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx))));
Round<cInt>(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)),
Round<cInt>(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx))));
}
//------------------------------------------------------------------------------
void ClipperOffset::DoMiter(int j, int k, double r)
{
double q = m_delta / r;
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q),
Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q)));
m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q),
Round<cInt>(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q)));
}
//------------------------------------------------------------------------------
@ -3722,21 +3719,21 @@ void ClipperOffset::DoRound(int j, int k)
{
double a = std::atan2(m_sinA,
m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y());
auto steps = std::max<int>(Round(m_StepsPerRad * std::fabs(a)), 1);
auto steps = std::max<int>(Round<cInt>(m_StepsPerRad * std::fabs(a)), 1);
double X = m_normals[k].x(), Y = m_normals[k].y(), X2;
for (int i = 0; i < steps; ++i)
{
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + X * m_delta),
Round(m_srcPoly[j].y() + Y * m_delta)));
Round<cInt>(m_srcPoly[j].x() + X * m_delta),
Round<cInt>(m_srcPoly[j].y() + Y * m_delta)));
X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
}
m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
}
//------------------------------------------------------------------------------

View File

@ -349,7 +349,7 @@ public:
return dist;
}
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) const
{
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}

View File

@ -73,7 +73,9 @@ private:
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
static void construct(BoundingBoxType &out, It from, It to)
{
if (from != to) {
if (from == to) {
out.defined = false;
} else {
auto it = from;
out.min = it->template cast<typename PointType::Scalar>();
out.max = out.min;

View File

@ -495,8 +495,10 @@ static void make_inner_brim(const Print &print,
loops = union_pt_chained_outside_in(loops);
std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(brim.entities, std::move(loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()),
float(flow.width()), float(print.skirt_first_layer_height()));
extrusion_entities_append_loops(brim.entities, std::move(loops),
ExtrusionAttributes{
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
}
// Produce brim lines around those objects, that have the brim enabled.
@ -677,7 +679,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
if (i + 1 == j && first_path.size() > 3 && first_path.front().x() == first_path.back().x() && first_path.front().y() == first_path.back().y()) {
auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop);
loop->paths.emplace_back(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
loop->paths.emplace_back(ExtrusionAttributes{
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
@ -688,7 +692,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) {
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
this_loop_trimmed.entities.emplace_back(new ExtrusionPath({
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = dynamic_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
@ -704,7 +710,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
}
}
} else {
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops),
ExtrusionAttributes{ ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
}
make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);

View File

@ -21,6 +21,11 @@ if (TARGET OpenVDB::openvdb)
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp OpenVDBUtilsLegacy.hpp)
endif()
find_package(LibBGCode REQUIRED COMPONENTS Convert)
slic3r_remap_configs(LibBGCode::bgcode_core RelWithDebInfo Release)
slic3r_remap_configs(LibBGCode::bgcode_binarize RelWithDebInfo Release)
slic3r_remap_configs(LibBGCode::bgcode_convert RelWithDebInfo Release)
set(SLIC3R_SOURCES
pchheader.cpp
pchheader.hpp
@ -151,8 +156,12 @@ set(SLIC3R_SOURCES
GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/ExtrusionProcessor.cpp
GCode/ExtrusionProcessor.hpp
GCode/FindReplace.cpp
GCode/FindReplace.hpp
GCode/GCodeWriter.cpp
GCode/GCodeWriter.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp
@ -165,10 +174,16 @@ set(SLIC3R_SOURCES
GCode/SpiralVase.hpp
GCode/SeamPlacer.cpp
GCode/SeamPlacer.hpp
GCode/SmoothPath.cpp
GCode/SmoothPath.hpp
GCode/ToolOrdering.cpp
GCode/ToolOrdering.hpp
GCode/Wipe.cpp
GCode/Wipe.hpp
GCode/WipeTower.cpp
GCode/WipeTower.hpp
GCode/WipeTowerIntegration.cpp
GCode/WipeTowerIntegration.hpp
GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
@ -179,10 +194,10 @@ set(SLIC3R_SOURCES
GCodeReader.hpp
# GCodeSender.cpp
# GCodeSender.hpp
GCodeWriter.cpp
GCodeWriter.hpp
Geometry.cpp
Geometry.hpp
Geometry/ArcWelder.cpp
Geometry/ArcWelder.hpp
Geometry/Bicubic.hpp
Geometry/Circle.cpp
Geometry/Circle.hpp
@ -570,6 +585,7 @@ target_link_libraries(libslic3r
ZLIB::ZLIB
JPEG::JPEG
qoi
LibBGCode::bgcode_convert
)
if (APPLE)

View File

@ -37,12 +37,15 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/format.hpp>
#include <string.h>
#include <LibBGCode/binarize/binarize.hpp>
//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
#include "PrintConfig.hpp"
@ -331,12 +334,9 @@ void ConfigDef::finalize()
if (def.type == coEnum) {
assert(def.enum_def);
assert(def.enum_def->is_valid_closed_enum());
assert(def.gui_type != ConfigOptionDef::GUIType::i_enum_open &&
def.gui_type != ConfigOptionDef::GUIType::f_enum_open &&
def.gui_type != ConfigOptionDef::GUIType::select_open);
assert(! def.is_gui_type_enum_open());
def.enum_def->finalize_closed_enum();
} else if (def.gui_type == ConfigOptionDef::GUIType::i_enum_open || def.gui_type == ConfigOptionDef::GUIType::f_enum_open ||
def.gui_type == ConfigOptionDef::GUIType::select_open) {
} else if (def.is_gui_type_enum_open()) {
assert(def.enum_def);
assert(def.enum_def->is_valid_open_enum());
assert(def.gui_type != ConfigOptionDef::GUIType::i_enum_open || def.type == coInt || def.type == coInts);
@ -740,11 +740,37 @@ void ConfigBase::setenv_() const
}
}
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
ConfigSubstitutions ConfigBase::load(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
return is_gcode_file(file) ?
this->load_from_gcode_file(file, compatibility_rule) :
this->load_from_ini(file, compatibility_rule);
enum class EFileType
{
Ini,
AsciiGCode,
BinaryGCode
};
EFileType file_type;
if (is_gcode_file(filename)) {
FILE* file = boost::nowide::fopen(filename.c_str(), "rb");
if (file == nullptr)
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
std::vector<uint8_t> cs_buffer(65536);
using namespace bgcode::core;
file_type = (is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == EResult::Success) ? EFileType::BinaryGCode : EFileType::AsciiGCode;
fclose(file);
}
else
file_type = EFileType::Ini;
switch (file_type)
{
case EFileType::Ini: { return this->load_from_ini(filename, compatibility_rule); }
case EFileType::AsciiGCode: { return this->load_from_gcode_file(filename, compatibility_rule);}
case EFileType::BinaryGCode: { return this->load_from_binary_gcode_file(filename, compatibility_rule);}
default: { throw Slic3r::RuntimeError(format("Invalid file %1%", filename)); }
}
}
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
@ -948,15 +974,15 @@ private:
};
// Load the config keys from the tail of a G-code file.
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &filename, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file, std::ifstream::binary);
boost::nowide::ifstream ifs(filename, std::ifstream::binary);
// Look for Slic3r or PrusaSlicer header.
// Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user.
bool has_delimiters = false;
{
static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r ";
static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r ";
static constexpr const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string header;
bool header_found = false;
@ -1006,7 +1032,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
break;
}
if (! end_found)
throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", file));
throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", filename));
std::string key, value;
while (reader.getline(line)) {
if (line == "; prusaslicer_config = begin") {
@ -1029,7 +1055,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
}
}
if (! begin_found)
throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", file));
throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", filename));
}
else
{
@ -1037,8 +1063,8 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
// Try a heuristics reading the G-code from back.
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length - header_end_pos);
ifs.seekg(file_length - data_length, ifs.beg);
auto data_length = std::min<std::fstream::pos_type>(65535, file_length - header_end_pos);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
@ -1046,7 +1072,48 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
}
if (key_value_pairs < 80)
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", filename, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
}
ConfigSubstitutions ConfigBase::load_from_binary_gcode_file(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") };
if (file.f == nullptr)
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
using namespace bgcode::core;
using namespace bgcode::binarize;
std::vector<uint8_t> cs_buffer(65536);
EResult res = is_valid_binary_gcode(*file.f, true, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("File %1% does not contain a valid binary gcode\nError: %2%", filename,
std::string(translate_result(res))));
FileHeader file_header;
res = read_header(*file.f, file_header, nullptr);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error while reading file %1%: %2%", filename, std::string(translate_result(res))));
// searches for config block
BlockHeader block_header;
res = read_next_block_header(*file.f, file_header, block_header, EBlockType::SlicerMetadata, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error while reading file %1%: %2%", filename, std::string(translate_result(res))));
if ((EBlockType)block_header.type != EBlockType::SlicerMetadata)
throw Slic3r::RuntimeError(format("Unable to find slicer metadata block in file %1%", filename));
SlicerMetadataBlock slicer_metadata_block;
res = slicer_metadata_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error while reading file %1%: %2%", filename, std::string(translate_result(res))));
// extracts data from block
for (const auto& [key, value] : slicer_metadata_block.raw_data) {
this->set_deserialize(key, value, substitutions_ctxt);
}
return std::move(substitutions_ctxt.substitutions);
}

View File

@ -1836,6 +1836,8 @@ public:
// Close parameter, string value could be one of the list values.
select_close,
};
static bool is_gui_type_enum_open(const GUIType gui_type)
{ return gui_type == ConfigOptionDef::GUIType::i_enum_open || gui_type == ConfigOptionDef::GUIType::f_enum_open || gui_type == ConfigOptionDef::GUIType::select_open; }
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
t_config_option_key opt_key;
@ -1853,6 +1855,8 @@ public:
// Create a default option to be inserted into a DynamicConfig.
ConfigOption* create_default_option() const;
bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; }
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
if (this->nullable) {
switch (this->type) {
@ -1923,6 +1927,7 @@ public:
// Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
// "select_open" - to open a selection dialog (currently only a serial port selection).
GUIType gui_type { GUIType::undefined };
bool is_gui_type_enum_open() const { return is_gui_type_enum_open(this->gui_type); }
// Usually empty. Otherwise "serialized" or "show_value"
// The flags may be combined.
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
@ -1986,7 +1991,7 @@ public:
void set_enum_values(GUIType gui_type, const std::initializer_list<std::string_view> il) {
this->enum_def_new();
assert(gui_type == GUIType::i_enum_open || gui_type == GUIType::f_enum_open || gui_type == GUIType::select_open);
assert(is_gui_type_enum_open(gui_type));
this->gui_type = gui_type;
enum_def->set_values(il);
}
@ -2098,6 +2103,7 @@ public:
out.push_back(kvp.first);
return out;
}
bool empty() const { return options.empty(); }
// Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(
@ -2328,7 +2334,8 @@ public:
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &filename, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_binary_gcode_file(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const;

View File

@ -5,7 +5,7 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
#include "GCode.hpp"
#include "GCodeWriter.hpp"
#include "GCode/GCodeWriter.hpp"
namespace Slic3r {

View File

@ -8,7 +8,7 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Extruder.hpp"
#include "GCodeWriter.hpp"
#include "GCode/GCodeWriter.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
@ -25,6 +25,7 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config) :
std::pair<double, double> Extruder::extrude(double dE)
{
assert(! std::isnan(dE));
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
@ -46,7 +47,8 @@ std::pair<double, double> Extruder::extrude(double dE)
value supplied will overwrite the previous one if any. */
std::pair<double, double> Extruder::retract(double retract_length, double restart_extra)
{
assert(restart_extra >= 0);
assert(! std::isnan(retract_length));
assert(! std::isnan(restart_extra) && restart_extra >= 0);
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;

View File

@ -9,6 +9,7 @@
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygon.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Extruder.hpp"
#include "Flow.hpp"
#include <cmath>
@ -16,7 +17,7 @@
#include <sstream>
namespace Slic3r {
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
@ -45,12 +46,12 @@ double ExtrusionPath::length() const
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (const Polyline &polyline : polylines)
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
collection->entities.emplace_back(new ExtrusionPath(polyline, this->attributes()));
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
polygons_append(out, offset(this->polyline, float(scale_(m_attributes.width/2)) + scaled_epsilon));
}
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
@ -58,8 +59,8 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
// Instantiating the Flow class to get the line spacing.
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
bool bridge = this->role().is_bridge();
assert(! bridge || this->width == this->height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
assert(! bridge || m_attributes.width == m_attributes.height);
auto flow = bridge ? Flow::bridging_flow(m_attributes.width, 0.f) : Flow(m_attributes.width, m_attributes.height, 0.f);
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
@ -94,7 +95,7 @@ double ExtrusionMultiPath::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
return min_mm3_per_mm;
}
@ -119,21 +120,34 @@ Polyline ExtrusionMultiPath::as_polyline() const
return out;
}
bool ExtrusionLoop::make_clockwise()
double ExtrusionLoop::area() const
{
bool was_ccw = this->polygon().is_counter_clockwise();
if (was_ccw) this->reverse();
return was_ccw;
}
bool ExtrusionLoop::make_counter_clockwise()
{
bool was_cw = this->polygon().is_clockwise();
if (was_cw) this->reverse();
return was_cw;
double a = 0;
for (const ExtrusionPath &path : this->paths) {
assert(path.size() >= 2);
if (path.size() >= 2) {
// Assumming that the last point of one path segment is repeated at the start of the following path segment.
auto it = path.polyline.points.begin();
Point prev = *it ++;
for (; it != path.polyline.points.end(); ++ it) {
a += cross2(prev.cast<double>(), it->cast<double>());
prev = *it;
}
}
}
return a * 0.5;
}
void ExtrusionLoop::reverse()
{
#if 0
this->reverse_loop();
#else
throw Slic3r::LogicError("ExtrusionLoop::reverse() must NOT be called");
#endif
}
void ExtrusionLoop::reverse_loop()
{
for (ExtrusionPath &path : this->paths)
path.reverse();
@ -255,8 +269,8 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
// now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p1(path.attributes());
ExtrusionPath p2(path.attributes());
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) {
@ -323,7 +337,7 @@ double ExtrusionLoop::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
return min_mm3_per_mm;
}

View File

@ -10,10 +10,12 @@
#include "libslic3r.h"
#include "ExtrusionRole.hpp"
#include "Flow.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include <assert.h>
#include <optional>
#include <string_view>
#include <numeric>
@ -62,28 +64,91 @@ public:
virtual double total_volume() const = 0;
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
using ExtrusionEntitiesPtr = std::vector<ExtrusionEntity*>;
// Const reference for ordering extrusion entities without having to modify them.
class ExtrusionEntityReference final
{
public:
ExtrusionEntityReference() = delete;
ExtrusionEntityReference(const ExtrusionEntity &extrusion_entity, bool flipped) :
m_extrusion_entity(&extrusion_entity), m_flipped(flipped) {}
ExtrusionEntityReference operator=(const ExtrusionEntityReference &rhs)
{ m_extrusion_entity = rhs.m_extrusion_entity; m_flipped = rhs.m_flipped; return *this; }
const ExtrusionEntity& extrusion_entity() const { return *m_extrusion_entity; }
template<typename Type>
const Type* cast() const { return dynamic_cast<const Type*>(m_extrusion_entity); }
bool flipped() const { return m_flipped; }
private:
const ExtrusionEntity *m_extrusion_entity;
bool m_flipped;
};
using ExtrusionEntityReferences = std::vector<ExtrusionEntityReference>;
struct ExtrusionFlow
{
ExtrusionFlow() = default;
ExtrusionFlow(double mm3_per_mm, float width, float height) :
mm3_per_mm{ mm3_per_mm }, width{ width }, height{ height } {}
ExtrusionFlow(const Flow &flow) :
mm3_per_mm(flow.mm3_per_mm()), width(flow.width()), height(flow.height()) {}
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
double mm3_per_mm{ -1. };
// Width of the extrusion, used for visualization purposes.
float width{ -1.f };
// Height of the extrusion, used for visualization purposes.
float height{ -1.f };
};
inline bool operator==(const ExtrusionFlow &lhs, const ExtrusionFlow &rhs)
{
return lhs.mm3_per_mm == rhs.mm3_per_mm && lhs.width == rhs.width && lhs.height == rhs.height;
}
struct OverhangAttributes {
float start_distance_from_prev_layer;
float end_distance_from_prev_layer;
float proximity_to_curled_lines; //value between 0 and 1
};
struct ExtrusionAttributes : ExtrusionFlow
{
ExtrusionAttributes() = default;
ExtrusionAttributes(ExtrusionRole role) : role{ role } {}
ExtrusionAttributes(ExtrusionRole role, const Flow &flow) : role{ role }, ExtrusionFlow{ flow } {}
ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow) : role{ role }, ExtrusionFlow{ flow } {}
// What is the role / purpose of this extrusion?
ExtrusionRole role{ ExtrusionRole::None };
// OVerhangAttributes are currently computed for perimeters if dynamic overhangs are enabled.
// They are used to control fan and print speed in export.
std::optional<OverhangAttributes> overhang_attributes;
};
inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs)
{
return static_cast<const ExtrusionFlow&>(lhs) == static_cast<const ExtrusionFlow&>(rhs) &&
lhs.role == rhs.role;
}
class ExtrusionPath : public ExtrusionEntity
{
public:
Polyline polyline;
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
double mm3_per_mm;
// Width of the extrusion, used for visualization purposes.
float width;
// Height of the extrusion, used for visualization purposes.
float height;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionRole role) : m_attributes{ role } {}
ExtrusionPath(const ExtrusionAttributes &attributes) : m_attributes(attributes) {}
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), m_attributes(rhs.m_attributes) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionAttributes &attribs) : polyline(polyline), m_attributes(attribs) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionAttributes &attribs) : polyline(std::move(polyline)), m_attributes(attribs) {}
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; return *this; }
ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; }
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
// Create a new object, initialize it with this object using the move semantics.
@ -104,35 +169,46 @@ public:
void clip_end(double distance);
void simplify(double tolerance);
double length() const override;
ExtrusionRole role() const override { return m_role; }
const ExtrusionAttributes& attributes() const { return m_attributes; }
ExtrusionRole role() const override { return m_attributes.role; }
float width() const { return m_attributes.width; }
float height() const { return m_attributes.height; }
double mm3_per_mm() const { return m_attributes.mm3_per_mm; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; }
std::optional<OverhangAttributes>& overhang_attributes_mutable() { return m_attributes.overhang_attributes; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
Polyline as_polyline() const override { return 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); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
Polyline as_polyline() const override { return 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); }
double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
ExtrusionRole m_role;
ExtrusionAttributes m_attributes;
};
class ExtrusionPathOriented : public ExtrusionPath
{
public:
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {}
ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {}
ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {}
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
@ -199,7 +275,8 @@ class ExtrusionLoop : public ExtrusionEntity
public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
ExtrusionLoop() = default;
ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
@ -211,16 +288,21 @@ public:
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
bool make_clockwise();
bool make_counter_clockwise();
void reverse() override;
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
double area() const;
bool is_counter_clockwise() const { return this->area() > 0; }
bool is_clockwise() const { return this->area() < 0; }
// Reverse shall never be called on ExtrusionLoop using a virtual function call, it is most likely never what one wants,
// as this->can_reverse() returns false for an ExtrusionLoop.
void reverse() override;
// Used by PerimeterGenerator to reorient extrusion loops.
void reverse_loop();
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
struct ClosestPathPoint {
size_t path_idx;
size_t segment_idx;
@ -266,59 +348,51 @@ public:
#endif /* NDEBUG */
private:
ExtrusionLoopRole m_loop_role;
ExtrusionLoopRole m_loop_role{ elrDefault };
};
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = polyline;
}
if (polyline.is_valid())
dst.emplace_back(polyline, attributes);
}
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = std::move(polyline);
}
if (polyline.is_valid())
dst.emplace_back(std::move(polyline), attributes);
polylines.clear();
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (const Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}
if (polyline.is_valid())
dst.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes));
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = std::move(polyline);
}
if (polyline.is_valid())
dst.emplace_back(can_reverse ?
new ExtrusionPath(std::move(polyline), attributes) :
new ExtrusionPathOriented(std::move(polyline), attributes));
polylines.clear();
}
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + loops.size());
for (Polygon &poly : loops) {
if (poly.is_valid()) {
ExtrusionPath path(role, mm3_per_mm, width, height);
ExtrusionPath path(attributes);
path.polyline.points = std::move(poly.points);
path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path)));
@ -327,22 +401,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
loops.clear();
}
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) {
if (polyline.is_valid()) {
if (polyline.is_closed()) {
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
extrusion_path.polyline = std::move(polyline);
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
} else {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
extrusion_path->polyline = std::move(polyline);
dst.emplace_back(extrusion_path);
}
}
}
for (Polyline &polyline : polylines)
if (polyline.is_valid())
dst.emplace_back(polyline.is_closed() ?
static_cast<ExtrusionEntity*>(new ExtrusionLoop(ExtrusionPath{ std::move(polyline), attributes })) :
static_cast<ExtrusionEntity*>(new ExtrusionPath(std::move(polyline), attributes)));
polylines.clear();
}

View File

@ -14,6 +14,7 @@
namespace Slic3r {
#if 0
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
{
if (role != ExtrusionRole::Mixed) {
@ -25,6 +26,7 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities,
last);
}
}
#endif
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false)
@ -91,18 +93,6 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
{
// Return a filtered copy of the collection.
ExtrusionEntityCollection out;
out.entities = filter_by_extrusion_role(extrusion_entities, role);
// Clone the extrusion entities.
for (auto &ptr : out.entities)
ptr = ptr->clone();
chain_and_reorder_extrusion_entities(out.entities, &start_near);
return out;
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (const ExtrusionEntity *entity : this->entities)

View File

@ -13,6 +13,7 @@
namespace Slic3r {
#if 0
// Remove those items from extrusion_entities, that do not match role.
// Do nothing if role is mixed.
// Removed elements are NOT being deleted.
@ -27,6 +28,7 @@ inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr
filter_by_extrusion_role_in_place(out, role);
return out;
}
#endif
class ExtrusionEntityCollection : public ExtrusionEntity
{
@ -102,9 +104,6 @@ public:
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed);
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed) const
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse() override;
const Point& first_point() const override { return this->entities.front()->first_point(); }
const Point& last_point() const override { return this->entities.back()->last_point(); }

View File

@ -87,6 +87,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); }
bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); }
bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
bool is_sparse_infill() const { return this->is_infill() && ! this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); }
@ -94,6 +95,9 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
bool is_support_base() const { return this->is_support() && ! this->is_external(); }
bool is_support_interface() const { return this->is_support() && this->is_external(); }
bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); }
// Brim is currently marked as skirt.
bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); }
};
// Special flags describing loop

View File

@ -961,9 +961,9 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
polyline.reserve(path.polyline.points.size());
float scalex = float(viewport.size().x()) / float(bbox.size().x());
float scaley = float(viewport.size().y()) / float(bbox.size().y());
float w = scale_(path.width) * scalex;
float w = scale_(path.width()) * scalex;
//float h = scale_(path.height) * scalex;
w = scale_(path.mm3_per_mm / path.height) * scalex;
w = scale_(path.mm3_per_mm() / path.height()) * scalex;
// printf("scalex: %f, scaley: %f\n", scalex, scaley);
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {

View File

@ -543,19 +543,19 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
flow_mm3_per_mm = new_flow.mm3_per_mm();
flow_width = new_flow.width();
}
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
auto fill_begin = uint32_t(layerm.fills().size());
layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
if (params.use_arachne) {
auto fill_begin = uint32_t(layerm.fills().size());
// Save into layer.
if (ExtrusionEntityCollection *eec = nullptr; params.use_arachne) {
for (const ThickPolyline &thick_polyline : thick_polylines) {
Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing));
ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled<float>(0.05), float(SCALED_EPSILON));
// Append paths to collection.
if (!multi_path.empty()) {
layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
if (multi_path.paths.front().first_point() == multi_path.paths.back().last_point())
eec->entities.emplace_back(new ExtrusionLoop(std::move(multi_path.paths)));
else
@ -565,10 +565,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
thick_polylines.clear();
} else {
layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
surface_fill.params.extrusion_role,
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
ExtrusionAttributes{ surface_fill.params.extrusion_role,
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() }
});
}
insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size()));
}
@ -913,8 +918,9 @@ void Layer::make_ironing()
eec->no_sort = true;
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
ExtrusionRole::Ironing,
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
ExtrusionAttributes{ ExtrusionRole::Ironing,
ExtrusionFlow{ flow_mm3_per_mm, extrusion_width, float(extrusion_height) }
});
insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size()));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,18 +19,22 @@
#include "JumpPointSearch.hpp"
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
#include "Geometry/ArcWelder.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/FindReplace.hpp"
#include "GCode/GCodeWriter.hpp"
#include "GCode/PressureEqualizer.hpp"
#include "GCode/RetractWhenCrossingPerimeters.hpp"
#include "GCode/SmoothPath.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/Wipe.hpp"
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/SeamPlacer.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp"
@ -40,12 +44,12 @@
#include <map>
#include <string>
#include "GCode/PressureEqualizer.hpp"
//#include "GCode/PressureEqualizer.hpp"
namespace Slic3r {
// Forward declarations.
class GCode;
class GCodeGenerator;
namespace { struct Item; }
struct PrintInstance;
@ -55,71 +59,11 @@ public:
bool enable;
OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen);
std::string pre_toolchange(GCodeGenerator &gcodegen);
std::string post_toolchange(GCodeGenerator &gcodegen);
private:
int _get_temp(const GCode &gcodegen) const;
};
class Wipe {
public:
bool enable;
Polyline path;
Wipe() : enable(false) {}
bool has_path() const { return ! this->path.empty(); }
void reset_path() { this->path.clear(); }
std::string wipe(GCode &gcodegen, bool toolchange);
};
class WipeTowerIntegration {
public:
WipeTowerIntegration(
const PrintConfig &print_config,
const std::vector<WipeTower::ToolChangeResult> &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_extruder_offsets(print_config.extruder_offset.values),
m_priming(priming),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0)
{}
std::string prime(GCode &gcodegen);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
const Vec2f m_wipe_tower_pos;
const float m_wipe_tower_rotation;
const std::vector<Vec2d> m_extruder_offsets;
// Reference to cached values at the Printer class.
const std::vector<WipeTower::ToolChangeResult> &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
int _get_temp(const GCodeGenerator &gcodegen) const;
};
class ColorPrintColors
@ -143,9 +87,10 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
};
class GCode {
class GCodeGenerator {
public:
GCode() :
GCodeGenerator() :
m_origin(Vec2d::Zero()),
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
@ -167,7 +112,7 @@ public:
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
~GCode() = default;
~GCodeGenerator() = default;
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
@ -179,9 +124,19 @@ public:
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.
Vec2d point_to_gcode(const Point &point) const;
template<typename Derived>
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector");
return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
- m_config.extruder_offset.get_at(m_writer.extruder()->id());
}
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
Vec2d point_to_gcode_quantized(const Point &point) const;
template<typename Derived>
Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &point) const {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode_quantized(): first parameter is not a 2D vector");
Vec2d p = this->point_to_gcode(point);
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
Point gcode_to_point(const Vec2d &point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
@ -201,6 +156,8 @@ public:
// append full config to the given string
static void append_full_config(const Print& print, std::string& str);
// translate full config into a list of <key, value> items
static void encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& config);
// Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp
@ -264,6 +221,7 @@ private:
// Set of object & print layers of the same PrintObject and with the same print_z.
const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools,
const GCode::SmoothPathCaches &smooth_path_caches,
const bool last_layer,
// Pairs of PrintObject index and its instance index.
const std::vector<const PrintInstance*> *ordering,
@ -278,6 +236,7 @@ private:
const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream);
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
@ -287,6 +246,7 @@ private:
const ToolOrdering &tool_ordering,
ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream);
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
@ -294,10 +254,13 @@ private:
void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble();
std::string change_layer(coordf_t print_z);
std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.);
std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.);
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
std::string extrude_path(ExtrusionPath path, 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_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed);
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
struct InstanceToPrint
{
@ -331,12 +294,14 @@ private:
const ObjectLayerToPrint &layer_to_print,
// Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools,
// Optional smooth path interpolating extrusion polylines.
const GCode::SmoothPathCache &smooth_path_cache,
// Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_extrusions);
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
@ -347,8 +312,6 @@ private:
// Cache for custom seam enforcers/blockers for each layer.
SeamPlacer m_seam_placer;
ExtrusionQualityEstimator m_extrusion_quality_estimator;
/* Origin of print coordinates expressed in unscaled G-code coordinates.
This affects the input arguments supplied to the extrude*() and travel_to()
methods. */
@ -389,7 +352,7 @@ private:
} m_placeholder_parser_integration;
OozePrevention m_ooze_prevention;
Wipe m_wipe;
GCode::Wipe m_wipe;
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
@ -432,7 +395,7 @@ private:
std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace;
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
std::unique_ptr<GCode::WipeTowerIntegration> m_wipe_tower;
// Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> m_skirt_done;
@ -448,7 +411,8 @@ private:
// Processor
GCodeProcessor m_processor;
std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1);
std::string _extrude(
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1);
void print_machine_envelope(GCodeOutputStream &file, Print &print);
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
@ -457,8 +421,12 @@ private:
// To control print speed of 1st object layer over raft interface.
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
friend class Wipe;
friend class WipeTowerIntegration;
// Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
// Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches.
static void smooth_path_interpolate(const ObjectLayerToPrint &layers, const GCode::SmoothPathCache::InterpolationParameters &params, GCode::SmoothPathCache &out);
friend class GCode::Wipe;
friend class GCode::WipeTowerIntegration;
friend class PressureEqualizer;
};

View File

@ -734,7 +734,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
return false;
}
static bool need_wipe(const GCode &gcodegen,
static bool need_wipe(const GCodeGenerator &gcodegen,
const ExPolygons &lslices_offset,
const std::vector<BoundingBox> &lslices_offset_bboxes,
const EdgeGrid::Grid &grid_lslices_offset,
@ -1171,7 +1171,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
@ -1474,7 +1474,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.

View File

@ -12,7 +12,7 @@
namespace Slic3r {
// Forward declarations.
class GCode;
class GCodeGenerator;
class Layer;
class Point;
@ -29,13 +29,13 @@ public:
void init_layer(const Layer &layer);
Polyline travel_to(const GCode& gcodegen, const Point& point)
Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point)
{
bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
}
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point, bool* could_be_wipe_disabled);
struct Boundary {
// Collection of boundaries used for detection of crossing perimeters for travels

View File

@ -48,7 +48,7 @@ public:
void raise()
{
if (valid()) {
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height(); }
_curPileIdx++;
}
}

View File

@ -29,7 +29,7 @@
namespace Slic3r {
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
CoolingBuffer::CoolingBuffer(GCodeGenerator &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
{
this->reset(gcodegen.writer().get_position());
@ -43,36 +43,45 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t
void CoolingBuffer::reset(const Vec3d &position)
{
m_current_pos.assign(5, 0.f);
m_current_pos[0] = float(position.x());
m_current_pos[1] = float(position.y());
m_current_pos[2] = float(position.z());
m_current_pos[4] = float(m_config.travel_speed.value);
assert(m_current_pos.size() == 5);
m_current_pos[AxisIdx::X] = float(position.x());
m_current_pos[AxisIdx::Y] = float(position.y());
m_current_pos[AxisIdx::Z] = float(position.z());
m_current_pos[AxisIdx::E] = 0.f;
m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value);
m_fan_speed = -1;
}
struct CoolingLine
{
enum Type {
enum Type : uint32_t {
TYPE_SET_TOOL = 1 << 0,
TYPE_EXTRUDE_END = 1 << 1,
TYPE_BRIDGE_FAN_START = 1 << 2,
TYPE_BRIDGE_FAN_END = 1 << 3,
TYPE_G0 = 1 << 4,
TYPE_G1 = 1 << 5,
TYPE_ADJUSTABLE = 1 << 6,
TYPE_EXTERNAL_PERIMETER = 1 << 7,
// G2 or G3: Arc interpolation
TYPE_G2G3 = 1 << 6,
TYPE_ADJUSTABLE = 1 << 7,
TYPE_EXTERNAL_PERIMETER = 1 << 8,
// Arc interpolation, counter-clockwise.
TYPE_G2G3_CCW = 1 << 9,
// Arc interpolation, arc defined by IJ (offset of arc center from its start position).
TYPE_G2G3_IJ = 1 << 10,
// Arc interpolation, arc defined by R (arc radius, positive - smaller, negative - larger).
TYPE_G2G3_R = 1 << 11,
// The line sets a feedrate.
TYPE_HAS_F = 1 << 8,
TYPE_WIPE = 1 << 9,
TYPE_G4 = 1 << 10,
TYPE_G92 = 1 << 11,
TYPE_HAS_F = 1 << 12,
TYPE_WIPE = 1 << 13,
TYPE_G4 = 1 << 14,
TYPE_G92 = 1 << 15,
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
// cannot have its speed adjusted. This should not happen (sic!).
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
TYPE_ADJUSTABLE_EMPTY = 1 << 16,
// Custom fan speed (introduced for overhang fan speed)
TYPE_SET_FAN_SPEED = 1 << 13,
TYPE_RESET_FAN_SPEED = 1 << 14,
TYPE_SET_FAN_SPEED = 1 << 17,
TYPE_RESET_FAN_SPEED = 1 << 18,
};
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@ -334,7 +343,7 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b
// Parse the layer G-code for the moves, which could be adjusted.
// Return the list of parsed lines, bucketed by an extruder.
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const
{
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
@ -357,7 +366,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// for a sequence of extrusion moves.
size_t active_speed_modifier = size_t(-1);
std::vector<float> new_pos;
std::array<float, AxisIdx::Count> new_pos;
for (; *line_start != 0; line_start = line_end)
{
while (*line_end != '\n' && *line_end != 0)
@ -372,12 +381,20 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_G0;
else if (boost::starts_with(sline, "G1 "))
line.type = CoolingLine::TYPE_G1;
else if (boost::starts_with(sline, "G2 "))
// Arc, clockwise.
line.type = CoolingLine::TYPE_G2G3;
else if (boost::starts_with(sline, "G3 "))
// Arc, counter-clockwise.
line.type = CoolingLine::TYPE_G2G3 | CoolingLine::TYPE_G2G3_CCW;
else if (boost::starts_with(sline, "G92 "))
line.type = CoolingLine::TYPE_G92;
if (line.type) {
// G0, G1 or G92
// G0, G1, G2, G3 or G92
// Initialize current_pos from new_pos, set IJKR to zero.
std::fill(std::copy(std::begin(current_pos), std::end(current_pos), std::begin(new_pos)),
std::end(new_pos), 0.f);
// Parse the G-code line.
new_pos = current_pos;
for (auto c = sline.begin() + 3;;) {
// Skip whitespaces.
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
@ -386,21 +403,31 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
(*c == extrusion_axis) ? AxisIdx::E : (*c == 'F') ? AxisIdx::F :
(*c >= 'I' && *c <= 'K') ? int(AxisIdx::I) + (*c - 'I') :
(*c == 'R') ? AxisIdx::R : size_t(-1);
if (axis != size_t(-1)) {
//auto [pend, ec] =
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
if (axis == 4) {
if (axis == AxisIdx::F) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
new_pos[AxisIdx::F] /= 60.f;
if ((line.type & CoolingLine::TYPE_G92) == 0)
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
line.type |= CoolingLine::TYPE_HAS_F;
}
} else if (axis >= AxisIdx::I && axis <= AxisIdx::J)
line.type |= CoolingLine::TYPE_G2G3_IJ;
else if (axis == AxisIdx::R)
line.type |= CoolingLine::TYPE_G2G3_R;
}
// Skip this word.
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c);
}
// If G2 or G3, then either center of the arc or radius has to be defined.
assert(! (line.type & CoolingLine::TYPE_G2G3) ||
(line.type & (CoolingLine::TYPE_G2G3_IJ | CoolingLine::TYPE_G2G3_R)));
// Arc is defined either by IJ or by R, not by both.
assert(! ((line.type & CoolingLine::TYPE_G2G3_IJ) && (line.type & CoolingLine::TYPE_G2G3_R)));
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
bool wipe = boost::contains(sline, ";_WIPE");
if (external_perimeter)
@ -412,23 +439,41 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
active_speed_modifier = adjustment->lines.size();
}
if ((line.type & CoolingLine::TYPE_G92) == 0) {
// G0 or G1. Calculate the duration.
// G0, G1, G2, G3. Calculate the duration.
assert((line.type & CoolingLine::TYPE_G0) != 0 + (line.type & CoolingLine::TYPE_G1) != 0 + (line.type & CoolingLine::TYPE_G2G3) != 0 == 1);
if (m_config.use_relative_e_distances.value)
// Reset extruder accumulator.
current_pos[3] = 0.f;
current_pos[AxisIdx::E] = 0.f;
float dif[4];
for (size_t i = 0; i < 4; ++ i)
dif[i] = new_pos[i] - current_pos[i];
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
float dxyz2 = dxy2 + dif[2] * dif[2];
float dxy2;
if (line.type & CoolingLine::TYPE_G2G3) {
// Measure arc length.
if (line.type & CoolingLine::TYPE_G2G3_IJ) {
dxy2 = sqr(Geometry::ArcWelder::arc_length(
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
Vec2d(current_pos[AxisIdx::X] + new_pos[AxisIdx::I], current_pos[AxisIdx::Y] + new_pos[AxisIdx::J]),
line.type & CoolingLine::TYPE_G2G3_CCW));
} else if (line.type & CoolingLine::TYPE_G2G3_R) {
dxy2 = sqr(Geometry::ArcWelder::arc_length(
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
double(new_pos[AxisIdx::R])));
} else
dxy2 = 0;
} else
dxy2 = sqr(dif[AxisIdx::X]) + sqr(dif[AxisIdx::Y]);
float dxyz2 = dxy2 + sqr(dif[AxisIdx::Z]);
if (dxyz2 > 0.f) {
// Movement in xyz, calculate time from the xyz Euclidian distance.
line.length = sqrt(dxyz2);
} else if (std::abs(dif[3]) > 0.f) {
} else if (std::abs(dif[AxisIdx::E]) > 0.f) {
// Movement in the extruder axis.
line.length = std::abs(dif[3]);
line.length = std::abs(dif[AxisIdx::E]);
}
line.feedrate = new_pos[4];
line.feedrate = new_pos[AxisIdx::F];
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
if (line.length > 0) {
assert(line.feedrate > 0);
@ -440,7 +485,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
assert(adjustment->min_print_speed >= 0);
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
}
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
if (active_speed_modifier < adjustment->lines.size() && (line.type & (CoolingLine::TYPE_G1 | CoolingLine::TYPE_G2G3))) {
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
CoolingLine &sm = adjustment->lines[active_speed_modifier];
@ -457,7 +502,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = 0;
}
}
current_pos = std::move(new_pos);
std::copy(std::begin(new_pos), std::begin(new_pos) + 5, std::begin(current_pos));
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
// Closing a block of non-zero length extrusion moves.
line.type = CoolingLine::TYPE_EXTRUDE_END;

View File

@ -17,7 +17,7 @@
namespace Slic3r {
class GCode;
class GCodeGenerator;
class Layer;
struct PerExtruderAdjustments;
@ -32,7 +32,7 @@ struct PerExtruderAdjustments;
//
class CoolingBuffer {
public:
CoolingBuffer(GCode &gcodegen);
CoolingBuffer(GCodeGenerator &gcodegen);
void reset(const Vec3d &position);
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
@ -41,7 +41,7 @@ public:
private:
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
@ -50,9 +50,11 @@ private:
// G-code snippet cached for the support layers preceding an object layer.
std::string m_gcode;
// Internal data.
// X,Y,Z,E,F
std::vector<char> m_axis;
std::vector<float> m_current_pos;
enum AxisIdx : int {
X = 0, Y, Z, E, F, I, J, K, R, Count
};
std::array<float, 5> m_current_pos;
// Current known fan speed or -1 if not known yet.
int m_fan_speed;
// Cached from GCodeWriter.
@ -61,7 +63,7 @@ private:
// Highest of m_extruder_ids plus 1.
unsigned int m_num_extruders { 0 };
const std::string m_toolchange_prefix;
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
// Referencs GCodeGenerator::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
const PrintConfig &m_config;
unsigned int m_current_extruder;

View File

@ -0,0 +1,216 @@
#include "ExtrusionProcessor.hpp"
#include <string>
namespace Slic3r { namespace ExtrusionProcessor {
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
{
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>(path.polyline.points,
unscaled_prev_layer, path.width());
std::vector<std::pair<float, float>> calculated_distances(extended_points.size());
for (size_t i = 0; i < extended_points.size(); i++) {
const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
float proximity_to_curled_lines = 0.0;
const double dist_limit = 10.0 * path.width();
{
Vec2d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_layer_curled_lines.all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle
// of this long line
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
if (len > 8) {
Vec2d dir = Vec2d(next.position - curr.position) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Polygon box_of_influence = {
scaled(Vec2d(curr.position + right * dist_limit)),
scaled(Vec2d(next.position + right * dist_limit)),
scaled(Vec2d(next.position - right * dist_limit)),
scaled(Vec2d(curr.position - right * dist_limit)),
};
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
if (inside.empty())
continue;
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
projected_lengths_sum += projected_length;
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
for (size_t idx : line_indices) {
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
float proximity = (1.0 - (distance_from_curled / dist_limit)) * (1.0 - (distance_from_curled / dist_limit)) *
(line.curled_height / (path.height() * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
proximity_to_curled_lines = std::max(proximity_to_curled_lines, proximity);
}
}
}
calculated_distances[i].first = std::max(curr.distance, next.distance);
calculated_distances[i].second = proximity_to_curled_lines;
}
ExtrusionPaths result;
ExtrusionAttributes new_attrs = path.attributes();
new_attrs.overhang_attributes = std::optional<OverhangAttributes>(
{calculated_distances[0].first, calculated_distances[0].first, calculated_distances[0].second});
result.emplace_back(new_attrs);
result.back().polyline.append(Point::new_scale(extended_points[0].position));
size_t sequence_start_index = 0;
for (size_t i = 1; i < extended_points.size(); i++) {
result.back().polyline.append(Point::new_scale(extended_points[i].position));
result.back().overhang_attributes_mutable()->end_distance_from_prev_layer = extended_points[i].distance;
if (std::abs(calculated_distances[sequence_start_index].first - calculated_distances[i].first) < 0.001 * path.attributes().width &&
std::abs(calculated_distances[sequence_start_index].second - calculated_distances[i].second) < 0.001) {
// do not start new path, the attributes are similar enough
// NOTE: a larger tolerance may be applied here. However, it makes the gcode preview much less smooth
// (But it has very likely zero impact on the print quality.)
} else if (i + 1 < extended_points.size()) { // do not start new path if this is last point!
// start new path, parameters differ
new_attrs.overhang_attributes->start_distance_from_prev_layer = calculated_distances[i].first;
new_attrs.overhang_attributes->end_distance_from_prev_layer = calculated_distances[i].first;
new_attrs.overhang_attributes->proximity_to_curled_lines = calculated_distances[i].second;
sequence_start_index = i;
result.emplace_back(new_attrs);
result.back().polyline.append(Point::new_scale(extended_points[i].position));
}
}
return result;
};
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const ExtrusionEntityCollection *ecc,
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
{
ExtrusionEntityCollection result{};
result.no_sort = ecc->no_sort;
for (const auto *e : ecc->entities) {
if (auto *col = dynamic_cast<const ExtrusionEntityCollection *>(e)) {
result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines));
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(e)) {
ExtrusionLoop new_loop = *loop;
new_loop.paths.clear();
for (const ExtrusionPath &p : loop->paths) {
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end());
}
result.append(new_loop);
} else if (auto *mp = dynamic_cast<const ExtrusionMultiPath *>(e)) {
ExtrusionMultiPath new_mp = *mp;
new_mp.paths.clear();
for (const ExtrusionPath &p : mp->paths) {
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
new_mp.paths.insert(new_mp.paths.end(), paths.begin(), paths.end());
}
result.append(new_mp);
} else if (auto *op = dynamic_cast<const ExtrusionPathOriented *>(e)) {
auto paths = calculate_and_split_overhanging_extrusions(*op, unscaled_prev_layer, prev_layer_curled_lines);
for (const ExtrusionPath &p : paths) {
result.append(ExtrusionPathOriented(p.polyline, p.attributes()));
}
} else if (auto *p = dynamic_cast<const ExtrusionPath *>(e)) {
auto paths = calculate_and_split_overhanging_extrusions(*p, unscaled_prev_layer, prev_layer_curled_lines);
result.append(paths);
} else {
throw Slic3r::InvalidArgument("Unknown extrusion entity type");
}
}
return result;
};
std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
const FullPrintConfig &config,
size_t extruder_id,
float external_perim_reference_speed,
float default_speed)
{
assert(attributes.overhang_attributes.has_value());
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
if (config.enable_dynamic_overhang_speeds) {
overhangs_with_speeds = {{0, config.overhang_speed_0},
{25, config.overhang_speed_1},
{50, config.overhang_speed_2},
{75, config.overhang_speed_3},
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
}
std::vector<std::pair<int, ConfigOptionInts>> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}};
if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0},
{25, config.overhang_fan_speed_1},
{50, config.overhang_fan_speed_2},
{75, config.overhang_fan_speed_3},
{100, ConfigOptionInts{0}}};
}
float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed;
std::map<float, float> speed_sections;
for (size_t i = 0; i < overhangs_with_speeds.size(); i++) {
float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0));
float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) :
overhangs_with_speeds[i].second.value;
if (speed < EPSILON)
speed = speed_base;
speed_sections[distance] = speed;
}
std::map<float, float> fan_speed_sections;
for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) {
float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0));
float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id);
fan_speed_sections[distance] = fan_speed;
}
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
auto upper_dist = values.lower_bound(distance);
if (upper_dist == values.end()) {
return values.rbegin()->second;
}
if (upper_dist == values.begin()) {
return upper_dist->second;
}
auto lower_dist = std::prev(upper_dist);
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
};
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));
float curled_base_speed = interpolate_speed(speed_sections,
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines);
float final_speed = std::min(curled_base_speed, extrusion_speed);
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));
if (!config.enable_dynamic_overhang_speeds) {
final_speed = -1;
}
if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
fan_speed = -1;
}
return {final_speed, fan_speed};
}
}} // namespace Slic3r::ExtrusionProcessor

View File

@ -18,25 +18,29 @@
#include "../Flow.hpp"
#include "../Config.hpp"
#include "../Line.hpp"
#include "../Exception.hpp"
#include "../PrintConfig.hpp"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <limits>
#include <numeric>
#include <optional>
#include <ostream>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Slic3r {
namespace Slic3r { namespace ExtrusionProcessor {
struct ExtendedPoint
{
Vec2d position;
float distance;
float curvature;
Vec2d position;
float distance;
float curvature;
};
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, typename POINTS, typename L>
@ -50,27 +54,30 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
if (input_points.empty())
return {};
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
std::vector<ExtendedPoint> points;
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
{
ExtendedPoint start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
points.push_back(start_point);
}
for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(input_points[i])};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
if (ADD_INTERSECTIONS &&
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
const ExtendedPoint &prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
const ExtendedPoint &prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
for (const auto &intersection : intersections) {
ExtendedPoint p{};
p.position = intersection.first.template cast<double>();
@ -83,47 +90,49 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint> new_points;
new_points.reserve(points.size()*2);
new_points.reserve(points.size() * 2);
new_points.push_back(points.front());
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
const ExtendedPoint &curr = points[point_idx];
const ExtendedPoint &next = points[point_idx + 1];
if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) ||
(next.distance > 0 && next.distance < boundary_offset + 2.0f)) {
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
double line_len = (next.position - curr.position).norm();
if (line_len > 4.0f) {
double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
double t0 = std::min(a0, a1);
double t1 = std::max(a0, a1);
if (t0 < 1.0) {
auto p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
auto p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l,
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
ExtendedPoint new_p{};
new_p.position = p0;
new_p.distance = float(p0_dist + boundary_offset);
new_p.position = p0;
new_p.distance = float(p0_dist + boundary_offset);
new_points.push_back(new_p);
}
if (t1 > 0.0) {
auto p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
auto p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l,
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
ExtendedPoint new_p{};
new_p.position = p1;
new_p.distance = float(p1_dist + boundary_offset);
new_p.position = p1;
new_p.distance = float(p1_dist + boundary_offset);
new_points.push_back(new_p);
}
}
}
new_points.push_back(next);
}
points = new_points;
points = std::move(new_points);
}
if (max_line_length > 0) {
std::vector<ExtendedPoint> new_points;
new_points.reserve(points.size()*2);
new_points.reserve(points.size() * 2);
{
for (size_t i = 0; i + 1 < points.size(); i++) {
const ExtendedPoint &curr = points[i];
@ -137,14 +146,14 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
auto [p_dist, p_near_l,
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
ExtendedPoint new_p{};
new_p.position = pos;
new_p.distance = float(p_dist + boundary_offset);
new_p.position = pos;
new_p.distance = float(p_dist + boundary_offset);
new_points.push_back(new_p);
}
}
new_points.push_back(points.back());
}
points = new_points;
points = std::move(new_points);
}
std::vector<float> angles_for_curvature(points.size());
@ -216,144 +225,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
return points;
}
struct ProcessedPoint
{
Point p;
float speed = 1.0f;
int fan_speed = 0;
};
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
class ExtrusionQualityEstimator
{
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
const PrintObject *current_object;
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(
const ExtrusionEntityCollection *ecc,
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
public:
void set_current_object(const PrintObject *object) { current_object = object; }
std::pair<float, float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
const FullPrintConfig &config,
size_t extruder_id,
float external_perim_reference_speed,
float default_speed);
void prepare_for_new_layer(const Layer *layer)
{
if (layer == nullptr)
return;
const PrintObject *object = layer->object();
prev_layer_boundaries[object] = next_layer_boundaries[object];
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
prev_curled_extrusions[object] = next_curled_extrusions[object];
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
}
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
const ExtrusionPath &path,
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
size_t extruder_id,
float ext_perimeter_speed,
float original_speed)
{
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::map<float, float> speed_sections;
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
overhangs_w_speeds[i].second.value;
if (speed < EPSILON) speed = speed_base;
speed_sections[distance] = speed;
}
std::map<float, float> fan_speed_sections;
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
fan_speed_sections[distance] = fan_speed;
}
std::vector<ExtendedPoint> extended_points =
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width);
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
for (size_t i = 0; i < extended_points.size(); i++) {
const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
float artificial_distance_to_curled_lines = 0.0;
const double dist_limit = 10.0 * path.width;
{
Vec2d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
if (len > 8) {
Vec2d dir = Vec2d(next.position - curr.position) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Polygon box_of_influence = {
scaled(Vec2d(curr.position + right * dist_limit)),
scaled(Vec2d(next.position + right * dist_limit)),
scaled(Vec2d(next.position - right * dist_limit)),
scaled(Vec2d(curr.position - right * dist_limit)),
};
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
if (inside.empty())
continue;
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
projected_lengths_sum += projected_length;
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) *
(1.0 - (distance_from_curled / dist_limit)) *
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
}
}
}
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
auto upper_dist = values.lower_bound(distance);
if (upper_dist == values.end()) {
return values.rbegin()->second;
}
if (upper_dist == values.begin()) {
return upper_dist->second;
}
auto lower_dist = std::prev(upper_dist);
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
};
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
interpolate_speed(speed_sections, next.distance));
float curled_base_speed = interpolate_speed(speed_sections, artificial_distance_to_curled_lines);
float final_speed = std::min(curled_base_speed, extrusion_speed);
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
interpolate_speed(fan_speed_sections, next.distance));
processed_points.push_back({scaled(curr.position), final_speed, int(fan_speed)});
}
return processed_points;
}
};
} // namespace Slic3r
}} // namespace Slic3r::ExtrusionProcessor
#endif // slic3r_ExtrusionProcessor_hpp_

View File

@ -9,8 +9,9 @@
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/Geometry/ArcWelder.hpp"
#include "GCodeProcessor.hpp"
#include <boost/algorithm/string/case_conv.hpp>
@ -48,8 +49,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
// taken from PrusaResearch.ini - [printer:Original Prusa i3 MK2.5 MMU2]
static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" };
static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!";
namespace Slic3r {
const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
@ -70,6 +69,19 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
const float GCodeProcessor::Wipe_Width = 0.05f;
const float GCodeProcessor::Wipe_Height = 0.05f;
bgcode::binarize::BinarizerConfig GCodeProcessor::s_binarizer_config{
{
bgcode::core::ECompressionType::None, // file metadata
bgcode::core::ECompressionType::None, // printer metadata
bgcode::core::ECompressionType::Deflate, // print metadata
bgcode::core::ECompressionType::Deflate, // slicer metadata
bgcode::core::ECompressionType::Heatshrink_12_4, // gcode
},
bgcode::core::EGCodeEncodingType::MeatPackComments,
bgcode::core::EMetadataEncodingType::INI,
bgcode::core::EChecksumType::CRC32
};
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:";
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
@ -460,7 +472,7 @@ void GCodeProcessorResult::reset() {
}
#else
void GCodeProcessorResult::reset() {
is_binary_file = false;
moves.clear();
lines_ends.clear();
bed_shape = Pointfs();
@ -559,6 +571,9 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
{
m_parser.apply_config(config);
m_binarizer.set_enabled(config.gcode_binary);
m_result.is_binary_file = config.gcode_binary;
m_producer = EProducer::PrusaSlicer;
m_flavor = config.gcode_flavor;
@ -1028,6 +1043,23 @@ static inline const char* remove_eols(const char *begin, const char *end) {
// Load a G-code into a stand-alone G-code viewer.
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
{
FILE* file = boost::nowide::fopen(filename.c_str(), "rb");
if (file == nullptr)
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
using namespace bgcode::core;
std::vector<uint8_t> cs_buffer(65536);
const bool is_binary = is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == EResult::Success;
fclose(file);
if (is_binary)
process_binary_file(filename, cancel_callback);
else
process_ascii_file(filename, cancel_callback);
}
void GCodeProcessor::process_ascii_file(const std::string& filename, std::function<void()> cancel_callback)
{
CNumericLocalesSetter locales_setter;
@ -1078,6 +1110,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
// process gcode
m_result.filename = filename;
m_result.is_binary_file = false;
m_result.id = ++s_result_id;
initialize_result_moves();
size_t parse_line_callback_cntr = 10000;
@ -1095,6 +1128,156 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
this->finalize(false);
}
static void update_lines_ends_and_out_file_pos(const std::string& out_string, std::vector<size_t>& lines_ends, size_t* out_file_pos)
{
for (size_t i = 0; i < out_string.size(); ++i) {
if (out_string[i] == '\n')
lines_ends.emplace_back((out_file_pos != nullptr) ? *out_file_pos + i + 1 : i + 1);
}
if (out_file_pos != nullptr)
*out_file_pos += out_string.size();
}
void GCodeProcessor::process_binary_file(const std::string& filename, std::function<void()> cancel_callback)
{
#if ENABLE_GCODE_VIEWER_STATISTICS
m_start_time = std::chrono::high_resolution_clock::now();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") };
if (file.f == nullptr)
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
fseek(file.f, 0, SEEK_END);
const long file_size = ftell(file.f);
rewind(file.f);
// read file header
using namespace bgcode::core;
using namespace bgcode::binarize;
FileHeader file_header;
EResult res = read_header(*file.f, file_header, nullptr);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("File %1% does not contain a valid binary gcode\nError: %2%", filename,
std::string(translate_result(res))));
// read file metadata block, if present
BlockHeader block_header;
std::vector<uint8_t> cs_buffer(65536);
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
if ((EBlockType)block_header.type != EBlockType::FileMetadata &&
(EBlockType)block_header.type != EBlockType::PrinterMetadata)
throw Slic3r::RuntimeError(format("Unable to find file metadata block in file %1%", filename));
if ((EBlockType)block_header.type == EBlockType::FileMetadata) {
FileMetadataBlock file_metadata_block;
res = file_metadata_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
auto producer_it = std::find_if(file_metadata_block.raw_data.begin(), file_metadata_block.raw_data.end(),
[](const std::pair<std::string, std::string>& item) { return item.first == "Producer"; });
if (producer_it != file_metadata_block.raw_data.end() && boost::starts_with(producer_it->second, std::string(SLIC3R_APP_NAME)))
m_producer = EProducer::PrusaSlicer;
else
m_producer = EProducer::Unknown;
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
}
else {
m_producer = EProducer::Unknown;
}
// read printer metadata block
if ((EBlockType)block_header.type != EBlockType::PrinterMetadata)
throw Slic3r::RuntimeError(format("Unable to find printer metadata block in file %1%", filename));
PrinterMetadataBlock printer_metadata_block;
res = printer_metadata_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
// read thumbnail blocks
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
while ((EBlockType)block_header.type == EBlockType::Thumbnail) {
ThumbnailBlock thumbnail_block;
res = thumbnail_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
}
// read print metadata block
if ((EBlockType)block_header.type != EBlockType::PrintMetadata)
throw Slic3r::RuntimeError(format("Unable to find print metadata block in file %1%", filename));
PrintMetadataBlock print_metadata_block;
res = print_metadata_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
// read slicer metadata block
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
if ((EBlockType)block_header.type != EBlockType::SlicerMetadata)
throw Slic3r::RuntimeError(format("Unable to find slicer metadata block in file %1%", filename));
SlicerMetadataBlock slicer_metadata_block;
res = slicer_metadata_block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
std::string str;
for (const auto& [key, value] : slicer_metadata_block.raw_data) {
str += key + " = " + value + "\n";
}
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
config.load_from_ini_string(str, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
m_result.filename = filename;
m_result.is_binary_file = true;
m_result.id = ++s_result_id;
initialize_result_moves();
// read gcodes block
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
if ((EBlockType)block_header.type != EBlockType::GCode)
throw Slic3r::RuntimeError(format("Unable to find gcode block in file %1%", filename));
while ((EBlockType)block_header.type == EBlockType::GCode) {
GCodeBlock block;
res = block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
std::vector<size_t>& lines_ends = m_result.lines_ends.emplace_back(std::vector<size_t>());
update_lines_ends_and_out_file_pos(block.raw_data, lines_ends, nullptr);
m_parser.parse_buffer(block.raw_data, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
this->process_gcode_line(line, true);
});
if (ftell(file.f) == file_size)
break;
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
if (res != EResult::Success)
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
}
// Don't post-process the G-code to update time stamps.
this->finalize(false);
}
void GCodeProcessor::initialize(const std::string& filename)
{
assert(is_decimal_separator_point());
@ -1137,7 +1320,7 @@ void GCodeProcessor::finalize(bool perform_post_process)
m_used_filaments.process_caches(this);
update_estimated_times_stats();
update_estimated_statistics();
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
std::cout << "\n";
@ -2368,13 +2551,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
if (line.has_e()) g1_axes[E] = (double)line.e();
std::optional<double> g1_feedrate = std::nullopt;
if (line.has_f()) g1_feedrate = (double)line.f();
std::optional<std::string> g1_cmt = std::nullopt;
if (!line.comment().empty()) g1_cmt = line.comment();
process_G1(g1_axes, g1_feedrate, g1_cmt);
process_G1(g1_axes, g1_feedrate);
}
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, std::optional<std::string> cmt)
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, G1DiscretizationOrigin origin)
{
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
const float filament_radius = 0.5f * filament_diameter;
@ -2457,7 +2637,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
m_height = m_forced_height;
else if (m_layer_id == 0)
m_height = m_first_layer_height + m_z_offset;
else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) {
else if (origin == G1DiscretizationOrigin::G1) {
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
m_height = m_end_position[Z] - m_extruded_last_z;
}
@ -2468,7 +2648,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0))
m_end_position[Z] = m_height;
if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG)
if (origin == G1DiscretizationOrigin::G1)
m_extruded_last_z = m_end_position[Z];
m_options_z_corrector.update(m_height);
@ -2698,18 +2878,48 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
}
// store move
store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG);
store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3);
}
void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise)
{
if (!line.has('I') || !line.has('J'))
enum class EFitting { None, IJ, R };
const EFitting fitting = line.has('R') ? EFitting::R : (line.has('I') && line.has('J')) ? EFitting::IJ : EFitting::None;
if (fitting == EFitting::None)
return;
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
const float filament_radius = 0.5f * filament_diameter;
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
AxisCoords end_position = m_start_position;
for (unsigned char a = X; a <= E; ++a) {
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
}
// relative center
Vec3f rel_center = Vec3f::Zero();
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
return;
#ifndef NDEBUG
double radius = 0.0;
#endif // NDEBUG
if (fitting == EFitting::R) {
float r;
if (!line.has_value('R', r) || r == 0.0f)
return;
#ifndef NDEBUG
radius = (double)std::abs(r);
#endif // NDEBUG
const Vec2f start_pos((float)m_start_position[X], (float)m_start_position[Y]);
const Vec2f end_pos((float)end_position[X], (float)end_position[Y]);
const Vec2f c = Geometry::ArcWelder::arc_center(start_pos, end_pos, r, !clockwise);
rel_center.x() = c.x() - m_start_position[X];
rel_center.y() = c.y() - m_start_position[Y];
}
else {
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
return;
}
// scale center, if needed
if (m_units == EUnits::Inches)
@ -2745,15 +2955,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
// arc center
arc.center = arc.start + rel_center.cast<double>();
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
const float filament_radius = 0.5f * filament_diameter;
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
AxisCoords end_position = m_start_position;
for (unsigned char a = X; a <= E; ++a) {
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
}
// arc end endpoint
arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]);
@ -2762,6 +2963,8 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
// what to do ???
}
assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON);
// updates feedrate from line
std::optional<float> feedrate;
if (line.has_f())
@ -2821,9 +3024,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
g1_feedrate = (double)*feedrate;
if (extrusion.has_value())
g1_axes[E] = target[E];
std::optional<std::string> g1_cmt = INTERNAL_G2G3_TAG;
process_G1(g1_axes, g1_feedrate, g1_cmt);
process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3);
};
// calculate arc segments
@ -2832,8 +3033,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
// https://github.com/prusa3d/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp
// segments count
#if 0
static const double MM_PER_ARC_SEGMENT = 1.0;
const size_t segments = std::max<size_t>(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1);
#else
static const double gcode_arc_tolerance = 0.0125;
const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance);
#endif
const double inv_segment = 1.0 / double(segments);
const double theta_per_segment = arc.angle * inv_segment;
@ -3440,8 +3646,68 @@ void GCodeProcessor::post_process()
// temporary file to contain modified gcode
std::string out_path = m_result.filename + ".postprocess";
FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") };
if (out.f == nullptr) {
if (out.f == nullptr)
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n"));
std::vector<double> filament_mm(m_result.extruders_count, 0.0);
std::vector<double> filament_cm3(m_result.extruders_count, 0.0);
std::vector<double> filament_g(m_result.extruders_count, 0.0);
std::vector<double> filament_cost(m_result.extruders_count, 0.0);
double filament_total_g = 0.0;
double filament_total_cost = 0.0;
for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) {
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
filament_cm3[id] = volume * 0.001;
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001;
filament_total_g += filament_g[id];
filament_total_cost += filament_cost[id];
}
if (m_binarizer.is_enabled()) {
// update print metadata
auto stringify = [](const std::vector<double>& values) {
std::string ret;
char buf[1024];
for (size_t i = 0; i < values.size(); ++i) {
sprintf(buf, i < values.size() - 1 ? "%.2lf, " : "%.2lf", values[i]);
ret += buf;
}
return ret;
};
// update binary data
bgcode::binarize::BinaryData& binary_data = m_binarizer.get_binary_data();
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedG, stringify({ filament_total_g }));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentCost, stringify({ filament_total_cost }));
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3)); // duplicated into print metadata
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = m_time_processor.machines[i];
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
char buf[128];
sprintf(buf, "(%s mode)", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent");
binary_data.print_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time));
binary_data.print_metadata.raw_data.emplace_back("estimated first layer printing time " + std::string(buf), get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front()));
binary_data.printer_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time));
}
}
const bgcode::core::EResult res = m_binarizer.initialize(*out.f, s_binarizer_config);
if (res != bgcode::core::EResult::Success)
throw Slic3r::RuntimeError(format("Unable to initialize the gcode binarizer.\nError: %1%", bgcode::core::translate_result(res)));
}
auto time_in_minutes = [](float time_in_seconds) {
@ -3560,12 +3826,14 @@ void GCodeProcessor::post_process()
size_t m_curr_g1_id{ 0 };
size_t m_out_file_pos{ 0 };
bgcode::binarize::Binarizer& m_binarizer;
public:
ExportLines(EWriteType type, TimeMachine& machine)
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
#ifndef NDEBUG
: m_statistics(*this), m_write_type(type), m_machine(machine) {}
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
#else
: m_write_type(type), m_machine(machine) {}
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
#endif // NDEBUG
void update(size_t lines_counter, size_t g1_lines_counter) {
@ -3678,7 +3946,14 @@ void GCodeProcessor::post_process()
}
}
write_to_file(out, out_string, result, out_path);
if (m_binarizer.is_enabled()) {
if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success)
throw Slic3r::RuntimeError("Error while sending gcode to the binarizer.");
}
else {
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos);
}
}
// flush the current content of the cache to file
@ -3694,7 +3969,14 @@ void GCodeProcessor::post_process()
m_statistics.remove_all_lines();
#endif // NDEBUG
write_to_file(out, out_string, result, out_path);
if (m_binarizer.is_enabled()) {
if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success)
throw Slic3r::RuntimeError("Error while sending gcode to the binarizer.");
}
else {
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos);
}
}
void synchronize_moves(GCodeProcessorResult& result) const {
@ -3713,22 +3995,19 @@ void GCodeProcessor::post_process()
private:
void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path) {
if (!out_string.empty()) {
fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n"));
if (!m_binarizer.is_enabled()) {
fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError("GCode processor post process export failed.\nIs the disk full?");
}
}
for (size_t i = 0; i < out_string.size(); ++i) {
if (out_string[i] == '\n')
result.lines_ends.emplace_back(m_out_file_pos + i + 1);
}
m_out_file_pos += out_string.size();
}
}
};
ExportLines export_lines(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[0]);
// replace placeholder lines with the proper final value
// gcode_line is in/out parameter, to reduce expensive memory allocation
@ -3791,23 +4070,6 @@ void GCodeProcessor::post_process()
return processed;
};
std::vector<double> filament_mm(m_result.extruders_count, 0.0);
std::vector<double> filament_cm3(m_result.extruders_count, 0.0);
std::vector<double> filament_g(m_result.extruders_count, 0.0);
std::vector<double> filament_cost(m_result.extruders_count, 0.0);
double filament_total_g = 0.0;
double filament_total_cost = 0.0;
for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) {
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
filament_cm3[id] = volume * 0.001;
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001;
filament_total_g += filament_g[id];
filament_total_cost += filament_cost[id];
}
auto process_used_filament = [&](std::string& gcode_line) {
// Prefilter for parsing speed.
if (gcode_line.size() < 8 || gcode_line[0] != ';' || gcode_line[1] != ' ')
@ -3828,12 +4090,12 @@ void GCodeProcessor::post_process()
};
bool ret = false;
ret |= process_tag(gcode_line, "; filament used [mm] =", filament_mm);
ret |= process_tag(gcode_line, "; filament used [g] =", filament_g);
ret |= process_tag(gcode_line, "; total filament used [g] =", { filament_total_g });
ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_cm3);
ret |= process_tag(gcode_line, "; filament cost =", filament_cost);
ret |= process_tag(gcode_line, "; total filament cost =", { filament_total_cost });
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedMmMask, filament_mm);
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedGMask, filament_g);
ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentUsedGMask, { filament_total_g });
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedCm3Mask, filament_cm3);
ret |= process_tag(gcode_line, PrintStatistics::FilamentCostMask, filament_cost);
ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentCostMask, { filament_total_cost });
return ret;
};
@ -3972,6 +4234,7 @@ void GCodeProcessor::post_process()
};
m_result.lines_ends.clear();
m_result.lines_ends.emplace_back(std::vector<size_t>());
unsigned int line_id = 0;
// Backtrace data for Tx gcode lines
@ -4041,9 +4304,66 @@ void GCodeProcessor::post_process()
export_lines.flush(out, m_result, out_path);
if (m_binarizer.is_enabled()) {
if (m_binarizer.finalize() != bgcode::core::EResult::Success)
throw Slic3r::RuntimeError("Error while finalizing the gcode binarizer.");
}
out.close();
in.close();
if (m_binarizer.is_enabled()) {
// updates m_result.lines_ends from binarized gcode file
m_result.lines_ends.clear();
FilePtr file(boost::nowide::fopen(out_path.c_str(), "rb"));
if (file.f != nullptr) {
fseek(file.f, 0, SEEK_END);
const long file_size = ftell(file.f);
rewind(file.f);
// read file header
using namespace bgcode::core;
using namespace bgcode::binarize;
FileHeader file_header;
EResult res = read_header(*file.f, file_header, nullptr);
if (res == EResult::Success) {
// search first GCode block
BlockHeader block_header;
res = read_next_block_header(*file.f, file_header, block_header, EBlockType::GCode, nullptr, 0);
while (res == EResult::Success) {
GCodeBlock block;
res = block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success)
break;
// extract lines ends from block
std::vector<size_t>& lines_ends = m_result.lines_ends.emplace_back(std::vector<size_t>());
for (size_t i = 0; i < block.raw_data.size(); ++i) {
if (block.raw_data[i] == '\n')
lines_ends.emplace_back(i + 1);
}
if (ftell(file.f) == file_size)
break;
// read next block header
res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0);
if (res != EResult::Success)
break;
if (block_header.type != (uint16_t)EBlockType::GCode) {
res = EResult::InvalidBlockType;
break;
}
}
}
if (res != EResult::Success && !m_result.lines_ends.empty() && !m_result.lines_ends.front().empty())
// some error occourred, clear lines ends
m_result.lines_ends = { std::vector<size_t>() };
}
}
export_lines.synchronize_moves(m_result);
if (rename_file(out_path, m_result.filename))
@ -4248,7 +4568,7 @@ void GCodeProcessor::simulate_st_synchronize(float additional_time)
}
}
void GCodeProcessor::update_estimated_times_stats()
void GCodeProcessor::update_estimated_statistics()
{
auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) {
PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast<size_t>(mode)];

View File

@ -13,6 +13,8 @@
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/CustomGCode.hpp"
#include <LibBGCode/binarize/binarize.hpp>
#include <cstdint>
#include <array>
#include <vector>
@ -62,16 +64,20 @@ namespace Slic3r {
time = 0.0f;
travel_time = 0.0f;
custom_gcode_times.clear();
custom_gcode_times.shrink_to_fit();
moves_times.clear();
moves_times.shrink_to_fit();
roles_times.clear();
roles_times.shrink_to_fit();
layers_times.clear();
layers_times.shrink_to_fit();
}
};
std::vector<double> volumes_per_color_change;
std::map<size_t, double> volumes_per_extruder;
std::vector<double> volumes_per_color_change;
std::map<size_t, double> volumes_per_extruder;
std::map<GCodeExtrusionRole, std::pair<double, double>> used_filaments_per_role;
std::map<size_t, double> cost_per_extruder;
std::map<size_t, double> cost_per_extruder;
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
@ -82,6 +88,7 @@ namespace Slic3r {
m.reset();
}
volumes_per_color_change.clear();
volumes_per_color_change.shrink_to_fit();
volumes_per_extruder.clear();
used_filaments_per_role.clear();
cost_per_extruder.clear();
@ -141,10 +148,13 @@ namespace Slic3r {
};
std::string filename;
bool is_binary_file;
unsigned int id;
std::vector<MoveVertex> moves;
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
std::vector<size_t> lines_ends;
// Binarized gcodes usually have several gcode blocks. Each block has its own list on ends of lines.
// Ascii gcodes have only one list on ends of lines
std::vector<std::vector<size_t>> lines_ends;
Pointfs bed_shape;
float max_print_height;
SettingsIds settings_ids;
@ -529,8 +539,12 @@ namespace Slic3r {
};
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
static bgcode::binarize::BinarizerConfig& get_binarizer_config() { return s_binarizer_config; }
private:
GCodeReader m_parser;
bgcode::binarize::Binarizer m_binarizer;
static bgcode::binarize::BinarizerConfig s_binarizer_config;
EUnits m_units;
EPositioningType m_global_positioning_type;
@ -628,6 +642,8 @@ namespace Slic3r {
void apply_config(const PrintConfig& config);
void set_print(Print* print) { m_print = print; }
bgcode::binarize::BinaryData& get_binary_data() { return m_binarizer.get_binary_data(); }
const bgcode::binarize::BinaryData& get_binary_data() const { return m_binarizer.get_binary_data(); }
void enable_stealth_time_estimator(bool enabled);
bool is_stealth_time_estimator_enabled() const {
@ -670,6 +686,9 @@ namespace Slic3r {
void apply_config_kissslicer(const std::string& filename);
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
void process_ascii_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
void process_binary_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
// Process tags embedded into comments
void process_tags(const std::string_view comment, bool producers_enabled);
bool process_producers_tags(const std::string_view comment);
@ -686,8 +705,12 @@ namespace Slic3r {
// Move
void process_G0(const GCodeReader::GCodeLine& line);
void process_G1(const GCodeReader::GCodeLine& line);
enum class G1DiscretizationOrigin {
G1,
G2G3,
};
void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt },
std::optional<double> feedrate = std::nullopt, std::optional<std::string> cmt = std::nullopt);
std::optional<double> feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1);
// Arc Move
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);
@ -821,7 +844,7 @@ namespace Slic3r {
// Simulates firmware st_synchronize() call
void simulate_st_synchronize(float additional_time = 0.0f);
void update_estimated_times_stats();
void update_estimated_statistics();
double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section);
};

View File

@ -12,12 +12,14 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include "../CustomGCode.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#include <string_view>
#ifdef __APPLE__
#include <boost/spirit/include/karma.hpp>
@ -26,6 +28,8 @@
#define FLAVOR_IS(val) this->config.gcode_flavor == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
using namespace std::string_view_literals;
namespace Slic3r {
// static
@ -103,17 +107,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return {};
std::string code, comment;
std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
code = "M109";
comment = "set temperature and wait for it to be reached";
code = "M109"sv;
comment = "set temperature and wait for it to be reached"sv;
} else {
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
code = "G10";
code = "G10"sv;
} else {
code = "M104";
code = "M104"sv;
}
comment = "set temperature";
comment = "set temperature"sv;
}
std::ostringstream gcode;
@ -143,22 +147,22 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
{
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
return std::string();
return {};
m_last_bed_temperature = temperature;
m_last_bed_temperature_reached = wait;
std::string code, comment;
std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109";
code = "M109"sv;
} else {
code = "M190";
code = "M190"sv;
}
comment = "set bed temperature and wait for it to be reached";
comment = "set bed temperature and wait for it to be reached"sv;
} else {
code = "M140";
comment = "set bed temperature";
code = "M140"sv;
comment = "set bed temperature"sv;
}
std::ostringstream gcode;
@ -189,7 +193,7 @@ std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned i
auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ;
if (acceleration == 0 || acceleration == last_value)
return std::string();
return {};
last_value = acceleration;
@ -258,7 +262,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
return gcode.str();
}
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const
std::string GCodeWriter::set_speed(double F, const std::string_view comment, const std::string_view cooling_marker) const
{
assert(F > 0.);
assert(F < 100000.);
@ -270,10 +274,9 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
return w.string();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
{
m_pos.x() = point.x();
m_pos.y() = point.y();
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w;
w.emit_xy(point);
@ -282,7 +285,40 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
return w.string();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &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.y()) < 1200.);
assert(std::abs(ij.x()) < 1200.);
assert(std::abs(ij.y()) < 1200.);
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_ij(ij);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
std::string GCodeWriter::travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment)
{
assert(std::abs(point.x()) < 1200.);
assert(std::abs(point.y()) < 1200.);
assert(std::abs(radius) >= 0.001);
assert(std::abs(radius) < 1800.);
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_radius(radius);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
{
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
// Calculation of feedrate was not updated accordingly. If you want to use
@ -315,7 +351,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
return w.string();
}
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform the move but we only adjust the nominal Z by
@ -334,7 +370,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
return this->_travel_to_z(z, comment);
}
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
{
m_pos.z() = z;
@ -361,10 +397,12 @@ bool GCodeWriter::will_move_z(double z) const
return true;
}
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
{
m_pos.x() = point.x();
m_pos.y() = point.y();
assert(dE != 0);
assert(std::abs(dE) < 1000.0);
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w;
w.emit_xy(point);
@ -373,8 +411,47 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
return w.string();
}
std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment)
{
assert(std::abs(dE) < 1000.0);
assert(dE != 0);
assert(std::abs(point.x()) < 1200.);
assert(std::abs(point.y()) < 1200.);
assert(std::abs(ij.x()) < 1200.);
assert(std::abs(ij.y()) < 1200.);
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_ij(ij);
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment)
{
assert(dE != 0);
assert(std::abs(dE) < 1000.0);
assert(std::abs(point.x()) < 1200.);
assert(std::abs(point.y()) < 1200.);
assert(std::abs(radius) >= 0.001);
assert(std::abs(radius) < 1800.);
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_radius(radius);
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#if 0
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment)
{
m_pos = point;
m_lifted = 0;
@ -410,8 +487,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
);
}
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string_view comment)
{
assert(std::abs(length) < 1000.0);
assert(std::abs(restart_extra) < 1000.0);
/* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which
might be 0, in which case the retraction logic gets skipped. */

View File

@ -9,13 +9,15 @@
#ifndef slic3r_GCodeWriter_hpp_
#define slic3r_GCodeWriter_hpp_
#include "libslic3r.h"
#include "../libslic3r.h"
#include "../Extruder.hpp"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include "CoolingBuffer.hpp"
#include <string>
#include <string_view>
#include <charconv>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
namespace Slic3r {
@ -64,13 +66,17 @@ public:
// printed with the same extruder.
std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id);
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
std::string travel_to_z(double z, const std::string &comment = std::string());
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
std::string travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment = {});
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
std::string travel_to_z(double z, const std::string_view comment = {});
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
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_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment);
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
@ -121,8 +127,8 @@ private:
Print
};
std::string _travel_to_z(double z, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment);
std::string _travel_to_z(double z, const std::string_view comment);
std::string _retract(double length, double restart_extra, const std::string_view comment);
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
};
@ -160,6 +166,10 @@ public:
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
static Vec2d quantize(const Vec2d &pt)
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
static Vec3d quantize(const Vec3d &pt)
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
void emit_axis(const char axis, const double v, size_t digits);
@ -178,7 +188,20 @@ public:
this->emit_axis('Z', z, XYZF_EXPORT_DIGITS);
}
void emit_e(const std::string &axis, double v) {
void emit_ij(const Vec2d &point) {
if (point.x() != 0)
this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS);
if (point.y() != 0)
this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS);
}
// Positive radius means a smaller arc,
// negative radius means a larger arc.
void emit_radius(const double radius) {
this->emit_axis('R', radius, XYZF_EXPORT_DIGITS);
}
void emit_e(const std::string_view axis, double v) {
if (! axis.empty()) {
// not gcfNoExtrusion
this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
@ -189,12 +212,12 @@ public:
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
}
void emit_string(const std::string &s) {
strncpy(ptr_err.ptr, s.c_str(), s.size());
void emit_string(const std::string_view s) {
strncpy(ptr_err.ptr, s.data(), s.size());
ptr_err.ptr += s.size();
}
void emit_comment(bool allow_comments, const std::string &comment) {
void emit_comment(bool allow_comments, const std::string_view comment) {
if (allow_comments && ! comment.empty()) {
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
this->emit_string(comment);
@ -218,14 +241,25 @@ public:
GCodeG1Formatter() {
this->buf[0] = 'G';
this->buf[1] = '1';
this->buf_end = buf + buflen;
this->ptr_err.ptr = this->buf + 2;
this->ptr_err.ptr += 2;
}
GCodeG1Formatter(const GCodeG1Formatter&) = delete;
GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete;
};
class GCodeG2G3Formatter : public GCodeFormatter {
public:
GCodeG2G3Formatter(bool ccw) {
this->buf[0] = 'G';
this->buf[1] = ccw ? '3' : '2';
this->ptr_err.ptr += 2;
}
GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete;
GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete;
};
} /* namespace Slic3r */
#endif /* slic3r_GCodeWriter_hpp_ */

View File

@ -35,7 +35,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
{
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width())));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@ -49,7 +49,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width()))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@ -63,7 +63,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width()))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);

View File

@ -1489,7 +1489,7 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
}
}
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first,
Point SeamPlacer::place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first,
const Point &last_pos) const {
using namespace SeamPlacerImpl;
const PrintObject *po = layer->object();
@ -1592,7 +1592,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
//lastly, for internal perimeters, do the staggering if requested
if (po->config().staggered_inner_seams && loop.length() > 0.0) {
//fix depth, it is sometimes strongly underestimated
depth = std::max(loop.paths[projected_point.path_idx].width, depth);
depth = std::max(loop.paths[projected_point.path_idx].width(), depth);
while (depth > 0.0f) {
auto next_point = get_next_loop_point(projected_point);
@ -1610,14 +1610,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
}
}
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
// thus empty path segments will not be produced by G-code export.
if (!loop.split_at_vertex(seam_point, scaled<double>(0.0015))) {
// The point is not in the original loop.
// Insert it.
loop.split_at(seam_point, true);
}
return seam_point;
}
} // namespace Slic3r

View File

@ -145,7 +145,7 @@ public:
void init(const Print &print, std::function<void(void)> throw_if_canceled_func);
void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
private:
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);

View File

@ -0,0 +1,257 @@
#include "SmoothPath.hpp"
#include "../ExtrusionEntity.hpp"
#include "../ExtrusionEntityCollection.hpp"
namespace Slic3r::GCode {
// Length of a smooth path.
double length(const SmoothPath &path)
{
double l = 0;
for (const SmoothPathElement &el : path)
l += Geometry::ArcWelder::path_length<double>(el.path);
return l;
}
// Returns true if the smooth path is longer than a threshold.
bool longer_than(const SmoothPath &path, double length)
{
for (const SmoothPathElement &el : path) {
for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) {
length -= Geometry::ArcWelder::segment_length<double>(*std::prev(it), *it);
if (length < 0)
return true;
}
}
return length < 0;
}
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance)
{
if (distance >= 0) {
for (const SmoothPathElement &el : path) {
auto it = el.path.begin();
auto end = el.path.end();
Point prev_point = it->point;
for (++ it; it != end; ++ it) {
Point point = it->point;
if (it->linear()) {
// Linear segment
Vec2d v = (point - prev_point).cast<double>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance))
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
distance -= sqrt(lsqr);
} else {
// Circular segment
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
double len = std::abs(it->radius) * angle;
if (len > distance) {
// Rotate the segment end point in reverse towards the start point.
return std::make_optional<Point>(prev_point.rotated(- angle * (distance / len),
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
}
distance -= len;
}
if (distance < 0)
return std::make_optional<Point>(point);
prev_point = point;
}
}
}
// Failed.
return {};
}
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance)
{
if (distance >= 0) {
for (const SmoothPathElement& el : path) {
auto it = el.path.begin();
auto end = el.path.end();
Point prev_point = it->point;
for (++it; it != end; ++it) {
Point point = it->point;
if (it->linear()) {
// Linear segment
Vec2d v = (point - prev_point).cast<double>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance))
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
distance -= sqrt(lsqr);
}
else {
// Circular segment
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
double len = std::abs(it->radius) * angle;
if (len > distance) {
// Rotate the segment end point in reverse towards the start point.
return std::make_optional<Point>(prev_point.rotated(-angle * (distance / len),
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
}
distance -= len;
}
if (distance < 0)
return std::make_optional<Point>(point);
prev_point = point;
}
}
}
// Failed.
return {};
}
double clip_end(SmoothPath &path, double distance)
{
while (! path.empty() && distance > 0) {
Geometry::ArcWelder::Path &p = path.back().path;
distance = clip_end(p, distance);
if (p.empty()) {
path.pop_back();
} else {
// Trailing path was trimmed and it is valid.
assert(path.back().path.size() > 1);
assert(distance == 0);
// Distance to go is zero.
return 0;
}
}
// Return distance to go after the whole smooth path was trimmed to zero.
return distance;
}
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters &params)
{
double tolerance = params.tolerance;
if (path.role().is_sparse_infill())
// Use 3x lower resolution than the object fine detail for sparse infill.
tolerance *= 3.;
else if (path.role().is_support())
// Use 4x lower resolution than the object fine detail for support.
tolerance *= 4.;
else if (path.role().is_skirt())
// Brim is currently marked as skirt.
// Use 4x lower resolution than the object fine detail for skirt & brim.
tolerance *= 4.;
m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance);
}
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : multi_path.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : loop.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params)
{
for (const ExtrusionEntity *ee : eec) {
if (ee->is_collection())
this->interpolate_add(*static_cast<const ExtrusionEntityCollection*>(ee), params);
else if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee); path)
this->interpolate_add(*path, params);
else if (const ExtrusionMultiPath *multi_path = dynamic_cast<const ExtrusionMultiPath*>(ee); multi_path)
this->interpolate_add(*multi_path, params);
else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(ee); loop)
this->interpolate_add(*loop, params);
else
assert(false);
}
}
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const Polyline *pl) const
{
auto it = m_cache.find(pl);
return it == m_cache.end() ? nullptr : &it->second;
}
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const ExtrusionPath &path) const
{
return this->resolve(&path.polyline);
}
Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &path, bool reverse, double tolerance) const
{
Geometry::ArcWelder::Path out;
if (const Geometry::ArcWelder::Path *cached = this->resolve(path); cached)
out = *cached;
else
out = Geometry::ArcWelder::fit_polyline(path.polyline.points, tolerance);
if (reverse)
Geometry::ArcWelder::reverse(out);
return out;
}
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const
{
SmoothPath out;
out.reserve(paths.size());
if (reverse) {
for (auto it = paths.crbegin(); it != paths.crend(); ++ it)
out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) });
} else {
for (auto it = paths.cbegin(); it != paths.cend(); ++ it)
out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) });
}
return out;
}
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const
{
return this->resolve_or_fit(multipath.paths, reverse, resolution);
}
SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam(
const ExtrusionLoop &loop, const bool reverse, const double resolution,
const Point &seam_point, const double seam_point_merge_distance_threshold) const
{
SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution);
assert(! out.empty());
if (! out.empty()) {
// Find a closest point on a vector of smooth paths.
Geometry::ArcWelder::PathSegmentProjection proj;
int proj_path = -1;
for (const SmoothPathElement &el : out)
if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2);
this_proj.valid()) {
// Found a better (closer) projection.
assert(this_proj.distance2 < proj.distance2);
assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size());
proj = this_proj;
proj_path = &el - out.data();
if (proj.distance2 == 0)
// There will be no better split point found than one with zero distance.
break;
}
assert(proj_path >= 0);
// Split the path at the closest point.
Geometry::ArcWelder::Path &path = out[proj_path].path;
std::pair<Geometry::ArcWelder::Path, Geometry::ArcWelder::Path> split = Geometry::ArcWelder::split_at(
path, proj, seam_point_merge_distance_threshold);
if (split.second.empty()) {
std::rotate(out.begin(), out.begin() + proj_path + 1, out.end());
assert(out.back().path == split.first);
} else {
ExtrusionAttributes attr = out[proj_path].path_attributes;
std::rotate(out.begin(), out.begin() + proj_path, out.end());
out.front().path = std::move(split.second);
if (! split.first.empty()) {
if (out.back().path_attributes == attr) {
// Merge with the last segment.
out.back().path.insert(out.back().path.end(), std::next(split.first.begin()), split.first.end());
} else
out.push_back({ attr, std::move(split.first) });
}
}
}
return out;
}
} // namespace Slic3r::GCode

View File

@ -0,0 +1,87 @@
#ifndef slic3r_GCode_SmoothPath_hpp_
#define slic3r_GCode_SmoothPath_hpp_
#include <ankerl/unordered_dense.h>
#include "../ExtrusionEntity.hpp"
#include "../Geometry/ArcWelder.hpp"
namespace Slic3r {
class ExtrusionEntityCollection;
namespace GCode {
struct SmoothPathElement
{
ExtrusionAttributes path_attributes;
Geometry::ArcWelder::Path path;
};
using SmoothPath = std::vector<SmoothPathElement>;
// Length of a smooth path.
double length(const SmoothPath &path);
// Returns true if the smooth path is longer than a threshold.
bool longer_than(const SmoothPath &path, const double length);
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance);
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance);
// Clip end of a smooth path, for seam hiding.
double clip_end(SmoothPath &path, double distance);
class SmoothPathCache
{
public:
struct InterpolationParameters {
double tolerance;
double fit_circle_tolerance;
};
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params);
const Geometry::ArcWelder::Path* resolve(const Polyline *pl) const;
const Geometry::ArcWelder::Path* resolve(const ExtrusionPath &path) const;
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const;
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const;
SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const;
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
SmoothPath resolve_or_fit_split_with_seam(
const ExtrusionLoop &path, const bool reverse, const double resolution,
const Point &seam_point, const double seam_point_merge_distance_threshold) const;
private:
ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache;
};
// Encapsulates references to global and layer local caches of smooth extrusion paths.
class SmoothPathCaches final
{
public:
SmoothPathCaches() = delete;
SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) :
m_global(&global), m_layer_local(&layer_local) {}
SmoothPathCaches operator=(const SmoothPathCaches &rhs)
{ m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; }
const SmoothPathCache& global() const { return *m_global; }
const SmoothPathCache& layer_local() const { return *m_layer_local; }
private:
const SmoothPathCache *m_global;
const SmoothPathCache *m_layer_local;
};
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_SmoothPath_hpp_

View File

@ -9,6 +9,9 @@
#include <jpeglib.h>
#include <jerror.h>
#include <boost/algorithm/string.hpp>
#include <string>
namespace Slic3r::GCodeThumbnails {
using namespace std::literals;
@ -120,4 +123,81 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &d
}
}
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext /*= "PNG"sv*/)
{
if (thumbnails_string.empty())
return {};
std::istringstream is(thumbnails_string);
std::string point_str;
ThumbnailErrors errors;
// generate thumbnails data to process it
GCodeThumbnailDefinitionsList thumbnails_list;
while (std::getline(is, point_str, ',')) {
Vec2d point(Vec2d::Zero());
GCodeThumbnailsFormat format;
std::istringstream iss(point_str);
std::string coord_str;
if (std::getline(iss, coord_str, 'x') && !coord_str.empty()) {
std::istringstream(coord_str) >> point(0);
if (std::getline(iss, coord_str, '/') && !coord_str.empty()) {
std::istringstream(coord_str) >> point(1);
if (0 < point(0) && point(0) < 1000 && 0 < point(1) && point(1) < 1000) {
std::string ext_str;
std::getline(iss, ext_str, '/');
if (ext_str.empty())
ext_str = def_ext.empty() ? "PNG"sv : def_ext;
// check validity of extention
boost::to_upper(ext_str);
if (!ConfigOptionEnum<GCodeThumbnailsFormat>::from_string(ext_str, format)) {
format = GCodeThumbnailsFormat::PNG;
errors = enum_bitmask(errors | ThumbnailError::InvalidExt);
}
thumbnails_list.emplace_back(std::make_pair(format, point));
}
else
errors = enum_bitmask(errors | ThumbnailError::OutOfRange);
continue;
}
}
errors = enum_bitmask(errors | ThumbnailError::InvalidVal);
}
return std::make_pair(std::move(thumbnails_list), errors);
}
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase& config)
{
// ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
// ??? If "thumbnails_format" is not defined, export to PNG.
// generate thumbnails data to process it
if (const auto thumbnails_value = config.option<ConfigOptionString>("thumbnails"))
return make_and_check_thumbnail_list(thumbnails_value->value);
return {};
}
std::string get_error_string(const ThumbnailErrors& errors)
{
std::string error_str;
if (errors.has(ThumbnailError::InvalidVal))
error_str += "\n - " + format("Invalid input format. Expected vector of dimensions in the following format: \"%1%\"", "XxYxEXT, XxYxEXT, ...");
if (errors.has(ThumbnailError::OutOfRange))
error_str += "\n - Input value is out of range";
if (errors.has(ThumbnailError::InvalidExt))
error_str += "\n - Some input extention is invalid";
return error_str;
}
} // namespace Slic3r::GCodeThumbnails

View File

@ -13,8 +13,18 @@
#include <memory>
#include <string_view>
#include <LibBGCode/binarize/binarize.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include "../libslic3r/enum_bitmask.hpp"
namespace Slic3r {
enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt };
using ThumbnailErrors = enum_bitmask<ThumbnailError>;
ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError);
}
namespace Slic3r::GCodeThumbnails {
struct CompressedImageBuffer
@ -27,35 +37,76 @@ struct CompressedImageBuffer
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format);
typedef std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> GCodeThumbnailDefinitionsList;
using namespace std::literals;
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG"sv);
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase &config);
std::string get_error_string(const ThumbnailErrors& errors);
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>>& thumbnails_list, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
{
// Write thumbnails using base64 encoding
if (thumbnail_cb != nullptr) {
static constexpr const size_t max_row_length = 78;
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true });
for (const ThumbnailData& data : thumbnails)
if (data.is_valid()) {
auto compressed = compress_thumbnail(data, format);
if (compressed->data && compressed->size) {
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size));
encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size));
for (const auto& [format, size] : thumbnails_list) {
static constexpr const size_t max_row_length = 78;
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true });
for (const ThumbnailData& data : thumbnails)
if (data.is_valid()) {
auto compressed = compress_thumbnail(data, format);
if (compressed->data && compressed->size) {
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size));
encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size));
output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str());
output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str());
while (encoded.size() > max_row_length) {
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
encoded = encoded.substr(max_row_length);
while (encoded.size() > max_row_length) {
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
encoded = encoded.substr(max_row_length);
}
if (encoded.size() > 0)
output((boost::format("; %s\n") % encoded).str().c_str());
output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str());
}
if (encoded.size() > 0)
output((boost::format("; %s\n") % encoded).str().c_str());
output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str());
throw_if_canceled();
}
throw_if_canceled();
}
}
}
}
template<typename ThrowIfCanceledCallback>
inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb, std::vector<bgcode::binarize::ThumbnailBlock>& out_thumbnails,
const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> &thumbnails_list, ThrowIfCanceledCallback throw_if_canceled)
{
using namespace bgcode::core;
using namespace bgcode::binarize;
out_thumbnails.clear();
assert(thumbnail_cb != nullptr);
if (thumbnail_cb != nullptr) {
for (const auto& [format, size] : thumbnails_list) {
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true });
for (const ThumbnailData &data : thumbnails)
if (data.is_valid()) {
auto compressed = compress_thumbnail(data, format);
if (compressed->data != nullptr && compressed->size > 0) {
ThumbnailBlock& block = out_thumbnails.emplace_back(ThumbnailBlock());
block.params.width = (uint16_t)data.width;
block.params.height = (uint16_t)data.height;
switch (format) {
case GCodeThumbnailsFormat::PNG: { block.params.format = (uint16_t)EThumbnailFormat::PNG; break; }
case GCodeThumbnailsFormat::JPG: { block.params.format = (uint16_t)EThumbnailFormat::JPG; break; }
case GCodeThumbnailsFormat::QOI: { block.params.format = (uint16_t)EThumbnailFormat::QOI; break; }
}
block.data.resize(compressed->size);
memcpy(block.data.data(), compressed->data, compressed->size);
}
}
}
}
}

View File

@ -0,0 +1,260 @@
#include "Wipe.hpp"
#include "../GCode.hpp"
#include <string_view>
#include <Eigen/Geometry>
using namespace std::string_view_literals;
namespace Slic3r::GCode {
void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extruders)
{
this->reset_path();
// Calculate maximum wipe length to accumulate by the wipe cache.
// Paths longer than wipe_xy should never be needed for the wipe move.
double wipe_xy = 0;
const bool multimaterial = extruders.size() > 1;
for (auto id : extruders)
if (config.wipe.get_at(id)) {
// Wipe length to extrusion ratio.
const double xy_to_e = this->calc_xy_to_e_ratio(config, id);
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id));
if (multimaterial)
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id));
}
if (wipe_xy == 0)
this->disable();
else
this->enable(wipe_xy);
}
void Wipe::set_path(SmoothPath &&path, bool reversed)
{
this->reset_path();
if (this->enabled() && ! path.empty()) {
if (reversed) {
m_path = std::move(path.back().path);
Geometry::ArcWelder::reverse(m_path);
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) {
if (it->path_attributes.role.is_bridge())
break; // Do not perform a wipe on bridges.
assert(it->path.size() >= 2);
assert(m_path.back().point == it->path.back().point);
if (m_path.back().point != it->path.back().point)
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
break;
len += Geometry::ArcWelder::estimate_path_length(it->path);
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
}
} else {
m_path = std::move(path.front().path);
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) {
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.front().point);
if (m_path.back().point != it->path.front().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.begin() + 1, it->path.end());
}
}
}
assert(m_path.empty() || m_path.size() > 1);
}
std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
{
std::string gcode;
const Extruder &extruder = *gcodegen.writer().extruder();
static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv;
// Remaining quantized retraction length.
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
retract_length > 0 && this->has_path()) {
// Delayed emitting of a wipe start tag.
bool wiped = false;
const double wipe_speed = this->calc_wipe_speed(gcodegen.writer().config);
auto start_wipe = [&wiped, &gcode, &gcodegen, wipe_speed](){
if (! wiped) {
wiped = true;
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE"sv : ""sv);
}
};
const double xy_to_e = this->calc_xy_to_e_ratio(gcodegen.writer().config, extruder.id());
auto wipe_linear = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev_quantized, Vec2d &p) {
Vec2d p_quantized = GCodeFormatter::quantize(p);
if (p_quantized == prev_quantized) {
p = p_quantized;
return false;
}
double segment_length = (p_quantized - prev_quantized).norm();
// Quantize E axis as it is to be extruded as a whole segment.
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
bool done = false;
if (dE > retract_length - EPSILON) {
if (dE > retract_length + EPSILON)
// Shorten the segment.
p = GCodeFormatter::quantize(Vec2d(prev_quantized + (p - prev_quantized) * (retract_length / dE)));
else
p = p_quantized;
dE = retract_length;
done = true;
} else
p = p_quantized;
gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment);
retract_length -= dE;
return done;
};
const bool emit_radius = gcodegen.config().arc_fitting == ArcFittingType::EmitRadius;
auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, emit_radius, &wipe_linear](
const Vec2d &prev_quantized, Vec2d &p, double radius_in, const bool ccw) {
Vec2d p_quantized = GCodeFormatter::quantize(p);
if (p_quantized == prev_quantized) {
p = p_quantized;
return false;
}
// Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values.
double radius = emit_radius ? GCodeFormatter::quantize_xyzf(radius_in) : radius_in;
if (radius == 0)
// Degenerated arc after quantization. Process it as if it was a line segment.
return wipe_linear(prev_quantized, p);
Vec2d center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius), ccw);
float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius));
assert(angle > 0);
double segment_length = angle * std::abs(radius);
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
bool done = false;
if (dE > retract_length - EPSILON) {
if (dE > retract_length + EPSILON) {
// Shorten the segment. Recalculate the arc from the unquantized end coordinate.
center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p.cast<double>(), double(radius), ccw);
angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p.cast<double>(), double(radius));
segment_length = angle * std::abs(radius);
dE = xy_to_e * segment_length;
p = GCodeFormatter::quantize(
Vec2d(center + Eigen::Rotation2D((ccw ? angle : -angle) * (retract_length / dE)) * (prev_quantized - center)));
} else
p = p_quantized;
dE = retract_length;
done = true;
} else
p = p_quantized;
assert(dE > 0);
if (emit_radius) {
gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, wipe_retract_comment);
} else {
// Calculate quantized IJ circle center offset.
Vec2d ij = GCodeFormatter::quantize(Vec2d(center - prev_quantized));
if (ij == Vec2d::Zero())
// Degenerated arc after quantization. Process it as if it was a line segment.
return wipe_linear(prev_quantized, p);
// The arc is valid.
gcode += gcodegen.writer().extrude_to_xy_G2G3IJ(
p, ij, ccw, -dE, wipe_retract_comment);
}
retract_length -= dE;
return done;
};
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
auto it = this->path().begin();
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
++ it;
bool done = false;
if (p != prev) {
start_wipe();
done = wipe_linear(prev, p);
}
if (! done) {
prev = p;
auto end = this->path().end();
for (; it != end && ! done; ++ it) {
p = gcodegen.point_to_gcode(it->point + m_offset);
if (p != prev) {
start_wipe();
if (it->linear() ?
wipe_linear(prev, p) :
wipe_arc(prev, p, unscaled<double>(it->radius), it->ccw()))
break;
prev = p;
}
}
}
if (wiped) {
// add tag for processor
assert(p == GCodeFormatter::quantize(p));
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
}
}
// Prevent wiping again on the same path.
this->reset_path();
return gcode;
}
// Make a little move inwards before leaving loop after path was extruded,
// thus the current extruder position is at the end of a path and the path
// may not be closed in case the loop was clipped to hide a seam.
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length)
{
assert(! path.empty());
assert(path.front().path.size() >= 2);
assert(path.back().path.size() >= 2);
// Heuristics for estimating whether there is a chance that the wipe move will fit inside a small perimeter
// or that the wipe move direction could be calculated with reasonable accuracy.
if (longer_than(path, 2.5 * wipe_length)) {
// The print head will be moved away from path end inside the island.
Point p_current = path.back().path.back().point;
Point p_next = path.front().path.front().point;
Point p_prev;
{
// Is the seam hiding gap large enough already?
double l = wipe_length - (p_next - p_current).cast<double>().norm();
if (l > 0) {
// Not yet.
std::optional<Point> n = sample_path_point_at_distance_from_start(path, l);
assert(n);
if (! n)
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
return {};
}
if (std::optional<Point> p = sample_path_point_at_distance_from_end(path, wipe_length); p)
p_prev = *p;
else
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
return {};
}
// Detect angle between last and first segment.
// The side depends on the original winding order of the polygon (left for contours, right for holes).
double angle_inside = angle(p_next - p_current, p_prev - p_current);
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
if (is_hole) {
if (angle_inside > 0)
angle_inside -= 2.0 * M_PI;
} else {
if (angle_inside < 0)
angle_inside += 2.0 * M_PI;
}
// Rotate the forward segment inside by 1/3 of the wedge angle.
auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast<double>().normalized();
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
}
return {};
}
} // namespace Slic3r::GCode

View File

@ -0,0 +1,73 @@
#ifndef slic3r_GCode_Wipe_hpp_
#define slic3r_GCode_Wipe_hpp_
#include "SmoothPath.hpp"
#include "../Geometry/ArcWelder.hpp"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include <cassert>
#include <optional>
namespace Slic3r {
class GCodeGenerator;
namespace GCode {
class Wipe {
public:
using Path = Slic3r::Geometry::ArcWelder::Path;
Wipe() = default;
void init(const PrintConfig &config, const std::vector<unsigned int> &extruders);
void enable(double wipe_len_max) { m_enabled = true; m_wipe_len_max = wipe_len_max; }
void disable() { m_enabled = false; }
bool enabled() const { return m_enabled; }
const Path& path() const { return m_path; }
bool has_path() const { assert(m_path.empty() || m_path.size() > 1); return ! m_path.empty(); }
void reset_path() { m_path.clear(); m_offset = Point::Zero(); }
void set_path(const Path &path) {
assert(path.empty() || path.size() > 1);
this->reset_path();
if (this->enabled() && path.size() > 1)
m_path = path;
}
void set_path(Path &&path) {
assert(path.empty() || path.size() > 1);
this->reset_path();
if (this->enabled() && path.size() > 1)
m_path = std::move(path);
}
void set_path(SmoothPath &&path, bool reversed);
void offset_path(const Point &v) { m_offset += v; }
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; }
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
// due to rounding (TODO: test and/or better math for this).
static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id)
{ return 0.95 * floor(config.retract_speed.get_at(extruder_id) + 0.5) / calc_wipe_speed(config); }
private:
bool m_enabled{ false };
// Maximum length of a path to accumulate. Only wipes shorter than this threshold will be requested.
double m_wipe_len_max{ 0. };
Path m_path;
// Offset from m_path to the current PrintObject active.
Point m_offset{ Point::Zero() };
};
// Make a little move inwards before leaving loop.
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length);
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_Wipe_hpp_

View File

@ -1008,7 +1008,7 @@ void WipeTower::toolchange_Change(
// This is where we want to place the custom gcodes. We will use placeholders for this.
// These will be substituted by the actual gcodes when the gcode is generated.
//writer.append("[end_filament_gcode]\n");
writer.append("[toolchange_gcode]\n");
writer.append("[toolchange_gcode_from_wipe_tower_generator]\n");
// 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

View File

@ -2,8 +2,8 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef WipeTower_
#define WipeTower_
#ifndef slic3r_GCode_WipeTower_hpp_
#define slic3r_GCode_WipeTower_hpp_
#include <cmath>
#include <string>
@ -415,4 +415,4 @@ private:
} // namespace Slic3r
#endif // WipeTowerPrusaMM_hpp_
#endif // slic3r_GCode_WipeTower_hpp_

View File

@ -0,0 +1,250 @@
#include "WipeTowerIntegration.hpp"
#include "../GCode.hpp"
#include "../libslic3r.h"
#include "boost/algorithm/string/replace.hpp"
namespace Slic3r::GCode {
static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, const Vec2f& wipe_tower_pt)
{
return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
}
std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
{
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
std::string gcode;
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
out += m_wipe_tower_pos;
return out;
};
Vec2f start_pos = tcr.start_pos;
Vec2f end_pos = tcr.end_pos;
if (! tcr.priming) {
start_pos = transform_wt_pt(start_pos);
end_pos = transform_wt_pt(end_pos);
}
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
double current_z = gcodegen.writer().get_position().z();
if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
const bool will_go_down = ! is_approx(z, current_z);
const bool is_ramming = (gcodegen.config().single_extruder_multi_material)
|| (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
const bool should_travel_to_tower = ! tcr.priming
&& (tcr.force_travel // wipe tower says so
|| ! needs_toolchange // this is just finishing the tower with no toolchange
|| is_ramming);
if (should_travel_to_tower) {
gcode += gcodegen.retract();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos),
ExtrusionRole::Mixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
} else {
// When this is multiextruder printer without any ramming, we can just change
// the tool without travelling to the tower.
}
if (will_go_down) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
gcode += gcodegen.writer().unretract();
}
std::string toolchange_gcode_str;
std::string deretraction_str;
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
if (is_ramming)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
if (gcodegen.config().wipe_tower)
deretraction_str = gcodegen.unretract();
}
assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n');
assert(deretraction_str.empty() || deretraction_str.back() == '\n');
// Insert the toolchange and deretraction gcode into the generated gcode.
boost::replace_first(tcr_rotated_gcode, "[toolchange_gcode_from_wipe_tower_generator]", toolchange_gcode_str);
boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
std::string tcr_gcode;
unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode);
gcode += tcr_gcode;
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
if (!is_approx(z, current_z)) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
gcode += gcodegen.writer().unretract();
}
else {
// Prepare a future wipe.
// Convert to a smooth path.
Geometry::ArcWelder::Path path;
path.reserve(tcr.wipe_path.size());
std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path),
[&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) {
return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) };
});
// Pass to the wipe cache.
gcodegen.m_wipe.set_path(std::move(path));
}
// Let the planner know we are traveling between objects.
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
return gcode;
}
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
{
Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
std::istringstream gcode_str(tcr.gcode);
std::string gcode_out;
std::string line;
Vec2f pos = tcr.start_pos;
Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
Vec2f old_pos(-1000.1f, -1000.1f);
while (gcode_str) {
std::getline(gcode_str, line); // we read the gcode line by line
// All G1 commands should be translated and rotated. X and Y coords are
// only pushed to the output when they differ from last time.
// WT generator can override this by appending the never_skip_tag
if (boost::starts_with(line, "G1 ")) {
bool never_skip = false;
auto it = line.find(WipeTower::never_skip_tag());
if (it != std::string::npos) {
// remove the tag and remember we saw it
never_skip = true;
line.erase(it, it + WipeTower::never_skip_tag().size());
}
std::ostringstream line_out;
std::istringstream line_str(line);
line_str >> std::noskipws; // don't skip whitespace
char ch = 0;
line_str >> ch >> ch; // read the "G1"
while (line_str >> ch) {
if (ch == 'X' || ch == 'Y')
line_str >> (ch == 'X' ? pos.x() : pos.y());
else
line_out << ch;
}
transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
if (transformed_pos != old_pos || never_skip) {
line = line_out.str();
boost::trim_left(line); // Remove leading spaces
std::ostringstream oss;
oss << std::fixed << std::setprecision(3) << "G1";
if (transformed_pos.x() != old_pos.x() || never_skip)
oss << " X" << transformed_pos.x() - extruder_offset.x();
if (transformed_pos.y() != old_pos.y() || never_skip)
oss << " Y" << transformed_pos.y() - extruder_offset.y();
if (! line.empty())
oss << " ";
line = oss.str() + line;
old_pos = transformed_pos;
}
}
gcode_out += line + "\n";
// If this was a toolchange command, we should change current extruder offset
if (line == "[toolchange_gcode]") {
extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
// If the extruder offset changed, add an extra move so everything is continuous
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(3)
<< "G1 X" << transformed_pos.x() - extruder_offset.x()
<< " Y" << transformed_pos.y() - extruder_offset.y()
<< "\n";
gcode_out += oss.str();
}
}
}
return gcode_out;
}
std::string WipeTowerIntegration::prime(GCodeGenerator &gcodegen)
{
std::string gcode;
for (const WipeTower::ToolChangeResult& tcr : m_priming) {
if (! tcr.extrusions.empty())
gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
}
return gcode;
}
std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer)
{
std::string gcode;
assert(m_layer_idx >= 0);
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < (int)m_tool_changes.size()) {
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
// resulting in a wipe tower with sparse layers.
double wipe_tower_z = -1;
bool ignore_sparse = false;
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
wipe_tower_z = m_last_wipe_tower_print_z;
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0);
if (m_tool_change_idx == 0 && !ignore_sparse)
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
}
if (!ignore_sparse) {
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
m_last_wipe_tower_print_z = wipe_tower_z;
}
}
}
return gcode;
}
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
{
std::string gcode;
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.change_layer(m_final_purge.print_z);
gcode += append_tcr(gcodegen, m_final_purge, -1);
return gcode;
}
} // namespace Slic3r::GCode

View File

@ -0,0 +1,65 @@
#ifndef slic3r_GCode_WipeTowerIntegration_hpp_
#define slic3r_GCode_WipeTowerIntegration_hpp_
#include "WipeTower.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
class GCodeGenerator;
namespace GCode {
class WipeTowerIntegration {
public:
WipeTowerIntegration(
const PrintConfig &print_config,
const std::vector<WipeTower::ToolChangeResult> &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_extruder_offsets(print_config.extruder_offset.values),
m_priming(priming),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0)
{}
std::string prime(GCodeGenerator &gcodegen);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCodeGenerator &gcodegen);
std::vector<float> used_filament_length() const;
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
const Vec2f m_wipe_tower_pos;
const float m_wipe_tower_rotation;
const std::vector<Vec2d> m_extruder_offsets;
// Reference to cached values at the Printer class.
const std::vector<WipeTower::ToolChangeResult> &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
};
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_WipeTowerIntegration_hpp_

View File

@ -199,10 +199,11 @@ bool GCodeReader::parse_file(const std::string &file, callback_t callback)
return this->parse_file_internal(file, callback, [](size_t){});
}
bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends)
bool GCodeReader::parse_file(const std::string& file, callback_t callback, std::vector<std::vector<size_t>>& lines_ends)
{
lines_ends.clear();
return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); });
lines_ends.push_back(std::vector<size_t>());
return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos) { lines_ends.front().emplace_back(file_pos); });
}
bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback)

View File

@ -135,7 +135,7 @@ public:
bool parse_file(const std::string &file, callback_t callback);
// Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code
// as an overlay in the 3D scene.
bool parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends);
bool parse_file(const std::string& file, callback_t callback, std::vector<std::vector<size_t>>& lines_ends);
// Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed.
bool parse_file_raw(const std::string &file, raw_line_callback_t callback);

View File

@ -0,0 +1,674 @@
// The following code for merging circles into arches originates from https://github.com/FormerLurker/ArcWelderLib
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Arc Welder: Anti-Stutter Library
//
// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
// This reduces file size and the number of gcodes per second.
//
// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
//
// Copyright(C) 2021 - Brad Hochgesang
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This program is free software : you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
// GNU Affero General Public License for more details.
//
//
// You can contact the author at the following email address:
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "ArcWelder.hpp"
#include "Circle.hpp"
#include "../MultiPoint.hpp"
#include "../Polygon.hpp"
#include <numeric>
#include <random>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Geometry { namespace ArcWelder {
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation)
{
Vec2d center = arc_center(p1.cast<double>(), p2.cast<double>(), radius, ccw);
double angle = arc_angle(p1.cast<double>(), p2.cast<double>(), radius);
assert(angle > 0);
double r = std::abs(radius);
size_t num_steps = arc_discretization_steps(r, angle, deviation);
double angle_step = angle / num_steps;
Points out;
out.reserve(num_steps + 1);
out.emplace_back(p1);
if (! ccw)
angle_step *= -1.;
for (size_t i = 1; i < num_steps; ++ i)
out.emplace_back(p1.rotated(angle_step * i, center.cast<coord_t>()));
out.emplace_back(p2);
return out;
}
struct Circle
{
Point center;
double radius;
};
// Interpolate three points with a circle.
// Returns false if the three points are collinear or if the radius is bigger than maximum allowed radius.
static std::optional<Circle> try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius)
{
if (auto center = Slic3r::Geometry::try_circle_center(p1.cast<double>(), p2.cast<double>(), p3.cast<double>(), SCALED_EPSILON); center) {
Point c = center->cast<coord_t>();
if (double r = sqrt(double((c - p1).cast<int64_t>().squaredNorm())); r <= max_radius)
return std::make_optional<Circle>({ c, float(r) });
}
return {};
}
// Returns a closest point on the segment.
// Returns false if the closest point is not inside the segment, but at its boundary.
static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt, Point &out)
{
Vec2i64 v21 = (p2 - p1).cast<int64_t>();
int64_t l2 = v21.squaredNorm();
if (l2 > int64_t(SCALED_EPSILON)) {
if (int64_t t = (pt - p1).cast<int64_t>().dot(v21);
t >= int64_t(SCALED_EPSILON) && t < l2 - int64_t(SCALED_EPSILON)) {
out = p1 + ((double(t) / double(l2)) * v21.cast<double>()).cast<coord_t>();
return true;
}
}
// The segment is short or the closest point is an end point.
return false;
}
static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance)
{
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(end - begin >= 3);
// Test the 1st point.
if (double distance_from_center = (*begin - circle.center).cast<double>().norm();
std::abs(distance_from_center - circle.radius) > tolerance)
return false;
for (auto it = std::next(begin); it != end; ++ it) {
if (double distance_from_center = (*it - circle.center).cast<double>().norm();
std::abs(distance_from_center - circle.radius) > tolerance)
return false;
Point closest_point;
if (foot_pt_on_segment(*std::prev(it), *it, circle.center, closest_point)) {
if (double distance_from_center = (closest_point - circle.center).cast<double>().norm();
std::abs(distance_from_center - circle.radius) > tolerance)
return false;
}
}
return true;
}
static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation)
{
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(end - begin >= 3);
total_deviation = 0;
const double tolerance2 = sqr(tolerance);
for (auto it = std::next(begin); std::next(it) != end; ++ it)
if (double deviation2 = sqr((*it - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
return false;
else
total_deviation += deviation2;
for (auto it = begin; std::next(it) != end; ++ it) {
Point closest_point;
if (foot_pt_on_segment(*it, *std::next(it), circle.center, closest_point)) {
if (double deviation2 = sqr((closest_point - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
return false;
else
total_deviation += deviation2;
}
}
return true;
}
static std::optional<Circle> try_create_circle(const Points::const_iterator begin, const Points::const_iterator end, const double max_radius, const double tolerance)
{
std::optional<Circle> out;
size_t size = end - begin;
if (size == 3) {
out = try_create_circle(*begin, *std::next(begin), *std::prev(end), max_radius);
if (out && ! circle_approximation_sufficient(*out, begin, end, tolerance))
out.reset();
} else {
#if 0
size_t ipivot = size / 2;
// Take a center difference of points at the center of the path.
//FIXME does it really help? For short arches, the linear interpolation may be
Point pivot = (size % 2 == 0) ? (*(begin + ipivot) + *(begin + ipivot - 1)) / 2 :
(*(begin + ipivot - 1) + *(begin + ipivot + 1)) / 2;
if (std::optional<Circle> circle = try_create_circle(*begin, pivot, *std::prev(end), max_radius);
circle && circle_approximation_sufficient(*circle, begin, end, tolerance))
return circle;
#endif
// Find the circle with the least deviation, if one exists.
double least_deviation = std::numeric_limits<double>::max();
double current_deviation;
for (auto it = std::next(begin); std::next(it) != end; ++ it)
if (std::optional<Circle> circle = try_create_circle(*begin, *it, *std::prev(end), max_radius);
circle && get_deviation_sum_squared(*circle, begin, end, tolerance, current_deviation)) {
if (current_deviation < least_deviation) {
out = circle;
least_deviation = current_deviation;
}
}
}
return out;
}
// ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc"
class Arc {
public:
Point start_point;
Point end_point;
Point center;
double radius;
Orientation direction { Orientation::Unknown };
};
static inline int sign(const int64_t i)
{
return i > 0 ? 1 : i < 0 ? -1 : 0;
}
// Return orientation of a polyline with regard to the center.
// Successive points are expected to take less than a PI angle step.
Orientation arc_orientation(
const Point &center,
const Points::const_iterator begin,
const Points::const_iterator end)
{
assert(end - begin >= 3);
// Assumption: Two successive points of a single segment span an angle smaller than PI.
Vec2i64 vstart = (*begin - center).cast<int64_t>();
Vec2i64 vprev = vstart;
int arc_dir = 0;
for (auto it = std::next(begin); it != end; ++ it) {
Vec2i64 v = (*it - center).cast<int64_t>();
int dir = sign(cross2(vprev, v));
if (dir == 0) {
// Ignore radial segments.
} else if (arc_dir * dir < 0) {
// The path turns back and overextrudes. Such path is likely invalid, but the arc interpolation should
// rather maintain such an invalid path instead of covering it up.
// Don't replace such a path with an arc.
return {};
} else {
// Success, either establishing the direction for the first time, or moving in the same direction as the last time.
arc_dir = dir;
vprev = v;
}
}
return arc_dir == 0 ?
// All points are radial wrt. the center, this is unexpected.
Orientation::Unknown :
// Arc is valid, either CCW or CW.
arc_dir > 0 ? Orientation::CCW : Orientation::CW;
}
static inline std::optional<Arc> try_create_arc_impl(
const Circle &circle,
const Points::const_iterator begin,
const Points::const_iterator end,
const double tolerance,
const double path_tolerance_percent)
{
assert(end - begin >= 3);
// Assumption: Two successive points of a single segment span an angle smaller than PI.
Orientation orientation = arc_orientation(circle.center, begin, end);
if (orientation == Orientation::Unknown)
return {};
Vec2i64 vstart = (*begin - circle.center).cast<int64_t>();
Vec2i64 vend = (*std::prev(end) - circle.center).cast<int64_t>();
double angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
if (orientation == Orientation::CW)
angle *= -1.;
if (angle < 0)
angle += 2. * M_PI;
assert(angle >= 0. && angle < 2. * M_PI + EPSILON);
// Check the length against the original length.
// This can trigger simply due to the differing path lengths
// but also could indicate that the vector calculation above
// got wrong direction
const double arc_length = circle.radius * angle;
const double approximate_length = length(begin, end);
assert(approximate_length > 0);
const double arc_length_difference_relative = (arc_length - approximate_length) / approximate_length;
if (std::fabs(arc_length_difference_relative) >= path_tolerance_percent) {
return {};
} else {
assert(circle_approximation_sufficient(circle, begin, end, tolerance + SCALED_EPSILON));
return std::make_optional<Arc>(Arc{
*begin,
*std::prev(end),
circle.center,
angle > M_PI ? - circle.radius : circle.radius,
orientation
});
}
}
static inline std::optional<Arc> try_create_arc(
const Points::const_iterator begin,
const Points::const_iterator end,
double max_radius = default_scaled_max_radius,
double tolerance = default_scaled_resolution,
double path_tolerance_percent = default_arc_length_percent_tolerance)
{
std::optional<Circle> circle = try_create_circle(begin, end, max_radius, tolerance);
if (! circle)
return {};
return try_create_arc_impl(*circle, begin, end, tolerance, path_tolerance_percent);
}
float arc_angle(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f &center_pos, bool is_ccw)
{
if ((end_pos - start_pos).squaredNorm() < sqr(1e-6)) {
// If start equals end, full circle is considered.
return float(2. * M_PI);
} else {
Vec2f v1 = start_pos - center_pos;
Vec2f v2 = end_pos - center_pos;
if (! is_ccw)
std::swap(v1, v2);
float radian = atan2(cross2(v1, v2), v1.dot(v2));
return radian < 0 ? float(2. * M_PI) + radian : radian;
}
}
float arc_length(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f &center_pos, bool is_ccw)
{
return (center_pos - start_pos).norm() * arc_angle(start_pos, end_pos, center_pos, is_ccw);
}
// Reduces polyline in the <begin, end) range in place,
// returns the new end iterator.
static inline Segments::iterator douglas_peucker_in_place(Segments::iterator begin, Segments::iterator end, const double tolerance)
{
return douglas_peucker<int64_t>(begin, end, begin, tolerance, [](const Segment &s) { return s.point; });
}
Path fit_path(const Points &src, double tolerance, double fit_circle_percent_tolerance)
{
assert(tolerance >= 0);
assert(fit_circle_percent_tolerance >= 0);
Path out;
out.reserve(src.size());
if (tolerance <= 0 || src.size() <= 2) {
// No simplification, just convert.
std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; });
} else if (fit_circle_percent_tolerance <= 0) {
// Convert and simplify to a polyline.
std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; });
out.erase(douglas_peucker_in_place(out.begin(), out.end(), tolerance), out.end());
} else {
// Perform simplification & fitting.
// Index of the start of a last polyline, which has not yet been decimated.
int begin_pl_idx = 0;
out.push_back({ src.front(), 0.f });
for (auto it = std::next(src.begin()); it != src.end();) {
// Minimum 2 additional points required for circle fitting.
auto begin = std::prev(it);
auto end = std::next(it);
assert(end <= src.end());
std::optional<Arc> arc;
while (end != src.end()) {
auto next_end = std::next(end);
if (std::optional<Arc> this_arc = try_create_arc(
begin, next_end,
ArcWelder::default_scaled_max_radius,
tolerance, fit_circle_percent_tolerance);
this_arc) {
assert(this_arc->direction != Orientation::Unknown);
arc = this_arc;
end = next_end;
} else
break;
}
if (arc) {
// If there is a trailing polyline, decimate it first before saving a new arc.
if (out.size() - begin_pl_idx > 2) {
// Decimating linear segmens only.
assert(std::all_of(out.begin() + begin_pl_idx + 1, out.end(), [](const Segment &seg) { return seg.linear(); }));
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
assert(out.back().linear());
}
// Save the index of an end of the new circle segment, which may become the first point of a possible future polyline.
begin_pl_idx = int(out.size());
// This will be the next point to try to add.
it = end;
// Add the new arc.
assert(*begin == arc->start_point);
assert(*std::prev(it) == arc->end_point);
assert(out.back().point == arc->start_point);
out.push_back({ arc->end_point, float(arc->radius), arc->direction });
#if 0
// Verify that all the source points are at tolerance distance from the interpolated path.
{
const Segment &seg_start = *std::prev(std::prev(out.end()));
const Segment &seg_end = out.back();
const Vec2d center = arc_center(seg_start.point.cast<double>(), seg_end.point.cast<double>(), double(seg_end.radius), seg_end.ccw());
assert(seg_start.point == *begin);
assert(seg_end.point == *std::prev(end));
assert(arc_orientation(center.cast<coord_t>(), begin, end) == arc->direction);
for (auto it = std::next(begin); it != end; ++ it) {
Point ptstart = *std::prev(it);
Point ptend = *it;
Point closest_point;
if (foot_pt_on_segment(ptstart, ptend, center.cast<coord_t>(), closest_point)) {
double distance_from_center = (closest_point.cast<double>() - center).norm();
assert(std::abs(distance_from_center - std::abs(seg_end.radius)) < tolerance + SCALED_EPSILON);
}
Vec2d v = (ptend - ptstart).cast<double>();
double len = v.norm();
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
for (size_t i = 0; i < num_segments; ++ i) {
Point p = ptstart + (v * (double(i) / double(num_segments))).cast<coord_t>();
assert(i == 0 || inside_arc_wedge(seg_start.point.cast<double>(), seg_end.point.cast<double>(), center, seg_end.radius > 0, seg_end.ccw(), p.cast<double>()));
double d2 = sqr((p.cast<double>() - center).norm() - std::abs(seg_end.radius));
assert(d2 < sqr(tolerance + SCALED_EPSILON));
}
}
}
#endif
} else {
// Arc is not valid, append a linear segment.
out.push_back({ *it ++ });
}
}
if (out.size() - begin_pl_idx > 2)
// Do the final polyline decimation.
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
}
#if 0
// Verify that all the source points are at tolerance distance from the interpolated path.
for (auto it = std::next(src.begin()); it != src.end(); ++ it) {
Point start = *std::prev(it);
Point end = *it;
Vec2d v = (end - start).cast<double>();
double len = v.norm();
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
for (size_t i = 0; i <= num_segments; ++ i) {
Point p = start + (v * (double(i) / double(num_segments))).cast<coord_t>();
PathSegmentProjection proj = point_to_path_projection(out, p);
assert(proj.valid());
assert(proj.distance2 < sqr(tolerance + SCALED_EPSILON));
}
}
#endif
return out;
}
void reverse(Path &path)
{
if (path.size() > 1) {
auto prev = path.begin();
for (auto it = std::next(prev); it != path.end(); ++ it) {
prev->radius = it->radius;
prev->orientation = it->orientation == Orientation::CCW ? Orientation::CW : Orientation::CCW;
prev = it;
}
path.back().radius = 0;
std::reverse(path.begin(), path.end());
}
}
double clip_start(Path &path, const double len)
{
reverse(path);
double remaining = clip_end(path, len);
reverse(path);
// Return remaining distance to go.
return remaining;
}
double clip_end(Path &path, double distance)
{
while (distance > 0) {
const Segment last = path.back();
path.pop_back();
if (path.empty())
break;
if (last.linear()) {
// Linear segment
Vec2d v = (path.back().point - last.point).cast<double>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance)) {
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>() });
// Length to go is zero.
return 0;
}
distance -= sqrt(lsqr);
} else {
// Circular segment
double angle = arc_angle(path.back().point.cast<double>(), last.point.cast<double>(), last.radius);
double len = std::abs(last.radius) * angle;
if (len > distance) {
// Rotate the segment end point in reverse towards the start point.
if (last.ccw())
angle *= -1.;
path.push_back({
last.point.rotated(angle * (distance / len),
arc_center(path.back().point.cast<double>(), last.point.cast<double>(), double(last.radius), last.ccw()).cast<coord_t>()),
last.radius, last.orientation });
// Length to go is zero.
return 0;
}
distance -= len;
}
}
// Return remaining distance to go.
assert(distance >= 0);
return distance;
}
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2)
{
assert(path.size() != 1);
// initialized to "invalid" state.
PathSegmentProjection out;
out.distance2 = search_radius2;
if (path.size() < 2 || path.front().point == point) {
// First point is the closest point.
if (path.empty()) {
// No closest point available.
} else if (const Point p0 = path.front().point; p0 == point) {
out.segment_id = 0;
out.point = p0;
out.distance2 = 0;
} else if (double d2 = (p0 - point).cast<double>().squaredNorm(); d2 < out.distance2) {
out.segment_id = 0;
out.point = p0;
out.distance2 = d2;
}
} else {
assert(path.size() >= 2);
// min_point_it will contain an end point of a segment with a closest projection found
// or path.cbegin() if no such closest projection closer than search_radius2 was found.
auto min_point_it = path.cbegin();
Point prev = path.front().point;
for (auto it = std::next(path.cbegin()); it != path.cend(); ++ it) {
if (it->linear()) {
// Linear segment
Point proj;
// distance_to_squared() will possibly return the start or end point of a line segment.
if (double d2 = line_alg::distance_to_squared(Line(prev, it->point), point, &proj); d2 < out.distance2) {
out.point = proj;
out.distance2 = d2;
min_point_it = it;
}
} else {
// Circular arc
Vec2i64 center = arc_center(prev.cast<double>(), it->point.cast<double>(), double(it->radius), it->ccw()).cast<int64_t>();
// Test whether point is inside the wedge.
Vec2i64 v1 = prev.cast<int64_t>() - center;
Vec2i64 v2 = it->point.cast<int64_t>() - center;
Vec2i64 vp = point.cast<int64_t>() - center;
if (inside_arc_wedge_vectors(v1, v2, it->radius > 0, it->ccw(), vp)) {
// Distance of the radii.
const auto r = double(std::abs(it->radius));
const auto rtest = sqrt(double(vp.squaredNorm()));
if (double d2 = sqr(rtest - r); d2 < out.distance2) {
if (rtest > SCALED_EPSILON)
// Project vp to the arc.
out.point = center.cast<coord_t>() + (vp.cast<double>() * (r / rtest)).cast<coord_t>();
else
// Test point is very close to the center of the radius. Any point of the arc is the closest.
// Pick the start.
out.point = prev;
out.distance2 = d2;
out.center = center.cast<coord_t>();
min_point_it = it;
}
} else {
// Distance to the start point.
if (double d2 = double((v1 - vp).squaredNorm()); d2 < out.distance2) {
out.point = prev;
out.distance2 = d2;
min_point_it = it;
}
}
}
prev = it->point;
}
if (! path.back().linear()) {
// Calculate distance to the end point.
if (double d2 = (path.back().point - point).cast<double>().squaredNorm(); d2 < out.distance2) {
out.point = path.back().point;
out.distance2 = d2;
min_point_it = std::prev(path.end());
}
}
// If a new closes point was found, it is closer than search_radius2.
assert((min_point_it == path.cbegin()) == (out.distance2 == search_radius2));
// Output is not valid yet.
assert(! out.valid());
if (min_point_it != path.cbegin()) {
// Make it valid by setting the segment.
out.segment_id = std::prev(min_point_it) - path.begin();
assert(out.valid());
}
}
assert(! out.valid() || (out.segment_id >= 0 && out.segment_id < path.size()));
return out;
}
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length)
{
assert(proj.valid());
assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size()));
assert(path.size() > 1);
std::pair<Path, Path> out;
if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point))
out.first = path;
else if (proj.segment_id == 0 && proj.point == path.front().point)
out.second = path;
else {
// Path will likely be split to two pieces.
assert(proj.valid() && proj.segment_id >= 0 && proj.segment_id + 1 < path.size());
const Segment &start = path[proj.segment_id];
const Segment &end = path[proj.segment_id + 1];
bool split_segment = true;
int split_segment_id = proj.segment_id;
if (int64_t d2 = (proj.point - start.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
split_segment = false;
int64_t d22 = (proj.point - end.point).cast<int64_t>().squaredNorm();
if (d22 < d2)
// Split at the end of the segment.
++ split_segment_id;
} else if (int64_t d2 = (proj.point - end.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
++ split_segment_id;
split_segment = false;
}
if (split_segment) {
out.first.assign(path.begin(), path.begin() + split_segment_id + 2);
out.second.assign(path.begin() + split_segment_id, path.end());
assert(out.first[out.first.size() - 2] == start);
assert(out.first.back() == end);
assert(out.second.front() == start);
assert(out.second[1] == end);
assert(out.first.size() + out.second.size() == path.size() + 2);
assert(out.first.back().radius == out.second[1].radius);
out.first.back().point = proj.point;
out.second.front().point = proj.point;
if (end.radius < 0) {
// A large arc (> PI) was split.
// At least one of the two arches that were created by splitting the original arch will become smaller.
// Make the radii of those arches that became < PI positive.
// In case of a projection onto an arc, proj.center should be filled in and valid.
auto vstart = (start.point - proj.center).cast<int64_t>();
auto vend = (end.point - proj.center).cast<int64_t>();
auto vproj = (proj.point - proj.center).cast<int64_t>();
if ((cross2(vstart, vproj) > 0) == end.ccw())
// Make the radius of a minor arc positive.
out.first.back().radius *= -1.f;
if ((cross2(vproj, vend) > 0) == end.ccw())
// Make the radius of a minor arc positive.
out.second[1].radius *= -1.f;
assert(out.first.size() > 1);
assert(out.second.size() > 1);
out.second.front().radius = 0;
}
} else {
assert(split_segment_id >= 0 && split_segment_id < path.size());
if (split_segment_id + 1 == int(path.size()))
out.first = path;
else if (split_segment_id == 0)
out.second = path;
else {
// Split at the start of proj.segment_id.
out.first.assign(path.begin(), path.begin() + split_segment_id + 1);
out.second.assign(path.begin() + split_segment_id, path.end());
assert(out.first.size() + out.second.size() == path.size() + 1);
assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end));
assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end));
assert(out.first.size() > 1);
assert(out.second.size() > 1);
out.second.front().radius = 0;
}
}
}
return out;
}
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length)
{
return split_at(path, point_to_path_projection(path, point), min_segment_length);
}
} } } // namespace Slic3r::Geometry::ArcWelder

View File

@ -0,0 +1,324 @@
#ifndef slic3r_Geometry_ArcWelder_hpp_
#define slic3r_Geometry_ArcWelder_hpp_
#include <optional>
#include "../Point.hpp"
namespace Slic3r { namespace Geometry { namespace ArcWelder {
// Calculate center point of an arc given two points and a radius.
// positive radius: take shorter arc
// negative radius: take longer arc
// radius must NOT be zero!
template<typename Derived, typename Derived2, typename Float>
inline Eigen::Matrix<Float, 2, 1, Eigen::DontAlign> arc_center(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const Float radius,
const bool is_ccw)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_center(): first parameter is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_center(): second parameter is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_center(): Both vectors must be of the same type.");
static_assert(std::is_same<typename Derived::Scalar, Float>::value, "arc_center(): Radius must be of the same type as the vectors.");
assert(radius != 0);
using Vector = Eigen::Matrix<Float, 2, 1, Eigen::DontAlign>;
auto v = end_pos - start_pos;
Float q2 = v.squaredNorm();
assert(q2 > 0);
Float t2 = sqr(radius) / q2 - Float(.25f);
// If the start_pos and end_pos are nearly antipodal, t2 may become slightly negative.
// In that case return a centroid of start_point & end_point.
Float t = t2 > 0 ? sqrt(t2) : Float(0);
auto mid = Float(0.5) * (start_pos + end_pos);
Vector vp{ -v.y() * t, v.x() * t };
return (radius > Float(0)) == is_ccw ? (mid + vp).eval() : (mid - vp).eval();
}
// Calculate angle of an arc given two points and a radius.
// Returned angle is in the range <0, 2 PI)
// positive radius: take shorter arc
// negative radius: take longer arc
// radius must NOT be zero!
template<typename Derived, typename Derived2>
inline typename Derived::Scalar arc_angle(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const typename Derived::Scalar radius)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_angle(): first parameter is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_angle(): second parameter is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_angle(): Both vectors must be of the same type.");
assert(radius != 0);
using Float = typename Derived::Scalar;
Float a = Float(0.5) * (end_pos - start_pos).norm() / radius;
return radius > Float(0) ?
// acute angle:
(a > Float( 1.) ? Float(M_PI) : Float(2.) * std::asin(a)) :
// obtuse angle:
(a < Float(-1.) ? Float(M_PI) : Float(2. * M_PI) + Float(2.) * std::asin(a));
}
// Calculate positive length of an arc given two points and a radius.
// positive radius: take shorter arc
// negative radius: take longer arc
// radius must NOT be zero!
template<typename Derived, typename Derived2>
inline typename Derived::Scalar arc_length(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const typename Derived::Scalar radius)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_length(): Both vectors must be of the same type.");
assert(radius != 0);
return arc_angle(start_pos, end_pos, radius) * std::abs(radius);
}
// Calculate positive length of an arc given two points, center and orientation.
template<typename Derived, typename Derived2, typename Derived3>
inline typename Derived::Scalar arc_length(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const Eigen::MatrixBase<Derived3> &center_pos,
const bool ccw)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): third parameter is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "arc_length(): All third points must be of the same type.");
using Float = typename Derived::Scalar;
auto vstart = start_pos - center_pos;
auto vend = end_pos - center_pos;
Float radius = vstart.norm();
Float angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
if (! ccw)
angle *= Float(-1.);
if (angle < 0)
angle += Float(2. * M_PI);
assert(angle >= Float(0.) && angle < Float(2. * M_PI + EPSILON));
return angle * radius;
}
// Test whether a point is inside a wedge of an arc.
template<typename Derived, typename Derived2, typename Derived3>
inline bool inside_arc_wedge_vectors(
const Eigen::MatrixBase<Derived> &start_vec,
const Eigen::MatrixBase<Derived2> &end_vec,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived3> &query_vec)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): start_vec is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): end_vec is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): query_vec is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "inside_arc_wedge_vectors(): All vectors must be of the same type.");
return shorter_arc ?
// Smaller (convex) wedge.
(ccw ?
cross2(start_vec, query_vec) > 0 && cross2(query_vec, end_vec) > 0 :
cross2(start_vec, query_vec) < 0 && cross2(query_vec, end_vec) < 0) :
// Larger (concave) wedge.
(ccw ?
cross2(end_vec, query_vec) < 0 || cross2(query_vec, start_vec) < 0 :
cross2(end_vec, query_vec) > 0 || cross2(query_vec, start_vec) > 0);
}
template<typename Derived, typename Derived2, typename Derived3, typename Derived4>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Eigen::MatrixBase<Derived3> &center_pt,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived4> &query_pt)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): center_pt is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived4::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived4::Scalar>::value, "inside_arc_wedge(): All vectors must be of the same type.");
return inside_arc_wedge_vectors(start_pt - center_pt, end_pt - center_pt, shorter_arc, ccw, query_pt - center_pt);
}
template<typename Derived, typename Derived2, typename Derived3, typename Float>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Float radius,
const bool ccw,
const Eigen::MatrixBase<Derived3> &query_pt)
{
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, Float>::value, "inside_arc_wedge(): All vectors + radius must be of the same type.");
return inside_arc_wedge(start_pt, end_pt,
arc_center(start_pt, end_pt, radius, ccw),
radius > 0, ccw, query_pt);
}
// Return number of linear segments necessary to interpolate arc of a given positive radius and positive angle to satisfy
// maximum deviation of an interpolating polyline from an analytic arc.
template<typename FloatType>
size_t arc_discretization_steps(const FloatType radius, const FloatType angle, const FloatType deviation)
{
assert(radius > 0);
assert(angle > 0);
assert(angle <= FloatType(2. * M_PI));
assert(deviation > 0);
FloatType d = radius - deviation;
return d < EPSILON ?
// Radius smaller than deviation.
( // Acute angle: a single segment interpolates the arc with sufficient accuracy.
angle < M_PI ||
// Obtuse angle: Test whether the furthest point (center) of an arc is closer than deviation to the center of a line segment.
radius * (FloatType(1.) + cos(M_PI - FloatType(.5) * angle)) < deviation ?
// Single segment is sufficient
1 :
// Two segments are necessary, the middle point is at the center of the arc.
2) :
size_t(ceil(angle / (2. * acos(d / radius))));
}
// Discretize arc given the radius, orientation and maximum deviation from the arc.
// Returned polygon starts with p1, ends with p2 and it is discretized to guarantee the maximum deviation.
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation);
// 1.2m diameter, maximum given by coord_t
static constexpr const double default_scaled_max_radius = scaled<double>(600.);
// 0.05mm
static constexpr const double default_scaled_resolution = scaled<double>(0.05);
// 5 percent
static constexpr const double default_arc_length_percent_tolerance = 0.05;
enum class Orientation : unsigned char {
Unknown,
CCW,
CW,
};
// Returns orientation of a polyline with regard to the center.
// Successive points are expected to take less than a PI angle step.
// Returns Orientation::Unknown if the orientation with regard to the center
// is not monotonous.
Orientation arc_orientation(
const Point &center,
const Points::const_iterator begin,
const Points::const_iterator end);
// Single segment of a smooth path.
struct Segment
{
// End point of a linear or circular segment.
// Start point is provided by the preceding segment.
Point point;
// Radius of a circular segment. Positive - take the shorter arc. Negative - take the longer arc. Zero - linear segment.
float radius{ 0.f };
// CCW or CW. Ignored for zero radius (linear segment).
Orientation orientation{ Orientation::CCW };
bool linear() const { return radius == 0; }
bool ccw() const { return orientation == Orientation::CCW; }
bool cw() const { return orientation == Orientation::CW; }
};
inline bool operator==(const Segment &lhs, const Segment &rhs) {
return lhs.point == rhs.point && lhs.radius == rhs.radius && lhs.orientation == rhs.orientation;
}
using Segments = std::vector<Segment>;
using Path = Segments;
// Interpolate polyline path with a sequence of linear / circular segments given the interpolation tolerance.
// Only convert to polyline if zero tolerance.
// Convert to polyline and decimate polyline if zero fit_circle_percent_tolerance.
// Path fitting is inspired with the arc fitting algorithm in
// Arc Welder: Anti-Stutter Library by Brad Hochgesang FormerLurker@pm.me
// https://github.com/FormerLurker/ArcWelderLib
Path fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance);
// Decimate polyline into a smooth path structure using Douglas-Peucker polyline decimation algorithm.
inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); }
template<typename FloatType>
inline FloatType segment_length(const Segment &start, const Segment &end)
{
return end.linear() ?
(end.point - start.point).cast<FloatType>().norm() :
arc_length(start.point.cast<FloatType>(), end.point.cast<FloatType>(), FloatType(end.radius));
}
template<typename FloatType>
inline FloatType path_length(const Path &path)
{
FloatType len = 0;
for (size_t i = 1; i < path.size(); ++ i)
len += segment_length<FloatType>(path[i - 1], path[i]);
return len;
}
// Estimate minimum path length of a segment cheaply without having to calculate center of an arc and it arc length.
// Used for caching a smooth path chunk that is certainly longer than a threshold.
inline int64_t estimate_min_segment_length(const Segment &start, const Segment &end)
{
if (end.linear() || end.radius > 0) {
// Linear segment or convex wedge, take the larger X or Y component.
Point v = (end.point - start.point).cwiseAbs();
return std::max(v.x(), v.y());
} else {
// Arc with angle > PI.
// Returns estimate of PI * r
return - int64_t(3) * int64_t(end.radius);
}
}
// Estimate minimum path length cheaply without having to calculate center of an arc and it arc length.
// Used for caching a smooth path chunk that is certainly longer than a threshold.
inline int64_t estimate_path_length(const Path &path)
{
int64_t len = 0;
for (size_t i = 1; i < path.size(); ++ i)
len += Geometry::ArcWelder::estimate_min_segment_length(path[i - 1], path[i]);
return len;
}
void reverse(Path &path);
// Clip start / end of a smooth path by len.
// If path is shorter than len, remaining path length to trim will be returned.
double clip_start(Path &path, const double len);
double clip_end(Path &path, const double len);
struct PathSegmentProjection
{
// Start segment of a projection on the path.
size_t segment_id { std::numeric_limits<size_t>::max() };
// Projection of the point on the segment.
Point point { 0, 0 };
// If the point lies on an arc, the arc center is cached here.
Point center { 0, 0 };
// Square of a distance of the projection.
double distance2 { std::numeric_limits<double>::max() };
bool valid() const { return this->segment_id != std::numeric_limits<size_t>::max(); }
};
// Returns closest segment and a parameter along the closest segment of a path to a point.
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2 = std::numeric_limits<double>::max());
// Split a path into two paths at a segment point. Snap to an existing point if the projection of "point is closer than min_segment_length.
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length);
// Split a path into two paths at a point closest to "point". Snap to an existing point if the projection of "point is closer than min_segment_length.
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length);
} } } // namespace Slic3r::Geometry::ArcWelder
#endif // slic3r_Geometry_ArcWelder_hpp_

View File

@ -21,9 +21,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con
return Point::new_scale(center.x(), center.y());
}
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
/// lie on.
// Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc.
// The method was proposed by G. Taubin in
// "Estimation Of Planar Curves, Surfaces And Nonplanar Space Curves Defined By Implicit Equations,
// With Applications To Edge And Range Image Segmentation", IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)."
// This particular implementation was adapted from
// "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126"
// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
// lie on.
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
{
// calculate the centroid of the data set

View File

@ -13,10 +13,17 @@ namespace Slic3r { namespace Geometry {
// https://en.wikipedia.org/wiki/Circumscribed_circle
// Circumcenter coordinates, Cartesian coordinates
template<typename Vector>
Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon)
// In case the three points are collinear, returns their centroid.
template<typename Derived, typename Derived2, typename Derived3>
Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
{
using Scalar = typename Vector::Scalar;
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "circle_center(): 1st point is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "circle_center(): 2nd point is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "circle_center(): 3rd point is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
"circle_center(): All three points must be of the same type.");
using Scalar = typename Derived::Scalar;
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
Vector b = bsrc - a;
Vector c = csrc - a;
Scalar lb = b.squaredNorm();
@ -34,6 +41,32 @@ Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, ty
}
}
// https://en.wikipedia.org/wiki/Circumscribed_circle
// Circumcenter coordinates, Cartesian coordinates
// Returns no value if the three points are collinear.
template<typename Derived, typename Derived2, typename Derived3>
std::optional<Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign>> try_circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
{
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "try_circle_center(): 1st point is not a 2D vector");
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "try_circle_center(): 2nd point is not a 2D vector");
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "try_circle_center(): 3rd point is not a 2D vector");
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
"try_circle_center(): All three points must be of the same type.");
using Scalar = typename Derived::Scalar;
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
Vector b = bsrc - a;
Vector c = csrc - a;
Scalar lb = b.squaredNorm();
Scalar lc = c.squaredNorm();
if (Scalar d = b.x() * c.y() - b.y() * c.x(); std::abs(d) < epsilon) {
// The three points are collinear.
return {};
} else {
Vector v = lc * b - lb * c;
return std::make_optional<Vector>(a + Vector(- v.y(), v.x()) / (2 * d));
}
}
// 2D circle defined by its center and squared radius
template<typename Vector>
struct CircleSq {
@ -69,7 +102,7 @@ struct Circle {
Vector center;
Scalar radius;
Circle() {}
Circle() = default;
Circle(const Vector &center, const Scalar radius) : center(center), radius(radius) {}
Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); }
Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }

View File

@ -189,7 +189,7 @@ void MeasuringImpl::update_planes()
int neighbor_idx = face_neighbors[facets[face_id]][edge_id];
if (neighbor_idx == -1)
goto PLANE_FAILURE;
if (visited[face_id][edge_id] || (int)face_to_plane[neighbor_idx] == plane_id) {
if (visited[face_id][edge_id] || face_to_plane[neighbor_idx] == plane_id) {
visited[face_id][edge_id] = true;
continue;
}
@ -223,7 +223,7 @@ void MeasuringImpl::update_planes()
// Remember all halfedges we saw to break out of such infinite loops.
boost::container::small_vector<Halfedge_index, 10> he_seen;
while ( (int)face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
he_seen.emplace_back(he);
he = sm.next_around_target(he);
if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())

View File

@ -40,7 +40,7 @@
#include "SVG.hpp"
#include <Eigen/Dense>
#include "GCodeWriter.hpp"
#include "GCode/GCodeWriter.hpp"
namespace Slic3r {

View File

@ -294,25 +294,26 @@ struct CutConnector
float height;
float radius_tolerance;// [0.f : 1.f]
float height_tolerance;// [0.f : 1.f]
float z_angle {0.f};
CutConnectorAttributes attribs;
CutConnector()
: pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f)
: pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f), z_angle(0.f)
{}
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, float za, CutConnectorAttributes attributes)
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), z_angle(za), attribs(attributes)
{}
CutConnector(const CutConnector& rhs) :
CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.z_angle, rhs.attribs) {}
bool operator==(const CutConnector& other) const;
bool operator!=(const CutConnector& other) const { return !(other == (*this)); }
template<class Archive> inline void serialize(Archive& ar) {
ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs);
ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, z_angle, attribs);
}
};

View File

@ -108,100 +108,6 @@ bool MultiPoint::remove_duplicate_points()
return false;
}
Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance)
{
Points result_pts;
auto tolerance_sq = int64_t(sqr(tolerance));
if (! pts.empty()) {
const Point *anchor = &pts.front();
size_t anchor_idx = 0;
const Point *floater = &pts.back();
size_t floater_idx = pts.size() - 1;
result_pts.reserve(pts.size());
result_pts.emplace_back(*anchor);
if (anchor_idx != floater_idx) {
assert(pts.size() > 1);
std::vector<size_t> dpStack;
dpStack.reserve(pts.size());
dpStack.emplace_back(floater_idx);
for (;;) {
int64_t max_dist_sq = 0;
size_t furthest_idx = anchor_idx;
// find point furthest from line seg created by (anchor, floater) and note it
{
const Point a = *anchor;
const Point f = *floater;
const Vec2i64 v = (f - a).cast<int64_t>();
if (const int64_t l2 = v.squaredNorm(); l2 == 0) {
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i)
if (int64_t dist_sq = (pts[i] - a).cast<int64_t>().squaredNorm(); dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest_idx = i;
}
} else {
const double dl2 = double(l2);
const Vec2d dv = v.cast<double>();
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) {
const Point p = pts[i];
const Vec2i64 va = (p - a).template cast<int64_t>();
const int64_t t = va.dot(v);
int64_t dist_sq;
if (t <= 0) {
dist_sq = va.squaredNorm();
} else if (t >= l2) {
dist_sq = (p - f).cast<int64_t>().squaredNorm();
} else {
const Vec2i64 w = ((double(t) / dl2) * dv).cast<int64_t>();
dist_sq = (w - va).squaredNorm();
}
if (dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest_idx = i;
}
}
}
}
// remove point if less than tolerance
if (max_dist_sq <= tolerance_sq) {
result_pts.emplace_back(*floater);
anchor_idx = floater_idx;
anchor = floater;
assert(dpStack.back() == floater_idx);
dpStack.pop_back();
if (dpStack.empty())
break;
floater_idx = dpStack.back();
} else {
floater_idx = furthest_idx;
dpStack.emplace_back(floater_idx);
}
floater = &pts[floater_idx];
}
}
assert(result_pts.front() == pts.front());
assert(result_pts.back() == pts.back());
#if 0
{
static int iRun = 0;
BoundingBox bbox(pts);
BoundingBox bbox2(result_pts);
bbox.merge(bbox2);
SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
if (pts.front() == pts.back())
svg.draw(Polygon(pts), "black");
else
svg.draw(Polyline(pts), "black");
if (result_pts.front() == result_pts.back())
svg.draw(Polygon(result_pts), "green", scale_(0.1));
else
svg.draw(Polyline(result_pts), "green", scale_(0.1));
}
#endif
}
return result_pts;
}
// Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825
// thanks to @fuchstraumer
/*
@ -224,7 +130,7 @@ struct vis_node{
// other node if it's area is less than the other node's area
bool operator<(const vis_node& other) { return (this->area < other.area); }
};
Points MultiPoint::visivalingam(const Points& pts, const double& tolerance)
Points MultiPoint::visivalingam(const Points &pts, const double tolerance)
{
// Make sure there's enough points in "pts" to bother with simplification.
assert(pts.size() >= 2);

View File

@ -17,6 +17,123 @@ namespace Slic3r {
class BoundingBox;
class BoundingBox3;
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename PointGetter>
inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter)
{
using InputIteratorCategory = typename std::iterator_traits<InputIterator>::iterator_category;
static_assert(std::is_base_of_v<std::input_iterator_tag, InputIteratorCategory>);
using Vector = Eigen::Matrix<SquareLengthType, 2, 1, Eigen::DontAlign>;
if (begin != end) {
// Supporting in-place reduction and the data type may be generic, thus we are always making a copy of the point value before there is a chance
// to override input by moving the data to the output.
auto a = point_getter(*begin);
*out ++ = std::move(*begin);
if (auto next = std::next(begin); next == end) {
// Single point input only.
} else if (std::next(next) == end) {
// Two points input.
*out ++ = std::move(*next);
} else {
const auto tolerance_sq = SquareLengthType(sqr(tolerance));
InputIterator anchor = begin;
InputIterator floater = std::prev(end);
std::vector<InputIterator> dpStack;
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, InputIteratorCategory>)
dpStack.reserve(end - begin);
dpStack.emplace_back(floater);
auto f = point_getter(*floater);
for (;;) {
assert(anchor != floater);
bool take_floater = false;
InputIterator furthest = anchor;
if (std::next(anchor) == floater) {
// Two point segment. Accept the floater.
take_floater = true;
} else {
SquareLengthType max_dist_sq = 0;
// Find point furthest from line seg created by (anchor, floater) and note it.
const Vector v = (f - a).template cast<SquareLengthType>();
if (const SquareLengthType l2 = v.squaredNorm(); l2 == 0) {
// Zero length segment, find the furthest point between anchor and floater.
for (auto it = std::next(anchor); it != floater; ++ it)
if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast<SquareLengthType>().squaredNorm();
dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest = it;
}
} else {
// Find Find the furthest point from the line <anchor, floater>.
const double dl2 = double(l2);
const Vec2d dv = v.template cast<double>();
for (auto it = std::next(anchor); it != floater; ++ it) {
const auto p = point_getter(*it);
const Vector va = (p - a).template cast<SquareLengthType>();
const SquareLengthType t = va.dot(v);
SquareLengthType dist_sq;
if (t <= 0) {
dist_sq = va.squaredNorm();
} else if (t >= l2) {
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
} else if (double dt = double(t) / dl2; dt <= 0) {
dist_sq = va.squaredNorm();
} else if (dt >= 1.) {
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
} else {
const Vector w = (dt * dv).cast<SquareLengthType>();
dist_sq = (w - va).squaredNorm();
}
if (dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest = it;
}
}
}
// remove point if less than tolerance
take_floater = max_dist_sq <= tolerance_sq;
}
if (take_floater) {
// The points between anchor and floater are close to the <anchor, floater> line.
// Drop the points between them.
a = f;
*out ++ = std::move(*floater);
anchor = floater;
assert(dpStack.back() == floater);
dpStack.pop_back();
if (dpStack.empty())
break;
floater = dpStack.back();
f = point_getter(*floater);
} else {
// The furthest point is too far from the segment <anchor, floater>.
// Divide recursively.
floater = furthest;
f = point_getter(*floater);
dpStack.emplace_back(floater);
}
}
}
}
return out;
}
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
template<typename OutputIterator>
inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance)
{
return douglas_peucker<int64_t>(begin, end, out, tolerance, [](const Point &p) { return p; });
}
inline Points douglas_peucker(const Points &src, const double tolerance)
{
Points out;
out.reserve(src.size());
douglas_peucker(src.begin(), src.end(), std::back_inserter(out), tolerance);
return out;
}
class MultiPoint
{
public:
@ -86,8 +203,8 @@ public:
}
}
static Points douglas_peucker(const Points &points, const double tolerance);
static Points visivalingam(const Points& pts, const double& tolerance);
static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); }
static Points visivalingam(const Points &src, const double tolerance);
inline auto begin() { return points.begin(); }
inline auto begin() const { return points.begin(); }
@ -124,16 +241,20 @@ extern BoundingBox get_extents(const MultiPoint &mp);
extern BoundingBox get_extents_rotated(const Points &points, double angle);
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
inline double length(const Points &pts) {
inline double length(const Points::const_iterator begin, const Points::const_iterator end) {
double total = 0;
if (! pts.empty()) {
auto it = pts.begin();
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
if (begin != end) {
auto it = begin;
for (auto it_prev = it ++; it != end; ++ it, ++ it_prev)
total += (*it - *it_prev).cast<double>().norm();
}
return total;
}
inline double length(const Points &pts) {
return length(pts.begin(), pts.end());
}
inline double area(const Points &polygon) {
double area = 0.;
for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)

View File

@ -126,20 +126,18 @@ ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickP
const double w = fmax(line.a_width, line.b_width);
const Flow new_flow = (role.is_bridge() && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
if (path.polyline.points.empty()) {
path.polyline.append(line.a);
path.polyline.append(line.b);
if (path.empty()) {
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
path = { ExtrusionAttributes{ path.role(), new_flow } };
path.polyline.append(line.a);
path.polyline.append(line.b);
#ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width);
#endif
path.mm3_per_mm = new_flow.mm3_per_mm();
path.width = new_flow.width();
path.height = new_flow.height();
} else {
assert(path.width >= EPSILON);
thickness_delta = scaled<double>(fabs(path.width - new_flow.width()));
assert(path.width() >= EPSILON);
thickness_delta = scaled<double>(fabs(path.width() - new_flow.width()));
if (thickness_delta <= merge_tolerance) {
// the width difference between this line and the current flow
// (of the previous line) width is within the accepted tolerance
@ -332,10 +330,12 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
extrusion_paths_append(
paths,
intersection_pl({ polygon }, lower_slices_polygons_clipped),
role_normal,
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height));
ExtrusionAttributes{
role_normal,
ExtrusionFlow{ is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height)
} });
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
@ -343,23 +343,26 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
extrusion_paths_append(
paths,
diff_pl({ polygon }, lower_slices_polygons_clipped),
role_overhang,
params.mm3_per_mm_overhang,
params.overhang_flow.width(),
params.overhang_flow.height());
ExtrusionAttributes{
role_overhang,
ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() }
});
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
} else {
ExtrusionPath path(role_normal);
path.polyline = polygon.split_at_first_point();
path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm;
path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width();
path.height = float(params.layer_height);
paths.push_back(path);
paths.emplace_back(polygon.split_at_first_point(),
ExtrusionAttributes{
role_normal,
ExtrusionFlow{
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height)
}
});
}
coll.append(ExtrusionLoop(std::move(paths), loop_role));
}
@ -390,11 +393,13 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
if (loop.is_contour) {
eloop->make_counter_clockwise();
if (eloop->is_clockwise())
eloop->reverse_loop();
out.append(std::move(children.entities));
out.entities.emplace_back(eloop);
} else {
eloop->make_clockwise();
if (eloop->is_counter_clockwise())
eloop->reverse_loop();
out.entities.emplace_back(eloop);
out.append(std::move(children.entities));
}
@ -610,10 +615,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
if (extrusion->is_closed) {
ExtrusionLoop extrusion_loop(std::move(paths));
// Restore the orientation of the extrusion loop.
if (pg_extrusion.is_contour)
extrusion_loop.make_counter_clockwise();
else
extrusion_loop.make_clockwise();
if (pg_extrusion.is_contour == extrusion_loop.is_clockwise())
extrusion_loop.reverse_loop();
for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) {
assert(it->polyline.points.size() >= 2);
@ -967,11 +970,9 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width
Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover);
if (!shrinked.empty()) {
if (!shrinked.empty())
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.height());
}
ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
Polylines fills;
ExPolygons gap = shrinked.empty() ? offset_ex(prev, overhang_flow.scaled_spacing() * 0.5) : to_expolygons(shrinked);
@ -982,14 +983,12 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
if (!fills.empty()) {
fills = intersection_pl(fills, shrinked_overhang_to_cover);
extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.height());
ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
}
break;
} else {
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.height());
ExtrusionAttributes{ExtrusionRole::OverhangPerimeter, overhang_flow });
}
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }

View File

@ -1651,6 +1651,7 @@ namespace client
// Check whether the table X values are sorted.
double x = expr_x.as_d();
assert(! std::isnan(x));
bool evaluated = false;
for (size_t i = 1; i < table.table.size(); ++i) {
double x0 = table.table[i - 1].x;
@ -2106,7 +2107,7 @@ namespace client
multiplicative_expression.name("multiplicative_expression");
assignment_statement =
variable_reference(_r1)[_a = _1] >> '=' >
(variable_reference(_r1)[_a = _1] >> '=') >
( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer.
initializer_list(_r1)[px::bind(&MyContext::vector_variable_assign_initializer_list, _r1, _a, _1)]
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.

View File

@ -59,14 +59,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
void Point::rotate(double angle, const Point &center)
{
double cur_x = (double)(*this)(0);
double cur_y = (double)(*this)(1);
double s = ::sin(angle);
double c = ::cos(angle);
double dx = cur_x - (double)center(0);
double dy = cur_y - (double)center(1);
(*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy );
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
Vec2d cur = this->cast<double>();
double s = ::sin(angle);
double c = ::cos(angle);
auto d = cur - center.cast<double>();
this->x() = fast_round_up<coord_t>(center.x() + c * d.x() - s * d.y());
this->y() = fast_round_up<coord_t>(center.y() + s * d.x() + c * d.y());
}
bool has_duplicate_points(Points &&pts)

View File

@ -192,11 +192,12 @@ public:
Point(const Point &rhs) { *this = rhs; }
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
// This constructor allows you to construct Point from Eigen expressions
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
template<typename OtherDerived>
static Point new_scale(const Eigen::MatrixBase<OtherDerived> &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
// This method allows you to assign Eigen expressions to MyVectorType
template<typename OtherDerived>

View File

@ -158,8 +158,10 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
}
if (this->points.front() == point) {
//FIXME why is p1 NOT empty as in the case above?
*p1 = { point };
*p2 = *this;
return;
}
auto min_dist2 = std::numeric_limits<double>::max();

View File

@ -44,6 +44,7 @@
#include "libslic3r.h"
#include "Utils.hpp"
#include "PlaceholderParser.hpp"
#include "GCode/Thumbnails.hpp"
using boost::property_tree::ptree;
@ -459,12 +460,13 @@ static std::vector<std::string> s_Preset_print_options {
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
"support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "gcode_binary", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"elefant_foot_compensation", "xy_size_compensation", "resolution", "gcode_resolution", "arc_fitting", "arc_fitting_tolerance",
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
@ -1298,7 +1300,6 @@ static const std::set<std::string> independent_from_extruder_number_options = {
"filament_ramming_parameters",
"gcode_substitutions",
"post_process",
"thumbnails",
};
bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key)
@ -1322,6 +1323,15 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
} else if (opt_key == "default_filament_profile") {
// Ignore this field, it is not presented to the user, therefore showing a "modified" flag for this parameter does not help.
// Also the length of this field may differ, which may lead to a crash if the block below is used.
} else if (opt_key == "thumbnails") {
// "thumbnails" can not containes a extentions in old config but are valid and use PNG extention by default
// So, check if "thumbnails" is really changed
// We will compare full thumnails instead of exactly config values
auto [thumbnails, er] = GCodeThumbnails::make_and_check_thumbnail_list(config_this);
auto [thumbnails_new, er_new] = GCodeThumbnails::make_and_check_thumbnail_list(config_other);
if (thumbnails != thumbnails_new || er != er_new)
// if those strings are actually the same, erase them from the list of dirty oprions
diff.emplace_back(opt_key);
} else {
switch (other_opt->type()) {
case coInts: add_correct_opts_to_diff<ConfigOptionInts >(opt_key, diff, config_other, config_this); break;
@ -1345,7 +1355,14 @@ bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference)
{
if (edited != nullptr && reference != nullptr) {
// Only compares options existing in both configs.
if (! reference->config.equals(edited->config))
bool is_dirty = !reference->config.equals(edited->config);
if (is_dirty && edited->type != Preset::TYPE_FILAMENT) {
// for non-filaments preset check deep difference for compared configs
// there can be cases (as for thumbnails), when configs can logically equal
// even when their values are not equal.
is_dirty = !deep_diff(edited->config, reference->config).empty();
}
if (is_dirty)
return true;
// The "compatible_printers" option key is handled differently from the others:
// It is not mandatory. If the key is missing, it means it is compatible with any printer.

View File

@ -28,6 +28,7 @@
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <LibBGCode/core/core.hpp>
// Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir.
// This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions.
@ -883,14 +884,22 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (is_gcode_file(path)) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
if (is_gcode_file(path)) {
FILE* file = boost::nowide::fopen(path.c_str(), "rb");
if (file == nullptr)
throw Slic3r::RuntimeError(format("Error opening file %1%", path));
std::vector<uint8_t> cs_buffer(65536);
const bool is_binary = bgcode::core::is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == bgcode::core::EResult::Success;
fclose(file);
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
ConfigSubstitutions config_substitutions = is_binary ? config.load_from_binary_gcode_file(path, compatibility_rule) :
config.load_from_gcode_file(path, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
// 1) Try to load the config file into a boost property tree.
boost::property_tree::ptree tree;

View File

@ -139,6 +139,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"perimeter_acceleration",
"post_process",
"gcode_substitutions",
"gcode_binary",
"printer_notes",
"retract_before_travel",
"retract_before_wipe",
@ -954,8 +955,10 @@ void Print::process()
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) {
for (size_t idx = range.begin(); idx < range.end(); ++idx) {
m_objects[idx]->generate_support_material();
m_objects[idx]->estimate_curled_extrusions();
PrintObject &obj = *m_objects[idx];
obj.generate_support_material();
obj.estimate_curled_extrusions();
obj.calculate_overhanging_perimeters();
}
}, tbb::simple_partitioner());
@ -1042,7 +1045,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
this->set_status(90, message);
// Create GCode on heap, it has quite a lot of data.
std::unique_ptr<GCode> gcode(new GCode);
std::unique_ptr<GCodeGenerator> gcode(new GCodeGenerator);
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
if (m_conflict_result.has_value())
@ -1158,13 +1161,15 @@ void Print::_make_skirt()
}
// Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath(
ExtrusionPath(
eloop.paths.emplace_back(
ExtrusionAttributes{
ExtrusionRole::Skirt,
(float)mm3_per_mm, // this will be overridden at G-code export time
flow.width(),
(float)first_layer_height // this will be overridden at G-code export time
)));
ExtrusionFlow{
float(mm3_per_mm), // this will be overridden at G-code export time
flow.width(),
float(first_layer_height) // this will be overridden at G-code export time
}
});
eloop.paths.back().polyline = loop.split_at_first_point();
m_skirt.append(eloop);
if (m_config.min_skirt_length.value > 0) {
@ -1590,6 +1595,26 @@ std::string Print::output_filename(const std::string &filename_base) const
return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config);
}
const std::string PrintStatistics::FilamentUsedG = "filament used [g]";
const std::string PrintStatistics::FilamentUsedGMask = "; filament used [g] =";
const std::string PrintStatistics::TotalFilamentUsedG = "total filament used [g]";
const std::string PrintStatistics::TotalFilamentUsedGMask = "; total filament used [g] =";
const std::string PrintStatistics::TotalFilamentUsedGValueMask = "; total filament used [g] = %.2lf\n";
const std::string PrintStatistics::FilamentUsedCm3 = "filament used [cm3]";
const std::string PrintStatistics::FilamentUsedCm3Mask = "; filament used [cm3] =";
const std::string PrintStatistics::FilamentUsedMm = "filament used [mm]";
const std::string PrintStatistics::FilamentUsedMmMask = "; filament used [mm] =";
const std::string PrintStatistics::FilamentCost = "filament cost";
const std::string PrintStatistics::FilamentCostMask = "; filament cost =";
const std::string PrintStatistics::TotalFilamentCost = "total filament cost";
const std::string PrintStatistics::TotalFilamentCostMask = "; total filament cost =";
const std::string PrintStatistics::TotalFilamentCostValueMask = "; total filament cost = %.2lf\n";
DynamicConfig PrintStatistics::config() const
{
DynamicConfig config;
@ -1670,8 +1695,8 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
}
ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh);
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
paths.push_back({ path });
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.

View File

@ -49,7 +49,7 @@
namespace Slic3r {
class GCode;
class GCodeGenerator;
class Layer;
class ModelObject;
class Print;
@ -87,7 +87,7 @@ enum PrintStep : unsigned int {
enum PrintObjectStep : unsigned int {
posSlice, posPerimeters, posPrepareInfill,
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount,
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters, posCount,
};
// A PrintRegion object represents a group of volumes to print
@ -396,6 +396,7 @@ private:
void generate_support_spots();
void generate_support_material();
void estimate_curled_extrusions();
void calculate_overhanging_perimeters();
void slice_volumes();
// Has any support (not counting the raft).
@ -548,6 +549,21 @@ struct PrintStatistics
filament_stats.clear();
printing_extruders.clear();
}
static const std::string FilamentUsedG;
static const std::string FilamentUsedGMask;
static const std::string TotalFilamentUsedG;
static const std::string TotalFilamentUsedGMask;
static const std::string TotalFilamentUsedGValueMask;
static const std::string FilamentUsedCm3;
static const std::string FilamentUsedCm3Mask;
static const std::string FilamentUsedMm;
static const std::string FilamentUsedMmMask;
static const std::string FilamentCost;
static const std::string FilamentCostMask;
static const std::string TotalFilamentCost;
static const std::string TotalFilamentCostMask;
static const std::string TotalFilamentCostValueMask;
};
using PrintObjectPtrs = std::vector<PrintObject*>;
@ -717,7 +733,7 @@ private:
Polygons m_sequential_print_clearance_contours;
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
friend class GCodeGenerator;
// To allow GCodeProcessor to emit warnings.
friend class GCodeProcessor;
// Allow PrintObject to access m_mutex and m_cancel_callback.

View File

@ -21,7 +21,7 @@ void PrintTryCancel::operator()() const
size_t PrintStateBase::g_last_timestamp = 0;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string & /* default_output_ext */) const
{
// get the first input file name
std::string input_file;
@ -54,7 +54,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
// get basename with and without suffix
const std::string input_filename = boost::filesystem::path(input_file).filename().string();
const std::string input_filename_base = input_filename.substr(0, input_filename.find_last_of("."));
config.set_key_value("input_filename", new ConfigOptionString(input_filename_base + default_ext));
// config.set_key_value("input_filename", new ConfigOptionString(input_filename_base + default_output_ext));
config.set_key_value("input_filename_base", new ConfigOptionString(input_filename_base));
}
}
@ -70,7 +70,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
PlaceholderParser::update_timestamp(cfg);
this->update_object_placeholders(cfg, default_ext);
if (! filename_base.empty()) {
cfg.set_key_value("input_filename", new ConfigOptionString(filename_base + default_ext));
// cfg.set_key_value("input_filename", new ConfigOptionString(filename_base + default_ext));
cfg.set_key_value("input_filename_base", new ConfigOptionString(filename_base));
}
try {

View File

@ -552,7 +552,7 @@ protected:
// To be called by this->output_filename() with the format string pulled from the configuration layer.
std::string output_filename(const std::string &format, const std::string &default_ext, const std::string &filename_base, const DynamicConfig *config_override = nullptr) const;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects.
void update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const;
void update_object_placeholders(DynamicConfig &config, const std::string &default_output_ext) const;
Model m_model;
DynamicPrintConfig m_full_print_config;

View File

@ -24,6 +24,7 @@
#include "I18N.hpp"
#include "SLA/SupportTree.hpp"
#include "GCode/Thumbnails.hpp"
#include <set>
#include <boost/algorithm/string/replace.hpp>
@ -55,6 +56,13 @@ static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }
static const t_config_enum_values s_keys_map_ArcFittingType {
{ "disabled", int(ArcFittingType::Disabled) },
{ "emit_center", int(ArcFittingType::EmitCenter) },
{ "emit_radius", int(ArcFittingType::EmitRadius) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ArcFittingType)
static t_config_enum_values s_keys_map_PrinterTechnology {
{ "FFF", ptFFF },
{ "SLA", ptSLA }
@ -299,12 +307,12 @@ void PrintConfigDef::init_common_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("thumbnails", coPoints);
def = this->add("thumbnails", coString);
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxYxEXT, XxYxEXT, ...\"");
def->mode = comExpert;
def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
def->set_default_value(new ConfigOptionString());
def = this->add("thumbnails_format", coEnum);
def->label = L("Format of G-code thumbnails");
@ -418,6 +426,27 @@ void PrintConfigDef::init_fff_params()
{
ConfigOptionDef* def;
def = this->add("arc_fitting", coEnum);
def->label = L("Arc fitting");
def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. "
"And the fitting tolerance is same with resolution");
def->set_enum<ArcFittingType>({
{ "disabled", "Disabled" },
{ "emit_center", "Enabled: G2/3 I J" },
{ "emit_radius", "Enabled: G2/3 R" }
});
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<ArcFittingType>(ArcFittingType::Disabled));
def = this->add("arc_fitting_tolerance", coFloatOrPercent);
def->label = L("Arc fitting tolerance");
def->sidetext = L("mm or %");
def->tooltip = L("When using the arc_fitting option, allow the curve to deviate a cetain % from the collection of strait paths.\n"
"Can be a mm value or a percentage of the current extrusion width.");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloatOrPercent(5, true));
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
const int max_temp = 1500;
def = this->add("avoid_crossing_curled_overhangs", coBool);
@ -1476,6 +1505,12 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionStrings());
def = this->add("gcode_binary", coBool);
def->label = L("Export as binary G-code");
def->tooltip = L("Exports G-code in binary format.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(0));
def = this->add("high_current_on_filament_swap", coBool);
def->label = L("High extruder current on filament swap");
def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange"
@ -3061,18 +3096,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("threads", coInt);
def->label = L("Threads");
def->tooltip = L("Threads are used to parallelize long-running tasks. Optimal threads number "
"is slightly above the number of available cores/processors.");
def->readonly = true;
def->min = 1;
{
int threads = (unsigned int)boost::thread::hardware_concurrency();
def->set_default_value(new ConfigOptionInt(threads > 0 ? threads : 2));
def->cli = ConfigOptionDef::nocli;
}
def = this->add("toolchange_gcode", coString);
def->label = L("Tool change G-code");
def->tooltip = L("This custom code is inserted before every toolchange. Placeholder variables for all PrusaSlicer settings "
@ -4349,6 +4372,35 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
// Don't convert single options here, implement such conversion in PrintConfigDef::handle_legacy() instead.
void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
{
if (config.has("thumbnails")) {
std::string extention;
if (config.has("thumbnails_format")) {
if (const ConfigOptionDef* opt = config.def()->get("thumbnails_format")) {
auto label = opt->enum_def->enum_to_label(config.option("thumbnails_format")->getInt());
if (label.has_value())
extention = *label;
}
}
std::string thumbnails_str = config.opt_string("thumbnails");
auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(thumbnails_str, extention);
if (errors != enum_bitmask<ThumbnailError>()) {
std::string error_str = "\n" + format("Invalid value provided for parameter %1%: %2%", "thumbnails", thumbnails_str);
error_str += GCodeThumbnails::get_error_string(errors);
throw BadOptionValueException(error_str);
}
if (!thumbnails_list.empty()) {
const auto& extentions = ConfigOptionEnum<GCodeThumbnailsFormat>::get_enum_names();
thumbnails_str.clear();
for (const auto& [ext, size] : thumbnails_list)
thumbnails_str += format("%1%x%2%/%3%, ", size.x(), size.y(), extentions[int(ext)]);
thumbnails_str.resize(thumbnails_str.length() - 2);
config.set_key_value("thumbnails", new ConfigOptionString(thumbnails_str));
}
}
}
const PrintConfigDef print_config_def;
@ -4991,6 +5043,309 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::
}
}
// SlicingStatesConfigDefs
ReadOnlySlicingStatesConfigDef::ReadOnlySlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("zhop", coFloat);
def->label = L("Current z-hop");
def->tooltip = L("Contains z-hop present at the beginning of the custom G-code block.");
}
ReadWriteSlicingStatesConfigDef::ReadWriteSlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("position", coFloats);
def->label = L("Position");
def->tooltip = L("Position of the extruder at the beginning of the custom G-code block. If the custom G-code travels somewhere else, "
"it should write to this variable so PrusaSlicer knows where it travels from when it gets control back.");
def = this->add("e_retracted", coFloats);
def->label = L("Retraction");
def->tooltip = L("Retraction state at the beginning of the custom G-code block. If the custom G-code moves the extruder axis, "
"it should write to this variable so PrusaSlicer deretracts correctly when it gets control back.");
def = this->add("e_restart_extra", coFloats);
def->label = L("Extra deretraction");
def->tooltip = L("Currently planned extra extruder priming after deretraction.");
def = this->add("e_position", coFloats);
def->label = L("Absolute E position");
def->tooltip = L("Current position of the extruder axis. Only used with absolute extruder addressing.");
}
OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("current_extruder", coInt);
def->label = L("Current extruder");
def->tooltip = L("Zero-based index of currently used extruder.");
def = this->add("current_object_idx", coInt);
def->label = L("Current object index");
def->tooltip = L("Specific for sequential printing. Zero-based index of currently printed object.");
def = this->add("has_single_extruder_multi_material_priming", coBool);
def->label = L("Has single extruder MM priming");
def->tooltip = L("Are the extra multi-material priming regions used in this print?");
def = this->add("has_wipe_tower", coBool);
def->label = L("Has wipe tower");
def->tooltip = L("Whether or not wipe tower is being generated in the print.");
def = this->add("initial_extruder", coInt);
def->label = L("Initial extruder");
def->tooltip = L("Zero-based index of the first extruder used in the print. Same as initial_tool.");
def = this->add("initial_filament_type", coString);
def->label = L("Initial filament type");
def->tooltip = L("String containing filament type of the first used extruder.");
def = this->add("initial_tool", coInt);
def->label = L("Initial tool");
def->tooltip = L("Zero-based index of the first extruder used in the print. Same as initial_extruder.");
def = this->add("is_extruder_used", coBools);
def->label = L("Is extruder used?");
def->tooltip = L("Vector of bools stating whether a given extruder is used in the print.");
}
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
{
ConfigOptionDef* def;
def = this->add("extruded_volume", coFloats);
def->label = L("Volume per extruder");
def->tooltip = L("Total filament volume extruded per extruder during the entire print.");
def = this->add("normal_print_time", coString);
def->label = L("Print time (normal mode)");
def->tooltip = L("Estimated print time when printed in normal mode (i.e. not in silent mode). Same as print_time.");
def = this->add("num_printing_extruders", coInt);
def->label = L("Number of printing extruders");
def->tooltip = L("Number of extruders used during the print.");
def = this->add("print_time", coString);
def->label = L("Print time (normal mode)");
def->tooltip = L("Estimated print time when printed in normal mode (i.e. not in silent mode). Same as normal_print_time.");
def = this->add("printing_filament_types", coString);
def->label = L("Used filament types");
def->tooltip = L("Comma-separated list of all filament types used during the print.");
def = this->add("silent_print_time", coString);
def->label = L("Print time (silent mode)");
def->tooltip = L("Estimated print time when printed in silent mode.");
def = this->add("total_cost", coFloat);
def->label = L("Total cost");
def->tooltip = L("Total cost of all material used in the print. Calculated from filament_cost value in Filament Settings.");
def = this->add("total_weight", coFloat);
def->label = L("Total weight");
def->tooltip = L("Total weight of the print. Calculated from filament_density value in Filament Settings.");
def = this->add("total_wipe_tower_cost", coFloat);
def->label = L("Total wipe tower cost");
def->tooltip = L("Total cost of the material wasted on the wipe tower. Calculated from filament_cost value in Filament Settings.");
def = this->add("total_wipe_tower_filament", coFloat);
def->label = L("Wipe tower volume");
def->tooltip = L("Total filament volume extruded on the wipe tower.");
def = this->add("used_filament", coFloat);
def->label = L("Used filament");
def->tooltip = L("Total length of filament used in the print.");
def = this->add("total_toolchanges", coInt);
def->label = L("Total toolchanges");
def->tooltip = L("Number of toolchanges during the print.");
def = this->add("extruded_volume_total", coFloat);
def->label = L("Total volume");
def->tooltip = L("Total volume of filament used during the entire print.");
def = this->add("extruded_weight", coFloats);
def->label = L("Weight per extruder");
def->tooltip = L("Weight per extruder extruded during the entire print. Calculated from filament_density value in Filament Settings.");
def = this->add("extruded_weight_total", coFloat);
def->label = L("Total weight");
def->tooltip = L("Total weight of the print. Calculated from filament_density value in Filament Settings.");
def = this->add("total_layer_count", coInt);
def->label = L("Total layer count");
def->tooltip = L("Number of layers in the entire print.");
}
ObjectsInfoConfigDef::ObjectsInfoConfigDef()
{
ConfigOptionDef* def;
def = this->add("num_objects", coInt);
def->label = L("Number of objects");
def->tooltip = L("Total number of objects in the print.");
def = this->add("num_instances", coInt);
def->label = L("Number of instances");
def->tooltip = L("Total number of object instances in the print, summed over all objects.");
def = this->add("scale", coStrings);
def->label = L("Scale per object");
def->tooltip = L("Contains a string with the information about what scaling was applied to the individual objects. "
"Indexing of the objects is zero-based (first object has index 0).\n"
"Example: 'x:100% y:50% z:100'.");
def = this->add("input_filename_base", coString);
def->label = L("Input filename without extension");
def->tooltip = L("Source filename of the first object, without extension.");
}
DimensionsConfigDef::DimensionsConfigDef()
{
ConfigOptionDef* def;
const std::string point_tooltip = L("The vector has two elements: x and y coordinate of the point. Values in mm.");
const std::string bb_size_tooltip = L("The vector has two elements: x and y dimension of the bounding box. Values in mm.");
def = this->add("first_layer_print_convex_hull", coPoints);
def->label = L("First layer convex hull");
def->tooltip = L("Vector of points of the first layer convex hull. Each element has the following format: "
"'[x, y]' (x and y are floating-point numbers in mm).");
def = this->add("first_layer_print_min", coFloats);
def->label = L("Bottom-left corner of first layer bounding box");
def->tooltip = point_tooltip;
def = this->add("first_layer_print_max", coFloats);
def->label = L("Top-right corner of first layer bounding box");
def->tooltip = point_tooltip;
def = this->add("first_layer_print_size", coFloats);
def->label = L("Size of the first layer bounding box");
def->tooltip = bb_size_tooltip;
def = this->add("print_bed_min", coFloats);
def->label = L("Bottom-left corner of print bed bounding box");
def->tooltip = point_tooltip;
def = this->add("print_bed_max", coFloats);
def->label = L("Top-right corner of print bed bounding box");
def->tooltip = point_tooltip;
def = this->add("print_bed_size", coFloats);
def->label = L("Size of the print bed bounding box");
def->tooltip = bb_size_tooltip;
}
TimestampsConfigDef::TimestampsConfigDef()
{
ConfigOptionDef* def;
def = this->add("timestamp", coString);
def->label = L("Timestamp");
def->tooltip = L("String containing current time in yyyyMMdd-hhmmss format.");
def = this->add("year", coInt);
def->label = L("Year");
def = this->add("month", coInt);
def->label = L("Month");
def = this->add("day", coInt);
def->label = L("Day");
def = this->add("hour", coInt);
def->label = L("Hour");
def = this->add("minute", coInt);
def->label = L("Minute");
def = this->add("second", coInt);
def->label = L("Second");
}
OtherPresetsConfigDef::OtherPresetsConfigDef()
{
ConfigOptionDef* def;
def = this->add("num_extruders", coInt);
def->label = L("Number of extruders");
def->tooltip = L("Total number of extruders, regardless of whether they are used in the current print.");
def = this->add("print_preset", coString);
def->label = L("Print preset name");
def->tooltip = L("Name of the print preset used for slicing.");
def = this->add("filament_preset", coStrings);
def->label = L("Filament preset name");
def->tooltip = L("Names of the filament presets used for slicing. The variable is a vector "
"containing one name for each extruder.");
def = this->add("printer_preset", coString);
def->label = L("Printer preset name");
def->tooltip = L("Name of the printer preset used for slicing.");
def = this->add("physical_printer_preset", coString);
def->label = L("Physical printer name");
def->tooltip = L("Name of the physical printer used for slicing.");
}
static std::map<t_custom_gcode_key, t_config_option_keys> s_CustomGcodeSpecificPlaceholders{
{"start_filament_gcode", {"layer_num", "layer_z", "max_layer_z", "filament_extruder_id"}},
{"end_filament_gcode", {"layer_num", "layer_z", "max_layer_z", "filament_extruder_id"}},
{"end_gcode", {"layer_num", "layer_z", "max_layer_z", "filament_extruder_id"}},
{"before_layer_gcode", {"layer_num", "layer_z", "max_layer_z"}},
{"layer_gcode", {"layer_num", "layer_z", "max_layer_z"}},
{"toolchange_gcode", {"layer_num", "layer_z", "max_layer_z", "previous_extruder", "next_extruder", "toolchange_z"}},
};
const std::map<t_custom_gcode_key, t_config_option_keys>& custom_gcode_specific_placeholders()
{
return s_CustomGcodeSpecificPlaceholders;
}
CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef()
{
ConfigOptionDef* def;
def = this->add("layer_num", coInt);
def->label = L("Layer number");
def->tooltip = L("Zero-based index of the current layer (i.e. first layer is number 0).");
def = this->add("layer_z", coFloat);
def->label = L("Layer z");
def->tooltip = L("Height of the current layer above the print bed, measured to the top of the layer.");
def = this->add("max_layer_z", coFloat);
def->label = L("Maximal layer z");
def->tooltip = L("Height of the last layer above the print bed.");
def = this->add("filament_extruder_id", coInt);
def->label = L("Current extruder index");
def->tooltip = L("Zero-based index of currently used extruder (i.e. first extruder has index 0).");
def = this->add("previous_extruder", coInt);
def->label = L("Previous extruder");
def->tooltip = L("Index of the extruder that is being unloaded. The index is zero based (first extruder has index 0).");
def = this->add("next_extruder", coInt);
def->label = L("Next extruder");
def->tooltip = L("Index of the extruder that is being loaded. The index is zero based (first extruder has index 0).");
def = this->add("toolchange_z", coFloat);
def->label = L("Toolchange z");
def->tooltip = L("Height above the print bed when the toolchange takes place. Usually the same as layer_z, but can be different.");
}
const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def;
uint64_t ModelConfig::s_last_timestamp = 1;
static Points to_points(const std::vector<Vec2d> &dpts)

View File

@ -47,6 +47,12 @@
namespace Slic3r {
enum class ArcFittingType {
Disabled,
EmitCenter,
EmitRadius,
};
enum GCodeFlavor : unsigned char {
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit,
gcfSmoothie, gcfNoExtrusion,
@ -158,6 +164,7 @@ enum class GCodeThumbnailsFormat {
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage)
@ -688,6 +695,8 @@ PRINT_CONFIG_CLASS_DEFINE(
PRINT_CONFIG_CLASS_DEFINE(
GCodeConfig,
((ConfigOptionEnum<ArcFittingType>, arc_fitting))
((ConfigOptionFloatOrPercent, arc_fitting_tolerance))
((ConfigOptionBool, autoemit_temperature_commands))
((ConfigOptionString, before_layer_gcode))
((ConfigOptionString, between_objects_gcode))
@ -727,6 +736,7 @@ PRINT_CONFIG_CLASS_DEFINE(
// i - case insensitive
// w - whole word
((ConfigOptionStrings, gcode_substitutions))
((ConfigOptionBool, gcode_binary))
((ConfigOptionString, layer_gcode))
((ConfigOptionFloat, max_print_speed))
((ConfigOptionFloat, max_volumetric_speed))
@ -843,7 +853,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionInts, temperature))
((ConfigOptionInt, threads))
((ConfigOptionPoints, thumbnails))
((ConfigOptionString, thumbnails))
((ConfigOptionEnum<GCodeThumbnailsFormat>, thumbnails_format))
((ConfigOptionFloat, top_solid_infill_acceleration))
((ConfigOptionFloat, travel_acceleration))
@ -1180,6 +1190,68 @@ public:
CLIMiscConfigDef();
};
typedef std::string t_custom_gcode_key;
// This map containes list of specific placeholders for each custom G-code, if any exist
const std::map<t_custom_gcode_key, t_config_option_keys>& custom_gcode_specific_placeholders();
// Next classes define placeholders used by GUI::EditGCodeDialog.
class ReadOnlySlicingStatesConfigDef : public ConfigDef
{
public:
ReadOnlySlicingStatesConfigDef();
};
class ReadWriteSlicingStatesConfigDef : public ConfigDef
{
public:
ReadWriteSlicingStatesConfigDef();
};
class OtherSlicingStatesConfigDef : public ConfigDef
{
public:
OtherSlicingStatesConfigDef();
};
class PrintStatisticsConfigDef : public ConfigDef
{
public:
PrintStatisticsConfigDef();
};
class ObjectsInfoConfigDef : public ConfigDef
{
public:
ObjectsInfoConfigDef();
};
class DimensionsConfigDef : public ConfigDef
{
public:
DimensionsConfigDef();
};
class TimestampsConfigDef : public ConfigDef
{
public:
TimestampsConfigDef();
};
class OtherPresetsConfigDef : public ConfigDef
{
public:
OtherPresetsConfigDef();
};
// This classes defines all custom G-code specific placeholders.
class CustomGcodeSpecificConfigDef : public ConfigDef
{
public:
CustomGcodeSpecificConfigDef();
};
extern const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def;
// This class defines the command line options representing actions.
extern const CLIActionsConfigDef cli_actions_config_def;

View File

@ -12,7 +12,9 @@
#include "ExPolygon.hpp"
#include "Exception.hpp"
#include "Flow.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include "KDTreeIndirect.hpp"
#include "Line.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@ -542,6 +544,65 @@ void PrintObject::estimate_curled_extrusions()
}
}
void PrintObject::calculate_overhanging_perimeters()
{
if (this->set_started(posCalculateOverhangingPerimeters)) {
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - start";
m_print->set_status(89, _u8L("Calculating overhanging perimeters"));
std::vector<unsigned int> extruders;
std::unordered_set<const PrintRegion *> regions_with_dynamic_speeds;
for (const PrintRegion *pr : this->print()->m_print_regions) {
if (pr->config().enable_dynamic_overhang_speeds.getBool()) {
regions_with_dynamic_speeds.insert(pr);
}
extruders.clear();
pr->collect_object_printing_extruders(*this->print(), extruders);
auto cfg = this->print()->config();
if (std::any_of(extruders.begin(), extruders.end(),
[&cfg](unsigned int extruder_id) { return cfg.enable_dynamic_fan_speeds.get_at(extruder_id); })) {
regions_with_dynamic_speeds.insert(pr);
}
}
if (!regions_with_dynamic_speeds.empty()) {
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<CurledLine>> curled_lines;
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<Linef>> unscaled_polygons_lines;
for (const Layer *l : this->layers()) {
curled_lines[l->id()] = AABBTreeLines::LinesDistancer<CurledLine>{l->curled_lines};
unscaled_polygons_lines[l->id()] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(l->lslices)};
}
curled_lines[size_t(-1)] = {};
unscaled_polygons_lines[size_t(-1)] = {};
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this, &curled_lines, &unscaled_polygons_lines,
&regions_with_dynamic_speeds](
const tbb::blocked_range<size_t> &range) {
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
auto l = m_layers[layer_idx];
if (l->id() == 0) { // first layer, do not split
continue;
}
for (LayerRegion *layer_region : l->regions()) {
if (regions_with_dynamic_speeds.find(layer_region->m_region) == regions_with_dynamic_speeds.end()) {
continue;
}
size_t prev_layer_id = l->lower_layer ? l->lower_layer->id() : size_t(-1);
layer_region->m_perimeters =
ExtrusionProcessor::calculate_and_split_overhanging_extrusions(&layer_region->m_perimeters,
unscaled_polygons_lines[prev_layer_id],
curled_lines[l->id()]);
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - end";
}
this->set_done(posCalculateOverhangingPerimeters);
}
}
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
{
@ -653,7 +714,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "first_layer_extrusion_width"
|| opt_key == "perimeter_extrusion_width"
|| opt_key == "infill_overlap"
|| opt_key == "external_perimeters_first") {
|| opt_key == "external_perimeters_first"
|| opt_key == "arc_fitting"
|| opt_key == "arc_fitting_tolerance") {
steps.emplace_back(posPerimeters);
} else if (
opt_key == "gap_fill_enabled"
@ -854,7 +917,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps
if (step == posPerimeters) {
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions });
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posPrepareInfill) {
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch});
@ -863,7 +926,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posSlice) {
invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch,
posSupportMaterial, posEstimateCurledExtrusions});
posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters});
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
m_slicing_params.valid = false;
} else if (step == posSupportMaterial) {
@ -1557,7 +1620,7 @@ void PrintObject::discover_vertical_shells()
to_polygons(m_layers[idx_layer + 1]->lslices) :
Polygons{};
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
internal_volume = closing(polygonsInternal, float(SCALED_EPSILON));
}
// The regularization operation may cause scattered tiny drops on the smooth parts of the model, filter them out

View File

@ -1010,16 +1010,20 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc
return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
}
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &entities, const Point *start_near, const bool reversed)
{
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
auto segment_end_point = [&entities, reversed](size_t idx, bool first_point) -> const Point& { return first_point == reversed ? entities[idx]->last_point() : entities[idx]->first_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(
segment_end_point, could_reverse, entities.size(), start_near);
for (std::pair<size_t, bool> &segment : out) {
ExtrusionEntity *ee = entities[segment.first];
if (ee->is_loop())
// Ignore reversals for loops, as the start point equals the end point.
segment.second = false;
else if (reversed)
// Input was already reversed.
segment.second = ! segment.second;
// Is can_reverse() respected by the reversals?
assert(ee->can_reverse() || ! segment.second);
}
@ -1045,6 +1049,33 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
}
ExtrusionEntityReferences chain_extrusion_references(const std::vector<ExtrusionEntity*> &entities, const Point *start_near, const bool reversed)
{
const std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(entities, start_near, reversed);
ExtrusionEntityReferences out;
out.reserve(chain.size());
for (const std::pair<size_t, bool> &idx : chain) {
assert(entities[idx.first] != nullptr);
out.push_back({ *entities[idx.first], idx.second });
}
return out;
}
ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near, const bool reversed)
{
if (eec.no_sort) {
ExtrusionEntityReferences out;
out.reserve(eec.entities.size());
for (const ExtrusionEntity *ee : eec.entities) {
assert(ee != nullptr);
// Never reverse a loop.
out.push_back({ *ee, ! ee->is_loop() && reversed });
}
return out;
} else
return chain_extrusion_references(eec.entities, start_near, reversed);
}
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };

View File

@ -22,13 +22,25 @@ namespace Slic3r {
class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
// Used by chain_expolygons()
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
// Used to give layer islands a print order.
std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag.
// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed once already.
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr, const bool reversed = false);
// Reorder & reverse extrusion entities in place based on the "chain" ordering.
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
// Reorder & reverse extrusion entities in place.
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag.
// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed.
ExtrusionEntityReferences chain_extrusion_references(const std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr, const bool reversed = false);
// The same as above, respect eec.no_sort flag.
ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near = nullptr, const bool reversed = false);
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);

View File

@ -489,8 +489,7 @@ static inline void fill_expolygon_generate_paths(
extrusion_entities_append_paths(
dst,
std::move(polylines),
role,
flow.mm3_per_mm(), flow.width(), flow.height());
{ role, flow });
}
static inline void fill_expolygons_generate_paths(
@ -648,7 +647,7 @@ static inline void tree_supports_generate_paths(
ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width());
if (level2.size() == 1) {
Polylines polylines;
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow },
// Disable reversal of the path, always start with the anchor, always print CCW.
false);
expoly = level2.front();
@ -754,7 +753,7 @@ static inline void tree_supports_generate_paths(
}
ExtrusionEntitiesPtr &out = eec ? eec->entities : dst;
extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
extrusion_entities_append_paths(out, std::move(polylines), { ExtrusionRole::SupportMaterial, flow },
// Disable reversal of the path, always start with the anchor, always print CCW.
false);
if (eec) {
@ -798,7 +797,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
eec->no_sort = true;
}
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow });
// Fill in the rest.
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
if (no_sort && ! eec->empty())
@ -1110,7 +1109,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact
extrusion_entities_append_paths(
top_contact_layer.extrusions,
std::move(loop_lines),
ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
{ ExtrusionRole::SupportMaterialInterface, flow });
}
#ifdef SLIC3R_DEBUG
@ -1152,24 +1151,19 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front());
assert(extrusion_path_template != nullptr);
ExtrusionRole extrusion_role = extrusion_path_template->role();
float extrusion_width = extrusion_path_template->width;
float extrusion_width = extrusion_path_template->width();
struct ExtrusionPathFragment
{
ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {};
ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {};
ExtrusionFlow flow;
Polylines polylines;
double mm3_per_mm;
float width;
float height;
};
// Split the extrusions by the overlapping layers, reduce their extrusion rate.
// The last path_fragment is from this_layer.
std::vector<ExtrusionPathFragment> path_fragments(
n_overlapping_layers + 1,
ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height));
ExtrusionPathFragment{ extrusion_path_template->attributes() });
// Don't use it, it will be released.
extrusion_path_template = nullptr;
@ -1246,8 +1240,8 @@ static void modulate_extrusion_by_overlapping_layers(
path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming);
// Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
assert(this_layer.print_z > overlapping_layer.print_z);
frag.height = float(this_layer.print_z - overlapping_layer.print_z);
frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
frag.flow.height = float(this_layer.print_z - overlapping_layer.print_z);
frag.flow.mm3_per_mm = Flow(frag.flow.width, frag.flow.height, -1.f).mm3_per_mm();
#ifdef SLIC3R_DEBUG
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
#endif /* SLIC3R_DEBUG */
@ -1330,15 +1324,14 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back();
if (path != nullptr) {
// Verify whether the path is compatible with the current fragment.
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm);
if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) {
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm);
if (path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm)
path = nullptr;
}
// Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer.
}
if (path == nullptr) {
// Allocate a new path.
multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height));
multipath.paths.emplace_back(ExtrusionAttributes{ extrusion_role, frag.flow });
path = &multipath.paths.back();
}
// The Clipper library may flip the order of the clipped polylines arbitrarily.
@ -1373,8 +1366,8 @@ static void modulate_extrusion_by_overlapping_layers(
}
// If there are any non-consumed fragments, add them separately.
//FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected.
for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment)
extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height);
for (ExtrusionPathFragment &fragment : path_fragments)
extrusion_entities_append_paths(extrusions_in_out, std::move(fragment.polylines), { extrusion_role, fragment.flow });
}
// Support layer that is covered by some form of dense interface.
@ -1736,7 +1729,8 @@ void generate_support_toolpaths(
// Filler and its parameters
filler, float(density),
// Extrusion parameters
ExtrusionRole::SupportMaterialInterface, interface_flow);
interface_as_base ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface,
interface_flow);
}
};
const bool top_interfaces = config.support_material_interface_layers.value != 0;

View File

@ -1035,7 +1035,7 @@ namespace SupportMaterialInternal {
assert(expansion_scaled >= 0.f);
for (const ExtrusionPath &ep : loop.paths)
if (ep.role() == ExtrusionRole::OverhangPerimeter && ! ep.polyline.empty()) {
float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled;
float exp = 0.5f * (float)scale_(ep.width()) + expansion_scaled;
if (ep.is_closed()) {
if (ep.size() >= 3) {
// This is a complete loop.

View File

@ -105,6 +105,7 @@ static inline void validate_range(const LineInformations &lines)
validate_range(l);
}
/*
static inline void check_self_intersections(const Polygons &polygons, const std::string_view message)
{
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
@ -112,6 +113,7 @@ static inline void check_self_intersections(const Polygons &polygons, const std:
::MessageBoxA(nullptr, (std::string("TreeSupport infill self intersections: ") + std::string(message)).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
}
*/
static inline void check_self_intersections(const ExPolygon &expoly, const std::string_view message)
{
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32

View File

@ -332,10 +332,10 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
return {};
}
const float flow_width = get_flow_width(layer_region, entity->role());
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points,
prev_layer_boundary, flow_width,
params.bridge_distance);
const float flow_width = get_flow_width(layer_region, entity->role());
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
ExtrusionProcessor::estimate_points_properties<true, true, true, true>(entity->as_polyline().points, prev_layer_boundary,
flow_width, params.bridge_distance);
std::vector<ExtrusionLine> lines_out;
lines_out.reserve(annotated_points.size());
@ -344,8 +344,8 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
std::optional<Vec2d> bridging_dir{};
for (size_t i = 0; i < annotated_points.size(); ++i) {
ExtendedPoint &curr_point = annotated_points[i];
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
SupportPointCause::LongBridge;
@ -387,19 +387,19 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
const float flow_width = get_flow_width(layer_region, entity->role());
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
prev_layer_lines, flow_width,
params.bridge_distance);
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
ExtrusionProcessor::estimate_points_properties<true, true, false, false>(entity->as_polyline().points, prev_layer_lines,
flow_width, params.bridge_distance);
std::vector<ExtrusionLine> lines_out;
lines_out.reserve(annotated_points.size());
float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) :
0.0f;
for (size_t i = 0; i < annotated_points.size(); ++i) {
ExtendedPoint &curr_point = annotated_points[i];
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
float line_len = (prev_point.position - curr_point.position).norm();
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
float line_len = (prev_point.position - curr_point.position).norm();
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
Vec2f middle = 0.5 * (line_out.a + line_out.b);
auto [middle_distance, bottom_line_idx, x] = prev_layer_lines.distance_from_lines_extra<false>(middle);
@ -1112,12 +1112,13 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width,
Polygon pol(pl.points);
pol.make_counter_clockwise();
auto annotated_points = estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines, flow_width);
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines,
flow_width);
for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion};
Vec2f middle = 0.5 * (line_out.a + line_out.b);
@ -1187,11 +1188,13 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
Points extrusion_pts;
extrusion->collect_points(extrusion_pts);
float flow_width = get_flow_width(layer_region, extrusion->role());
auto annotated_points = estimate_points_properties<true, true, false, false>(extrusion_pts, prev_layer_lines, flow_width,
params.bridge_distance);
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(extrusion_pts,
prev_layer_lines,
flow_width,
params.bridge_distance);
for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i];
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion};
@ -1201,7 +1204,8 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
prev_layer_lines.get_line(bottom_line_idx);
// correctify the distance sign using slice polygons
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f :
1.0f;
line_out.curled_up_height = estimate_curled_up_height(middle_distance * sign, 0.5 * (a.curvature + b.curvature),
l->height, flow_width, bottom_line.curled_up_height, params);

View File

@ -58,4 +58,7 @@
// Enable OpenGL debug messages using debug context
#define ENABLE_OPENGL_DEBUG_OPTION (1 && ENABLE_GL_CORE_PROFILE)
// Enable imgui dialog which allows to set the parameters used to export binarized gcode
#define ENABLE_BINARIZED_GCODE_DEBUG_WINDOW 0
#endif // _prusaslicer_technologies_h_

View File

@ -1943,7 +1943,7 @@ std::vector<ExPolygons> slice_mesh_ex(
&expolygons);
#if 0
//#ifndef _NDEBUG
//#ifndef NDEBUG
// Test whether the expolygons in a single layer overlap.
for (size_t i = 0; i < expolygons.size(); ++ i)
for (size_t j = i + 1; j < expolygons.size(); ++ j) {
@ -1952,7 +1952,7 @@ std::vector<ExPolygons> slice_mesh_ex(
}
#endif
#if 0
//#ifndef _NDEBUG
//#ifndef NDEBUG
for (const ExPolygon &ex : expolygons) {
assert(! has_duplicate_points(ex.contour));
for (const Polygon &hole : ex.holes)
@ -1960,7 +1960,7 @@ std::vector<ExPolygons> slice_mesh_ex(
assert(! has_duplicate_points(ex));
}
assert(!has_duplicate_points(expolygons));
#endif // _NDEBUG
#endif // NDEBUG
//FIXME simplify
if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour)
keep_largest_contour_only(expolygons);
@ -1972,7 +1972,7 @@ std::vector<ExPolygons> slice_mesh_ex(
expolygons = std::move(simplified);
}
#if 0
//#ifndef _NDEBUG
//#ifndef NDEBUG
for (const ExPolygon &ex : expolygons) {
assert(! has_duplicate_points(ex.contour));
for (const Polygon &hole : ex.holes)
@ -1980,7 +1980,7 @@ std::vector<ExPolygons> slice_mesh_ex(
assert(! has_duplicate_points(ex));
}
assert(! has_duplicate_points(expolygons));
#endif // _NDEBUG
#endif // NDEBUG
}
});
// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end";

View File

@ -58,6 +58,11 @@ const std::string& sys_shapes_dir();
// Return a full path to the custom shapes gallery directory.
std::string custom_shapes_dir();
// Set a path with shapes gallery files.
void set_custom_gcodes_dir(const std::string &path);
// Return a full path to the system shapes gallery directory.
const std::string& custom_gcodes_dir();
// Set a path with preset files.
void set_data_dir(const std::string &path);
// Return a full path to the GUI resource files.

View File

@ -199,6 +199,18 @@ const std::string& sys_shapes_dir()
return g_sys_shapes_dir;
}
static std::string g_custom_gcodes_dir;
void set_custom_gcodes_dir(const std::string &dir)
{
g_custom_gcodes_dir = dir;
}
const std::string& custom_gcodes_dir()
{
return g_custom_gcodes_dir;
}
// Translate function callback, to call wxWidgets translate function to convert non-localized UTF8 string to a localized one.
Slic3r::I18N::translate_fn_type Slic3r::I18N::translate_fn = nullptr;
@ -786,8 +798,9 @@ bool is_idx_file(const boost::filesystem::directory_entry &dir_entry)
bool is_gcode_file(const std::string &path)
{
return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") ||
boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc");
return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") ||
boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc") ||
boost::iends_with(path, ".bgcode") || boost::iends_with(path, ".bgc");
}
bool is_img_file(const std::string &path)

View File

@ -120,6 +120,8 @@ set(SLIC3R_GUI_SOURCES
GUI/PresetComboBoxes.cpp
GUI/BitmapComboBox.hpp
GUI/BitmapComboBox.cpp
GUI/EditGCodeDialog.hpp
GUI/EditGCodeDialog.cpp
GUI/SavePresetDialog.hpp
GUI/SavePresetDialog.cpp
GUI/PhysicalPrinterDialog.hpp

View File

@ -1453,8 +1453,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, flo
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines = polyline.lines();
std::vector<double> widths(lines.size(), extrusion_path.width);
std::vector<double> heights(lines.size(), extrusion_path.height);
std::vector<double> widths(lines.size(), extrusion_path.width());
std::vector<double> heights(lines.size(), extrusion_path.height());
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
}
@ -1470,8 +1470,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, flo
polyline.translate(copy);
const Lines lines_this = polyline.lines();
append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
}
thick_lines_to_verts(lines, widths, heights, true, print_z, geometry);
}
@ -1488,8 +1488,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_mult
polyline.translate(copy);
const Lines lines_this = polyline.lines();
append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
}
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
}

View File

@ -345,6 +345,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("min_feature_size", have_arachne);
toggle_field("min_bead_width", have_arachne);
toggle_field("thin_walls", !have_arachne);
bool has_arc_fitting = config->opt_enum<ArcFittingType>("arc_fitting") != ArcFittingType::Disabled;
toggle_field("arc_fitting_tolerance", has_arc_fitting);
}
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)

View File

@ -786,6 +786,16 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
reload_presets();
set_compatible_printers_html_window(std::vector<std::string>(), false);
}
void PageMaterials::check_and_update_presets(bool force_reload_presets /*= false*/)
{
if (presets_loaded)
return;
wizard_p()->update_materials(materials->technology);
if (force_reload_presets)
reload_presets();
}
void PageMaterials::on_paint()
{
}
@ -1299,10 +1309,7 @@ void PageMaterials::clear()
void PageMaterials::on_activate()
{
if (! presets_loaded) {
wizard_p()->update_materials(materials->technology);
reload_presets();
}
check_and_update_presets(true);
first_paint = true;
}
@ -2369,7 +2376,7 @@ void ConfigWizard::priv::load_pages()
return a.first < b.first;
});
for (const std::pair<std::wstring, std::string> v : sorted_vendors) {
for (const std::pair<std::wstring, std::string> &v : sorted_vendors) {
const auto& pages = pages_3rdparty.find(v.second);
if (pages == pages_3rdparty.end())
continue; // Should not happen
@ -2507,7 +2514,7 @@ void ConfigWizard::priv::load_vendors()
}
}
appconfig_new.set_section(section_name, section_new);
};
}
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
@ -2589,78 +2596,37 @@ void ConfigWizard::priv::set_run_reason(RunReason run_reason)
void ConfigWizard::priv::update_materials(Technology technology)
{
auto add_material = [](Materials& materials, PresetAliases& aliases, const Preset& preset, const Preset* printer = nullptr) {
if (!materials.containts(&preset)) {
materials.push(&preset);
if (!preset.alias.empty())
aliases[preset.alias].emplace(&preset);
}
if (printer) {
materials.add_printer(printer);
materials.compatibility_counter[preset.alias]++;
}
};
if ((any_fff_selected || custom_printer_in_bundle || custom_printer_selected) && (technology & T_FFF)) {
filaments.clear();
aliases_fff.clear();
// Iterate filaments in all bundles
for (const auto &pair : bundles) {
for (const auto &filament : pair.second.preset_bundle->filaments) {
// Check if filament is already added
if (filaments.containts(&filament))
continue;
for (const auto &[name, bundle] : bundles) {
for (const auto &filament : bundle.preset_bundle->filaments) {
// Iterate printers in all bundles
for (const auto &printer : pair.second.preset_bundle->printers) {
for (const auto &printer : bundle.preset_bundle->printers) {
if (!printer.is_visible || printer.printer_technology() != ptFFF)
continue;
// Filter out inapplicable printers
if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
if (!filaments.containts(&filament)) {
filaments.push(&filament);
if (!filament.alias.empty())
aliases_fff[filament.alias].insert(filament.name);
}
filaments.add_printer(&printer);
}
if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor)))
add_material(filaments, aliases_fff, filament, &printer);
}
// template filament bundle has no printers - filament would be never added
if(pair.second.vendor_profile&& pair.second.vendor_profile->templates_profile && pair.second.preset_bundle->printers.begin() == pair.second.preset_bundle->printers.end())
{
if (!filaments.containts(&filament)) {
filaments.push(&filament);
if (!filament.alias.empty())
aliases_fff[filament.alias].insert(filament.name);
}
}
if(bundle.vendor_profile && bundle.vendor_profile->templates_profile && bundle.preset_bundle->printers.begin() == bundle.preset_bundle->printers.end())
add_material(filaments, aliases_fff, filament);
}
}
// count compatible printers
for (const auto& preset : filaments.presets) {
// skip template filaments
if (preset->vendor && preset->vendor->templates_profile)
continue;
const auto filter = [preset](const std::pair<std::string, size_t> element) {
return preset->alias == element.first;
};
if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) {
continue;
}
// find all aliases (except templates)
std::vector<size_t> idx_with_same_alias;
for (size_t i = 0; i < filaments.presets.size(); ++i) {
if (preset->alias == filaments.presets[i]->alias && ((filaments.presets[i]->vendor && !filaments.presets[i]->vendor->templates_profile) || !filaments.presets[i]->vendor))
idx_with_same_alias.push_back(i);
}
// check compatibility with each printer
size_t counter = 0;
for (const auto& printer : filaments.printers) {
if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF)
continue;
bool compatible = false;
// Test other materials with same alias
for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]);
const Preset& prntr = *printer;
if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
compatible = true;
break;
}
}
if (compatible)
counter++;
}
filaments.compatibility_counter.emplace_back(preset->alias, counter);
}
}
if (any_sla_selected && (technology & T_SLA)) {
@ -2668,62 +2634,20 @@ void ConfigWizard::priv::update_materials(Technology technology)
aliases_sla.clear();
// Iterate SLA materials in all bundles
for (const auto &pair : bundles) {
for (const auto &material : pair.second.preset_bundle->sla_materials) {
// Check if material is already added
if (sla_materials.containts(&material))
continue;
for (const auto& [name, bundle] : bundles) {
for (const auto &material : bundle.preset_bundle->sla_materials) {
// Iterate printers in all bundles
// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
for (const auto& printer : pair.second.preset_bundle->printers) {
for (const auto& printer : bundle.preset_bundle->printers) {
if(!printer.is_visible || printer.printer_technology() != ptSLA)
continue;
// Filter out inapplicable printers
if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr)))
// Check if material is already added
if(!sla_materials.containts(&material)) {
sla_materials.push(&material);
if (!material.alias.empty())
aliases_sla[material.alias].insert(material.name);
}
sla_materials.add_printer(&printer);
}
add_material(sla_materials, aliases_sla, material, &printer);
}
}
}
// count compatible printers
for (const auto& preset : sla_materials.presets) {
const auto filter = [preset](const std::pair<std::string, size_t> element) {
return preset->alias == element.first;
};
if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) {
continue;
}
std::vector<size_t> idx_with_same_alias;
for (size_t i = 0; i < sla_materials.presets.size(); ++i) {
if(preset->alias == sla_materials.presets[i]->alias)
idx_with_same_alias.push_back(i);
}
size_t counter = 0;
for (const auto& printer : sla_materials.printers) {
if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA)
continue;
bool compatible = false;
// Test otrher materials with same alias
for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) {
const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]);
const Preset& prntr = *printer;
if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) {
compatible = true;
break;
}
}
if (compatible)
counter++;
}
sla_materials.compatibility_counter.emplace_back(preset->alias, counter);
}
}
}
@ -2857,21 +2781,15 @@ bool ConfigWizard::priv::on_bnt_finish()
index->go_to(page_downloader);
return false;
}
/* When Filaments or Sla Materials pages are activated,
* materials for this pages are automaticaly updated and presets are reloaded.
*
* But, if _Finish_ button was clicked without activation of those pages
* (for example, just some printers were added/deleted),
* than last changes wouldn't be updated for filaments/materials.
* SO, do that before close of Wizard
*/
update_materials(T_ANY);
if (any_fff_selected)
page_filaments->reload_presets();
if (any_sla_selected)
page_sla_materials->reload_presets();
// theres no need to check that filament is selected if we have only custom printer
/* If some printers were added/deleted, but related MaterialPage wasn't activated,
* than last changes wouldn't be updated for filaments/materials.
* SO, do that before check_and_install_missing_materials()
*/
page_filaments->check_and_update_presets();
page_sla_materials->check_and_update_presets();
// there's no need to check that filament is selected if we have only custom printer
if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true;
// check, that there is selected at least one filament/material
return check_and_install_missing_materials(T_ANY);
@ -3357,8 +3275,8 @@ void ConfigWizard::priv::update_presets_in_config(const std::string& section, co
// add or delete presets had a same alias
auto it = aliases.find(alias_key);
if (it != aliases.end())
for (const std::string& name : it->second)
update(section, name);
for (const Preset* preset : it->second)
update(section, preset->name);
}
bool ConfigWizard::priv::check_fff_selected()

View File

@ -296,6 +296,7 @@ struct PageMaterials: ConfigWizardPage
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
void check_and_update_presets(bool force_reload_presets = false);
void reload_presets();
void update_lists(int sel_type, int sel_vendor, int last_selected_printer = -1);
void on_material_highlighted(int sel_material);
@ -321,8 +322,8 @@ struct Materials
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
// String is alias of material, size_t number of compatible printers counters
std::map<std::string, size_t> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
@ -579,7 +580,7 @@ wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
// ConfigWizard private data
typedef std::map<std::string, std::set<std::string>> PresetAliases;
typedef std::map<std::string, std::set<const Preset*>> PresetAliases;
struct ConfigWizard::priv
{
@ -592,14 +593,14 @@ struct ConfigWizard::priv
// PrinterPickers state.
Materials filaments; // Holds available filament presets and their types & vendors
Materials sla_materials; // Ditto for SLA materials
PresetAliases aliases_fff; // Map of aliase to preset names
PresetAliases aliases_sla; // Map of aliase to preset names
PresetAliases aliases_fff; // Map of alias to material presets
PresetAliases aliases_sla; // Map of alias to material presets
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
bool custom_printer_selected { false }; // New custom printer is requested
bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custom printers)
bool only_sla_mode { false };
bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig)

View File

@ -0,0 +1,666 @@
#include "EditGCodeDialog.hpp"
#include <vector>
#include <string>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
#include <wx/wupdlock.h>
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
#include "BitmapCache.hpp"
#include "ExtraRenderers.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "libslic3r/PlaceholderParser.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Print.hpp"
namespace Slic3r {
namespace GUI {
//------------------------------------------
// EditGCodeDialog
//------------------------------------------
EditGCodeDialog::EditGCodeDialog(wxWindow* parent, const std::string& key, const std::string& value) :
DPIDialog(parent, wxID_ANY, format_wxstr(_L("Edit Custom G-code (%1%)"), key), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
SetFont(wxGetApp().normal_font());
wxGetApp().UpdateDarkUI(this);
int border = 10;
int em = em_unit();
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Built-in placeholders (Double click item to add to G-code)") + ":");
auto* grid_sizer = new wxFlexGridSizer(1, 3, 5, 15);
grid_sizer->SetFlexibleDirection(wxBOTH);
m_params_list = new ParamsViewCtrl(this, wxSize(em * 30, em * 70));
m_params_list->SetFont(wxGetApp().code_font());
wxGetApp().UpdateDarkUI(m_params_list);
m_add_btn = new ScalableButton(this, wxID_ANY, "add_copies");
m_add_btn->SetToolTip(_L("Add selected placeholder to G-code"));
m_gcode_editor = new wxTextCtrl(this, wxID_ANY, value, wxDefaultPosition, wxSize(em * 75, em * 70), wxTE_MULTILINE
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
);
m_gcode_editor->SetFont(wxGetApp().code_font());
wxGetApp().UpdateDarkUI(m_gcode_editor);
grid_sizer->Add(m_params_list, 1, wxEXPAND);
grid_sizer->Add(m_add_btn, 0, wxALIGN_CENTER_VERTICAL);
grid_sizer->Add(m_gcode_editor, 2, wxEXPAND);
grid_sizer->AddGrowableRow(0, 1);
grid_sizer->AddGrowableCol(0, 1);
grid_sizer->AddGrowableCol(2, 1);
m_param_label = new wxStaticText(this, wxID_ANY, _L("Select placeholder"));
m_param_label->SetFont(wxGetApp().bold_font());
m_param_description = new wxStaticText(this, wxID_ANY, wxEmptyString);
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxGetApp().UpdateDarkUI(this->FindWindowById(wxID_OK, this));
wxGetApp().UpdateDarkUI(this->FindWindowById(wxID_CANCEL, this));
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(grid_sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_param_label , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_param_description , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(btns , 0, wxEXPAND | wxALL, border);
SetSizer(topSizer);
topSizer->SetSizeHints(this);
this->Fit();
this->Layout();
this->CenterOnScreen();
init_params_list(key);
bind_list_and_button();
}
std::string EditGCodeDialog::get_edited_gcode() const
{
return into_u8(m_gcode_editor->GetValue());
}
static ParamType get_type(const std::string& opt_key, const ConfigOptionDef& opt_def)
{
return opt_def.is_scalar() ? ParamType::Scalar : ParamType::Vector;
}
void EditGCodeDialog::init_params_list(const std::string& custom_gcode_name)
{
const auto& custom_gcode_placeholders = custom_gcode_specific_placeholders();
const auto& specific_params = custom_gcode_placeholders.count(custom_gcode_name) > 0 ?
custom_gcode_placeholders.at(custom_gcode_name) : t_config_option_keys({});
// Add slicing states placeholders
wxDataViewItem slicing_state = m_params_list->AppendGroup(_L("[Global] Slicing State"), "re_slice");
if (!cgp_ro_slicing_states_config_def.empty()) {
wxDataViewItem read_only = m_params_list->AppendSubGroup(slicing_state, _L("Read Only"), "lock_closed");
for (const auto& [opt_key, def]: cgp_ro_slicing_states_config_def.options)
m_params_list->AppendParam(read_only, get_type(opt_key, def), opt_key);
}
if (!cgp_rw_slicing_states_config_def.empty()) {
wxDataViewItem read_write = m_params_list->AppendSubGroup(slicing_state, _L("Read Write"), "lock_open");
for (const auto& [opt_key, def] : cgp_rw_slicing_states_config_def.options)
m_params_list->AppendParam(read_write, get_type(opt_key, def), opt_key);
}
// add other universal params, which are related to slicing state
if (!cgp_other_slicing_states_config_def.empty()) {
slicing_state = m_params_list->AppendGroup(_L("Slicing State"), "re_slice");
for (const auto& [opt_key, def] : cgp_other_slicing_states_config_def.options)
m_params_list->AppendParam(slicing_state, get_type(opt_key, def), opt_key);
}
// Add universal placeholders
{
// Add print statistics subgroup
if (!cgp_print_statistics_config_def.empty()) {
wxDataViewItem statistics = m_params_list->AppendGroup(_L("Print Statistics"), "info");
for (const auto& [opt_key, def] : cgp_print_statistics_config_def.options)
m_params_list->AppendParam(statistics, get_type(opt_key, def), opt_key);
}
// Add objects info subgroup
if (!cgp_objects_info_config_def.empty()) {
wxDataViewItem objects_info = m_params_list->AppendGroup(_L("Objects Info"), "advanced_plus");
for (const auto& [opt_key, def] : cgp_objects_info_config_def.options)
m_params_list->AppendParam(objects_info, get_type(opt_key, def), opt_key);
}
// Add dimensions subgroup
if (!cgp_dimensions_config_def.empty()) {
wxDataViewItem dimensions = m_params_list->AppendGroup(_L("Dimensions"), "measure");
for (const auto& [opt_key, def] : cgp_dimensions_config_def.options)
m_params_list->AppendParam(dimensions, get_type(opt_key, def), opt_key);
}
// Add timestamp subgroup
if (!cgp_timestamps_config_def.empty()) {
wxDataViewItem dimensions = m_params_list->AppendGroup(_L("Timestamps"), "time");
for (const auto& [opt_key, def] : cgp_timestamps_config_def.options)
m_params_list->AppendParam(dimensions, get_type(opt_key, def), opt_key);
}
}
// Add specific placeholders
if (!specific_params.empty()) {
wxDataViewItem group = m_params_list->AppendGroup(format_wxstr(_L("Specific for %1%"), custom_gcode_name), "add_gcode");
for (const auto& opt_key : specific_params)
if (custom_gcode_specific_config_def.has(opt_key)) {
auto def = custom_gcode_specific_config_def.get(opt_key);
m_params_list->AppendParam(group, get_type(opt_key, *def), opt_key);
}
m_params_list->Expand(group);
}
// Add placeholders from presets
wxDataViewItem presets = add_presets_placeholders();
// add other params which are related to presets
if (!cgp_other_presets_config_def.empty())
for (const auto& [opt_key, def] : cgp_other_presets_config_def.options)
m_params_list->AppendParam(presets, get_type(opt_key, def), opt_key);
}
wxDataViewItem EditGCodeDialog::add_presets_placeholders()
{
auto get_set_from_vec = [](const std::vector<std::string>&vec) {
return std::set<std::string>(vec.begin(), vec.end());
};
const bool is_fff = wxGetApp().plater()->printer_technology() == ptFFF;
const std::set<std::string> print_options = get_set_from_vec(is_fff ? Preset::print_options() : Preset::sla_print_options());
const std::set<std::string> material_options = get_set_from_vec(is_fff ? Preset::filament_options() : Preset::sla_material_options());
const std::set<std::string> printer_options = get_set_from_vec(is_fff ? Preset::printer_options() : Preset::sla_printer_options());
const auto&full_config = wxGetApp().preset_bundle->full_config();
wxDataViewItem group = m_params_list->AppendGroup(_L("Presets"), "cog");
wxDataViewItem print = m_params_list->AppendSubGroup(group, _L("Print settings"), "cog");
for (const auto&opt : print_options)
if (const ConfigOption *optptr = full_config.optptr(opt))
m_params_list->AppendParam(print, optptr->is_scalar() ? ParamType::Scalar : ParamType::Vector, opt);
wxDataViewItem material = m_params_list->AppendSubGroup(group, _(is_fff ? L("Filament settings") : L("SLA Materials settings")), is_fff ? "spool" : "resin");
for (const auto&opt : material_options)
if (const ConfigOption *optptr = full_config.optptr(opt))
m_params_list->AppendParam(material, optptr->is_scalar() ? ParamType::Scalar : ParamType::FilamentVector, opt);
wxDataViewItem printer = m_params_list->AppendSubGroup(group, _L("Printer settings"), is_fff ? "printer" : "sla_printer");
for (const auto&opt : printer_options)
if (const ConfigOption *optptr = full_config.optptr(opt))
m_params_list->AppendParam(printer, optptr->is_scalar() ? ParamType::Scalar : ParamType::Vector, opt);
return group;
}
void EditGCodeDialog::add_selected_value_to_gcode()
{
const wxString val = m_params_list->GetSelectedValue();
if (!val.IsEmpty())
m_gcode_editor->WriteText(val + "\n");
}
void EditGCodeDialog::bind_list_and_button()
{
m_params_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& evt) {
wxString label;
wxString description;
const std::string opt_key = m_params_list->GetSelectedParamKey();
if (!opt_key.empty()) {
const ConfigOptionDef* def { nullptr };
const auto& full_config = wxGetApp().preset_bundle->full_config();
if (const ConfigDef* config_def = full_config.def(); config_def && config_def->has(opt_key)) {
def = config_def->get(opt_key);
}
else {
for (const ConfigDef* config: std::initializer_list<const ConfigDef*> {
&custom_gcode_specific_config_def,
&cgp_ro_slicing_states_config_def,
&cgp_rw_slicing_states_config_def,
&cgp_other_slicing_states_config_def,
&cgp_print_statistics_config_def,
&cgp_objects_info_config_def,
&cgp_dimensions_config_def,
&cgp_timestamps_config_def,
&cgp_other_presets_config_def
}) {
if (config->has(opt_key)) {
def = config->get(opt_key);
break;
}
}
}
if (def) {
const ConfigOptionType scalar_type = def->is_scalar() ? def->type : static_cast<ConfigOptionType>(def->type - coVectorType);
wxString type_str = scalar_type == coNone ? "none" :
scalar_type == coFloat ? "float" :
scalar_type == coInt ? "integer" :
scalar_type == coString ? "string" :
scalar_type == coPercent ? "percent" :
scalar_type == coFloatOrPercent ? "float or percent" :
scalar_type == coPoint ? "point" :
scalar_type == coBool ? "bool" :
scalar_type == coEnum ? "enum" : "undef";
if (!def->is_scalar())
type_str += "[]";
label = (!def || (def->full_label.empty() && def->label.empty()) ) ? format_wxstr("%1%\n(%2%)", opt_key, type_str) :
(!def->full_label.empty() && !def->label.empty() ) ?
format_wxstr("%1% > %2%\n(%3%)", _(def->full_label), _(def->label), type_str) :
format_wxstr("%1%\n(%2%)", def->label.empty() ? _(def->full_label) : _(def->label), type_str);
if (def)
description = get_wraped_wxString(_(def->tooltip), 120);
}
else
label = "Undef optptr";
}
m_param_label->SetLabel(label);
m_param_description->SetLabel(description);
Layout();
});
m_params_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& ) {
add_selected_value_to_gcode();
});
m_add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
add_selected_value_to_gcode();
});
}
void EditGCodeDialog::on_dpi_changed(const wxRect&suggested_rect)
{
const int& em = em_unit();
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
const wxSize& size = wxSize(45 * em, 35 * em);
SetMinSize(size);
Fit();
Refresh();
}
void EditGCodeDialog::on_sys_color_changed()
{
m_add_btn->sys_color_changed();
}
const std::map<ParamType, std::string> ParamsInfo {
// Type BitmapName
{ ParamType::Scalar, "scalar_param" },
{ ParamType::Vector, "vector_param" },
{ ParamType::FilamentVector,"vector_filament_param" },
};
static void make_bold(wxString& str)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
str = format_wxstr("<b>%1%</b>", str);
#endif
}
// ----------------------------------------------------------------------------
// ParamsModelNode: a node inside ParamsModel
// ----------------------------------------------------------------------------
ParamsNode::ParamsNode(const wxString& group_name, const std::string& icon_name)
: icon_name(icon_name)
, text(group_name)
{
make_bold(text);
}
ParamsNode::ParamsNode( ParamsNode * parent,
const wxString& sub_group_name,
const std::string& icon_name)
: m_parent(parent)
, icon_name(icon_name)
, text(sub_group_name)
{
make_bold(text);
}
ParamsNode::ParamsNode( ParamsNode* parent,
ParamType param_type,
const std::string& param_key)
: m_parent(parent)
, m_param_type(param_type)
, m_container(false)
, param_key(param_key)
{
text = from_u8(param_key);
if (param_type == ParamType::Vector)
text += "[]";
else if (param_type == ParamType::FilamentVector)
text += "[current_extruder]";
icon_name = ParamsInfo.at(param_type);
}
// ----------------------------------------------------------------------------
// ParamsModel
// ----------------------------------------------------------------------------
ParamsModel::ParamsModel()
{
}
wxDataViewItem ParamsModel::AppendGroup(const wxString& group_name,
const std::string& icon_name)
{
m_group_nodes.emplace_back(std::make_unique<ParamsNode>(group_name, icon_name));
wxDataViewItem parent(nullptr);
wxDataViewItem child((void*)m_group_nodes.back().get());
ItemAdded(parent, child);
m_ctrl->Expand(parent);
return child;
}
wxDataViewItem ParamsModel::AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string& icon_name)
{
ParamsNode* parent_node = static_cast<ParamsNode*>(parent.GetID());
if (!parent_node)
return wxDataViewItem(0);
parent_node->Append(std::make_unique<ParamsNode>(parent_node, sub_group_name, icon_name));
const wxDataViewItem sub_group_item((void*)parent_node->GetChildren().back().get());
ItemAdded(parent, sub_group_item);
return sub_group_item;
}
wxDataViewItem ParamsModel::AppendParam(wxDataViewItem parent,
ParamType param_type,
const std::string& param_key)
{
ParamsNode* parent_node = static_cast<ParamsNode*>(parent.GetID());
if (!parent_node)
return wxDataViewItem(0);
parent_node->Append(std::make_unique<ParamsNode>(parent_node, param_type, param_key));
const wxDataViewItem child_item((void*)parent_node->GetChildren().back().get());
ItemAdded(parent, child_item);
return child_item;
}
wxString ParamsModel::GetParamName(wxDataViewItem item)
{
if (item.IsOk()) {
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (node->IsParamNode())
return node->text;
}
return wxEmptyString;
}
std::string ParamsModel::GetParamKey(wxDataViewItem item)
{
if (item.IsOk()) {
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
return node->param_key;
}
return std::string();
}
wxDataViewItem ParamsModel::Delete(const wxDataViewItem& item)
{
auto ret_item = wxDataViewItem(nullptr);
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (!node) // happens if item.IsOk()==false
return ret_item;
// first remove the node from the parent's array of children;
// NOTE: m_group_nodes is only a vector of _pointers_
// thus removing the node from it doesn't result in freeing it
ParamsNodePtrArray& children = node->GetChildren();
// Delete all children
while (!children.empty())
Delete(wxDataViewItem(children.back().get()));
auto node_parent = node->GetParent();
ParamsNodePtrArray& parents_children = node_parent ? node_parent->GetChildren() : m_group_nodes;
auto it = find_if(parents_children.begin(), parents_children.end(),
[node](std::unique_ptr<ParamsNode>& child) { return child.get() == node; });
assert(it != parents_children.end());
it = parents_children.erase(it);
if (it != parents_children.end())
ret_item = wxDataViewItem(it->get());
wxDataViewItem parent(node_parent);
// set m_container to FALSE if parent has no child
if (node_parent) {
#ifndef __WXGTK__
if (node_parent->GetChildren().empty())
node_parent->SetContainer(false);
#endif //__WXGTK__
ret_item = parent;
}
// notify control
ItemDeleted(parent, item);
return ret_item;
}
void ParamsModel::Clear()
{
while (!m_group_nodes.empty())
Delete(wxDataViewItem(m_group_nodes.back().get()));
}
void ParamsModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
{
assert(item.IsOk());
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (col == (unsigned int)0)
#ifdef __linux__
variant << wxDataViewIconText(node->text, get_bmp_bundle(node->icon_name)->GetIconFor(m_ctrl->GetParent()));
#else
variant << DataViewBitmapText(node->text, get_bmp_bundle(node->icon_name)->GetBitmapFor(m_ctrl->GetParent()));
#endif //__linux__
else
wxLogError("DiffModel::GetValue: wrong column %d", col);
}
bool ParamsModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
{
assert(item.IsOk());
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (col == (unsigned int)0) {
#ifdef __linux__
wxDataViewIconText data;
data << variant;
node->icon = data.GetIcon();
#else
DataViewBitmapText data;
data << variant;
node->icon = data.GetBitmap();
#endif
node->text = data.GetText();
return true;
}
wxLogError("DiffModel::SetValue: wrong column");
return false;
}
wxDataViewItem ParamsModel::GetParent(const wxDataViewItem&item) const
{
// the invisible root node has no parent
if (!item.IsOk())
return wxDataViewItem(nullptr);
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (node->IsGroupNode())
return wxDataViewItem(nullptr);
return wxDataViewItem((void*)node->GetParent());
}
bool ParamsModel::IsContainer(const wxDataViewItem& item) const
{
// the invisble root node can have children
if (!item.IsOk())
return true;
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
return node->IsContainer();
}
unsigned int ParamsModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
{
ParamsNode* parent_node = (ParamsNode*)parent.GetID();
if (parent_node == nullptr) {
for (const auto& group : m_group_nodes)
array.Add(wxDataViewItem((void*)group.get()));
}
else {
const ParamsNodePtrArray& children = parent_node->GetChildren();
for (const std::unique_ptr<ParamsNode>& child : children)
array.Add(wxDataViewItem((void*)child.get()));
}
return array.Count();
}
// ----------------------------------------------------------------------------
// ParamsViewCtrl
// ----------------------------------------------------------------------------
ParamsViewCtrl::ParamsViewCtrl(wxWindow *parent, wxSize size)
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_SINGLE | wxDV_NO_HEADER// | wxDV_ROW_LINES
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
),
m_em_unit(em_unit(parent))
{
wxGetApp().UpdateDVCDarkUI(this);
model = new ParamsModel();
this->AssociateModel(model);
model->SetAssociatedControl(this);
#ifdef __linux__
wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer();
#ifdef SUPPORTS_MARKUP
rd->EnableMarkup(true);
#endif
wxDataViewColumn* column = new wxDataViewColumn("", rd, 0, 20 * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT);
#else
wxDataViewColumn* column = new wxDataViewColumn("", new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), 0, 20 * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE);
#endif //__linux__
this->AppendColumn(column);
this->SetExpanderColumn(column);
}
wxDataViewItem ParamsViewCtrl::AppendGroup(const wxString& group_name, const std::string& icon_name)
{
return model->AppendGroup(group_name, icon_name);
}
wxDataViewItem ParamsViewCtrl::AppendSubGroup( wxDataViewItem parent,
const wxString& sub_group_name,
const std::string& icon_name)
{
return model->AppendSubGroup(parent, sub_group_name, icon_name);
}
wxDataViewItem ParamsViewCtrl::AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key)
{
return model->AppendParam(parent, param_type, param_key);
}
wxString ParamsViewCtrl::GetValue(wxDataViewItem item)
{
return model->GetParamName(item);
}
wxString ParamsViewCtrl::GetSelectedValue()
{
return model->GetParamName(this->GetSelection());
}
std::string ParamsViewCtrl::GetSelectedParamKey()
{
return model->GetParamKey(this->GetSelection());
}
void ParamsViewCtrl::CheckAndDeleteIfEmpty(wxDataViewItem item)
{
wxDataViewItemArray children;
model->GetChildren(item, children);
if (children.IsEmpty())
model->Delete(item);
}
void ParamsViewCtrl::Clear()
{
model->Clear();
}
void ParamsViewCtrl::Rescale(int em/* = 0*/)
{
// model->Rescale();
Refresh();
}
}} // namespace Slic3r::GUI

View File

@ -0,0 +1,232 @@
#ifndef slic3r_EditGCodeDialog_hpp_
#define slic3r_EditGCodeDialog_hpp_
#include <vector>
#include <wx/gdicmn.h>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PrintConfig.hpp"
class wxListBox;
class wxTextCtrl;
class ScalableButton;
class wxStaticText;
namespace Slic3r {
namespace GUI {
class ParamsViewCtrl;
//------------------------------------------
// EditGCodeDialog
//------------------------------------------
class EditGCodeDialog : public DPIDialog
{
ParamsViewCtrl* m_params_list {nullptr};
ScalableButton* m_add_btn {nullptr};
wxTextCtrl* m_gcode_editor {nullptr};
wxStaticText* m_param_label {nullptr};
wxStaticText* m_param_description {nullptr};
ReadOnlySlicingStatesConfigDef cgp_ro_slicing_states_config_def;
ReadWriteSlicingStatesConfigDef cgp_rw_slicing_states_config_def;
OtherSlicingStatesConfigDef cgp_other_slicing_states_config_def;
PrintStatisticsConfigDef cgp_print_statistics_config_def;
ObjectsInfoConfigDef cgp_objects_info_config_def;
DimensionsConfigDef cgp_dimensions_config_def;
TimestampsConfigDef cgp_timestamps_config_def;
OtherPresetsConfigDef cgp_other_presets_config_def;
public:
EditGCodeDialog(wxWindow*parent, const std::string&key, const std::string&value);
~EditGCodeDialog() {}
std::string get_edited_gcode() const;
void init_params_list(const std::string& custom_gcode_name);
wxDataViewItem add_presets_placeholders();
void add_selected_value_to_gcode();
void bind_list_and_button();
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override;
};
// ----------------------------------------------------------------------------
// ParamsModelNode: a node inside ParamsModel
// ----------------------------------------------------------------------------
class ParamsNode;
using ParamsNodePtrArray = std::vector<std::unique_ptr<ParamsNode>>;
enum class ParamType {
Undef,
Scalar,
Vector,
FilamentVector,
};
// On all of 3 different platforms Bitmap+Text icon column looks different
// because of Markup text is missed or not implemented.
// As a temporary workaround, we will use:
// MSW - DataViewBitmapText (our custom renderer wxBitmap + wxString, supported Markup text)
// OSX - -//-, but Markup text is not implemented right now
// GTK - wxDataViewIconText (wxWidgets for GTK renderer wxIcon + wxString, supported Markup text)
class ParamsNode
{
ParamsNode* m_parent{ nullptr };
ParamsNodePtrArray m_children;
ParamType m_param_type{ ParamType::Undef };
// TODO/FIXME:
// the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded)
// needs to know in advance if a node is or _will be_ a container.
// Thus implementing:
// bool IsContainer() const
// { return m_children.size()>0; }
// doesn't work with wxGTK when DiffModel::AddToClassical is called
// AND the classical node was removed (a new node temporary without children
// would be added to the control)
bool m_container{ true };
public:
#ifdef __linux__
wxIcon icon;
#else
wxBitmap icon;
#endif //__linux__
std::string icon_name;
std::string param_key;
wxString text;
// Group params(root) node
ParamsNode(const wxString& group_name, const std::string& icon_name);
// sub SlicingState node
ParamsNode(ParamsNode* parent,
const wxString& sub_group_name,
const std::string& icon_name);
// parametre node
ParamsNode( ParamsNode* parent,
ParamType param_type,
const std::string& param_key);
bool IsContainer() const { return m_container; }
bool IsGroupNode() const { return m_parent == nullptr; }
bool IsParamNode() const { return m_param_type != ParamType::Undef; }
void SetContainer(bool is_container) { m_container = is_container; }
ParamsNode* GetParent() { return m_parent; }
ParamsNodePtrArray& GetChildren() { return m_children; }
void Append(std::unique_ptr<ParamsNode> child) { m_children.emplace_back(std::move(child)); }
};
// ----------------------------------------------------------------------------
// ParamsModel
// ----------------------------------------------------------------------------
class ParamsModel : public wxDataViewModel
{
ParamsNodePtrArray m_group_nodes;
wxDataViewCtrl* m_ctrl{ nullptr };
public:
ParamsModel();
~ParamsModel() override = default;
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
wxDataViewItem AppendGroup(const wxString& group_name,
const std::string& icon_name);
wxDataViewItem AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string&icon_name);
wxDataViewItem AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key);
wxDataViewItem Delete(const wxDataViewItem& item);
wxString GetParamName(wxDataViewItem item);
std::string GetParamKey(wxDataViewItem item);
void Clear();
wxDataViewItem GetParent(const wxDataViewItem& item) const override;
unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override;
void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override;
bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override;
bool IsContainer(const wxDataViewItem& item) const override;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
};
// ----------------------------------------------------------------------------
// ParamsViewCtrl
// ----------------------------------------------------------------------------
class ParamsViewCtrl : public wxDataViewCtrl
{
int m_em_unit;
public:
ParamsViewCtrl(wxWindow* parent, wxSize size);
~ParamsViewCtrl() override {
if (model) {
Clear();
model->DecRef();
}
}
ParamsModel* model{ nullptr };
wxDataViewItem AppendGroup(const wxString& group_name,
const std::string& icon_name);
wxDataViewItem AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string&icon_name);
wxDataViewItem AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key);
wxString GetValue(wxDataViewItem item);
wxString GetSelectedValue();
std::string GetSelectedParamKey();
void CheckAndDeleteIfEmpty(wxDataViewItem item);
void Clear();
void Rescale(int em = 0);
void set_em_unit(int em) { m_em_unit = em; }
};
} // namespace GUI
} // namespace Slic3r
#endif

View File

@ -14,6 +14,8 @@
#include "format.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/enum_bitmask.hpp"
#include "libslic3r/GCode/Thumbnails.hpp"
#include <regex>
#include <wx/numformatter.h>
@ -32,7 +34,7 @@
#define wxOSX false
#endif
namespace Slic3r { namespace GUI {
namespace Slic3r :: GUI {
wxString double_to_string(double const value, const int max_precision /*= 4*/)
{
@ -64,16 +66,22 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/)
return s;
}
wxString get_thumbnails_string(const std::vector<Vec2d>& values)
ThumbnailErrors validate_thumbnails_string(wxString& str, const wxString& def_ext = "PNG")
{
wxString ret_str;
for (size_t i = 0; i < values.size(); ++ i) {
const Vec2d& el = values[i];
ret_str += wxString::Format((i == 0) ? "%ix%i" : ", %ix%i", int(el[0]), int(el[1]));
}
return ret_str;
}
std::string input_string = into_u8(str);
str.Clear();
auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(input_string);
if (!thumbnails_list.empty()) {
const auto& extentions = ConfigOptionEnum<GCodeThumbnailsFormat>::get_enum_names();
for (const auto& [format, size] : thumbnails_list)
str += format_wxstr("%1%x%2%/%3%, ", size.x(), size.y(), extentions[int(format)]);
str.resize(str.Len() - 2);
}
return errors;
}
Field::~Field()
{
@ -179,6 +187,12 @@ void Field::on_back_to_sys_value()
m_back_to_sys_value(m_opt_id);
}
void Field::on_edit_value()
{
if (m_fn_edit_value)
m_fn_edit_value(m_opt_id);
}
wxString Field::get_tooltip_text(const wxString& default_string)
{
if (m_opt.tooltip.empty())
@ -361,56 +375,35 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
}
}
m_value = into_u8(str);
break; }
case coPoints: {
std::vector<Vec2d> out_values;
str.Replace(" ", wxEmptyString, true);
if (!str.IsEmpty()) {
bool invalid_val = false;
bool out_of_range_val = false;
wxStringTokenizer thumbnails(str, ",");
while (thumbnails.HasMoreTokens()) {
wxString token = thumbnails.GetNextToken();
double x, y;
wxStringTokenizer thumbnail(token, "x");
if (thumbnail.HasMoreTokens()) {
wxString x_str = thumbnail.GetNextToken();
if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) {
wxString y_str = thumbnail.GetNextToken();
if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) {
if (0 < x && x < 1000 && 0 < y && y < 1000) {
out_values.push_back(Vec2d(x, y));
continue;
}
out_of_range_val = true;
break;
}
}
if (m_opt.opt_key == "thumbnails") {
wxString str_out = str;
ThumbnailErrors errors = validate_thumbnails_string(str_out);
if (errors != enum_bitmask<ThumbnailError>()) {
set_value(str_out, true);
wxString error_str;
if (errors.has(ThumbnailError::InvalidVal))
error_str += format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""), "XxYxEXT, XxYxEXT, ...");
if (errors.has(ThumbnailError::OutOfRange)) {
if (!error_str.empty())
error_str += "\n\n";
error_str += _L("Input value is out of range");
}
invalid_val = true;
break;
if (errors.has(ThumbnailError::InvalidExt)) {
if (!error_str.empty())
error_str += "\n\n";
error_str += _L("Some input extention is invalid");
}
show_error(m_parent, error_str);
}
if (out_of_range_val) {
wxString text_value;
if (!m_value.empty())
text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
set_value(text_value, true);
show_error(m_parent, _L("Input value is out of range"));
}
else if (invalid_val) {
wxString text_value;
if (!m_value.empty())
text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
set_value(text_value, true);
show_error(m_parent, format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""),"XxY, XxY, ..." ));
else if (str_out != str) {
str = str_out;
set_value(str, true);
}
}
m_value = out_values;
break; }
m_value = into_u8(str);
break;
}
default:
break;
@ -490,9 +483,6 @@ void TextCtrl::BUILD() {
text_value = vec->get_at(m_opt_idx);
break;
}
case coPoints:
text_value = get_thumbnails_string(m_opt.get_default_value<ConfigOptionPoints>()->values);
break;
default:
break;
}
@ -1668,7 +1658,7 @@ void SliderCtrl::BUILD()
m_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
m_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT);
temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0);
temp->Add(m_slider, 1, wxEXPAND, 0);
temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) {
@ -1711,5 +1701,5 @@ boost::any& SliderCtrl::get_value()
}
} // GUI
} // Slic3r
} // Slic3r :: GUI

View File

@ -41,7 +41,6 @@ using t_change = std::function<void(const t_config_option_key&, const boost::any
using t_back_to_init = std::function<void(const std::string&)>;
wxString double_to_string(double const value, const int max_precision = 4);
wxString get_thumbnails_string(const std::vector<Vec2d>& values);
class UndoValueUIManager
{
@ -99,6 +98,30 @@ class UndoValueUIManager
UndoValueUI m_undo_ui;
struct EditValueUI {
// Bitmap and Tooltip text for m_Edit_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* bitmap{ nullptr };
wxString tooltip { wxEmptyString };
bool set_bitmap(const ScalableBitmap* bmp) {
if (bitmap != bmp) {
bitmap = bmp;
return true;
}
return false;
}
bool set_tooltip(const wxString& tip) {
if (tooltip != tip) {
tooltip = tip;
return true;
}
return false;
}
};
EditValueUI m_edit_ui;
public:
UndoValueUIManager() {}
~UndoValueUIManager() {}
@ -109,6 +132,9 @@ public:
bool set_undo_tooltip(const wxString* tip) { return m_undo_ui.set_undo_tooltip(tip); }
bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); }
bool set_edit_bitmap(const ScalableBitmap* bmp) { return m_edit_ui.set_bitmap(bmp); }
bool set_edit_tooltip(const wxString& tip) { return m_edit_ui.set_tooltip(tip); }
// ui items used for revert line value
bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; }
const wxBitmapBundle& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); }
@ -116,8 +142,17 @@ public:
const wxBitmapBundle& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); }
const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; }
const wxColour* label_color() const { return m_undo_ui.label_color; }
// Extentions
// Search blinker
const bool blink() const { return m_undo_ui.blink; }
bool* get_blink_ptr() { return &m_undo_ui.blink; }
// Edit field button
bool has_edit_ui() const { return !m_edit_ui.tooltip.IsEmpty(); }
const wxBitmapBundle* edit_bitmap() const { return &m_edit_ui.bitmap->bmp(); }
const wxString* edit_tooltip() const { return &m_edit_ui.tooltip; }
};
@ -151,6 +186,8 @@ public:
void on_back_to_initial_value();
/// Call the attached m_back_to_sys_value method.
void on_back_to_sys_value();
/// Call the attached m_fn_edit_value method.
void on_edit_value();
public:
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
@ -166,6 +203,9 @@ public:
t_back_to_init m_back_to_initial_value{ nullptr };
t_back_to_init m_back_to_sys_value{ nullptr };
/// Callback function to edit field value
t_back_to_init m_fn_edit_value{ nullptr };
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
bool m_disable_change_event {false};
bool m_is_modified_value {false};

View File

@ -845,7 +845,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);

View File

@ -368,72 +368,162 @@ void GCodeViewer::SequentialView::Marker::render()
ImGui::PopStyleVar();
}
void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector<size_t>& lines_ends)
void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const GCodeProcessorResult& gcode_result)
{
assert(! m_file.is_open());
if (m_file.is_open())
return;
m_filename = filename;
m_lines_ends = lines_ends;
m_selected_line_id = 0;
m_last_lines_size = 0;
try
{
m_file.open(boost::filesystem::path(m_filename));
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window.";
reset();
}
m_filename = gcode_result.filename;
m_is_binary_file = gcode_result.is_binary_file;
m_lines_ends = gcode_result.lines_ends;
}
void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const
void GCodeViewer::SequentialView::GCodeWindow::add_gcode_line_to_lines_cache(const std::string& src)
{
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
std::vector<Line> ret;
ret.reserve(end_id - start_id + 1);
for (uint64_t id = start_id; id <= end_id; ++id) {
// read line from file
const size_t start = id == 1 ? 0 : m_lines_ends[id - 2];
const size_t len = m_lines_ends[id - 1] - start;
std::string gline(m_file.data() + start, len);
std::string command;
std::string parameters;
std::string comment;
std::string command;
std::string parameters;
std::string comment;
// extract comment
std::vector<std::string> tokens;
boost::split(tokens, src, boost::is_any_of(";"), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1)
comment = ";" + tokens.back();
// extract comment
std::vector<std::string> tokens;
boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1)
comment = ";" + tokens.back();
// extract gcode command and parameters
if (!command.empty()) {
boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1) {
for (size_t i = 1; i < tokens.size(); ++i) {
parameters += " " + tokens[i];
}
}
}
// extract gcode command and parameters
if (!command.empty()) {
boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1) {
for (size_t i = 1; i < tokens.size(); ++i) {
parameters += " " + tokens[i];
m_lines_cache.push_back({ command, parameters, comment });
}
void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, size_t curr_line_id)
{
auto update_lines_ascii = [this]() {
m_lines_cache.clear();
m_lines_cache.reserve(m_cache_range.size());
const std::vector<size_t>& lines_ends = m_lines_ends.front();
FILE* file = boost::nowide::fopen(m_filename.c_str(), "rb");
if (file != nullptr) {
for (size_t id = *m_cache_range.min; id <= *m_cache_range.max; ++id) {
assert(id > 0);
// read line from file
const size_t begin = id == 1 ? 0 : lines_ends[id - 2];
const size_t len = lines_ends[id - 1] - begin;
std::string gline(len, '\0');
fseek(file, begin, SEEK_SET);
const size_t rsize = fread((void*)gline.data(), 1, len, file);
if (ferror(file) || rsize != len) {
m_lines_cache.clear();
break;
}
add_gcode_line_to_lines_cache(gline);
}
fclose(file);
}
};
auto update_lines_binary = [this]() {
m_lines_cache.clear();
m_lines_cache.reserve(m_cache_range.size());
size_t cumulative_lines_count = 0;
std::vector<size_t> cumulative_lines_counts;
cumulative_lines_counts.reserve(m_lines_ends.size());
for (size_t i = 0; i < m_lines_ends.size(); ++i) {
cumulative_lines_count += m_lines_ends[i].size();
cumulative_lines_counts.emplace_back(cumulative_lines_count);
}
size_t first_block_id = 0;
for (size_t i = 0; i < cumulative_lines_counts.size(); ++i) {
if (*m_cache_range.min <= cumulative_lines_counts[i]) {
first_block_id = i;
break;
}
}
size_t last_block_id = 0;
for (size_t i = 0; i < cumulative_lines_counts.size(); ++i) {
if (*m_cache_range.max <= cumulative_lines_counts[i]) {
last_block_id = i;
break;
}
}
assert(last_block_id >= first_block_id);
FilePtr file(boost::nowide::fopen(m_filename.c_str(), "rb"));
if (file.f != nullptr) {
fseek(file.f, 0, SEEK_END);
const long file_size = ftell(file.f);
rewind(file.f);
// read file header
using namespace bgcode::core;
using namespace bgcode::binarize;
FileHeader file_header;
EResult res = read_header(*file.f, file_header, nullptr);
if (res == EResult::Success) {
// search first GCode block
BlockHeader block_header;
res = read_next_block_header(*file.f, file_header, block_header, EBlockType::GCode, nullptr, 0);
if (res == EResult::Success) {
for (size_t i = 0; i < first_block_id; ++i) {
skip_block(*file.f, file_header, block_header);
res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0);
if (res != EResult::Success || block_header.type != (uint16_t)EBlockType::GCode) {
m_lines_cache.clear();
return;
}
}
for (size_t i = first_block_id; i <= last_block_id; ++i) {
GCodeBlock block;
res = block.read_data(*file.f, file_header, block_header);
if (res != EResult::Success) {
m_lines_cache.clear();
return;
}
const size_t ref_id = (i == 0) ? 0 : i - 1;
const size_t first_line_id = (i == 0) ? *m_cache_range.min :
(*m_cache_range.min - 1 >= cumulative_lines_counts[ref_id]) ? *m_cache_range.min - cumulative_lines_counts[ref_id] : 1;
const size_t last_line_id = (*m_cache_range.max - 1 <= cumulative_lines_counts[i]) ?
(i == 0) ? *m_cache_range.max : *m_cache_range.max - cumulative_lines_counts[ref_id] : m_lines_ends[i].size() - 1;
for (size_t j = first_line_id; j <= last_line_id; ++j) {
const size_t begin = (j == 1) ? 0 : m_lines_ends[i][j - 2];
const size_t end = m_lines_ends[i][j - 1];
std::string gline;
gline.insert(gline.end(), block.raw_data.begin() + begin, block.raw_data.begin() + end);
add_gcode_line_to_lines_cache(gline);
}
if (ftell(file.f) == file_size)
break;
res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0);
if (res != EResult::Success || block_header.type != (uint16_t)EBlockType::GCode) {
m_lines_cache.clear();
return;
}
}
}
}
ret.push_back({ command, parameters, comment });
}
return ret;
};
static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT;
static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT;
static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK;
static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f };
static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f };
static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f };
static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f };
static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f };
static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f };
static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f };
static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f };
if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0)
return;
@ -444,37 +534,46 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u
// number of visible lines
const float text_height = ImGui::CalcTextSize("0").y;
const ImGuiStyle& style = ImGui::GetStyle();
const uint64_t lines_count = static_cast<uint64_t>((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y));
const size_t visible_lines_count = static_cast<size_t>((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y));
if (lines_count == 0)
if (visible_lines_count == 0)
return;
if (m_lines_ends.empty() || m_lines_ends.front().empty())
return;
auto resize_range = [&](Range& range, size_t lines_count) {
const size_t half_lines_count = lines_count / 2;
range.min = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 1;
range.max = *range.min + lines_count - 1;
size_t lines_ends_count = 0;
for (const auto& le : m_lines_ends) {
lines_ends_count += le.size();
}
if (*range.max >= lines_ends_count) {
range.max = lines_ends_count - 1;
range.min = *range.max - lines_count + 1;
}
};
// visible range
const uint64_t half_lines_count = lines_count / 2;
uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0;
uint64_t end_id = start_id + lines_count - 1;
if (end_id >= static_cast<uint64_t>(m_lines_ends.size())) {
end_id = static_cast<uint64_t>(m_lines_ends.size()) - 1;
start_id = end_id - lines_count + 1;
Range visible_range;
resize_range(visible_range, visible_lines_count);
// update cache if needed
if (m_cache_range.empty() || !m_cache_range.contains(visible_range)) {
resize_range(m_cache_range, 4 * visible_range.size());
if (m_is_binary_file)
update_lines_binary();
else
update_lines_ascii();
}
// updates list of lines to show, if needed
if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) {
try
{
*const_cast<std::vector<Line>*>(&m_lines) = update_lines(start_id, end_id);
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window.";
return;
}
*const_cast<uint64_t*>(&m_selected_line_id) = curr_line_id;
*const_cast<size_t*>(&m_last_lines_size) = m_lines.size();
}
if (m_lines_cache.empty())
return;
// line number's column width
const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x;
const float id_width = ImGui::CalcTextSize(std::to_string(*visible_range.max).c_str()).x;
ImGuiWrapper& imgui = *wxGetApp().imgui();
@ -494,14 +593,10 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u
current_length += out_text.length();
ImGui::SameLine(0.0f, spacing);
ImGui::PushStyleColor(ImGuiCol_Text, color);
imgui.text(out_text);
ImGui::PopStyleColor();
imgui.text_colored(color, out_text);
if (reduced) {
ImGui::SameLine(0.0f, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ELLIPSIS_COLOR);
imgui.text("...");
ImGui::PopStyleColor();
imgui.text_colored(ELLIPSIS_COLOR, "...");
}
return reduced;
@ -512,14 +607,15 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::SetNextWindowBgAlpha(0.6f);
imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
// center the text in the window by pushing down the first line
const float f_lines_count = static_cast<float>(lines_count);
const float f_lines_count = static_cast<float>(visible_lines_count);
ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y));
// render text lines
for (uint64_t id = start_id; id <= end_id; ++id) {
const Line& line = m_lines[id - start_id];
size_t max_line_length = 0;
for (size_t id = *visible_range.min; id <= *visible_range.max; ++id) {
const Line& line = m_lines_cache[id - *m_cache_range.min];
// rect around the current selected line
if (id == curr_line_id) {
@ -547,16 +643,18 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u
if (!stop_adding && !line.comment.empty())
// render comment
stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length);
max_line_length = std::max(max_line_length, line_length);
}
imgui.end();
ImGui::PopStyleVar();
}
void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file()
{
if (m_file.is_open())
m_file.close();
// request an extra frame if window's width changed
if (m_max_line_length != max_line_length) {
m_max_line_length = max_line_length;
imgui.set_requires_extra_frame();
}
}
void GCodeViewer::SequentialView::render(float legend_height)
@ -737,7 +835,7 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr
// release gpu memory, if used
reset();
m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends);
m_sequential_view.gcode_window.load_gcode(gcode_result);
if (wxGetApp().is_gcode_viewer())
m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z;
@ -2360,17 +2458,19 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; }
case EViewType::LayerTimeLinear:
case EViewType::LayerTimeLogarithmic: {
const Path::Sub_Path& sub_path = path.sub_paths.front();
double z = static_cast<double>(sub_path.first.position.z());
const std::vector<double>& zs = m_layers.get_zs();
const std::vector<Layers::Range>& ranges = m_layers.get_ranges();
size_t time_mode_id = static_cast<size_t>(m_time_estimate_mode);
for (size_t i = 0; i < zs.size(); ++i) {
if (std::abs(zs[i] - z) < EPSILON) {
if (ranges[i].contains(sub_path.first.s_id)) {
color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i],
(m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic);
break;
if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) {
const Path::Sub_Path& sub_path = path.sub_paths.front();
double z = static_cast<double>(sub_path.first.position.z());
const std::vector<double>& zs = m_layers.get_zs();
const std::vector<Layers::Range>& ranges = m_layers.get_ranges();
size_t time_mode_id = static_cast<size_t>(m_time_estimate_mode);
for (size_t i = 0; i < zs.size(); ++i) {
if (std::abs(zs[i] - z) < EPSILON) {
if (ranges[i].contains(sub_path.first.s_id)) {
color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i],
(m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic);
break;
}
}
}
}
@ -3643,17 +3743,25 @@ void GCodeViewer::render_legend(float& legend_height)
ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f });
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f });
imgui.combo(std::string(), { _u8L("Feature type"),
_u8L("Height (mm)"),
_u8L("Width (mm)"),
_u8L("Speed (mm/s)"),
_u8L("Fan speed (%)"),
_u8L("Temperature (°C)"),
_u8L("Volumetric flow rate (mm³/s)"),
_u8L("Layer time (linear)"),
_u8L("Layer time (logarithmic)"),
_u8L("Tool"),
_u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f);
std::vector<std::string> view_options;
std::vector<int> view_options_id;
if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) {
view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"),
_u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Layer time (linear)"), _u8L("Layer time (logarithmic)"),
_u8L("Tool"), _u8L("Color Print") };
view_options_id = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
}
else {
view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"),
_u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Tool"), _u8L("Color Print") };
view_options_id = { 0, 1, 2, 3, 4, 5, 6, 9, 10 };
if (view_type == 7 || view_type == 8)
view_type = 0;
}
auto view_type_it = std::find(view_options_id.begin(), view_options_id.end(), view_type);
int view_type_id = (view_type_it == view_options_id.end()) ? 0 : std::distance(view_options_id.begin(), view_type_it);
if (imgui.combo(std::string(), view_options, view_type_id, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f))
view_type = view_options_id[view_type_id];
ImGui::PopStyleColor(2);
if (old_view_type != view_type) {

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