Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorenricoturri1966 <enricoturri@seznam.cz>2020-09-01 11:15:00 +0300
committerenricoturri1966 <enricoturri@seznam.cz>2020-09-01 11:15:00 +0300
commit5f27b0f851a3463fd70d3aea076007273dea9ac0 (patch)
tree75465c9c6634e8882706dc94ee0d0b1d655a802c
parent143e3a6a3588fe467e3cb3940cf78df30af56f96 (diff)
parente32930aa6c5008a86323a6d2c9a7d6a4a67be930 (diff)
Merge remote-tracking branch 'origin/et_gcode_viewer'
-rw-r--r--resources/icons/PrusaSlicerGCodeViewer_128px.pngbin0 -> 15949 bytes
-rw-r--r--resources/icons/thumb_left.svg54
-rw-r--r--resources/icons/thumb_right.svg54
-rw-r--r--resources/shaders/gouraud_light.fs11
-rw-r--r--resources/shaders/gouraud_light.vs38
-rw-r--r--resources/shaders/options_110.fs8
-rw-r--r--resources/shaders/options_110.vs11
-rw-r--r--resources/shaders/options_120.fs22
-rw-r--r--resources/shaders/options_120.vs11
-rw-r--r--resources/shaders/toolpaths_lines.fs28
-rw-r--r--resources/shaders/toolpaths_lines.vs19
-rw-r--r--src/PrusaSlicer.cpp4
-rw-r--r--src/libslic3r/CMakeLists.txt2
-rw-r--r--src/libslic3r/CustomGCode.cpp12
-rw-r--r--src/libslic3r/ExtrusionEntity.cpp40
-rw-r--r--src/libslic3r/ExtrusionEntity.hpp1
-rw-r--r--src/libslic3r/GCode.cpp1248
-rw-r--r--src/libslic3r/GCode.hpp55
-rw-r--r--src/libslic3r/GCode/Analyzer.cpp12
-rw-r--r--src/libslic3r/GCode/Analyzer.hpp4
-rw-r--r--src/libslic3r/GCode/GCodeProcessor.cpp2162
-rw-r--r--src/libslic3r/GCode/GCodeProcessor.hpp559
-rw-r--r--src/libslic3r/GCode/PreviewData.cpp4
-rw-r--r--src/libslic3r/GCode/PreviewData.hpp15
-rw-r--r--src/libslic3r/GCode/WipeTower.cpp92
-rw-r--r--src/libslic3r/GCodeReader.cpp5
-rw-r--r--src/libslic3r/GCodeReader.hpp6
-rw-r--r--src/libslic3r/GCodeTimeEstimator.cpp4
-rw-r--r--src/libslic3r/GCodeTimeEstimator.hpp4
-rw-r--r--src/libslic3r/Model.cpp2
-rw-r--r--src/libslic3r/Preset.cpp20
-rw-r--r--src/libslic3r/Preset.hpp4
-rw-r--r--src/libslic3r/Print.cpp30
-rw-r--r--src/libslic3r/Print.hpp14
-rw-r--r--src/libslic3r/Technologies.hpp7
-rw-r--r--src/libslic3r/Utils.hpp24
-rw-r--r--src/slic3r/CMakeLists.txt6
-rw-r--r--src/slic3r/GUI/3DBed.cpp280
-rw-r--r--src/slic3r/GUI/3DBed.hpp51
-rw-r--r--src/slic3r/GUI/3DScene.cpp111
-rw-r--r--src/slic3r/GUI/3DScene.hpp10
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.cpp20
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.hpp26
-rw-r--r--src/slic3r/GUI/BitmapCache.cpp5
-rw-r--r--src/slic3r/GUI/Camera.cpp1
-rw-r--r--src/slic3r/GUI/Camera.hpp1
-rw-r--r--src/slic3r/GUI/DoubleSlider.cpp302
-rw-r--r--src/slic3r/GUI/DoubleSlider.hpp26
-rw-r--r--src/slic3r/GUI/GCodeViewer.cpp2350
-rw-r--r--src/slic3r/GUI/GCodeViewer.hpp459
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp461
-rw-r--r--src/slic3r/GUI/GLCanvas3D.hpp75
-rw-r--r--src/slic3r/GUI/GLModel.cpp531
-rw-r--r--src/slic3r/GUI/GLModel.hpp68
-rw-r--r--src/slic3r/GUI/GLShader.cpp488
-rw-r--r--src/slic3r/GUI/GLShader.hpp98
-rw-r--r--src/slic3r/GUI/GLShadersManager.cpp75
-rw-r--r--src/slic3r/GUI/GLShadersManager.hpp30
-rw-r--r--src/slic3r/GUI/GLToolbar.cpp13
-rw-r--r--src/slic3r/GUI/GLToolbar.hpp4
-rw-r--r--src/slic3r/GUI/GUI.cpp60
-rw-r--r--src/slic3r/GUI/GUI.hpp14
-rw-r--r--src/slic3r/GUI/GUI_App.cpp14
-rw-r--r--src/slic3r/GUI/GUI_App.hpp12
-rw-r--r--src/slic3r/GUI/GUI_Preview.cpp622
-rw-r--r--src/slic3r/GUI/GUI_Preview.hpp95
-rw-r--r--src/slic3r/GUI/GUI_Utils.hpp9
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp3
-rw-r--r--src/slic3r/GUI/ImGuiWrapper.cpp44
-rw-r--r--src/slic3r/GUI/ImGuiWrapper.hpp10
-rw-r--r--src/slic3r/GUI/KBShortcutsDialog.cpp227
-rw-r--r--src/slic3r/GUI/KBShortcutsDialog.hpp4
-rw-r--r--src/slic3r/GUI/MainFrame.cpp589
-rw-r--r--src/slic3r/GUI/MainFrame.hpp49
-rw-r--r--src/slic3r/GUI/OpenGLManager.cpp84
-rw-r--r--src/slic3r/GUI/OpenGLManager.hpp8
-rw-r--r--src/slic3r/GUI/Plater.cpp338
-rw-r--r--src/slic3r/GUI/Plater.hpp32
-rw-r--r--src/slic3r/GUI/Selection.cpp144
-rw-r--r--src/slic3r/GUI/Selection.hpp16
-rw-r--r--src/slic3r/GUI/wxExtensions.cpp18
-rw-r--r--src/slic3r/GUI/wxExtensions.hpp1
-rw-r--r--tests/fff_print/test_data.cpp6
-rw-r--r--tests/fff_print/test_model.cpp4
-rw-r--r--xs/src/perlglue.cpp2
-rw-r--r--xs/xsp/GCode.xsp62
-rw-r--r--xs/xsp/Print.xsp8
-rw-r--r--xs/xsp/my.map6
-rw-r--r--xs/xsp/typemap.xspt6
89 files changed, 10784 insertions, 1780 deletions
diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png
new file mode 100644
index 000000000..0bf85abbd
--- /dev/null
+++ b/resources/icons/PrusaSlicerGCodeViewer_128px.png
Binary files differ
diff --git a/resources/icons/thumb_left.svg b/resources/icons/thumb_left.svg
new file mode 100644
index 000000000..ef78bd141
--- /dev/null
+++ b/resources/icons/thumb_left.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+ sodipodi:docname="thumb_left.svg"
+ xml:space="preserve"
+ enable-background="new 0 0 16 16"
+ viewBox="0 0 16 16"
+ y="0px"
+ x="0px"
+ id="Layer_1"
+ version="1.0"><metadata
+ id="metadata32"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs30" /><sodipodi:namedview
+ inkscape:current-layer="Layer_1"
+ inkscape:window-maximized="0"
+ inkscape:window-y="0"
+ inkscape:window-x="1268"
+ inkscape:cy="8"
+ inkscape:cx="8"
+ inkscape:zoom="63"
+ showgrid="false"
+ id="namedview28"
+ inkscape:window-height="1368"
+ inkscape:window-width="1283"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+<g
+ transform="rotate(-90,8.0158731,7.984127)"
+ id="hex_x5F_plus">
+ <g
+ id="g24">
+ <polygon
+ id="polygon22"
+ style="stroke:#ffffff;stroke-width:1"
+ points="15,7 15,5 8,0 1,5 1,7 1,8 15,8 "
+ fill="#ed6b21" />
+ </g>
+</g>
+</svg>
diff --git a/resources/icons/thumb_right.svg b/resources/icons/thumb_right.svg
new file mode 100644
index 000000000..f3748525d
--- /dev/null
+++ b/resources/icons/thumb_right.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+ sodipodi:docname="thumb_right.svg"
+ xml:space="preserve"
+ enable-background="new 0 0 16 16"
+ viewBox="0 0 16 16"
+ y="0px"
+ x="0px"
+ id="Layer_1"
+ version="1.0"><metadata
+ id="metadata32"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs30" /><sodipodi:namedview
+ inkscape:current-layer="Layer_1"
+ inkscape:window-maximized="0"
+ inkscape:window-y="0"
+ inkscape:window-x="1268"
+ inkscape:cy="8"
+ inkscape:cx="8"
+ inkscape:zoom="63"
+ showgrid="false"
+ id="namedview28"
+ inkscape:window-height="1368"
+ inkscape:window-width="1283"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+<g
+ transform="matrix(0,-1,-1,0,16.012532,16)"
+ id="hex_x5F_plus">
+ <g
+ id="g24">
+ <polygon
+ id="polygon22"
+ style="stroke:#ffffff;stroke-width:1"
+ points="15,8 15,7 15,5 8,0 1,5 1,7 1,8 "
+ fill="#ed6b21" />
+ </g>
+</g>
+</svg>
diff --git a/resources/shaders/gouraud_light.fs b/resources/shaders/gouraud_light.fs
new file mode 100644
index 000000000..1a58abc85
--- /dev/null
+++ b/resources/shaders/gouraud_light.fs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform vec4 uniform_color;
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
+}
diff --git a/resources/shaders/gouraud_light.vs b/resources/shaders/gouraud_light.vs
new file mode 100644
index 000000000..d4f71938a
--- /dev/null
+++ b/resources/shaders/gouraud_light.vs
@@ -0,0 +1,38 @@
+#version 110
+
+#define INTENSITY_CORRECTION 0.6
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
+#define LIGHT_TOP_SHININESS 20.0
+
+// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
+const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
+#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
+
+#define INTENSITY_AMBIENT 0.3
+
+// x = tainted, y = specular;
+varying vec2 intensity;
+
+void main()
+{
+ // First transform the normal into camera space and normalize the result.
+ vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
+ float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
+
+ intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
+ vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
+ intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
+
+ // Perform the same lighting calculation for the 2nd light source (no specular applied).
+ NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+ intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
+
+ gl_Position = ftransform();
+}
diff --git a/resources/shaders/options_110.fs b/resources/shaders/options_110.fs
new file mode 100644
index 000000000..ab656998d
--- /dev/null
+++ b/resources/shaders/options_110.fs
@@ -0,0 +1,8 @@
+#version 110
+
+uniform vec4 uniform_color;
+
+void main()
+{
+ gl_FragColor = uniform_color;
+}
diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs
new file mode 100644
index 000000000..7592f86a4
--- /dev/null
+++ b/resources/shaders/options_110.vs
@@ -0,0 +1,11 @@
+#version 110
+
+uniform float zoom;
+uniform float point_size;
+uniform float near_plane_height;
+
+void main()
+{
+ gl_Position = ftransform();
+ gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
+}
diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs
new file mode 100644
index 000000000..e9b61304f
--- /dev/null
+++ b/resources/shaders/options_120.fs
@@ -0,0 +1,22 @@
+// version 120 is needed for gl_PointCoord
+#version 120
+
+uniform vec4 uniform_color;
+uniform float percent_outline_radius;
+uniform float percent_center_radius;
+
+vec4 calc_color(float radius, vec4 color)
+{
+ return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ?
+ vec4(0.5 * color.rgb, color.a) : color;
+}
+
+void main()
+{
+ vec2 pos = (gl_PointCoord - 0.5) * 2.0;
+ float radius = length(pos);
+ if (radius > 1.0)
+ discard;
+
+ gl_FragColor = calc_color(radius, uniform_color);
+}
diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120.vs
new file mode 100644
index 000000000..baf3cd3a7
--- /dev/null
+++ b/resources/shaders/options_120.vs
@@ -0,0 +1,11 @@
+#version 120
+
+uniform float zoom;
+uniform float point_size;
+uniform float near_plane_height;
+
+void main()
+{
+ gl_Position = ftransform();
+ gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w;
+}
diff --git a/resources/shaders/toolpaths_lines.fs b/resources/shaders/toolpaths_lines.fs
new file mode 100644
index 000000000..31151cdc1
--- /dev/null
+++ b/resources/shaders/toolpaths_lines.fs
@@ -0,0 +1,28 @@
+#version 110
+
+// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
+const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
+const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0);
+
+// x = ambient, y = top diffuse, z = front diffuse, w = global
+uniform vec4 light_intensity;
+uniform vec4 uniform_color;
+
+varying vec3 eye_normal;
+
+void main()
+{
+ vec3 normal = normalize(eye_normal);
+
+ // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
+ // Since these two are normalized the cosine is the dot product. Take the abs value to light the lines no matter in which direction the normal points.
+ float NdotL = abs(dot(normal, LIGHT_TOP_DIR));
+
+ float intensity = light_intensity.x + NdotL * light_intensity.y;
+
+ // Perform the same lighting calculation for the 2nd light source.
+ NdotL = abs(dot(normal, LIGHT_FRONT_DIR));
+ intensity += NdotL * light_intensity.z;
+
+ gl_FragColor = vec4(uniform_color.rgb * light_intensity.w * intensity, uniform_color.a);
+}
diff --git a/resources/shaders/toolpaths_lines.vs b/resources/shaders/toolpaths_lines.vs
new file mode 100644
index 000000000..85d5c641f
--- /dev/null
+++ b/resources/shaders/toolpaths_lines.vs
@@ -0,0 +1,19 @@
+#version 110
+
+varying vec3 eye_normal;
+
+vec3 world_normal()
+{
+ // the world normal is always parallel to the world XY plane
+ // the x component is stored into gl_Vertex.w
+ float x = gl_Vertex.w;
+ float y = sqrt(1.0 - x * x);
+ return vec3(x, y, 0.0);
+}
+
+void main()
+{
+ vec4 world_position = vec4(gl_Vertex.xyz, 1.0);
+ gl_Position = gl_ModelViewProjectionMatrix * world_position;
+ eye_normal = gl_NormalMatrix * world_normal();
+}
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index c37afb805..aaf3db915 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -469,7 +469,11 @@ int CLI::run(int argc, char **argv)
print->process();
if (printer_technology == ptFFF) {
// The outfile is processed by a PlaceholderParser.
+#if ENABLE_GCODE_VIEWER
+ outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
+#else
outfile = fff_print.export_gcode(outfile, nullptr);
+#endif // ENABLE_GCODE_VIEWER
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
} else {
outfile = sla_print.output_filepath(outfile);
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index aea324722..3d241dd37 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -99,6 +99,8 @@ add_library(libslic3r STATIC
GCode/ToolOrdering.hpp
GCode/WipeTower.cpp
GCode/WipeTower.hpp
+ GCode/GCodeProcessor.cpp
+ GCode/GCodeProcessor.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp
index ba1890a1f..fb4f69d06 100644
--- a/src/libslic3r/CustomGCode.cpp
+++ b/src/libslic3r/CustomGCode.cpp
@@ -1,6 +1,10 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode.hpp"
+#else
#include "GCode/PreviewData.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "GCodeWriter.hpp"
namespace Slic3r {
@@ -17,8 +21,12 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint
return;
if (info.gcodes.empty() && ! colorprint_heights->values.empty()) {
// Convert the old colorprint_heighs only if there is no equivalent data in a new format.
- const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
- const auto& colorprint_values = colorprint_heights->values;
+#if ENABLE_GCODE_VIEWER
+ const std::vector<std::string>& colors = ColorPrintColors::get();
+#else
+ const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
+#endif // ENABLE_GCODE_VIEWER
+ const auto& colorprint_values = colorprint_heights->values;
info.gcodes.clear();
info.gcodes.reserve(colorprint_values.size());
int i = 0;
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 69b3a6455..b2c5e1350 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -306,7 +306,11 @@ double ExtrusionLoop::min_mm3_per_mm() const
std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
{
switch (role) {
+#if ENABLE_GCODE_VIEWER
+ case erNone : return L("Unknown");
+#else
case erNone : return L("None");
+#endif // ENABLE_GCODE_VIEWER
case erPerimeter : return L("Perimeter");
case erExternalPerimeter : return L("External perimeter");
case erOverhangPerimeter : return L("Overhang perimeter");
@@ -327,4 +331,40 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
return "";
}
+ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role)
+{
+ if (role == L("Perimeter"))
+ return erPerimeter;
+ else if (role == L("External perimeter"))
+ return erExternalPerimeter;
+ else if (role == L("Overhang perimeter"))
+ return erOverhangPerimeter;
+ else if (role == L("Internal infill"))
+ return erInternalInfill;
+ else if (role == L("Solid infill"))
+ return erSolidInfill;
+ else if (role == L("Top solid infill"))
+ return erTopSolidInfill;
+ else if (role == L("Ironing"))
+ return erIroning;
+ else if (role == L("Bridge infill"))
+ return erBridgeInfill;
+ else if (role == L("Gap fill"))
+ return erGapFill;
+ else if (role == L("Skirt"))
+ return erSkirt;
+ else if (role == L("Support material"))
+ return erSupportMaterial;
+ else if (role == L("Support material interface"))
+ return erSupportMaterialInterface;
+ else if (role == L("Wipe tower"))
+ return erWipeTower;
+ else if (role == L("Custom"))
+ return erCustom;
+ else if (role == L("Mixed"))
+ return erMixed;
+ else
+ return erNone;
+}
+
}
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index 879f564b6..3d7e58112 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -106,6 +106,7 @@ public:
virtual double total_volume() const = 0;
static std::string role_to_string(ExtrusionRole role);
+ static ExtrusionRole string_to_role(const std::string& role);
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 7d8067718..9e1600cb4 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -48,594 +48,599 @@ using namespace std::literals::string_view_literals;
namespace Slic3r {
-//! macro used to mark string used at localization,
-//! return same string
+ //! macro used to mark string used at localization,
+ //! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// Only add a newline in case the current G-code does not end with a newline.
-static inline void check_add_eol(std::string &gcode)
-{
- if (! gcode.empty() && gcode.back() != '\n')
- gcode += '\n';
-}
+ static inline void check_add_eol(std::string& gcode)
+ {
+ if (!gcode.empty() && gcode.back() != '\n')
+ gcode += '\n';
+ }
-// Return true if tch_prefix is found in custom_gcode
-static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
-{
- bool ok = false;
- size_t from_pos = 0;
- size_t pos = 0;
- while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
- if (pos+1 == custom_gcode.size())
- break;
- from_pos = pos+1;
- // only whitespace is allowed before the command
- while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
- if (! std::isspace(custom_gcode[pos]))
- goto NEXT;
- }
- {
- // we should also check that the extruder changes to what was expected
- std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
- unsigned num = 0;
- if (ss >> num)
- ok = (num == next_extruder);
+ // Return true if tch_prefix is found in custom_gcode
+ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
+ {
+ bool ok = false;
+ size_t from_pos = 0;
+ size_t pos = 0;
+ while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
+ if (pos + 1 == custom_gcode.size())
+ break;
+ from_pos = pos + 1;
+ // only whitespace is allowed before the command
+ while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
+ if (!std::isspace(custom_gcode[pos]))
+ goto NEXT;
+ }
+ {
+ // we should also check that the extruder changes to what was expected
+ std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
+ unsigned num = 0;
+ if (ss >> num)
+ ok = (num == next_extruder);
+ }
+ NEXT:;
}
-NEXT: ;
+ return ok;
}
- return ok;
-}
-void AvoidCrossingPerimeters::init_external_mp(const Print &print)
-{
- m_external_mp = Slic3r::make_unique<MotionPlanner>(union_ex(this->collect_contours_all_layers(print.objects())));
-}
-
-// Plan a travel move while minimizing the number of perimeter crossings.
-// point is in unscaled coordinates, in the coordinate system of the current active object
-// (set by gcodegen.set_origin()).
-Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point)
-{
- // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
- // Otherwise perform the path planning in the coordinate system of the active object.
- bool use_external = this->use_external_mp || this->use_external_mp_once;
- Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
- Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
- shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
- if (use_external)
- result.translate(- scaled_origin);
- return result;
-}
+ void AvoidCrossingPerimeters::init_external_mp(const Print& print)
+ {
+ m_external_mp = Slic3r::make_unique<MotionPlanner>(union_ex(this->collect_contours_all_layers(print.objects())));
+ }
-// Collect outer contours of all objects over all layers.
-// Discard objects only containing thin walls (offset would fail on an empty polygon).
-// Used by avoid crossing perimeters feature.
-Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
-{
- Polygons islands;
- for (const PrintObject *object : objects) {
- // Reducing all the object slices into the Z projection in a logarithimc fashion.
- // First reduce to half the number of layers.
- std::vector<Polygons> polygons_per_layer((object->layers().size() + 1) / 2);
- tbb::parallel_for(tbb::blocked_range<size_t>(0, object->layers().size() / 2),
- [&object, &polygons_per_layer](const tbb::blocked_range<size_t> &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- const Layer* layer1 = object->layers()[i * 2];
- const Layer* layer2 = object->layers()[i * 2 + 1];
- Polygons polys;
- polys.reserve(layer1->lslices.size() + layer2->lslices.size());
- for (const ExPolygon &expoly : layer1->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- for (const ExPolygon &expoly : layer2->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer[i] = union_(polys);
- }
- });
- if (object->layers().size() & 1) {
- const Layer *layer = object->layers().back();
- Polygons polys;
- polys.reserve(layer->lslices.size());
- for (const ExPolygon &expoly : layer->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer.back() = union_(polys);
- }
- // Now reduce down to a single layer.
- size_t cnt = polygons_per_layer.size();
- while (cnt > 1) {
- tbb::parallel_for(tbb::blocked_range<size_t>(0, cnt / 2),
- [&polygons_per_layer](const tbb::blocked_range<size_t> &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- Polygons polys;
- polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
- polygons_append(polys, polygons_per_layer[i * 2]);
- polygons_append(polys, polygons_per_layer[i * 2 + 1]);
- polygons_per_layer[i * 2] = union_(polys);
- }
- });
- for (size_t i = 0; i < cnt / 2; ++ i)
- polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
- if (cnt & 1)
- polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
- cnt = (cnt + 1) / 2;
- }
- // And collect copies of the objects.
- for (const PrintInstance &instance : object->instances()) {
- // All the layers were reduced to the 1st item of polygons_per_layer.
- size_t i = islands.size();
- polygons_append(islands, polygons_per_layer.front());
- for (; i < islands.size(); ++ i)
- islands[i].translate(instance.shift);
+ // Plan a travel move while minimizing the number of perimeter crossings.
+ // point is in unscaled coordinates, in the coordinate system of the current active object
+ // (set by gcodegen.set_origin()).
+ Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point)
+ {
+ // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
+ // Otherwise perform the path planning in the coordinate system of the active object.
+ bool use_external = this->use_external_mp || this->use_external_mp_once;
+ Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
+ Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
+ shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
+ if (use_external)
+ result.translate(-scaled_origin);
+ return result;
+ }
+
+ // Collect outer contours of all objects over all layers.
+ // Discard objects only containing thin walls (offset would fail on an empty polygon).
+ // Used by avoid crossing perimeters feature.
+ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
+ {
+ Polygons islands;
+ for (const PrintObject* object : objects) {
+ // Reducing all the object slices into the Z projection in a logarithimc fashion.
+ // First reduce to half the number of layers.
+ std::vector<Polygons> polygons_per_layer((object->layers().size() + 1) / 2);
+ tbb::parallel_for(tbb::blocked_range<size_t>(0, object->layers().size() / 2),
+ [&object, &polygons_per_layer](const tbb::blocked_range<size_t>& range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ const Layer* layer1 = object->layers()[i * 2];
+ const Layer* layer2 = object->layers()[i * 2 + 1];
+ Polygons polys;
+ polys.reserve(layer1->lslices.size() + layer2->lslices.size());
+ for (const ExPolygon& expoly : layer1->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ for (const ExPolygon& expoly : layer2->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ polygons_per_layer[i] = union_(polys);
+ }
+ });
+ if (object->layers().size() & 1) {
+ const Layer* layer = object->layers().back();
+ Polygons polys;
+ polys.reserve(layer->lslices.size());
+ for (const ExPolygon& expoly : layer->lslices)
+ //FIXME no holes?
+ polys.emplace_back(expoly.contour);
+ polygons_per_layer.back() = union_(polys);
+ }
+ // Now reduce down to a single layer.
+ size_t cnt = polygons_per_layer.size();
+ while (cnt > 1) {
+ tbb::parallel_for(tbb::blocked_range<size_t>(0, cnt / 2),
+ [&polygons_per_layer](const tbb::blocked_range<size_t>& range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ Polygons polys;
+ polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
+ polygons_append(polys, polygons_per_layer[i * 2]);
+ polygons_append(polys, polygons_per_layer[i * 2 + 1]);
+ polygons_per_layer[i * 2] = union_(polys);
+ }
+ });
+ for (size_t i = 0; i < cnt / 2; ++i)
+ polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
+ if (cnt & 1)
+ polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
+ cnt = (cnt + 1) / 2;
+ }
+ // And collect copies of the objects.
+ for (const PrintInstance& instance : object->instances()) {
+ // All the layers were reduced to the 1st item of polygons_per_layer.
+ size_t i = islands.size();
+ polygons_append(islands, polygons_per_layer.front());
+ for (; i < islands.size(); ++i)
+ islands[i].translate(instance.shift);
+ }
}
+ return islands;
}
- return islands;
-}
-std::string OozePrevention::pre_toolchange(GCode &gcodegen)
-{
- std::string gcode;
-
- // move to the nearest standby point
- if (!this->standby_points.empty()) {
- // get current position in print coordinates
- Vec3d writer_pos = gcodegen.writer().get_position();
- Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
-
- // find standby point
- Point standby_point;
- pos.nearest_point(this->standby_points, &standby_point);
-
- /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
- triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
- of the destination point must not be transformed by origin nor current extruder offset. */
- gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
- "move to standby position");
- }
-
- if (gcodegen.config().standby_temperature_delta.value != 0) {
- // we assume that heating is always slower than cooling, so no need to block
- gcode += gcodegen.writer().set_temperature
- (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
- }
-
- return gcode;
-}
+ std::string OozePrevention::pre_toolchange(GCode& gcodegen)
+ {
+ std::string gcode;
-std::string OozePrevention::post_toolchange(GCode &gcodegen)
-{
- return (gcodegen.config().standby_temperature_delta.value != 0) ?
- gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
- std::string();
-}
+ // move to the nearest standby point
+ if (!this->standby_points.empty()) {
+ // get current position in print coordinates
+ Vec3d writer_pos = gcodegen.writer().get_position();
+ Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
-int
-OozePrevention::_get_temp(GCode &gcodegen)
-{
- return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
- ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
- : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
-}
+ // find standby point
+ Point standby_point;
+ pos.nearest_point(this->standby_points, &standby_point);
-std::string Wipe::wipe(GCode &gcodegen, bool toolchange)
-{
- std::string gcode;
-
- /* 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. */
- double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
-
- // get the retraction length
- double length = toolchange
- ? gcodegen.writer().extruder()->retract_length_toolchange()
- : gcodegen.writer().extruder()->retract_length();
- // Shorten the retraction length by the amount already retracted before wipe.
- length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
-
- if (length > 0) {
- /* Calculate how long we need to travel in order to consume the required
- amount of retraction. In other words, how far do we move in XY at wipe_speed
- for the time needed to consume retract_length at retract_speed? */
- double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
-
- /* Take the stored wipe path and replace first point with the current actual position
- (they might be different, for example, in case of loop clipping). */
- Polyline wipe_path;
- wipe_path.append(gcodegen.last_pos());
- wipe_path.append(
- this->path.points.begin() + 1,
- this->path.points.end()
- );
-
- wipe_path.clip_end(wipe_path.length() - wipe_dist);
-
- // subdivide the retraction in segments
- if (! wipe_path.empty()) {
- for (const Line &line : wipe_path.lines()) {
- double segment_length = line.length();
- /* 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) */
- double dE = length * (segment_length / wipe_dist) * 0.95;
- //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
- // Is it here for the cooling markers? Or should it be outside of the cycle?
- gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
- gcode += gcodegen.writer().extrude_to_xy(
- gcodegen.point_to_gcode(line.b),
- -dE,
- "wipe and retract"
- );
- }
- gcodegen.set_last_pos(wipe_path.points.back());
+ /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
+ triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
+ of the destination point must not be transformed by origin nor current extruder offset. */
+ gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
+ "move to standby position");
}
-
- // prevent wiping again on same path
- this->reset_path();
- }
-
- return gcode;
-}
-static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt)
-{
- return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
-}
+ if (gcodegen.config().standby_temperature_delta.value != 0) {
+ // we assume that heating is always slower than cooling, so no need to block
+ gcode += gcodegen.writer().set_temperature
+ (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
+ }
-std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const
-{
- if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
- throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
+ return gcode;
+ }
- std::string gcode;
+ std::string OozePrevention::post_toolchange(GCode& gcodegen)
+ {
+ return (gcodegen.config().standby_temperature_delta.value != 0) ?
+ gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
+ std::string();
+ }
- // 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);
- Vec2f start_pos = tcr.start_pos;
- Vec2f end_pos = tcr.end_pos;
- if (!tcr.priming) {
- start_pos = Eigen::Rotation2Df(alpha) * start_pos;
- start_pos += m_wipe_tower_pos;
- end_pos = Eigen::Rotation2Df(alpha) * end_pos;
- end_pos += m_wipe_tower_pos;
+ int
+ OozePrevention::_get_temp(GCode& gcodegen)
+ {
+ return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
+ ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
+ : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
}
- Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
- float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
+ std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
+ {
+ std::string gcode;
- std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
+ /* 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. */
+ double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
+
+ // get the retraction length
+ double length = toolchange
+ ? gcodegen.writer().extruder()->retract_length_toolchange()
+ : gcodegen.writer().extruder()->retract_length();
+ // Shorten the retraction length by the amount already retracted before wipe.
+ length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
+
+ if (length > 0) {
+ /* Calculate how long we need to travel in order to consume the required
+ amount of retraction. In other words, how far do we move in XY at wipe_speed
+ for the time needed to consume retract_length at retract_speed? */
+ double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
+
+ /* Take the stored wipe path and replace first point with the current actual position
+ (they might be different, for example, in case of loop clipping). */
+ Polyline wipe_path;
+ wipe_path.append(gcodegen.last_pos());
+ wipe_path.append(
+ this->path.points.begin() + 1,
+ this->path.points.end()
+ );
+
+ wipe_path.clip_end(wipe_path.length() - wipe_dist);
+
+ // subdivide the retraction in segments
+ if (!wipe_path.empty()) {
+ for (const Line& line : wipe_path.lines()) {
+ double segment_length = line.length();
+ /* 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) */
+ double dE = length * (segment_length / wipe_dist) * 0.95;
+ //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
+ // Is it here for the cooling markers? Or should it be outside of the cycle?
+ gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
+ gcode += gcodegen.writer().extrude_to_xy(
+ gcodegen.point_to_gcode(line.b),
+ -dE,
+ "wipe and retract"
+ );
+ }
+ gcodegen.set_last_pos(wipe_path.points.back());
+ }
- if (!tcr.priming) {
- // Move over the wipe tower.
- // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
- gcode += gcodegen.retract(true);
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- gcode += gcodegen.travel_to(
- wipe_tower_point_to_object_point(gcodegen, start_pos),
- erMixed,
- "Travel to a Wipe Tower");
- gcode += gcodegen.unretract();
+ // prevent wiping again on same path
+ this->reset_path();
+ }
+
+ return gcode;
}
- 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;
- if (! is_approx(z, current_z)) {
- gcode += gcodegen.writer().retract();
- gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
- gcode += gcodegen.writer().unretract();
+ static inline Point wipe_tower_point_to_object_point(GCode& 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(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
+ {
+ if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
+ throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
+
+ std::string gcode;
- // Process the end filament gcode.
- std::string end_filament_gcode_str;
- if (gcodegen.writer().extruder() != nullptr) {
- // Process the custom end_filament_gcode in case of single_extruder_multi_material.
- unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
- const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
- if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) {
- end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
- check_add_eol(end_filament_gcode_str);
+ // 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);
+ Vec2f start_pos = tcr.start_pos;
+ Vec2f end_pos = tcr.end_pos;
+ if (!tcr.priming) {
+ start_pos = Eigen::Rotation2Df(alpha) * start_pos;
+ start_pos += m_wipe_tower_pos;
+ end_pos = Eigen::Rotation2Df(alpha) * end_pos;
+ end_pos += m_wipe_tower_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);
+
+ if (!tcr.priming) {
+ // Move over the wipe tower.
+ // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
+ gcode += gcodegen.retract(true);
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ gcode += gcodegen.travel_to(
+ wipe_tower_point_to_object_point(gcodegen, start_pos),
+ erMixed,
+ "Travel to a Wipe Tower");
+ gcode += gcodegen.unretract();
+ }
+
+ 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;
+ if (!is_approx(z, current_z)) {
+ gcode += gcodegen.writer().retract();
+ gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
+ gcode += gcodegen.writer().unretract();
+ }
+
+
+ // Process the end filament gcode.
+ std::string end_filament_gcode_str;
+ if (gcodegen.writer().extruder() != nullptr) {
+ // Process the custom end_filament_gcode in case of single_extruder_multi_material.
+ unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
+ const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
+ if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
+ end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
+ check_add_eol(end_filament_gcode_str);
+ }
}
- }
- // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
- // Otherwise, leave control to the user completely.
- std::string toolchange_gcode_str;
- if (true /*gcodegen.writer().extruder() != nullptr*/) {
- const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
- if (!toolchange_gcode.empty()) {
- DynamicConfig config;
- int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
- config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
- config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
- config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
- config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
- toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
- check_add_eol(toolchange_gcode_str);
- }
-
- std::string toolchange_command;
- if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
- toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
- if (! custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
- toolchange_gcode_str += toolchange_command;
- else {
- // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
+ // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
+ // Otherwise, leave control to the user completely.
+ std::string toolchange_gcode_str;
+ if (true /*gcodegen.writer().extruder() != nullptr*/) {
+ const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
+ if (!toolchange_gcode.empty()) {
+ DynamicConfig config;
+ int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
+ config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
+ config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
+ config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
+ config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
+ toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
+ check_add_eol(toolchange_gcode_str);
+ }
+
+ std::string toolchange_command;
+ if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
+ toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
+ if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
+ toolchange_gcode_str += toolchange_command;
+ else {
+ // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
+ }
}
- }
- gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
+ gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
- // Process the start filament gcode.
- std::string start_filament_gcode_str;
- const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
- if (! start_filament_gcode.empty()) {
- // Process the start_filament_gcode for the active filament only.
- DynamicConfig config;
- config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
- start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
- check_add_eol(start_filament_gcode_str);
- }
+ // Process the start filament gcode.
+ std::string start_filament_gcode_str;
+ const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
+ if (!start_filament_gcode.empty()) {
+ // Process the start_filament_gcode for the active filament only.
+ DynamicConfig config;
+ config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
+ start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
+ check_add_eol(start_filament_gcode_str);
+ }
- // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
- DynamicConfig config;
- config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
- config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
- config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
- std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
- unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
- gcode += tcr_gcode;
- check_add_eol(toolchange_gcode_str);
+ // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
+ DynamicConfig config;
+ config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
+ config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
+ config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
+ std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
+ unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
+ gcode += tcr_gcode;
+ check_add_eol(toolchange_gcode_str);
- // 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();
- }
+ // 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.
- gcodegen.m_wipe.path.points.clear();
- if (new_extruder_id >= 0) {
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
- end_pos.y())));
+ else {
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ if (new_extruder_id >= 0) {
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
+ end_pos.y())));
+ }
}
- }
- // Let the planner know we are traveling between objects.
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- return gcode;
-}
+ // Let the planner know we are traveling between objects.
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ 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 = pos;
- 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 (line.find("G1 ") == 0) {
- 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;
- while (line_str >> ch) {
- if (ch == 'X' || ch =='Y')
- line_str >> (ch == 'X' ? pos.x() : pos.y());
- else
- line_out << ch;
- }
+ // 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 = pos;
+ 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 (line.find("G1 ") == 0) {
+ 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;
+ 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();
- 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();
- oss << " ";
- line.replace(line.find("G1 "), 3, oss.str());
- old_pos = transformed_pos;
+ transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
+
+ if (transformed_pos != old_pos || never_skip) {
+ line = line_out.str();
+ 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();
+ oss << " ";
+ line.replace(line.find("G1 "), 3, oss.str());
+ old_pos = transformed_pos;
+ }
}
- }
- gcode_out += line + "\n";
+ 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 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();
+ // 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;
}
- return gcode_out;
-}
-std::string WipeTowerIntegration::prime(GCode &gcodegen)
-{
- assert(m_layer_idx == 0);
- std::string gcode;
+ std::string WipeTowerIntegration::prime(GCode& gcodegen)
+ {
+ assert(m_layer_idx == 0);
+ std::string gcode;
- // Disable linear advance for the wipe tower operations.
- //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
+ // Disable linear advance for the wipe tower operations.
+ //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
- for (const WipeTower::ToolChangeResult& tcr : m_priming) {
- if (!tcr.extrusions.empty())
- gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
+ for (const WipeTower::ToolChangeResult& tcr : m_priming) {
+ if (!tcr.extrusions.empty())
+ gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
- // Let the tool change be executed by the wipe tower class.
- // Inform the G-code writer about the changes done behind its back.
- //gcode += tcr.gcode;
- // Let the m_writer know the current extruder_id, but ignore the generated G-code.
- // unsigned int current_extruder_id = tcr.extrusions.back().tool;
- // gcodegen.writer().toolchange(current_extruder_id);
- // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
+ // Let the tool change be executed by the wipe tower class.
+ // Inform the G-code writer about the changes done behind its back.
+ //gcode += tcr.gcode;
+ // Let the m_writer know the current extruder_id, but ignore the generated G-code.
+ // unsigned int current_extruder_id = tcr.extrusions.back().tool;
+ // gcodegen.writer().toolchange(current_extruder_id);
+ // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
- }
+ }
- // A phony move to the end position at the wipe tower.
- /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Prepare a future wipe.
- gcodegen.m_wipe.path.points.clear();
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
- m_priming.back().end_pos.y)));*/
+ // A phony move to the end position at the wipe tower.
+ /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
+ m_priming.back().end_pos.y)));*/
- return gcode;
-}
+ return gcode;
+ }
-std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
-{
- std::string gcode;
- assert(m_layer_idx >= 0);
- if (! m_brim_done || 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 std::runtime_error("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_brim_done && 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);
- 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;
- }
+ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer)
+ {
+ std::string gcode;
+ assert(m_layer_idx >= 0);
+ if (!m_brim_done || 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 std::runtime_error("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_brim_done && 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);
+ 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;
+ 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;
+ }
}
- }
- m_brim_done = true;
+ m_brim_done = true;
+ }
+ return gcode;
}
- return gcode;
-}
-// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
-std::string WipeTowerIntegration::finalize(GCode &gcodegen)
-{
- std::string gcode;
- if (std::abs(gcodegen.writer().get_position()(2) - 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;
-}
+ // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
+ std::string WipeTowerIntegration::finalize(GCode& gcodegen)
+ {
+ std::string gcode;
+ if (std::abs(gcodegen.writer().get_position()(2) - 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;
+ }
+
+#if ENABLE_GCODE_VIEWER
+ const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
+#endif // ENABLE_GCODE_VIEWER
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
// Collect pairs of object_layer + support_layer sorted by print_z.
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
-std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject &object)
+std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject& object)
{
std::vector<GCode::LayerToPrint> layers_to_print;
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
- // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
- // This is the same logic as in support generator.
- //FIXME should we use the printing extruders instead?
- double gap_over_supports = object.config().support_material_contact_distance;
- // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
- assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
+ // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
+ // This is the same logic as in support generator.
+ //FIXME should we use the printing extruders instead?
+ double gap_over_supports = object.config().support_material_contact_distance;
+ // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
+ assert(!object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
if (gap_over_supports != 0.) {
gap_over_supports = std::max(0., gap_over_supports);
- // Not a soluble support,
- double support_layer_height_min = 1000000.;
- for (auto lh : object.print()->config().min_layer_height.values)
- support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
- gap_over_supports += support_layer_height_min;
+ // Not a soluble support,
+ double support_layer_height_min = 1000000.;
+ for (auto lh : object.print()->config().min_layer_height.values)
+ support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
+ gap_over_supports += support_layer_height_min;
}
// Pair the object layers with the support layers by z.
- size_t idx_object_layer = 0;
+ size_t idx_object_layer = 0;
size_t idx_support_layer = 0;
const LayerToPrint* last_extrusion_layer = nullptr;
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
LayerToPrint layer_to_print;
- layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr;
- layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr;
+ layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
+ layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
if (layer_to_print.object_layer && layer_to_print.support_layer) {
if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
layer_to_print.support_layer = nullptr;
- -- idx_support_layer;
- } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
+ --idx_support_layer;
+ }
+ else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
layer_to_print.object_layer = nullptr;
- -- idx_object_layer;
+ --idx_object_layer;
}
}
layers_to_print.emplace_back(layer_to_print);
bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
+ || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
// Check that there are extrusions on the very first layer.
if (layers_to_print.size() == 1u) {
- if (! has_extrusions)
+ if (!has_extrusions)
throw std::runtime_error(_(L("There is an object with no extrusions on the first layer.")));
}
// In case there are extrusions on this layer, check there is a layer to lay it on.
if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
- || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
+ // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
+ || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer)
- ? gap_over_supports
- : 0.;
+ ? gap_over_supports
+ : 0.;
double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.)
- + layer_to_print.layer()->height
- + support_contact_z;
+ + layer_to_print.layer()->height
+ + support_contact_z;
// Negative support_contact_z is not taken into account, it can result in false positives in cases
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
@@ -644,8 +649,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
_(L("Empty layers detected, the output would not be printable.")) + "\n\n" +
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
- "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
- "the model or change its orientation on the bed.")));
+ "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
+ "the model or change its orientation on the bed.")));
}
// Remember last layer with extrusions.
@@ -660,7 +665,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
// will be printed for all objects at once.
// Return a list of <print_z, per object LayerToPrint> items.
-std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print &print)
+std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print& print)
{
struct OrderingItem {
coordf_t print_z;
@@ -675,15 +680,15 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
OrderingItem ordering_item;
ordering_item.object_idx = i;
ordering.reserve(ordering.size() + per_object[i].size());
- const LayerToPrint &front = per_object[i].front();
- for (const LayerToPrint &ltp : per_object[i]) {
- ordering_item.print_z = ltp.print_z();
+ const LayerToPrint& front = per_object[i].front();
+ for (const LayerToPrint& ltp : per_object[i]) {
+ ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = &ltp - &front;
ordering.emplace_back(ordering_item);
}
}
- std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
+ std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
@@ -692,14 +697,14 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
// Find the last layer with roughly the same print_z.
size_t j = i + 1;
coordf_t zmax = ordering[i].print_z + EPSILON;
- for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ;
+ for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
// Merge into layers_to_print.
std::pair<coordf_t, std::vector<LayerToPrint>> merged;
// Assign an average print_z to the set of layers with nearly equal print_z.
- merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z);
+ merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
merged.second.assign(print.objects().size(), LayerToPrint());
for (; i < j; ++i) {
- const OrderingItem &oi = ordering[i];
+ const OrderingItem& oi = ordering[i];
assert(merged.second[oi.object_idx].layer() == nullptr);
merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
}
@@ -709,7 +714,22 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
return layers_to_print;
}
+#if ENABLE_GCODE_VIEWER
+// free functions called by GCode::do_export()
+namespace DoExport {
+ static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
+ {
+ const GCodeProcessor::Result& result = processor.get_result();
+ print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
+ print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
+ get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
+ }
+} // namespace DoExport
+
+void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
+#else
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
+#endif // ENABLE_GCODE_VIEWER
{
PROFILE_CLEAR();
@@ -731,7 +751,9 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
if (file == nullptr)
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
+#if !ENABLE_GCODE_VIEWER
m_enable_analyzer = preview_data != nullptr;
+#endif // !ENABLE_GCODE_VIEWER
try {
m_placeholder_parser_failed_templates.clear();
@@ -764,6 +786,12 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
throw std::runtime_error(msg);
}
+#if ENABLE_GCODE_VIEWER
+ m_processor.process_file(path_tmp);
+ DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
+ if (result != nullptr)
+ *result = std::move(m_processor.extract_result());
+#else
GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
@@ -772,8 +800,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
BOOST_LOG_TRIVIAL(debug) << "Time estimator post processing" << log_memory_info();
GCodeTimeEstimator::post_process(path_tmp, 60.0f, remaining_times_enabled ? &normal_data : nullptr, (remaining_times_enabled && m_silent_time_estimator_enabled) ? &silent_data : nullptr);
- if (remaining_times_enabled)
- {
+ if (remaining_times_enabled) {
m_normal_time_estimator.reset();
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.reset();
@@ -785,6 +812,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); });
m_analyzer.reset();
}
+#endif // ENABLE_GCODE_VIEWER
if (rename_file(path_tmp, path))
throw std::runtime_error(
@@ -801,7 +829,8 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
// free functions called by GCode::_do_export()
namespace DoExport {
- static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
+#if !ENABLE_GCODE_VIEWER
+ static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
{
// resets time estimators
normal_time_estimator.reset();
@@ -873,8 +902,18 @@ namespace DoExport {
normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
}
}
+#endif // !ENABLE_GCODE_VIEWER
- static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
+#if ENABLE_GCODE_VIEWER
+ static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
+ {
+ silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
+ processor.reset();
+ processor.apply_config(config);
+ processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
+ }
+#else
+ static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
{
// resets analyzer
analyzer.reset();
@@ -898,7 +937,8 @@ namespace DoExport {
// tell analyzer about the gcode flavor
analyzer.set_gcode_flavor(config.gcode_flavor);
- }
+ }
+#endif // ENABLE_GCODE_VIEWER
static double autospeed_volumetric_limit(const Print &print)
{
@@ -1020,24 +1060,28 @@ namespace DoExport {
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
- static std::string update_print_stats_and_format_filament_stats(
- const GCodeTimeEstimator &normal_time_estimator,
+ static std::string update_print_stats_and_format_filament_stats(
+#if !ENABLE_GCODE_VIEWER
+ const GCodeTimeEstimator &normal_time_estimator,
const GCodeTimeEstimator &silent_time_estimator,
const bool silent_time_estimator_enabled,
- const bool has_wipe_tower,
+#endif // !ENABLE_GCODE_VIEWER
+ const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const std::vector<Extruder> &extruders,
PrintStatistics &print_statistics)
- {
+ {
std::string filament_stats_string_out;
print_statistics.clear();
- print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
- print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
- print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true);
- if (silent_time_estimator_enabled)
- print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true);
- print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
+#if !ENABLE_GCODE_VIEWER
+ print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
+ print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
+ print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true);
+ if (silent_time_estimator_enabled)
+ print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true);
+#endif // !ENABLE_GCODE_VIEWER
+ print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
@@ -1127,15 +1171,29 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
{
PROFILE_FUNC();
- DoExport::init_time_estimators(print.config(),
- // modifies the following:
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
+#if ENABLE_GCODE_VIEWER
+ // modifies m_silent_time_estimator_enabled
+ DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
+#else
+ DoExport::init_time_estimators(print.config(),
+ // modifies the following:
+ m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
DoExport::init_gcode_analyzer(print.config(), m_analyzer);
+#endif // ENABLE_GCODE_VIEWER
// resets analyzer's tracking data
+#if ENABLE_GCODE_VIEWER
+ m_last_height = 0.0f;
+ m_last_layer_z = 0.0f;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_last_mm3_per_mm = 0.0;
+ m_last_width = 0.0f;
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#else
m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
m_last_width = GCodeAnalyzer::Default_Width;
m_last_height = GCodeAnalyzer::Default_Height;
+#endif // ENABLE_GCODE_VIEWER
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
@@ -1226,12 +1284,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
print.throw_if_canceled();
// adds tags for time estimators
+#if ENABLE_GCODE_VIEWER
if (print.config().remaining_times.value)
- {
+ _writeln(file, GCodeProcessor::First_Line_M73_Placeholder_Tag);
+#else
+ if (print.config().remaining_times.value) {
_writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
if (m_silent_time_estimator_enabled)
_writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
}
+#endif // ENABLE_GCODE_VIEWER
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
@@ -1336,9 +1398,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
+#if ENABLE_GCODE_VIEWER
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
+#else
if (m_enable_analyzer)
// adds tag for analyzer
_write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+#endif // ENABLE_GCODE_VIEWER
// Write the custom start G-code
_writeln(file, start_gcode);
@@ -1489,9 +1556,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write(file, this->retract());
_write(file, m_writer.set_fan(false));
+#if ENABLE_GCODE_VIEWER
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
+#else
if (m_enable_analyzer)
// adds tag for analyzer
_write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+#endif // ENABLE_GCODE_VIEWER
// Process filament-specific gcode in extruder order.
{
@@ -1516,25 +1588,33 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write(file, m_writer.postamble());
// adds tags for time estimators
+#if ENABLE_GCODE_VIEWER
if (print.config().remaining_times.value)
- {
+ _writeln(file, GCodeProcessor::Last_Line_M73_Placeholder_Tag);
+#else
+ if (print.config().remaining_times.value) {
_writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag);
if (m_silent_time_estimator_enabled)
_writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag);
}
+#endif // ENABLE_GCODE_VIEWER
print.throw_if_canceled();
// calculates estimated printing time
+#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator.calculate_time(false);
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.calculate_time(false);
+#endif // !ENABLE_GCODE_VIEWER
// Get filament stats.
_write(file, DoExport::update_print_stats_and_format_filament_stats(
// Const inputs
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
- has_wipe_tower, print.wipe_tower_data(),
+#if !ENABLE_GCODE_VIEWER
+ m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
+#endif // !ENABLE_GCODE_VIEWER
+ has_wipe_tower, print.wipe_tower_data(),
m_writer.extruders(),
// Modifies
print.m_print_statistics));
@@ -1543,9 +1623,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0)
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
+#if ENABLE_GCODE_VIEWER
+ _writeln(file, GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag);
+#else
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
if (m_silent_time_estimator_enabled)
_write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
+#endif // ENABLE_GCODE_VIEWER
// Append full config.
_write(file, "\n");
@@ -1815,10 +1899,15 @@ namespace ProcessLayer
{
assert(m600_extruder_before_layer >= 0);
// Color Change or Tool Change as Color Change.
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
- // add tag for time estimator
- gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+ // add tag for time estimator
+ gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
// && !MMU1
@@ -1837,20 +1926,32 @@ namespace ProcessLayer
{
if (gcode_type == CustomGCode::PausePrint) // Pause print
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
- //! FIXME_in_fw show message during print pause
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Pause_Print_Tag + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
+ //! FIXME_in_fw show message during print pause
if (!pause_print_msg.empty())
gcode += "M117 " + pause_print_msg + "\n";
- // add tag for time estimator
+#if !ENABLE_GCODE_VIEWER
+ // add tag for time estimator
gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
gcode += config.pause_print_gcode;
- }
+#endif // !ENABLE_GCODE_VIEWER
+ }
else
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
- // add tag for time estimator
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Custom_Code_Tag + "\n";
+#else
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
+#endif // ENABLE_GCODE_VIEWER
+ // add tag for time estimator
//gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
if (gcode_type == CustomGCode::Template) // Template Cistom Gcode
gcode += config.template_custom_gcode;
@@ -1993,6 +2094,22 @@ void GCode::process_layer(
std::string gcode;
+#if ENABLE_GCODE_VIEWER
+ // add tag for processor
+ gcode += "; " + GCodeProcessor::Layer_Change_Tag + "\n";
+ // export layer z
+ char buf[64];
+ sprintf(buf, ";Z:%g\n", print_z);
+ gcode += buf;
+ // export layer height
+ float height = first_layer ? static_cast<float>(print_z) : static_cast<float>(print_z) - m_last_layer_z;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), height);
+ gcode += buf;
+ // update caches
+ m_last_layer_z = static_cast<float>(print_z);
+ m_last_height = height;
+#endif // ENABLE_GCODE_VIEWER
+
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config().before_layer_gcode.value.empty()) {
DynamicConfig config;
@@ -2206,9 +2323,15 @@ void GCode::process_layer(
m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) :
this->set_extruder(extruder_id, print_z);
+#if ENABLE_GCODE_VIEWER
+ // let analyzer tag generator aware of a role type change
+ if (layer_tools.has_wipe_tower && m_wipe_tower)
+ m_last_processor_extrusion_role = erWipeTower;
+#else
// let analyzer tag generator aware of a role type change
if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower)
m_last_analyzer_extrusion_role = erWipeTower;
+#endif // ENABLE_GCODE_VIEWER
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair<size_t, size_t> loops = loops_it->second;
@@ -2312,11 +2435,13 @@ void GCode::process_layer(
if (m_cooling_buffer)
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
+#if !ENABLE_GCODE_VIEWER
// add tag for analyzer
if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos)
gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos)
gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
+#endif // !ENABLE_GCODE_VIEWER
#ifdef HAS_PRESSURE_EQUALIZER
// Apply pressure equalization if enabled;
@@ -2327,12 +2452,14 @@ void GCode::process_layer(
#endif /* HAS_PRESSURE_EQUALIZER */
_write(file, gcode);
- BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
+#if !ENABLE_GCODE_VIEWER
+ BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
", time estimator memory: " <<
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
- ", analyzer memory: " <<
+ ", analyzer memory: " <<
format_memsize_MB(m_analyzer.memory_used()) <<
- log_memory_info();
+ log_memory_info();
+#endif // !ENABLE_GCODE_VIEWER
}
void GCode::apply_print_config(const PrintConfig &print_config)
@@ -2972,15 +3099,21 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
void GCode::_write(FILE* file, const char *what)
{
if (what != nullptr) {
+#if ENABLE_GCODE_VIEWER
+ const char* gcode = what;
+#else
// apply analyzer, if enabled
const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what;
+#endif // !ENABLE_GCODE_VIEWER
// writes string to file
fwrite(gcode, 1, ::strlen(gcode), file);
+#if !ENABLE_GCODE_VIEWER
// updates time estimator and gcode lines vector
m_normal_time_estimator.add_gcode_block(gcode);
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.add_gcode_block(gcode);
+#endif // !ENABLE_GCODE_VIEWER
}
}
@@ -3119,42 +3252,71 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
}
- // adds analyzer tags and updates analyzer's tracking data
- if (m_enable_analyzer)
- {
+ // adds processor tags and updates processor tracking data
+#if ENABLE_GCODE_VIEWER
+ // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height
+ // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines
+ bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower);
+#else
+ if (m_enable_analyzer) {
// PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width
// so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines
bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower);
+#endif // ENABLE_GCODE_VIEWER
char buf[64];
- if (path.role() != m_last_analyzer_extrusion_role)
- {
+#if ENABLE_GCODE_VIEWER
+ if (path.role() != m_last_processor_extrusion_role) {
+ m_last_processor_extrusion_role = path.role();
+ sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str());
+ gcode += buf;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) {
+ m_last_mm3_per_mm = path.mm3_per_mm;
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
+ gcode += buf;
+ }
+
+ if (last_was_wipe_tower || m_last_width != path.width) {
+ m_last_width = path.width;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width);
+ gcode += buf;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) {
+ m_last_height = path.height;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height);
+ gcode += buf;
+ }
+#else
+ if (path.role() != m_last_analyzer_extrusion_role) {
m_last_analyzer_extrusion_role = path.role();
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role));
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm))
- {
+ if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) {
m_last_mm3_per_mm = path.mm3_per_mm;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_width != path.width))
- {
+ if (last_was_wipe_tower || m_last_width != path.width) {
m_last_width = path.width;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width);
gcode += buf;
}
- if (last_was_wipe_tower || (m_last_height != path.height))
- {
+ if (last_was_wipe_tower || m_last_height != path.height) {
m_last_height = path.height;
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height);
gcode += buf;
}
}
+#endif // ENABLE_GCODE_VIEWER
std::string comment;
if (m_enable_cooling_markers) {
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 8d4733783..8bae2ef43 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -13,9 +13,13 @@
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode/GCodeProcessor.hpp"
+#else
+#include "GCode/Analyzer.hpp"
#include "GCodeTimeEstimator.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "EdgeGrid.hpp"
-#include "GCode/Analyzer.hpp"
#include "GCode/ThumbnailData.hpp"
#include <memory>
@@ -29,7 +33,9 @@ namespace Slic3r {
// Forward declarations.
class GCode;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
namespace { struct Item; }
struct PrintInstance;
@@ -138,6 +144,15 @@ private:
double m_last_wipe_tower_print_z = 0.f;
};
+#if ENABLE_GCODE_VIEWER
+class ColorPrintColors
+{
+ static const std::vector<std::string> Colors;
+public:
+ static const std::vector<std::string>& get() { return Colors; }
+};
+#endif // ENABLE_GCODE_VIEWER
+
class GCode {
public:
GCode() :
@@ -145,21 +160,33 @@ public:
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
m_enable_extrusion_role_markers(false),
+#if ENABLE_GCODE_VIEWER
+ m_last_processor_extrusion_role(erNone),
+#else
m_enable_analyzer(false),
m_last_analyzer_extrusion_role(erNone),
+#endif // ENABLE_GCODE_VIEWER
m_layer_count(0),
m_layer_index(-1),
m_layer(nullptr),
m_volumetric_speed(0),
m_last_pos_defined(false),
m_last_extrusion_role(erNone),
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_last_mm3_per_mm(0.0),
+ m_last_width(0.0f),
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#if !ENABLE_GCODE_VIEWER
m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm),
m_last_width(GCodeAnalyzer::Default_Width),
m_last_height(GCodeAnalyzer::Default_Height),
+#endif // !ENABLE_GCODE_VIEWER
m_brim_done(false),
m_second_layer_things_done(false),
+#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator(GCodeTimeEstimator::Normal),
m_silent_time_estimator(GCodeTimeEstimator::Silent),
+#endif // !ENABLE_GCODE_VIEWER
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
@@ -167,7 +194,11 @@ public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
+#if ENABLE_GCODE_VIEWER
+ void do_export(Print* print, const char* path, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#else
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#endif // ENABLE_GCODE_VIEWER
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
@@ -327,11 +358,16 @@ private:
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
bool m_enable_extrusion_role_markers;
+#if ENABLE_GCODE_VIEWER
+ // Keeps track of the last extrusion role passed to the processor
+ ExtrusionRole m_last_processor_extrusion_role;
+#else
// Enableds the G-code Analyzer.
// Extended markers will be added during G-code generation.
// The G-code Analyzer will remove these comments from the final G-code.
bool m_enable_analyzer;
ExtrusionRole m_last_analyzer_extrusion_role;
+#endif // ENABLE_GCODE_VIEWER
// How many times will change_layer() be called?
// change_layer() will update the progress bar.
unsigned int m_layer_count;
@@ -344,10 +380,20 @@ private:
double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role;
+#if ENABLE_GCODE_VIEWER
+ // Support for G-Code Processor
+ float m_last_height{ 0.0f };
+ float m_last_layer_z{ 0.0f };
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ double m_last_mm3_per_mm;
+ float m_last_width{ 0.0f };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+#else
// Support for G-Code Analyzer
double m_last_mm3_per_mm;
float m_last_width;
float m_last_height;
+#endif // ENABLE_GCODE_VIEWER
Point m_last_pos;
bool m_last_pos_defined;
@@ -368,13 +414,20 @@ private:
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
+#if !ENABLE_GCODE_VIEWER
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;
GCodeTimeEstimator m_silent_time_estimator;
+#endif // !ENABLE_GCODE_VIEWER
bool m_silent_time_estimator_enabled;
+#if ENABLE_GCODE_VIEWER
+ // Processor
+ GCodeProcessor m_processor;
+#else
// Analyzer
GCodeAnalyzer m_analyzer;
+#endif // ENABLE_GCODE_VIEWER
// Write a string into a file.
void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); }
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index 73c20f1b5..d022b3798 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -12,6 +12,8 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
+#if !ENABLE_GCODE_VIEWER
+
static const std::string AXIS_STR = "XYZE";
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float INCHES_TO_MM = 25.4f;
@@ -350,7 +352,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
if (delta_pos[E] < 0.0f)
{
if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
- type = GCodeMove::Move;
+ type = GCodeMove::Move;
else
type = GCodeMove::Retract;
}
@@ -651,7 +653,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // pause print tag
pos = comment.find(Pause_Print_Tag);
if (pos != comment.npos)
{
@@ -659,7 +661,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // custom code tag
pos = comment.find(Custom_Code_Tag);
if (pos != comment.npos)
{
@@ -667,7 +669,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
- // color change tag
+ // end pause print or custom code tag
pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag);
if (pos != comment.npos)
{
@@ -1191,3 +1193,5 @@ size_t GCodeAnalyzer::memory_used() const
}
} // namespace Slic3r
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
index 4ac383fea..37d907259 100644
--- a/src/libslic3r/GCode/Analyzer.hpp
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -1,6 +1,8 @@
#ifndef slic3r_GCode_Analyzer_hpp_
#define slic3r_GCode_Analyzer_hpp_
+#if !ENABLE_GCODE_VIEWER
+
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
@@ -302,4 +304,6 @@ private:
} // namespace Slic3r
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCode_Analyzer_hpp_ */
diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
new file mode 100644
index 000000000..54addbd97
--- /dev/null
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -0,0 +1,2162 @@
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Print.hpp"
+#include "GCodeProcessor.hpp"
+
+#include <boost/log/trivial.hpp>
+#include <boost/nowide/fstream.hpp>
+
+#include <float.h>
+#include <assert.h>
+
+#if ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+#include <chrono>
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+static const float INCHES_TO_MM = 25.4f;
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+
+static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+
+namespace Slic3r {
+
+const std::string GCodeProcessor::Extrusion_Role_Tag = "TYPE:";
+const std::string GCodeProcessor::Height_Tag = "HEIGHT:";
+const std::string GCodeProcessor::Layer_Change_Tag = "LAYER_CHANGE";
+const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE";
+const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT";
+const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_GCODE";
+
+const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _GP_FIRST_LINE_M73_PLACEHOLDER";
+const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER";
+const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER";
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+const std::string GCodeProcessor::Width_Tag = "WIDTH:";
+const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:";
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+static bool is_valid_extrusion_role(int value)
+{
+ return (static_cast<int>(erNone) <= value) && (value <= static_cast<int>(erMixed));
+}
+
+static void set_option_value(ConfigOptionFloats& option, size_t id, float value)
+{
+ if (id < option.values.size())
+ option.values[id] = static_cast<double>(value);
+};
+
+static float get_option_value(const ConfigOptionFloats& option, size_t id)
+{
+ return option.values.empty() ? 0.0f :
+ ((id < option.values.size()) ? static_cast<float>(option.values[id]) : static_cast<float>(option.values.back()));
+}
+
+static float estimated_acceleration_distance(float initial_rate, float target_rate, float acceleration)
+{
+ return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration);
+}
+
+static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance)
+{
+ return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
+}
+
+static float speed_from_distance(float initial_feedrate, float distance, float acceleration)
+{
+ // to avoid invalid negative numbers due to numerical errors
+ float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance);
+ return ::sqrt(value);
+}
+
+// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
+// acceleration within the allotted distance.
+static float max_allowable_speed(float acceleration, float target_velocity, float distance)
+{
+ // to avoid invalid negative numbers due to numerical errors
+ float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance);
+ return std::sqrt(value);
+}
+
+static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration)
+{
+ return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f;
+}
+
+void GCodeProcessor::CachedPosition::reset()
+{
+ std::fill(position.begin(), position.end(), FLT_MAX);
+ feedrate = FLT_MAX;
+}
+
+void GCodeProcessor::CpColor::reset()
+{
+ counter = 0;
+ current = 0;
+}
+
+float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const
+{
+ return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration);
+}
+
+float GCodeProcessor::Trapezoid::cruise_time() const
+{
+ return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f;
+}
+
+float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const
+{
+ return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration);
+}
+
+float GCodeProcessor::Trapezoid::cruise_distance() const
+{
+ return decelerate_after - accelerate_until;
+}
+
+void GCodeProcessor::TimeBlock::calculate_trapezoid()
+{
+ trapezoid.cruise_feedrate = feedrate_profile.cruise;
+
+ float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration));
+ float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration));
+ float cruise_distance = distance - accelerate_distance - decelerate_distance;
+
+ // Not enough space to reach the nominal feedrate.
+ // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration
+ // and start braking in order to reach the exit_feedrate exactly at the end of this block.
+ if (cruise_distance < 0.0f) {
+ accelerate_distance = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance);
+ cruise_distance = 0.0f;
+ trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration);
+ }
+
+ trapezoid.accelerate_until = accelerate_distance;
+ trapezoid.decelerate_after = accelerate_distance + cruise_distance;
+}
+
+float GCodeProcessor::TimeBlock::time() const
+{
+ return trapezoid.acceleration_time(feedrate_profile.entry, acceleration)
+ + trapezoid.cruise_time()
+ + trapezoid.deceleration_time(distance, acceleration);
+}
+
+void GCodeProcessor::TimeMachine::State::reset()
+{
+ feedrate = 0.0f;
+ safe_feedrate = 0.0f;
+ axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f };
+ abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f };
+}
+
+void GCodeProcessor::TimeMachine::CustomGCodeTime::reset()
+{
+ needed = false;
+ cache = 0.0f;
+ times = std::vector<std::pair<CustomGCode::Type, float>>();
+}
+
+void GCodeProcessor::TimeMachine::reset()
+{
+ enabled = false;
+ acceleration = 0.0f;
+ max_acceleration = 0.0f;
+ extrude_factor_override_percentage = 1.0f;
+ time = 0.0f;
+ curr.reset();
+ prev.reset();
+ gcode_time.reset();
+ blocks = std::vector<TimeBlock>();
+ g1_times_cache = std::vector<float>();
+ std::fill(moves_time.begin(), moves_time.end(), 0.0f);
+ std::fill(roles_time.begin(), roles_time.end(), 0.0f);
+ layers_time = std::vector<float>();
+}
+
+void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time)
+{
+ if (!enabled)
+ return;
+
+ time += additional_time;
+ gcode_time.cache += additional_time;
+ calculate_time();
+}
+
+static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr)
+{
+ // If the previous block is an acceleration block, but it is not long enough to complete the
+ // full speed change within the block, we need to adjust the entry speed accordingly. Entry
+ // speeds have already been reset, maximized, and reverse planned by reverse planner.
+ // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck.
+ if (!prev.flags.nominal_length) {
+ if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) {
+ float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance));
+
+ // Check for junction speed change
+ if (curr.feedrate_profile.entry != entry_speed) {
+ curr.feedrate_profile.entry = entry_speed;
+ curr.flags.recalculate = true;
+ }
+ }
+ }
+}
+
+void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& next)
+{
+ // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising.
+ // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and
+ // check for maximum allowable speed reductions to ensure maximum possible planned speed.
+ if (curr.feedrate_profile.entry != curr.max_entry_speed) {
+ // If nominal length true, max junction speed is guaranteed to be reached. Only compute
+ // for max allowable speed if block is decelerating and nominal length is false.
+ if (!curr.flags.nominal_length && curr.max_entry_speed > next.feedrate_profile.entry)
+ curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance));
+ else
+ curr.feedrate_profile.entry = curr.max_entry_speed;
+
+ curr.flags.recalculate = true;
+ }
+}
+
+static void recalculate_trapezoids(std::vector<GCodeProcessor::TimeBlock>& blocks)
+{
+ GCodeProcessor::TimeBlock* curr = nullptr;
+ GCodeProcessor::TimeBlock* next = nullptr;
+
+ for (size_t i = 0; i < blocks.size(); ++i) {
+ GCodeProcessor::TimeBlock& b = blocks[i];
+
+ curr = next;
+ next = &b;
+
+ if (curr != nullptr) {
+ // Recalculate if current block entry or exit junction speed has changed.
+ if (curr->flags.recalculate || next->flags.recalculate) {
+ // NOTE: Entry and exit factors always > 0 by all previous logic operations.
+ GCodeProcessor::TimeBlock block = *curr;
+ block.feedrate_profile.exit = next->feedrate_profile.entry;
+ block.calculate_trapezoid();
+ curr->trapezoid = block.trapezoid;
+ curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed
+ }
+ }
+ }
+
+ // Last/newest block in buffer. Always recalculated.
+ if (next != nullptr) {
+ GCodeProcessor::TimeBlock block = *next;
+ block.feedrate_profile.exit = next->safe_feedrate;
+ block.calculate_trapezoid();
+ next->trapezoid = block.trapezoid;
+ next->flags.recalculate = false;
+ }
+}
+
+void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
+{
+ if (!enabled || blocks.size() < 2)
+ return;
+
+ assert(keep_last_n_blocks <= blocks.size());
+
+ // forward_pass
+ for (size_t i = 0; i + 1 < blocks.size(); ++i) {
+ planner_forward_pass_kernel(blocks[i], blocks[i + 1]);
+ }
+
+ // reverse_pass
+ for (int i = static_cast<int>(blocks.size()) - 1; i > 0; --i)
+ planner_reverse_pass_kernel(blocks[i - 1], blocks[i]);
+
+ recalculate_trapezoids(blocks);
+
+ size_t n_blocks_process = blocks.size() - keep_last_n_blocks;
+ for (size_t i = 0; i < n_blocks_process; ++i) {
+ const TimeBlock& block = blocks[i];
+ float block_time = block.time();
+ time += block_time;
+ gcode_time.cache += block_time;
+ moves_time[static_cast<size_t>(block.move_type)] += block_time;
+ roles_time[static_cast<size_t>(block.role)] += block_time;
+ if (block.layer_id > 0) {
+ if (block.layer_id >= layers_time.size()) {
+ size_t curr_size = layers_time.size();
+ layers_time.resize(block.layer_id);
+ for (size_t i = curr_size; i < layers_time.size(); ++i) {
+ layers_time[i] = 0.0f;
+ }
+ }
+ layers_time[block.layer_id - 1] += block_time;
+ }
+ g1_times_cache.push_back(time);
+ }
+
+ if (keep_last_n_blocks)
+ blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process);
+ else
+ blocks.clear();
+}
+
+void GCodeProcessor::TimeProcessor::reset()
+{
+ extruder_unloaded = true;
+ export_remaining_time_enabled = false;
+ machine_envelope_processing_enabled = false;
+ machine_limits = MachineEnvelopeConfig();
+ filament_load_times = std::vector<float>();
+ filament_unload_times = std::vector<float>();
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ machines[i].reset();
+ }
+ machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true;
+}
+
+void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
+{
+ boost::nowide::ifstream in(filename);
+ if (!in.good())
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
+
+ // temporary file to contain modified gcode
+ std::string out_path = filename + ".postprocess";
+ FILE* out = boost::nowide::fopen(out_path.c_str(), "wb");
+ if (out == nullptr)
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
+
+ auto time_in_minutes = [](float time_in_seconds) {
+ return int(::roundf(time_in_seconds / 60.0f));
+ };
+
+ auto format_line_M73 = [](const std::string& mask, int percent, int time) {
+ char line_M73[64];
+ sprintf(line_M73, mask.c_str(),
+ std::to_string(percent).c_str(),
+ std::to_string(time).c_str());
+ return std::string(line_M73);
+ };
+
+ GCodeReader parser;
+ std::string gcode_line;
+ size_t g1_lines_counter = 0;
+ // keeps track of last exported pair <percent, remaining time>
+ std::array<std::pair<int, int>, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported;
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ last_exported[i] = { 0, time_in_minutes(machines[i].time) };
+ }
+
+ // buffer line to export only when greater than 64K to reduce writing calls
+ std::string export_line;
+
+ // replace placeholder lines with the proper final value
+ auto process_placeholders = [&](const std::string& gcode_line) {
+ // remove trailing '\n'
+ std::string line = gcode_line.substr(0, gcode_line.length() - 1);
+
+ std::string ret;
+
+ if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) {
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled) {
+ ret += format_line_M73(machine.line_m73_mask.c_str(),
+ (line == First_Line_M73_Placeholder_Tag) ? 0 : 100,
+ (line == First_Line_M73_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0);
+ }
+ }
+ }
+ else if (line == Estimated_Printing_Time_Placeholder_Tag) {
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled) {
+ char buf[128];
+ sprintf(buf, "; estimated printing time (%s mode) = %s\n",
+ (static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
+ get_time_dhms(machine.time).c_str());
+ ret += buf;
+ }
+ }
+ }
+
+ return std::make_pair(!ret.empty(), ret.empty() ? gcode_line : ret);
+ };
+
+ // check for temporary lines
+ auto is_temporary_decoration = [](const std::string& gcode_line) {
+ // remove trailing '\n'
+ std::string line = gcode_line.substr(0, gcode_line.length() - 1);
+ if (line == "; " + Layer_Change_Tag)
+ return true;
+ else
+ return false;
+ };
+
+ // add lines M73 to exported gcode
+ auto process_line_G1 = [&]() {
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ const TimeMachine& machine = machines[i];
+ if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) {
+ float elapsed_time = machine.g1_times_cache[g1_lines_counter];
+ std::pair<int, int> to_export = { int(::roundf(100.0f * elapsed_time / machine.time)),
+ time_in_minutes(machine.time - elapsed_time) };
+ if (last_exported[i] != to_export) {
+ export_line += format_line_M73(machine.line_m73_mask.c_str(),
+ to_export.first, to_export.second);
+ last_exported[i] = to_export;
+ }
+ }
+ }
+ };
+
+ // helper function to write to disk
+ auto write_string = [&](const std::string& str) {
+ fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
+ if (ferror(out)) {
+ in.close();
+ fclose(out);
+ boost::nowide::remove(out_path.c_str());
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
+ }
+ export_line.clear();
+ };
+
+ while (std::getline(in, gcode_line)) {
+ if (!in.good()) {
+ fclose(out);
+ throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
+ }
+
+ gcode_line += "\n";
+ // replace placeholder lines
+ auto [processed, result] = process_placeholders(gcode_line);
+ gcode_line = result;
+ if (!processed) {
+ // remove temporary lines
+ if (is_temporary_decoration(gcode_line))
+ continue;
+
+ // add lines M73 where needed
+ parser.parse_line(gcode_line,
+ [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
+ if (line.cmd_is("G1")) {
+ process_line_G1();
+ ++g1_lines_counter;
+ }
+ });
+ }
+
+ export_line += gcode_line;
+ if (export_line.length() > 65535)
+ write_string(export_line);
+ }
+
+ if (!export_line.empty())
+ write_string(export_line);
+
+ fclose(out);
+ in.close();
+
+ if (rename_file(out_path, filename))
+ throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
+ "Is " + out_path + " locked?" + '\n');
+}
+
+const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
+ { EProducer::PrusaSlicer, "PrusaSlicer" },
+ { EProducer::Cura, "Cura_SteamEngine" },
+ { EProducer::Simplify3D, "Simplify3D" },
+ { EProducer::CraftWare, "CraftWare" },
+ { EProducer::ideaMaker, "ideaMaker" }
+};
+
+unsigned int GCodeProcessor::s_result_id = 0;
+
+GCodeProcessor::GCodeProcessor()
+{
+ reset();
+ m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n";
+ m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n";
+}
+
+void GCodeProcessor::apply_config(const PrintConfig& config)
+{
+ m_parser.apply_config(config);
+
+ m_flavor = config.gcode_flavor;
+
+ size_t extruders_count = config.nozzle_diameter.values.size();
+
+ m_extruder_offsets.resize(extruders_count);
+ for (size_t i = 0; i < extruders_count; ++i) {
+ Vec2f offset = config.extruder_offset.get_at(i).cast<float>();
+ m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
+ }
+
+ m_extruder_colors.resize(extruders_count);
+ for (size_t i = 0; i < extruders_count; ++i) {
+ m_extruder_colors[i] = static_cast<unsigned char>(i);
+ }
+
+ m_filament_diameters.resize(config.filament_diameter.values.size());
+ for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) {
+ m_filament_diameters[i] = static_cast<float>(config.filament_diameter.values[i]);
+ }
+
+ m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
+ // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
+ // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+ // are considered to be active for the single extruder multi-material printers only.
+ m_time_processor.filament_load_times.resize(config.filament_load_time.values.size());
+ for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) {
+ m_time_processor.filament_load_times[i] = static_cast<float>(config.filament_load_time.values[i]);
+ }
+ m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size());
+ for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) {
+ m_time_processor.filament_unload_times[i] = static_cast<float>(config.filament_unload_time.values[i]);
+ }
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
+ m_time_processor.machines[i].max_acceleration = max_acceleration;
+ m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
+ }
+
+ m_time_processor.export_remaining_time_enabled = config.remaining_times.value;
+}
+
+void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
+{
+ m_parser.apply_config(config);
+
+ const ConfigOptionEnum<GCodeFlavor>* gcode_flavor = config.option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor");
+ if (gcode_flavor != nullptr)
+ m_flavor = gcode_flavor->value;
+
+ const ConfigOptionPoints* bed_shape = config.option<ConfigOptionPoints>("bed_shape");
+ if (bed_shape != nullptr)
+ m_result.bed_shape = bed_shape->values;
+
+ const ConfigOptionString* printer_settings_id = config.option<ConfigOptionString>("printer_settings_id");
+ if (printer_settings_id != nullptr)
+ m_result.printer_settings_id = printer_settings_id->value;
+
+ const ConfigOptionFloats* filament_diameters = config.option<ConfigOptionFloats>("filament_diameter");
+ if (filament_diameters != nullptr) {
+ for (double diam : filament_diameters->values) {
+ m_filament_diameters.push_back(static_cast<float>(diam));
+ }
+ }
+
+ const ConfigOptionPoints* extruder_offset = config.option<ConfigOptionPoints>("extruder_offset");
+ if (extruder_offset != nullptr) {
+ m_extruder_offsets.resize(extruder_offset->values.size());
+ for (size_t i = 0; i < extruder_offset->values.size(); ++i) {
+ Vec2f offset = extruder_offset->values[i].cast<float>();
+ m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
+ }
+ }
+
+ // ensure at least one (default) color is defined
+ std::string default_color = "#FF8000";
+ m_result.extruder_colors = std::vector<std::string>(1, default_color);
+ const ConfigOptionStrings* extruder_colour = config.option<ConfigOptionStrings>("extruder_colour");
+ if (extruder_colour != nullptr) {
+ // takes colors from config
+ m_result.extruder_colors = extruder_colour->values;
+ // try to replace missing values with filament colors
+ const ConfigOptionStrings* filament_colour = config.option<ConfigOptionStrings>("filament_colour");
+ if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) {
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ if (m_result.extruder_colors[i].empty())
+ m_result.extruder_colors[i] = filament_colour->values[i];
+ }
+ }
+ }
+
+ // replace missing values with default
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ if (m_result.extruder_colors[i].empty())
+ m_result.extruder_colors[i] = default_color;
+ }
+
+ m_extruder_colors.resize(m_result.extruder_colors.size());
+ for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
+ m_extruder_colors[i] = static_cast<unsigned char>(i);
+ }
+
+ const ConfigOptionFloats* filament_load_time = config.option<ConfigOptionFloats>("filament_load_time");
+ if (filament_load_time != nullptr) {
+ m_time_processor.filament_load_times.resize(filament_load_time->values.size());
+ for (size_t i = 0; i < filament_load_time->values.size(); ++i) {
+ m_time_processor.filament_load_times[i] = static_cast<float>(filament_load_time->values[i]);
+ }
+ }
+
+ const ConfigOptionFloats* filament_unload_time = config.option<ConfigOptionFloats>("filament_unload_time");
+ if (filament_unload_time != nullptr) {
+ m_time_processor.filament_unload_times.resize(filament_unload_time->values.size());
+ for (size_t i = 0; i < filament_unload_time->values.size(); ++i) {
+ m_time_processor.filament_unload_times[i] = static_cast<float>(filament_unload_time->values[i]);
+ }
+ }
+
+ const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("machine_max_acceleration_x");
+ if (machine_max_acceleration_x != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_y = config.option<ConfigOptionFloats>("machine_max_acceleration_y");
+ if (machine_max_acceleration_y != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_z = config.option<ConfigOptionFloats>("machine_max_acceleration_z");
+ if (machine_max_acceleration_z != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_e = config.option<ConfigOptionFloats>("machine_max_acceleration_e");
+ if (machine_max_acceleration_e != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_x = config.option<ConfigOptionFloats>("machine_max_feedrate_x");
+ if (machine_max_feedrate_x != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_y = config.option<ConfigOptionFloats>("machine_max_feedrate_y");
+ if (machine_max_feedrate_y != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_z = config.option<ConfigOptionFloats>("machine_max_feedrate_z");
+ if (machine_max_feedrate_z != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values;
+
+ const ConfigOptionFloats* machine_max_feedrate_e = config.option<ConfigOptionFloats>("machine_max_feedrate_e");
+ if (machine_max_feedrate_e != nullptr)
+ m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values;
+
+ const ConfigOptionFloats* machine_max_jerk_x = config.option<ConfigOptionFloats>("machine_max_jerk_x");
+ if (machine_max_jerk_x != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values;
+
+ const ConfigOptionFloats* machine_max_jerk_y = config.option<ConfigOptionFloats>("machine_max_jerk_y");
+ if (machine_max_jerk_y != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values;
+
+ const ConfigOptionFloats* machine_max_jerk_z = config.option<ConfigOptionFloats>("machine_max_jerkz");
+ if (machine_max_jerk_z != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values;
+
+ const ConfigOptionFloats* machine_max_jerk_e = config.option<ConfigOptionFloats>("machine_max_jerk_e");
+ if (machine_max_jerk_e != nullptr)
+ m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_extruding = config.option<ConfigOptionFloats>("machine_max_acceleration_extruding");
+ if (machine_max_acceleration_extruding != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values;
+
+ const ConfigOptionFloats* machine_max_acceleration_retracting = config.option<ConfigOptionFloats>("machine_max_acceleration_retracting");
+ if (machine_max_acceleration_retracting != nullptr)
+ m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values;
+
+ const ConfigOptionFloats* machine_min_extruding_rate = config.option<ConfigOptionFloats>("machine_min_extruding_rate");
+ if (machine_min_extruding_rate != nullptr)
+ m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values;
+
+ const ConfigOptionFloats* machine_min_travel_rate = config.option<ConfigOptionFloats>("machine_min_travel_rate");
+ if (machine_min_travel_rate != nullptr)
+ m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values;
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
+ m_time_processor.machines[i].max_acceleration = max_acceleration;
+ m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
+ }
+}
+
+void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
+{
+ m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled;
+}
+
+void GCodeProcessor::reset()
+{
+ static const size_t Min_Extruder_Count = 5;
+
+ m_units = EUnits::Millimeters;
+ m_global_positioning_type = EPositioningType::Absolute;
+ m_e_local_positioning_type = EPositioningType::Absolute;
+ m_extruder_offsets = std::vector<Vec3f>(Min_Extruder_Count, Vec3f::Zero());
+ m_flavor = gcfRepRap;
+
+ m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_origin = { 0.0f, 0.0f, 0.0f, 0.0f };
+ m_cached_position.reset();
+
+ m_feedrate = 0.0f;
+ m_width = 0.0f;
+ m_height = 0.0f;
+ m_mm3_per_mm = 0.0f;
+ m_fan_speed = 0.0f;
+
+ m_extrusion_role = erNone;
+ m_extruder_id = 0;
+ m_extruder_colors.resize(Min_Extruder_Count);
+ for (size_t i = 0; i < Min_Extruder_Count; ++i) {
+ m_extruder_colors[i] = static_cast<unsigned char>(i);
+ }
+
+ m_filament_diameters = std::vector<float>(Min_Extruder_Count, 1.75f);
+ m_extruded_last_z = 0.0f;
+ m_layer_id = 0;
+ m_cp_color.reset();
+
+ m_producer = EProducer::Unknown;
+ m_producers_enabled = false;
+
+ m_time_processor.reset();
+
+ m_result.reset();
+ m_result.id = ++s_result_id;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_mm3_per_mm_compare.reset();
+ m_height_compare.reset();
+ m_width_compare.reset();
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+}
+
+void GCodeProcessor::process_file(const std::string& filename)
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ // pre-processing
+ // parse the gcode file to detect its producer
+ if (m_producers_enabled) {
+ m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
+ std::string cmd = line.cmd();
+ if (cmd.length() == 0) {
+ std::string comment = line.comment();
+ if (comment.length() > 1 && detect_producer(comment))
+ m_parser.quit_parsing_file();
+ }
+ });
+
+ // if the gcode was produced by PrusaSlicer,
+ // extract the config from it
+ if (m_producer == EProducer::PrusaSlicer) {
+ DynamicPrintConfig config;
+ config.apply(FullPrintConfig::defaults());
+ config.load_from_gcode_file(filename);
+ apply_config(config);
+ }
+ }
+
+ m_result.id = ++s_result_id;
+ m_result.moves.emplace_back(MoveVertex());
+ m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); });
+
+ // process the time blocks
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time;
+ machine.calculate_time();
+ if (gcode_time.needed && gcode_time.cache != 0.0f)
+ gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache });
+ }
+
+ update_estimated_times_stats();
+
+ // post-process to add M73 lines into the gcode
+ if (m_time_processor.export_remaining_time_enabled)
+ m_time_processor.post_process(filename);
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ std::cout << "\n";
+ m_mm3_per_mm_compare.output();
+ m_height_compare.output();
+ m_width_compare.output();
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast<size_t>(mode)].time : 0.0f;
+}
+
+std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast<size_t>(mode)].time)) : std::string("N/A");
+}
+
+std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const
+{
+ std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ const TimeMachine& machine = m_time_processor.machines[static_cast<size_t>(mode)];
+ float total_time = 0.0f;
+ for (const auto& [type, time] : machine.gcode_time.times) {
+ float remaining = include_remaining ? machine.time - total_time : 0.0f;
+ ret.push_back({ type, { time, remaining } });
+ total_time += time;
+ }
+ }
+ return ret;
+}
+
+std::vector<std::pair<EMoveType, float>> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ std::vector<std::pair<EMoveType, float>> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ for (size_t i = 0; i < m_time_processor.machines[static_cast<size_t>(mode)].moves_time.size(); ++i) {
+ float time = m_time_processor.machines[static_cast<size_t>(mode)].moves_time[i];
+ if (time > 0.0f)
+ ret.push_back({ static_cast<EMoveType>(i), time });
+ }
+ }
+ return ret;
+}
+
+std::vector<std::pair<ExtrusionRole, float>> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ std::vector<std::pair<ExtrusionRole, float>> ret;
+ if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) {
+ for (size_t i = 0; i < m_time_processor.machines[static_cast<size_t>(mode)].roles_time.size(); ++i) {
+ float time = m_time_processor.machines[static_cast<size_t>(mode)].roles_time[i];
+ if (time > 0.0f)
+ ret.push_back({ static_cast<ExtrusionRole>(i), time });
+ }
+ }
+ return ret;
+}
+
+std::vector<float> GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ?
+ m_time_processor.machines[static_cast<size_t>(mode)].layers_time :
+ std::vector<float>();
+}
+
+void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
+{
+/* std::cout << line.raw() << std::endl; */
+
+ // update start position
+ m_start_position = m_end_position;
+
+ std::string cmd = line.cmd();
+ if (cmd.length() > 1) {
+ // process command lines
+ switch (::toupper(cmd[0]))
+ {
+ case 'G':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 0: { process_G0(line); break; } // Move
+ case 1: { process_G1(line); break; } // Move
+ case 10: { process_G10(line); break; } // Retract
+ case 11: { process_G11(line); break; } // Unretract
+ case 20: { process_G20(line); break; } // Set Units to Inches
+ case 21: { process_G21(line); break; } // Set Units to Millimeters
+ case 22: { process_G22(line); break; } // Firmware controlled retract
+ case 23: { process_G23(line); break; } // Firmware controlled unretract
+ case 90: { process_G90(line); break; } // Set to Absolute Positioning
+ case 91: { process_G91(line); break; } // Set to Relative Positioning
+ case 92: { process_G92(line); break; } // Set Position
+ default: { break; }
+ }
+ break;
+ }
+ case 'M':
+ {
+ switch (::atoi(&cmd[1]))
+ {
+ case 1: { process_M1(line); break; } // Sleep or Conditional stop
+ case 82: { process_M82(line); break; } // Set extruder to absolute mode
+ case 83: { process_M83(line); break; } // Set extruder to relative mode
+ case 106: { process_M106(line); break; } // Set fan speed
+ case 107: { process_M107(line); break; } // Disable fan
+ case 108: { process_M108(line); break; } // Set tool (Sailfish)
+ case 132: { process_M132(line); break; } // Recall stored home offsets
+ case 135: { process_M135(line); break; } // Set tool (MakerWare)
+ case 201: { process_M201(line); break; } // Set max printing acceleration
+ case 203: { process_M203(line); break; } // Set maximum feedrate
+ case 204: { process_M204(line); break; } // Set default acceleration
+ case 205: { process_M205(line); break; } // Advanced settings
+ case 221: { process_M221(line); break; } // Set extrude factor override percentage
+ case 401: { process_M401(line); break; } // Repetier: Store x, y and z position
+ case 402: { process_M402(line); break; } // Repetier: Go to stored position
+ case 566: { process_M566(line); break; } // Set allowable instantaneous speed change
+ case 702: { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print.
+ default: { break; }
+ }
+ break;
+ }
+ case 'T':
+ {
+ process_T(line); // Select Tool
+ break;
+ }
+ default: { break; }
+ }
+ }
+ else {
+ std::string comment = line.comment();
+ if (comment.length() > 1)
+ // process tags embedded into comments
+ process_tags(comment);
+ }
+}
+
+void GCodeProcessor::process_tags(const std::string& comment)
+{
+ // producers tags
+ if (m_producers_enabled) {
+ if (m_producer != EProducer::Unknown) {
+ if (process_producers_tags(comment))
+ return;
+ }
+ }
+
+ // extrusion role tag
+ size_t pos = comment.find(Extrusion_Role_Tag);
+ if (pos != comment.npos) {
+ m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(pos + Extrusion_Role_Tag.length()));
+ return;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // width tag
+ pos = comment.find(Width_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(comment.substr(pos + Width_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ return;
+ }
+
+ // height tag
+ pos = comment.find(Height_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ return;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ // color change tag
+ pos = comment.find(Color_Change_Tag);
+ if (pos != comment.npos) {
+ pos = comment.find_last_of(",T");
+ try {
+ unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast<unsigned char>(std::stoi(comment.substr(pos + 1)));
+
+ m_extruder_colors[extruder_id] = static_cast<unsigned char>(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
+ ++m_cp_color.counter;
+ if (m_cp_color.counter == UCHAR_MAX)
+ m_cp_color.counter = 0;
+
+ if (m_extruder_id == extruder_id) {
+ m_cp_color.current = m_extruder_colors[extruder_id];
+ store_move_vertex(EMoveType::Color_change);
+ }
+
+ process_custom_gcode_time(CustomGCode::ColorChange);
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
+ }
+
+ return;
+ }
+
+ // pause print tag
+ pos = comment.find(Pause_Print_Tag);
+ if (pos != comment.npos) {
+ store_move_vertex(EMoveType::Pause_Print);
+ process_custom_gcode_time(CustomGCode::PausePrint);
+ return;
+ }
+
+ // custom code tag
+ pos = comment.find(Custom_Code_Tag);
+ if (pos != comment.npos) {
+ store_move_vertex(EMoveType::Custom_GCode);
+ return;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // mm3_per_mm print tag
+ pos = comment.find(Mm3_Per_Mm_Tag);
+ if (pos != comment.npos) {
+ try {
+ m_mm3_per_mm_compare.last_tag_value = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ").";
+ }
+ return;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ // layer change tag
+ pos = comment.find(Layer_Change_Tag);
+ if (pos != comment.npos) {
+ ++m_layer_id;
+ return;
+ }
+}
+
+bool GCodeProcessor::process_producers_tags(const std::string& comment)
+{
+ switch (m_producer)
+ {
+ case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); }
+ case EProducer::Cura: { return process_cura_tags(comment); }
+ case EProducer::Simplify3D: { return process_simplify3d_tags(comment); }
+ case EProducer::CraftWare: { return process_craftware_tags(comment); }
+ case EProducer::ideaMaker: { return process_ideamaker_tags(comment); }
+ default: { return false; }
+ }
+}
+
+bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment)
+{
+ return false;
+}
+
+bool GCodeProcessor::process_cura_tags(const std::string& comment)
+{
+ // TYPE -> extrusion role
+ std::string tag = "TYPE:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "SKIRT")
+ m_extrusion_role = erSkirt;
+ else if (type == "WALL-OUTER")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "WALL-INNER")
+ m_extrusion_role = erPerimeter;
+ else if (type == "SKIN")
+ m_extrusion_role = erSolidInfill;
+ else if (type == "FILL")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "SUPPORT")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SUPPORT-INTERFACE")
+ m_extrusion_role = erSupportMaterialInterface;
+ else if (type == "PRIME-TOWER")
+ m_extrusion_role = erWipeTower;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+
+ return true;
+ }
+
+ // flavor
+ tag = "FLAVOR:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string flavor = comment.substr(pos + tag.length());
+ if (flavor == "BFB")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Mach3")
+ m_flavor = gcfMach3;
+ else if (flavor == "Makerbot")
+ m_flavor = gcfMakerWare;
+ else if (flavor == "UltiGCode")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Marlin(Volumetric)")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Griffin")
+ m_flavor = gcfMarlin; // << ???????????????????????
+ else if (flavor == "Repetier")
+ m_flavor = gcfRepetier;
+ else if (flavor == "RepRap")
+ m_flavor = gcfRepRap;
+ else if (flavor == "Marlin")
+ m_flavor = gcfMarlin;
+ else
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
+{
+ // extrusion roles
+
+ // ; skirt
+ size_t pos = comment.find(" skirt");
+ if (pos == 0) {
+ m_extrusion_role = erSkirt;
+ return true;
+ }
+
+ // ; outer perimeter
+ pos = comment.find(" outer perimeter");
+ if (pos == 0) {
+ m_extrusion_role = erExternalPerimeter;
+ return true;
+ }
+
+ // ; inner perimeter
+ pos = comment.find(" inner perimeter");
+ if (pos == 0) {
+ m_extrusion_role = erPerimeter;
+ return true;
+ }
+
+ // ; gap fill
+ pos = comment.find(" gap fill");
+ if (pos == 0) {
+ m_extrusion_role = erGapFill;
+ return true;
+ }
+
+ // ; infill
+ pos = comment.find(" infill");
+ if (pos == 0) {
+ m_extrusion_role = erInternalInfill;
+ return true;
+ }
+
+ // ; solid layer
+ pos = comment.find(" solid layer");
+ if (pos == 0) {
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ return true;
+ }
+
+ // ; bridge
+ pos = comment.find(" bridge");
+ if (pos == 0) {
+ m_extrusion_role = erBridgeInfill;
+ return true;
+ }
+
+ // ; support
+ pos = comment.find(" support");
+ if (pos == 0) {
+ m_extrusion_role = erSupportMaterial;
+ return true;
+ }
+
+ // ; prime pillar
+ pos = comment.find(" prime pillar");
+ if (pos == 0) {
+ m_extrusion_role = erWipeTower;
+ return true;
+ }
+
+ // ; ooze shield
+ pos = comment.find(" ooze shield");
+ if (pos == 0) {
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ return true;
+ }
+
+ // ; raft
+ pos = comment.find(" raft");
+ if (pos == 0) {
+ m_extrusion_role = erSkirt;
+ return true;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // geometry
+
+ // ; tool
+ std::string tag = " tool";
+ pos = comment.find(tag);
+ if (pos == 0) {
+ std::string data = comment.substr(pos + tag.length());
+ std::string h_tag = "H";
+ size_t h_start = data.find(h_tag);
+ size_t h_end = data.find_first_of(' ', h_start);
+ std::string w_tag = "W";
+ size_t w_start = data.find(w_tag);
+ size_t w_end = data.find_first_of(' ', w_start);
+ if (h_start != data.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ }
+ if (w_start != data.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ }
+
+ return true;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ return false;
+}
+
+bool GCodeProcessor::process_craftware_tags(const std::string& comment)
+{
+ // segType -> extrusion role
+ std::string tag = "segType:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "Skirt")
+ m_extrusion_role = erSkirt;
+ else if (type == "Perimeter")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "HShell")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "InnerHair")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "Loop")
+ m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ else if (type == "Infill")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "Raft")
+ m_extrusion_role = erSkirt;
+ else if (type == "Support")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SupportTouch")
+ m_extrusion_role = erSupportMaterial;
+ else if (type == "SoftSupport")
+ m_extrusion_role = erSupportMaterialInterface;
+ else if (type == "Pillar")
+ m_extrusion_role = erWipeTower;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
+{
+ // TYPE -> extrusion role
+ std::string tag = "TYPE:";
+ size_t pos = comment.find(tag);
+ if (pos != comment.npos) {
+ std::string type = comment.substr(pos + tag.length());
+ if (type == "RAFT")
+ m_extrusion_role = erSkirt;
+ else if (type == "WALL-OUTER")
+ m_extrusion_role = erExternalPerimeter;
+ else if (type == "WALL-INNER")
+ m_extrusion_role = erPerimeter;
+ else if (type == "SOLID-FILL")
+ m_extrusion_role = erSolidInfill;
+ else if (type == "FILL")
+ m_extrusion_role = erInternalInfill;
+ else if (type == "BRIDGE")
+ m_extrusion_role = erBridgeInfill;
+ else if (type == "SUPPORT")
+ m_extrusion_role = erSupportMaterial;
+ else {
+ m_extrusion_role = erNone;
+ BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type;
+ }
+ return true;
+ }
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // geometry
+
+ // width
+ tag = "WIDTH:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ try {
+ m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
+ }
+ return true;
+ }
+
+ // height
+ tag = "HEIGHT:";
+ pos = comment.find(tag);
+ if (pos != comment.npos) {
+ try {
+ m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
+ }
+ return true;
+ }
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ return false;
+}
+
+bool GCodeProcessor::detect_producer(const std::string& comment)
+{
+ for (const auto& [id, search_string] : Producers) {
+ size_t pos = comment.find(search_string);
+ if (pos != comment.npos) {
+ m_producer = id;
+ BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string;
+ return true;
+ }
+ }
+ return false;
+}
+
+void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line)
+{
+ process_G1(line);
+}
+
+void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
+{
+ auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1)
+ {
+ bool is_relative = (m_global_positioning_type == EPositioningType::Relative);
+ if (axis == E)
+ is_relative |= (m_e_local_positioning_type == EPositioningType::Relative);
+
+ if (lineG1.has(Slic3r::Axis(axis))) {
+ float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+ float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
+ return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret;
+ }
+ else
+ return m_start_position[axis];
+ };
+
+ auto move_type = [this](const AxisCoords& delta_pos) {
+ EMoveType type = EMoveType::Noop;
+
+ if (delta_pos[E] < 0.0f) {
+ type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract;
+ }
+ else if (delta_pos[E] > 0.0f) {
+ if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f)
+ type = EMoveType::Unretract;
+ else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f)
+ type = EMoveType::Extrude;
+ }
+ else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f)
+ type = EMoveType::Travel;
+
+ return type;
+ };
+
+ // enable processing of lines M201/M203/M204/M205
+ m_time_processor.machine_envelope_processing_enabled = true;
+
+ // updates axes positions from line
+ for (unsigned char a = X; a <= E; ++a) {
+ m_end_position[a] = absolute_position((Axis)a, line);
+ }
+
+ // updates feedrate from line, if present
+ if (line.has_f())
+ m_feedrate = line.f() * MMMIN_TO_MMSEC;
+
+ // calculates movement deltas
+ float max_abs_delta = 0.0f;
+ AxisCoords delta_pos;
+ for (unsigned char a = X; a <= E; ++a) {
+ delta_pos[a] = m_end_position[a] - m_start_position[a];
+ max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a]));
+ }
+
+ // no displacement, return
+ if (max_abs_delta == 0.0f)
+ return;
+
+ EMoveType type = move_type(delta_pos);
+ if (type == EMoveType::Extrude && m_end_position[Z] == 0.0f)
+ type = EMoveType::Travel;
+
+ if (type == EMoveType::Extrude) {
+ float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
+ float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back();
+ float filament_radius = 0.5f * filament_diameter;
+ float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
+ float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
+ float area_toolpath_cross_section = volume_extruded_filament / d_xyz;
+
+ // volume extruded filament / tool displacement = area toolpath cross section
+ m_mm3_per_mm = area_toolpath_cross_section;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
+ m_height = m_end_position[Z] - m_extruded_last_z;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_height_compare.update(m_height, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_extruded_last_z = m_end_position[Z];
+ }
+
+ if (m_extrusion_role == erExternalPerimeter)
+ // cross section: rectangle
+ m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height);
+ else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone)
+ // cross section: circle
+ m_width = static_cast<float>(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz);
+ else
+ // cross section: rectangle + 2 semicircles
+ m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_width_compare.update(m_width, m_extrusion_role);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ }
+
+ if (type == EMoveType::Extrude && (m_extrusion_role == erCustom || m_width == 0.0f || m_height == 0.0f))
+ type = EMoveType::Travel;
+
+ // time estimate section
+ auto move_length = [](const AxisCoords& delta_pos) {
+ float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]);
+ return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]);
+ };
+
+ auto is_extrusion_only_move = [](const AxisCoords& delta_pos) {
+ return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f;
+ };
+
+ float distance = move_length(delta_pos);
+ assert(distance != 0.0f);
+ float inv_distance = 1.0f / distance;
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ if (!machine.enabled)
+ continue;
+
+ TimeMachine::State& curr = machine.curr;
+ TimeMachine::State& prev = machine.prev;
+ std::vector<TimeBlock>& blocks = machine.blocks;
+
+ curr.feedrate = (delta_pos[E] == 0.0f) ?
+ minimum_travel_feedrate(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), m_feedrate) :
+ minimum_feedrate(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), m_feedrate);
+
+ TimeBlock block;
+ block.move_type = type;
+ block.role = m_extrusion_role;
+ block.distance = distance;
+ block.layer_id = m_layer_id;
+
+ // calculates block cruise feedrate
+ float min_feedrate_factor = 1.0f;
+ for (unsigned char a = X; a <= E; ++a) {
+ curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance;
+ if (a == E)
+ curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage;
+
+ curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]);
+ if (curr.abs_axis_feedrate[a] != 0.0f) {
+ float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), static_cast<Axis>(a));
+ if (axis_max_feedrate != 0.0f)
+ min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
+ }
+ }
+
+ block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate;
+
+ if (min_feedrate_factor < 1.0f) {
+ for (unsigned char a = X; a <= E; ++a) {
+ curr.axis_feedrate[a] *= min_feedrate_factor;
+ curr.abs_axis_feedrate[a] *= min_feedrate_factor;
+ }
+ }
+
+ // calculates block acceleration
+ float acceleration = is_extrusion_only_move(delta_pos) ?
+ get_retract_acceleration(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i)) :
+ get_acceleration(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i));
+
+ for (unsigned char a = X; a <= E; ++a) {
+ float axis_max_acceleration = get_axis_max_acceleration(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), static_cast<Axis>(a));
+ if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration)
+ acceleration = axis_max_acceleration;
+ }
+
+ block.acceleration = acceleration;
+
+ // calculates block exit feedrate
+ curr.safe_feedrate = block.feedrate_profile.cruise;
+
+ for (unsigned char a = X; a <= E; ++a) {
+ float axis_max_jerk = get_axis_max_jerk(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), static_cast<Axis>(a));
+ if (curr.abs_axis_feedrate[a] > axis_max_jerk)
+ curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk);
+ }
+
+ block.feedrate_profile.exit = curr.safe_feedrate;
+
+ static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
+
+ // calculates block entry feedrate
+ float vmax_junction = curr.safe_feedrate;
+ if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) {
+ bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise;
+ float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise);
+ // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
+ vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate;
+
+ float v_factor = 1.0f;
+ bool limited = false;
+
+ for (unsigned char a = X; a <= E; ++a) {
+ // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
+ float v_exit = prev.axis_feedrate[a];
+ float v_entry = curr.axis_feedrate[a];
+
+ if (prev_speed_larger)
+ v_exit *= smaller_speed_factor;
+
+ if (limited) {
+ v_exit *= v_factor;
+ v_entry *= v_factor;
+ }
+
+ // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction.
+ float jerk =
+ (v_exit > v_entry) ?
+ (((v_entry > 0.0f) || (v_exit < 0.0f)) ?
+ // coasting
+ (v_exit - v_entry) :
+ // axis reversal
+ std::max(v_exit, -v_entry)) :
+ // v_exit <= v_entry
+ (((v_entry < 0.0f) || (v_exit > 0.0f)) ?
+ // coasting
+ (v_entry - v_exit) :
+ // axis reversal
+ std::max(-v_exit, v_entry));
+
+ float axis_max_jerk = get_axis_max_jerk(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), static_cast<Axis>(a));
+ if (jerk > axis_max_jerk) {
+ v_factor *= axis_max_jerk / jerk;
+ limited = true;
+ }
+ }
+
+ if (limited)
+ vmax_junction *= v_factor;
+
+ // Now the transition velocity is known, which maximizes the shared exit / entry velocity while
+ // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints.
+ float vmax_junction_threshold = vmax_junction * 0.99f;
+
+ // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
+ if ((prev.safe_feedrate > vmax_junction_threshold) && (curr.safe_feedrate > vmax_junction_threshold))
+ vmax_junction = curr.safe_feedrate;
+ }
+
+ float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance);
+ block.feedrate_profile.entry = std::min(vmax_junction, v_allowable);
+
+ block.max_entry_speed = vmax_junction;
+ block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable);
+ block.flags.recalculate = true;
+ block.safe_feedrate = curr.safe_feedrate;
+
+ // calculates block trapezoid
+ block.calculate_trapezoid();
+
+ // updates previous
+ prev = curr;
+
+ blocks.push_back(block);
+
+ if (blocks.size() > TimeProcessor::Planner::refresh_threshold)
+ machine.calculate_time(TimeProcessor::Planner::queue_size);
+ }
+
+ // store move
+ store_move_vertex(type);
+}
+
+void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ store_move_vertex(EMoveType::Retract);
+}
+
+void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ store_move_vertex(EMoveType::Unretract);
+}
+
+void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line)
+{
+ m_units = EUnits::Inches;
+}
+
+void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line)
+{
+ m_units = EUnits::Millimeters;
+}
+
+void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line)
+{
+ // stores retract move
+ store_move_vertex(EMoveType::Retract);
+}
+
+void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line)
+{
+ // stores unretract move
+ store_move_vertex(EMoveType::Unretract);
+}
+
+void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line)
+{
+ m_global_positioning_type = EPositioningType::Absolute;
+}
+
+void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line)
+{
+ m_global_positioning_type = EPositioningType::Relative;
+}
+
+void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
+{
+ float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+ bool any_found = false;
+
+ if (line.has_x()) {
+ m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_y()) {
+ m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_z()) {
+ m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor;
+ any_found = true;
+ }
+
+ if (line.has_e()) {
+ // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
+ // we set the value taken from the G92 line as the new current position for it
+ m_end_position[E] = line.e() * lengths_scale_factor;
+ any_found = true;
+ }
+ else
+ simulate_st_synchronize();
+
+ if (!any_found && !line.has_unknown_axis()) {
+ // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510,
+ // where G92 A0 B0 is called although the extruder axis is till E.
+ for (unsigned char a = X; a <= E; ++a) {
+ m_origin[a] = m_end_position[a];
+ }
+ }
+}
+
+void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line)
+{
+ simulate_st_synchronize();
+}
+
+void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line)
+{
+ m_e_local_positioning_type = EPositioningType::Absolute;
+}
+
+void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line)
+{
+ m_e_local_positioning_type = EPositioningType::Relative;
+}
+
+void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line)
+{
+ if (!line.has('P')) {
+ // The absence of P means the print cooling fan, so ignore anything else.
+ float new_fan_speed;
+ if (line.has_value('S', new_fan_speed))
+ m_fan_speed = (100.0f / 255.0f) * new_fan_speed;
+ else
+ m_fan_speed = 100.0f;
+ }
+}
+
+void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line)
+{
+ m_fan_speed = 0.0f;
+}
+
+void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line)
+{
+ // These M-codes are used by Sailfish to change active tool.
+ // They have to be processed otherwise toolchanges will be unrecognised
+ // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
+
+ if (m_flavor != gcfSailfish)
+ return;
+
+ std::string cmd = line.raw();
+ size_t pos = cmd.find("T");
+ if (pos != std::string::npos)
+ process_T(cmd.substr(pos));
+}
+
+void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line)
+{
+ // This command is used by Makerbot to load the current home position from EEPROM
+ // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md
+ // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082
+
+ if (line.has_x())
+ m_origin[X] = 0.0f;
+
+ if (line.has_y())
+ m_origin[Y] = 0.0f;
+
+ if (line.has_z())
+ m_origin[Z] = 0.0f;
+
+ if (line.has_e())
+ m_origin[E] = 0.0f;
+}
+
+void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line)
+{
+ // These M-codes are used by MakerWare to change active tool.
+ // They have to be processed otherwise toolchanges will be unrecognised
+ // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
+
+ if (m_flavor != gcfMakerWare)
+ return;
+
+ std::string cmd = line.raw();
+ size_t pos = cmd.find("T");
+ if (pos != std::string::npos)
+ process_T(cmd.substr(pos));
+}
+
+void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
+ float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
+ }
+}
+
+void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ if (m_flavor == gcfRepetier)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ // http://smoothieware.org/supported-g-codes
+ float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC;
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor);
+ }
+}
+
+void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ float value;
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_value('S', value)) {
+ // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
+ // and it is also generated by Slic3r to control acceleration per extrusion type
+ // (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
+ set_acceleration(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), value);
+ if (line.has_value('T', value))
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
+ }
+ else {
+ // New acceleration format, compatible with the upstream Marlin.
+ if (line.has_value('P', value))
+ set_acceleration(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i), value);
+ if (line.has_value('R', value))
+ set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value);
+ if (line.has_value('T', value)) {
+ // Interpret the T value as the travel acceleration in the new Marlin format.
+ //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
+ // set_travel_acceleration(value);
+ }
+ }
+ }
+}
+
+void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line)
+{
+ if (!m_time_processor.machine_envelope_processing_enabled)
+ return;
+
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x()) {
+ float max_jerk = line.x();
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk);
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk);
+ }
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y());
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z());
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e());
+
+ float value;
+ if (line.has_value('S', value))
+ set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value);
+
+ if (line.has_value('T', value))
+ set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value);
+ }
+}
+
+void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line)
+{
+ float value_s;
+ float value_t;
+ if (line.has_value('S', value_s) && !line.has_value('T', value_t)) {
+ value_s *= 0.01f;
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ m_time_processor.machines[i].extrude_factor_override_percentage = value_s;
+ }
+ }
+}
+
+void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line)
+{
+ if (m_flavor != gcfRepetier)
+ return;
+
+ for (unsigned char a = 0; a <= 3; ++a) {
+ m_cached_position.position[a] = m_start_position[a];
+ }
+ m_cached_position.feedrate = m_feedrate;
+}
+
+void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line)
+{
+ if (m_flavor != gcfRepetier)
+ return;
+
+ // see for reference:
+ // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp
+ // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed)
+
+ bool has_xyz = !(line.has_x() || line.has_y() || line.has_z());
+
+ float p = FLT_MAX;
+ for (unsigned char a = X; a <= Z; ++a) {
+ if (has_xyz || line.has(a)) {
+ p = m_cached_position.position[a];
+ if (p != FLT_MAX)
+ m_start_position[a] = p;
+ }
+ }
+
+ p = m_cached_position.position[E];
+ if (p != FLT_MAX)
+ m_start_position[E] = p;
+
+ p = FLT_MAX;
+ if (!line.has_value(4, p))
+ p = m_cached_position.feedrate;
+
+ if (p != FLT_MAX)
+ m_feedrate = p;
+}
+
+void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line)
+{
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ if (line.has_x())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC);
+
+ if (line.has_y())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC);
+
+ if (line.has_z())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC);
+
+ if (line.has_e())
+ set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC);
+ }
+}
+
+void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line)
+{
+ if (line.has('C')) {
+ // MK3 MMU2 specific M code:
+ // M702 C is expected to be sent by the custom end G-code when finalizing a print.
+ // The MK3 unit shall unload and park the active filament into the MMU2 unit.
+ m_time_processor.extruder_unloaded = true;
+ simulate_st_synchronize(get_filament_unload_time(m_extruder_id));
+ }
+}
+
+void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line)
+{
+ process_T(line.cmd());
+}
+
+void GCodeProcessor::process_T(const std::string& command)
+{
+ if (command.length() > 1) {
+ try {
+ unsigned char id = static_cast<unsigned char>(std::stoi(command.substr(1)));
+ if (m_extruder_id != id) {
+ unsigned char extruders_count = static_cast<unsigned char>(m_extruder_offsets.size());
+ if (id >= extruders_count)
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode.";
+ else {
+ unsigned char old_extruder_id = m_extruder_id;
+ m_extruder_id = id;
+ m_cp_color.current = m_extruder_colors[id];
+ // Specific to the MK3 MMU2:
+ // The initial value of extruder_unloaded is set to true indicating
+ // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet.
+ float extra_time = get_filament_unload_time(static_cast<size_t>(old_extruder_id));
+ m_time_processor.extruder_unloaded = false;
+ extra_time += get_filament_load_time(static_cast<size_t>(m_extruder_id));
+ simulate_st_synchronize(extra_time);
+ }
+
+ // store tool change move
+ store_move_vertex(EMoveType::Tool_change);
+ }
+ }
+ catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
+ }
+ }
+}
+
+void GCodeProcessor::store_move_vertex(EMoveType type)
+{
+ MoveVertex vertex = {
+ type,
+ m_extrusion_role,
+ m_extruder_id,
+ m_cp_color.current,
+ Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id],
+ m_end_position[E] - m_start_position[E],
+ m_feedrate,
+ m_width,
+ m_height,
+ m_mm3_per_mm,
+ m_fan_speed,
+ static_cast<float>(m_result.moves.size())
+ };
+ m_result.moves.emplace_back(vertex);
+}
+
+float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const
+{
+ if (m_time_processor.machine_limits.machine_min_extruding_rate.empty())
+ return feedrate;
+
+ return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast<size_t>(mode)));
+}
+
+float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const
+{
+ if (m_time_processor.machine_limits.machine_min_travel_rate.empty())
+ return feedrate;
+
+ return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast<size_t>(mode)));
+}
+
+float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast<size_t>(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast<size_t>(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast<size_t>(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast<size_t>(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast<size_t>(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast<size_t>(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast<size_t>(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast<size_t>(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const
+{
+ switch (axis)
+ {
+ case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast<size_t>(mode)); }
+ case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast<size_t>(mode)); }
+ case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast<size_t>(mode)); }
+ case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast<size_t>(mode)); }
+ default: { return 0.0f; }
+ }
+}
+
+float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast<size_t>(mode));
+}
+
+float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const
+{
+ size_t id = static_cast<size_t>(mode);
+ return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION;
+}
+
+void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value)
+{
+ size_t id = static_cast<size_t>(mode);
+ if (id < m_time_processor.machines.size()) {
+ m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value :
+ // Clamp the acceleration with the maximum.
+ std::min(value, m_time_processor.machines[id].max_acceleration);
+ }
+}
+
+float GCodeProcessor::get_filament_load_time(size_t extruder_id)
+{
+ return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ?
+ 0.0f :
+ ((extruder_id < m_time_processor.filament_load_times.size()) ?
+ m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front());
+}
+
+float GCodeProcessor::get_filament_unload_time(size_t extruder_id)
+{
+ return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ?
+ 0.0f :
+ ((extruder_id < m_time_processor.filament_unload_times.size()) ?
+ m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front());
+}
+
+void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code)
+{
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ TimeMachine& machine = m_time_processor.machines[i];
+ if (!machine.enabled)
+ continue;
+
+ TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time;
+ gcode_time.needed = true;
+ //FIXME this simulates st_synchronize! is it correct?
+ // The estimated time may be longer than the real print time.
+ machine.simulate_st_synchronize();
+ if (gcode_time.cache != 0.0f) {
+ gcode_time.times.push_back({ code, gcode_time.cache });
+ gcode_time.cache = 0.0f;
+ }
+ }
+}
+
+void GCodeProcessor::simulate_st_synchronize(float additional_time)
+{
+ for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+ m_time_processor.machines[i].simulate_st_synchronize(additional_time);
+ }
+}
+
+void GCodeProcessor::update_estimated_times_stats()
+{
+ auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) {
+ PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast<size_t>(mode)];
+ data.time = get_time(mode);
+ data.custom_gcode_times = get_custom_gcode_times(mode, true);
+ data.moves_times = get_moves_time(mode);
+ data.roles_times = get_roles_time(mode);
+ data.layers_times = get_layers_time(mode);
+ };
+
+ update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal);
+ if (m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled)
+ update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth);
+ else
+ m_result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset();
+}
+
+} /* namespace Slic3r */
+
+#endif // ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp
new file mode 100644
index 000000000..22aeed762
--- /dev/null
+++ b/src/libslic3r/GCode/GCodeProcessor.hpp
@@ -0,0 +1,559 @@
+#ifndef slic3r_GCodeProcessor_hpp_
+#define slic3r_GCodeProcessor_hpp_
+
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCodeReader.hpp"
+#include "libslic3r/Point.hpp"
+#include "libslic3r/ExtrusionEntity.hpp"
+#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/CustomGCode.hpp"
+
+#include <array>
+#include <vector>
+#include <string>
+
+namespace Slic3r {
+
+ enum class EMoveType : unsigned char
+ {
+ Noop,
+ Retract,
+ Unretract,
+ Tool_change,
+ Color_change,
+ Pause_Print,
+ Custom_GCode,
+ Travel,
+ Extrude,
+ Count
+ };
+
+ struct PrintEstimatedTimeStatistics
+ {
+ enum class ETimeMode : unsigned char
+ {
+ Normal,
+ Stealth,
+ Count
+ };
+
+ struct Mode
+ {
+ float time;
+ std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> custom_gcode_times;
+ std::vector<std::pair<EMoveType, float>> moves_times;
+ std::vector<std::pair<ExtrusionRole, float>> roles_times;
+ std::vector<float> layers_times;
+
+ void reset() {
+ time = 0.0f;
+ custom_gcode_times.clear();
+ moves_times.clear();
+ roles_times.clear();
+ layers_times.clear();
+ }
+ };
+
+ std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
+
+ PrintEstimatedTimeStatistics() { reset(); }
+
+ void reset() {
+ for (auto m : modes) {
+ m.reset();
+ }
+ }
+ };
+
+ class GCodeProcessor
+ {
+ public:
+ static const std::string Extrusion_Role_Tag;
+ static const std::string Height_Tag;
+ static const std::string Layer_Change_Tag;
+ static const std::string Color_Change_Tag;
+ static const std::string Pause_Print_Tag;
+ static const std::string Custom_Code_Tag;
+ static const std::string First_Line_M73_Placeholder_Tag;
+ static const std::string Last_Line_M73_Placeholder_Tag;
+ static const std::string Estimated_Printing_Time_Placeholder_Tag;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ static const std::string Width_Tag;
+ static const std::string Mm3_Per_Mm_Tag;
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ private:
+ using AxisCoords = std::array<float, 4>;
+ using ExtruderColors = std::vector<unsigned char>;
+
+ enum class EUnits : unsigned char
+ {
+ Millimeters,
+ Inches
+ };
+
+ enum class EPositioningType : unsigned char
+ {
+ Absolute,
+ Relative
+ };
+
+ struct CachedPosition
+ {
+ AxisCoords position; // mm
+ float feedrate; // mm/s
+
+ void reset();
+ };
+
+ struct CpColor
+ {
+ unsigned char counter;
+ unsigned char current;
+
+ void reset();
+ };
+
+ public:
+ struct FeedrateProfile
+ {
+ float entry{ 0.0f }; // mm/s
+ float cruise{ 0.0f }; // mm/s
+ float exit{ 0.0f }; // mm/s
+ };
+
+ struct Trapezoid
+ {
+ float accelerate_until{ 0.0f }; // mm
+ float decelerate_after{ 0.0f }; // mm
+ float cruise_feedrate{ 0.0f }; // mm/sec
+
+ float acceleration_time(float entry_feedrate, float acceleration) const;
+ float cruise_time() const;
+ float deceleration_time(float distance, float acceleration) const;
+ float cruise_distance() const;
+ };
+
+ struct TimeBlock
+ {
+ struct Flags
+ {
+ bool recalculate{ false };
+ bool nominal_length{ false };
+ };
+
+ EMoveType move_type{ EMoveType::Noop };
+ ExtrusionRole role{ erNone };
+ unsigned int layer_id{ 0 };
+ float distance{ 0.0f }; // mm
+ float acceleration{ 0.0f }; // mm/s^2
+ float max_entry_speed{ 0.0f }; // mm/s
+ float safe_feedrate{ 0.0f }; // mm/s
+ Flags flags;
+ FeedrateProfile feedrate_profile;
+ Trapezoid trapezoid;
+
+ // Calculates this block's trapezoid
+ void calculate_trapezoid();
+
+ float time() const;
+ };
+
+ private:
+ struct TimeMachine
+ {
+ struct State
+ {
+ float feedrate; // mm/s
+ float safe_feedrate; // mm/s
+ AxisCoords axis_feedrate; // mm/s
+ AxisCoords abs_axis_feedrate; // mm/s
+
+ void reset();
+ };
+
+ struct CustomGCodeTime
+ {
+ bool needed;
+ float cache;
+ std::vector<std::pair<CustomGCode::Type, float>> times;
+
+ void reset();
+ };
+
+ bool enabled;
+ float acceleration; // mm/s^2
+ // hard limit for the acceleration, to which the firmware will clamp.
+ float max_acceleration; // mm/s^2
+ float extrude_factor_override_percentage;
+ float time; // s
+ std::string line_m73_mask;
+ State curr;
+ State prev;
+ CustomGCodeTime gcode_time;
+ std::vector<TimeBlock> blocks;
+ std::vector<float> g1_times_cache;
+ std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
+ std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
+ std::vector<float> layers_time;
+
+ void reset();
+
+ // Simulates firmware st_synchronize() call
+ void simulate_st_synchronize(float additional_time = 0.0f);
+ void calculate_time(size_t keep_last_n_blocks = 0);
+ };
+
+ struct TimeProcessor
+ {
+ struct Planner
+ {
+ // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
+ // Let's be conservative and plan for newer boards with more memory.
+ static constexpr size_t queue_size = 64;
+ // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
+ // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
+ static constexpr size_t refresh_threshold = queue_size * 4;
+ };
+
+ // extruder_id is currently used to correctly calculate filament load / unload times into the total print time.
+ // This is currently only really used by the MK3 MMU2:
+ // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
+ bool extruder_unloaded;
+ // whether or not to export post-process the gcode to export lines M73 in it
+ bool export_remaining_time_enabled;
+ // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope()
+ bool machine_envelope_processing_enabled;
+ MachineEnvelopeConfig machine_limits;
+ // Additional load / unload times for a filament exchange sequence.
+ std::vector<float> filament_load_times;
+ std::vector<float> filament_unload_times;
+ std::array<TimeMachine, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines;
+
+ void reset();
+
+ // post process the file with the given filename to add remaining time lines M73
+ void post_process(const std::string& filename);
+ };
+
+ public:
+ struct MoveVertex
+ {
+ EMoveType type{ EMoveType::Noop };
+ ExtrusionRole extrusion_role{ erNone };
+ unsigned char extruder_id{ 0 };
+ unsigned char cp_color_id{ 0 };
+ Vec3f position{ Vec3f::Zero() }; // mm
+ float delta_extruder{ 0.0f }; // mm
+ float feedrate{ 0.0f }; // mm/s
+ float width{ 0.0f }; // mm
+ float height{ 0.0f }; // mm
+ float mm3_per_mm{ 0.0f };
+ float fan_speed{ 0.0f }; // percentage
+ float time{ 0.0f }; // s
+
+ float volumetric_rate() const { return feedrate * mm3_per_mm; }
+ };
+
+ struct Result
+ {
+ unsigned int id;
+ std::vector<MoveVertex> moves;
+ Pointfs bed_shape;
+ std::string printer_settings_id;
+ std::vector<std::string> extruder_colors;
+ PrintEstimatedTimeStatistics time_statistics;
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ long long time{ 0 };
+ void reset()
+ {
+ time = 0;
+ moves = std::vector<MoveVertex>();
+ bed_shape = Pointfs();
+ extruder_colors = std::vector<std::string>();
+ }
+#else
+ void reset()
+ {
+ moves = std::vector<MoveVertex>();
+ bed_shape = Pointfs();
+ extruder_colors = std::vector<std::string>();
+ }
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ };
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ struct DataChecker
+ {
+ struct Error
+ {
+ float value;
+ float tag_value;
+ ExtrusionRole role;
+ };
+
+ std::string type;
+ float threshold{ 0.01f };
+ float last_tag_value{ 0.0f };
+ unsigned int count{ 0 };
+ std::vector<Error> errors;
+
+ DataChecker(const std::string& type, float threshold)
+ : type(type), threshold(threshold)
+ {}
+
+ void update(float value, ExtrusionRole role) {
+ ++count;
+ if (last_tag_value != 0.0f) {
+ if (std::abs(value - last_tag_value) / last_tag_value > threshold)
+ errors.push_back({ value, last_tag_value, role });
+ }
+ }
+
+ void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; }
+
+ std::pair<float, float> get_min() const {
+ float delta_min = FLT_MAX;
+ float perc_min = 0.0f;
+ for (const Error& e : errors) {
+ if (delta_min > e.value - e.tag_value) {
+ delta_min = e.value - e.tag_value;
+ perc_min = 100.0f * delta_min / e.tag_value;
+ }
+ }
+ return { delta_min, perc_min };
+ }
+
+ std::pair<float, float> get_max() const {
+ float delta_max = -FLT_MAX;
+ float perc_max = 0.0f;
+ for (const Error& e : errors) {
+ if (delta_max < e.value - e.tag_value) {
+ delta_max = e.value - e.tag_value;
+ perc_max = 100.0f * delta_max / e.tag_value;
+ }
+ }
+ return { delta_max, perc_max };
+ }
+
+ void output() const {
+ if (!errors.empty()) {
+ std::cout << type << ":\n";
+ std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n";
+ auto [min, perc_min] = get_min();
+ auto [max, perc_max] = get_max();
+ std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n";
+ }
+ }
+ };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ private:
+ GCodeReader m_parser;
+
+ EUnits m_units;
+ EPositioningType m_global_positioning_type;
+ EPositioningType m_e_local_positioning_type;
+ std::vector<Vec3f> m_extruder_offsets;
+ GCodeFlavor m_flavor;
+
+ AxisCoords m_start_position; // mm
+ AxisCoords m_end_position; // mm
+ AxisCoords m_origin; // mm
+ CachedPosition m_cached_position;
+
+ float m_feedrate; // mm/s
+ float m_width; // mm
+ float m_height; // mm
+ float m_mm3_per_mm;
+ float m_fan_speed; // percentage
+ ExtrusionRole m_extrusion_role;
+ unsigned char m_extruder_id;
+ ExtruderColors m_extruder_colors;
+ std::vector<float> m_filament_diameters;
+ float m_extruded_last_z;
+ unsigned int m_layer_id;
+ CpColor m_cp_color;
+
+ enum class EProducer
+ {
+ Unknown,
+ PrusaSlicer,
+ Cura,
+ Simplify3D,
+ CraftWare,
+ ideaMaker
+ };
+
+ static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers;
+ EProducer m_producer;
+ bool m_producers_enabled;
+
+ TimeProcessor m_time_processor;
+
+ Result m_result;
+ static unsigned int s_result_id;
+
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f };
+ DataChecker m_height_compare{ "height", 0.01f };
+ DataChecker m_width_compare{ "width", 0.01f };
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ public:
+ GCodeProcessor();
+
+ void apply_config(const PrintConfig& config);
+ void apply_config(const DynamicPrintConfig& config);
+ void enable_stealth_time_estimator(bool enabled);
+ bool is_stealth_time_estimator_enabled() const {
+ return m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled;
+ }
+ void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; }
+ void enable_producers(bool enabled) { m_producers_enabled = enabled; }
+ void reset();
+
+ const Result& get_result() const { return m_result; }
+ Result&& extract_result() { return std::move(m_result); }
+
+ // Process the gcode contained in the file with the given filename
+ void process_file(const std::string& filename);
+
+ float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const;
+
+ std::vector<std::pair<EMoveType, float>> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector<std::pair<ExtrusionRole, float>> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ std::vector<float> get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+
+ private:
+ void process_gcode_line(const GCodeReader::GCodeLine& line);
+
+ // Process tags embedded into comments
+ void process_tags(const std::string& comment);
+ bool process_producers_tags(const std::string& comment);
+ bool process_prusaslicer_tags(const std::string& comment);
+ bool process_cura_tags(const std::string& comment);
+ bool process_simplify3d_tags(const std::string& comment);
+ bool process_craftware_tags(const std::string& comment);
+ bool process_ideamaker_tags(const std::string& comment);
+
+ bool detect_producer(const std::string& comment);
+
+ // Move
+ void process_G0(const GCodeReader::GCodeLine& line);
+ void process_G1(const GCodeReader::GCodeLine& line);
+
+ // Retract
+ void process_G10(const GCodeReader::GCodeLine& line);
+
+ // Unretract
+ void process_G11(const GCodeReader::GCodeLine& line);
+
+ // Set Units to Inches
+ void process_G20(const GCodeReader::GCodeLine& line);
+
+ // Set Units to Millimeters
+ void process_G21(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Retract
+ void process_G22(const GCodeReader::GCodeLine& line);
+
+ // Firmware controlled Unretract
+ void process_G23(const GCodeReader::GCodeLine& line);
+
+ // Set to Absolute Positioning
+ void process_G90(const GCodeReader::GCodeLine& line);
+
+ // Set to Relative Positioning
+ void process_G91(const GCodeReader::GCodeLine& line);
+
+ // Set Position
+ void process_G92(const GCodeReader::GCodeLine& line);
+
+ // Sleep or Conditional stop
+ void process_M1(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to absolute mode
+ void process_M82(const GCodeReader::GCodeLine& line);
+
+ // Set extruder to relative mode
+ void process_M83(const GCodeReader::GCodeLine& line);
+
+ // Set fan speed
+ void process_M106(const GCodeReader::GCodeLine& line);
+
+ // Disable fan
+ void process_M107(const GCodeReader::GCodeLine& line);
+
+ // Set tool (Sailfish)
+ void process_M108(const GCodeReader::GCodeLine& line);
+
+ // Recall stored home offsets
+ void process_M132(const GCodeReader::GCodeLine& line);
+
+ // Set tool (MakerWare)
+ void process_M135(const GCodeReader::GCodeLine& line);
+
+ // Set max printing acceleration
+ void process_M201(const GCodeReader::GCodeLine& line);
+
+ // Set maximum feedrate
+ void process_M203(const GCodeReader::GCodeLine& line);
+
+ // Set default acceleration
+ void process_M204(const GCodeReader::GCodeLine& line);
+
+ // Advanced settings
+ void process_M205(const GCodeReader::GCodeLine& line);
+
+ // Set extrude factor override percentage
+ void process_M221(const GCodeReader::GCodeLine& line);
+
+ // Repetier: Store x, y and z position
+ void process_M401(const GCodeReader::GCodeLine& line);
+
+ // Repetier: Go to stored position
+ void process_M402(const GCodeReader::GCodeLine& line);
+
+ // Set allowable instantaneous speed change
+ void process_M566(const GCodeReader::GCodeLine& line);
+
+ // Unload the current filament into the MK3 MMU2 unit at the end of print.
+ void process_M702(const GCodeReader::GCodeLine& line);
+
+ // Processes T line (Select Tool)
+ void process_T(const GCodeReader::GCodeLine& line);
+ void process_T(const std::string& command);
+
+ void store_move_vertex(EMoveType type);
+
+ float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
+ float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
+ float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
+ float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
+ void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value);
+ float get_filament_load_time(size_t extruder_id);
+ float get_filament_unload_time(size_t extruder_id);
+
+ void process_custom_gcode_time(CustomGCode::Type code);
+
+ // Simulates firmware st_synchronize() call
+ void simulate_st_synchronize(float additional_time = 0.0f);
+
+ void update_estimated_times_stats();
+ };
+
+} /* namespace Slic3r */
+
+#endif // ENABLE_GCODE_VIEWER
+
+#endif /* slic3r_GCodeProcessor_hpp_ */
+
+
diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp
index 551c13345..8aec327db 100644
--- a/src/libslic3r/GCode/PreviewData.cpp
+++ b/src/libslic3r/GCode/PreviewData.cpp
@@ -5,6 +5,8 @@
#include <boost/format.hpp>
+#if !ENABLE_GCODE_VIEWER
+
//! macro used to mark string used at localization,
#define L(s) (s)
@@ -516,3 +518,5 @@ Color operator * (float f, const Color& color)
}
} // namespace Slic3r
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp
index 35bbfa50a..930c1659e 100644
--- a/src/libslic3r/GCode/PreviewData.hpp
+++ b/src/libslic3r/GCode/PreviewData.hpp
@@ -1,6 +1,8 @@
#ifndef slic3r_GCode_PreviewData_hpp_
#define slic3r_GCode_PreviewData_hpp_
+#if !ENABLE_GCODE_VIEWER
+
#include "../libslic3r.h"
#include "../ExtrusionEntity.hpp"
#include "../Point.hpp"
@@ -56,8 +58,7 @@ public:
// Color mapping to convert a float into a smooth rainbow of 10 colors.
class RangeBase
{
- public:
-
+ public:
virtual void reset() = 0;
virtual bool empty() const = 0;
virtual float min() const = 0;
@@ -73,7 +74,7 @@ public:
// Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors.
class Range : public RangeBase
{
- public:
+ public:
Range();
// RangeBase Overrides
@@ -97,8 +98,7 @@ public:
template <typename EnumRangeType>
class MultiRange : public RangeBase
{
- public:
-
+ public:
void reset() override
{
bounds = decltype(bounds){};
@@ -160,8 +160,7 @@ public:
mode.set(static_cast<std::size_t>(range_type_value), enable);
}
- private:
-
+ private:
// Interval bounds
struct Bounds
{
@@ -394,4 +393,6 @@ public:
} // namespace Slic3r
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCode_PreviewData_hpp_ */
diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp
index c0f778687..0752b6dfc 100644
--- a/src/libslic3r/GCode/WipeTower.cpp
+++ b/src/libslic3r/GCode/WipeTower.cpp
@@ -21,7 +21,11 @@ TODO LIST
#include <vector>
#include <numeric>
+#if ENABLE_GCODE_VIEWER
+#include "GCodeProcessor.hpp"
+#else
#include "Analyzer.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "BoundingBox.hpp"
#if defined(__linux) || defined(__GNUC__ )
@@ -47,37 +51,70 @@ public:
m_extrusion_flow(0.f),
m_preview_suppressed(false),
m_elapsed_time(0.f),
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_default_analyzer_line_width(line_width),
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_gcode_flavor(flavor),
m_filpar(filament_parameters)
{
// adds tag for analyzer:
char buf[64];
+#if ENABLE_GCODE_VIEWER
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
+ m_gcode += buf;
+ sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str());
+ m_gcode += buf;
+#else
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
m_gcode += buf;
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
m_gcode += buf;
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
change_analyzer_line_width(line_width);
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
}
- WipeTowerWriter& change_analyzer_line_width(float line_width) {
- // adds tag for analyzer:
- char buf[64];
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
- m_gcode += buf;
- return *this;
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ WipeTowerWriter& change_analyzer_line_width(float line_width) {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width);
+ m_gcode += buf;
+ return *this;
}
- WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
- static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
- float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
- // adds tag for analyzer:
- char buf[64];
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
- m_gcode += buf;
- return *this;
+ WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
+ static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
+ float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
+ // adds tag for processor:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
+ m_gcode += buf;
+ return *this;
+ }
+#else
+#if !ENABLE_GCODE_VIEWER
+ WipeTowerWriter& change_analyzer_line_width(float line_width) {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
+ m_gcode += buf;
+ return *this;
}
+ WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
+ static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
+ float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
+ m_gcode += buf;
+ return *this;
+ }
+#endif // !ENABLE_GCODE_VIEWER
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
m_wipe_tower_width = width;
m_wipe_tower_depth = depth;
@@ -111,8 +148,13 @@ public:
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
// is asked to suppres output of some lines, which look like extrusions.
- WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
- WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
+ WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
+ WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+#else
+ WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; }
+ WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; }
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& feedrate(float f)
{
@@ -149,8 +191,14 @@ public:
Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go
if (! m_preview_suppressed && e > 0.f && len > 0.f) {
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ change_analyzer_mm3_per_mm(len, e);
+#else
+#if !ENABLE_GCODE_VIEWER
change_analyzer_mm3_per_mm(len, e);
- // Width of a squished extrusion, corrected for the roundings of the squished extrusions.
+#endif // !ENABLE_GCODE_VIEWER
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+ // Width of a squished extrusion, corrected for the roundings of the squished extrusions.
// This is left zero if it is a travel move.
float width = e * m_filpar[0].filament_area / (len * m_layer_height);
// Correct for the roundings of a squished extrusion.
@@ -411,7 +459,9 @@ private:
float m_wipe_tower_depth = 0.f;
unsigned m_last_fan_speed = 0;
int current_temp = -1;
+#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
const float m_default_analyzer_line_width;
+#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
float m_used_filament_length = 0.f;
GCodeFlavor m_gcode_flavor;
const std::vector<WipeTower::FilamentParameters>& m_filpar;
@@ -852,8 +902,12 @@ void WipeTower::toolchange_Unload(
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.append("; CP TOOLCHANGE UNLOAD\n")
- .change_analyzer_line_width(line_width);
+ .change_analyzer_line_width(line_width);
+#else
+ writer.append("; CP TOOLCHANGE UNLOAD\n");
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
unsigned i = 0; // iterates through ramming_speed
m_left_to_right = true; // current direction of ramming
@@ -930,7 +984,9 @@ void WipeTower::toolchange_Unload(
}
}
Vec2f end_of_ramming(writer.x(),writer.y());
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// Retraction:
float old_x = writer.x();
diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp
index e68bc5ad2..ab77b0141 100644
--- a/src/libslic3r/GCodeReader.cpp
+++ b/src/libslic3r/GCodeReader.cpp
@@ -115,7 +115,12 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback)
{
std::ifstream f(file);
std::string line;
+#if ENABLE_GCODE_VIEWER
+ m_parsing_file = true;
+ while (m_parsing_file && std::getline(f, line))
+#else
while (std::getline(f, line))
+#endif // ENABLE_GCODE_VIEWER
this->parse_line(line, callback);
}
diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp
index 9503ddcc1..7e0793cd9 100644
--- a/src/libslic3r/GCodeReader.hpp
+++ b/src/libslic3r/GCodeReader.hpp
@@ -107,6 +107,9 @@ public:
{ GCodeLine gline; this->parse_line(line.c_str(), gline, callback); }
void parse_file(const std::string &file, callback_t callback);
+#if ENABLE_GCODE_VIEWER
+ void quit_parsing_file() { m_parsing_file = false; }
+#endif // ENABLE_GCODE_VIEWER
float& x() { return m_position[X]; }
float x() const { return m_position[X]; }
@@ -145,6 +148,9 @@ private:
char m_extrusion_axis;
float m_position[NUM_AXES];
bool m_verbose;
+#if ENABLE_GCODE_VIEWER
+ bool m_parsing_file{ false };
+#endif // ENABLE_GCODE_VIEWER
};
} /* namespace Slic3r */
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp
index 9e8137ef0..aa9ee2f64 100644
--- a/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/src/libslic3r/GCodeTimeEstimator.cpp
@@ -9,6 +9,8 @@
#include <boost/nowide/cstdio.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#if !ENABLE_GCODE_VIEWER
+
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float MILLISEC_TO_SEC = 0.001f;
static const float INCHES_TO_MM = 25.4f;
@@ -1671,3 +1673,5 @@ namespace Slic3r {
}
#endif // ENABLE_MOVE_STATS
}
+
+#endif // !ENABLE_GCODE_VIEWER
diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp
index 63e11c4fa..0dd3407cb 100644
--- a/src/libslic3r/GCodeTimeEstimator.hpp
+++ b/src/libslic3r/GCodeTimeEstimator.hpp
@@ -6,6 +6,8 @@
#include "GCodeReader.hpp"
#include "CustomGCode.hpp"
+#if !ENABLE_GCODE_VIEWER
+
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
@@ -481,4 +483,6 @@ namespace Slic3r {
} /* namespace Slic3r */
+#endif // !ENABLE_GCODE_VIEWER
+
#endif /* slic3r_GCodeTimeEstimator_hpp_ */
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 3beb74f23..ad48bd6bd 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -21,7 +21,9 @@
#include "SVG.hpp"
#include <Eigen/Dense>
#include "GCodeWriter.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
namespace Slic3r {
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index 7176ca81a..a5160d2db 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -1812,6 +1812,26 @@ namespace PresetUtils {
}
return out;
}
+
+#if ENABLE_GCODE_VIEWER
+ std::string system_printer_bed_model(const Preset& preset)
+ {
+ std::string out;
+ const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
+ if (pm != nullptr && !pm->bed_model.empty())
+ out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model;
+ return out;
+ }
+
+ std::string system_printer_bed_texture(const Preset& preset)
+ {
+ std::string out;
+ const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
+ if (pm != nullptr && !pm->bed_texture.empty())
+ out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
+ return out;
+ }
+#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
} // namespace Slic3r
diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp
index e34fca4dd..30edfc859 100644
--- a/src/libslic3r/Preset.hpp
+++ b/src/libslic3r/Preset.hpp
@@ -527,6 +527,10 @@ public:
namespace PresetUtils {
// PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile.
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
+#if ENABLE_GCODE_VIEWER
+ std::string system_printer_bed_model(const Preset& preset);
+ std::string system_printer_bed_texture(const Preset& preset);
+#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 0c8a11fcf..7924f1890 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1632,13 +1632,21 @@ void Print::process()
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
+#if ENABLE_GCODE_VIEWER
+std::string Print::export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
+#else
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
+#endif // ENABLE_GCODE_VIEWER
{
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message;
+#if ENABLE_GCODE_VIEWER
+ if (!path.empty() && result == nullptr) {
+#else
if (! path.empty() && preview_data == nullptr) {
+#endif // ENABLE_GCODE_VIEWER
// Only show the path if preview_data is not set -> running from command line.
message = L("Exporting G-code");
message += " to ";
@@ -1649,7 +1657,11 @@ std::string Print::export_gcode(const std::string& path_template, GCodePreviewDa
// The following line may die for multiple reasons.
GCode gcode;
+#if ENABLE_GCODE_VIEWER
+ gcode.do_export(this, path.c_str(), result, thumbnail_cb);
+#else
gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb);
+#endif // ENABLE_GCODE_VIEWER
return path.c_str();
}
@@ -2180,16 +2192,16 @@ DynamicConfig PrintStatistics::config() const
DynamicConfig config;
std::string normal_print_time = short_time(this->estimated_normal_print_time);
std::string silent_print_time = short_time(this->estimated_silent_print_time);
- config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
- config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
- config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
- config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.));
- config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
- config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
+ config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
+ config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
+ config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
+ config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.));
+ config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume));
+ config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
- config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
- config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
- config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
+ config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
+ config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
+ config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
return config;
}
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 05929dd2e..da98bba5f 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -11,6 +11,9 @@
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/ThumbnailData.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GCode/GCodeProcessor.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "libslic3r.h"
@@ -20,7 +23,9 @@ class Print;
class PrintObject;
class ModelObject;
class GCode;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
enum class SlicingMode : uint32_t;
class Layer;
class SupportLayer;
@@ -300,8 +305,10 @@ struct PrintStatistics
PrintStatistics() { clear(); }
std::string estimated_normal_print_time;
std::string estimated_silent_print_time;
+#if !ENABLE_GCODE_VIEWER
std::vector<std::pair<CustomGCode::Type, std::string>> estimated_normal_custom_gcode_print_times;
std::vector<std::pair<CustomGCode::Type, std::string>> estimated_silent_custom_gcode_print_times;
+#endif // !ENABLE_GCODE_VIEWER
double total_used_filament;
double total_extruded_volume;
double total_cost;
@@ -319,10 +326,12 @@ struct PrintStatistics
std::string finalize_output_path(const std::string &path_in) const;
void clear() {
+#if !ENABLE_GCODE_VIEWER
estimated_normal_print_time.clear();
estimated_silent_print_time.clear();
estimated_normal_custom_gcode_print_times.clear();
estimated_silent_custom_gcode_print_times.clear();
+#endif // !ENABLE_GCODE_VIEWER
total_used_filament = 0.;
total_extruded_volume = 0.;
total_cost = 0.;
@@ -362,7 +371,11 @@ public:
void process() override;
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
// If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
+#if ENABLE_GCODE_VIEWER
+ std::string export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#else
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
+#endif // ENABLE_GCODE_VIEWER
// methods for handling state
bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); }
@@ -417,6 +430,7 @@ public:
const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; }
const PrintStatistics& print_statistics() const { return m_print_statistics; }
+ PrintStatistics& print_statistics() { return m_print_statistics; }
// Wipe tower support.
bool has_wipe_tower() const;
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index e4b71697d..a0484b259 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -15,7 +15,7 @@
#define ENABLE_RENDER_STATISTICS 0
// Shows an imgui dialog with camera related data
#define ENABLE_CAMERA_STATISTICS 0
-// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering)
+// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering)
#define ENABLE_RENDER_PICKING_PASS 0
// Enable extracting thumbnails from selected gcode and save them as png files
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0
@@ -54,5 +54,10 @@
// Enable built-in DPI changed event handler of wxWidgets 3.1.3
#define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1)
+// Enable G-Code viewer
+#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1)
+#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER)
+#define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER)
+#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER)
#endif // _prusaslicer_technologies_h_
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
index 5cdf75037..ef531169d 100644
--- a/src/libslic3r/Utils.hpp
+++ b/src/libslic3r/Utils.hpp
@@ -110,19 +110,20 @@ std::string header_slic3r_generated();
// getpid platform wrapper
extern unsigned get_current_pid();
+#if !ENABLE_GCODE_VIEWER
template <typename Real>
Real round_nearest(Real value, unsigned int decimals)
{
Real res = (Real)0;
if (decimals == 0)
res = ::round(value);
- else
- {
+ else {
Real power = ::pow((Real)10, (int)decimals);
res = ::round(value * power + (Real)0.5) / power;
}
return res;
}
+#endif // !ENABLE_GCODE_VIEWER
// Compute the next highest power of 2 of 32-bit v
// http://graphics.stanford.edu/~seander/bithacks.html
@@ -337,6 +338,25 @@ inline std::string get_time_dhms(float time_in_secs)
return buffer;
}
+inline std::string get_time_dhm(float time_in_secs)
+{
+ int days = (int)(time_in_secs / 86400.0f);
+ time_in_secs -= (float)days * 86400.0f;
+ int hours = (int)(time_in_secs / 3600.0f);
+ time_in_secs -= (float)hours * 3600.0f;
+ int minutes = (int)(time_in_secs / 60.0f);
+
+ char buffer[64];
+ if (days > 0)
+ ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs);
+ else if (hours > 0)
+ ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs);
+ else if (minutes > 0)
+ ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs);
+
+ return buffer;
+}
+
} // namespace Slic3r
#if WIN32
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index b64cfdf4f..4cc66e36f 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -21,6 +21,8 @@ set(SLIC3R_GUI_SOURCES
GUI/3DScene.cpp
GUI/3DScene.hpp
GUI/format.hpp
+ GUI/GLShadersManager.hpp
+ GUI/GLShadersManager.cpp
GUI/GLShader.cpp
GUI/GLShader.hpp
GUI/GLCanvas3D.hpp
@@ -53,10 +55,14 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoHollow.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
+ GUI/GLModel.hpp
+ GUI/GLModel.cpp
GUI/GLTexture.hpp
GUI/GLTexture.cpp
GUI/GLToolbar.hpp
GUI/GLToolbar.cpp
+ GUI/GCodeViewer.hpp
+ GUI/GCodeViewer.cpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetHints.cpp
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index f2f9f6301..8a29d08bd 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -5,15 +5,24 @@
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/Geometry.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "GUI_App.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GLCanvas3D.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "3DScene.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
+#if ENABLE_GCODE_VIEWER
+#include <boost/log/trivial.hpp>
+#endif // ENABLE_GCODE_VIEWER
static const float GROUND_Z = -0.02f;
@@ -36,10 +45,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
float max_y = min_y;
unsigned int v_count = 0;
- for (const Polygon& t : triangles)
- {
- for (unsigned int i = 0; i < 3; ++i)
- {
+ for (const Polygon& t : triangles) {
+ for (unsigned int i = 0; i < 3; ++i) {
Vertex& v = m_vertices[v_count];
const Point& p = t.points[i];
@@ -50,8 +57,7 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
v.position[1] = y;
v.position[2] = z;
- if (generate_tex_coords)
- {
+ if (generate_tex_coords) {
v.tex_coords[0] = x;
v.tex_coords[1] = y;
@@ -65,17 +71,14 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
}
}
- if (generate_tex_coords)
- {
+ if (generate_tex_coords) {
float size_x = max_x - min_x;
float size_y = max_y - min_y;
- if ((size_x != 0.0f) && (size_y != 0.0f))
- {
+ if ((size_x != 0.0f) && (size_y != 0.0f)) {
float inv_size_x = 1.0f / size_x;
float inv_size_y = -1.0f / size_y;
- for (Vertex& v : m_vertices)
- {
+ for (Vertex& v : m_vertices) {
v.tex_coords[0] = (v.tex_coords[0] - min_x) * inv_size_x;
v.tex_coords[1] = (v.tex_coords[1] - min_y) * inv_size_y;
}
@@ -96,8 +99,7 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
- for (const Line& l : lines)
- {
+ for (const Line& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
@@ -119,10 +121,24 @@ const float* GeometryBuffer::get_vertices_data() const
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
}
+#if ENABLE_GCODE_VIEWER
+const float Bed3D::Axes::DefaultStemRadius = 0.5f;
+const float Bed3D::Axes::DefaultStemLength = 25.0f;
+const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius;
+const float Bed3D::Axes::DefaultTipLength = 5.0f;
+#else
const double Bed3D::Axes::Radius = 0.5;
const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius;
const double Bed3D::Axes::ArrowLength = 5.0;
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+void Bed3D::Axes::set_stem_length(float length)
+{
+ m_stem_length = length;
+ m_arrow.reset();
+}
+#else
Bed3D::Axes::Axes()
: origin(Vec3d::Zero())
, length(25.0 * Vec3d::Ones())
@@ -137,9 +153,47 @@ Bed3D::Axes::~Axes()
if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric);
}
+#endif // ENABLE_GCODE_VIEWER
void Bed3D::Axes::render() const
{
+#if ENABLE_GCODE_VIEWER
+ auto render_axis = [this](const Transform3f& transform) {
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixf(transform.data()));
+ m_arrow.render();
+ glsafe(::glPopMatrix());
+ };
+
+ m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length));
+
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ shader->start_using();
+
+ // x axis
+ std::array<float, 4> color = { 0.75f, 0.0f, 0.0f, 1.0f };
+ shader->set_uniform("uniform_color", color);
+ render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast<float>());
+
+ // y axis
+ color = { 0.0f, 0.75f, 0.0f, 1.0f };
+ shader->set_uniform("uniform_color", color);
+ render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast<float>());
+
+ // z axis
+ color = { 0.0f, 0.0f, 0.75f, 1.0f };
+ shader->set_uniform("uniform_color", color);
+ render_axis(Geometry::assemble_transform(m_origin).cast<float>());
+
+ shader->stop_using();
+
+ glsafe(::glDisable(GL_DEPTH_TEST));
+#else
if (m_quadric == nullptr)
return;
@@ -171,8 +225,10 @@ void Bed3D::Axes::render() const
glsafe(::glDisable(GL_LIGHTING));
glsafe(::glDisable(GL_DEPTH_TEST));
+#endif // !ENABLE_GCODE_VIEWER
}
+#if !ENABLE_GCODE_VIEWER
void Bed3D::Axes::render_axis(double length) const
{
::gluQuadricOrientation(m_quadric, GLU_OUTSIDE);
@@ -185,6 +241,7 @@ void Bed3D::Axes::render_axis(double length) const
::gluQuadricOrientation(m_quadric, GLU_INSIDE);
::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1);
}
+#endif // !ENABLE_GCODE_VIEWER
Bed3D::Bed3D()
: m_type(Custom)
@@ -193,7 +250,7 @@ Bed3D::Bed3D()
{
}
-bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
+bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
{
auto check_texture = [](const std::string& texture) {
return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture);
@@ -203,30 +260,39 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model);
};
- auto [new_type, system_model, system_texture] = detect_type(shape);
+ EType type;
+ std::string model;
+ std::string texture;
+ if (force_as_custom)
+ type = Custom;
+ else {
+ auto [new_type, system_model, system_texture] = detect_type(shape);
+ type = new_type;
+ model = system_model;
+ texture = system_texture;
+ }
- std::string texture_filename = custom_texture.empty() ? system_texture : custom_texture;
+ std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
if (!check_texture(texture_filename))
texture_filename.clear();
- std::string model_filename = custom_model.empty() ? system_model : custom_model;
+ std::string model_filename = custom_model.empty() ? model : custom_model;
if (!check_model(model_filename))
model_filename.clear();
- if ((m_shape == shape) && (m_type == new_type) && (m_texture_filename == texture_filename) && (m_model_filename == model_filename))
+ if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename)
// No change, no need to update the UI.
return false;
m_shape = shape;
m_texture_filename = texture_filename;
m_model_filename = model_filename;
- m_type = new_type;
+ m_type = type;
calc_bounding_boxes();
ExPolygon poly;
- for (const Vec2d& p : m_shape)
- {
+ for (const Vec2d& p : m_shape) {
poly.contour.append(Point(scale_(p(0)), scale_(p(1))));
}
@@ -242,8 +308,13 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
m_model.reset();
// Set the origin and size for rendering the coordinate system axes.
+#if ENABLE_GCODE_VIEWER
+ m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
+ m_axes.set_stem_length(0.1f * static_cast<float>(m_bounding_box.max_size()));
+#else
m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones();
+#endif // ENABLE_GCODE_VIEWER
// Let the calee to update the UI.
return true;
@@ -282,25 +353,35 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor,
void Bed3D::calc_bounding_boxes() const
{
m_bounding_box = BoundingBoxf3();
- for (const Vec2d& p : m_shape)
- {
+ for (const Vec2d& p : m_shape) {
m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
}
m_extended_bounding_box = m_bounding_box;
// extend to contain axes
- m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones());
+#if ENABLE_GCODE_VIEWER
+ m_extended_bounding_box.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones());
+ m_extended_bounding_box.merge(m_extended_bounding_box.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, m_extended_bounding_box.max(2)));
// extend to contain model, if any
+ BoundingBoxf3 model_bb = m_model.get_bounding_box();
+ if (model_bb.defined) {
+ model_bb.translate(m_model_offset);
+ m_extended_bounding_box.merge(model_bb);
+ }
+#else
+ m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones());
+ // extend to contain model, if any
if (!m_model.get_filename().empty())
m_extended_bounding_box.merge(m_model.get_transformed_bounding_box());
+#endif // ENABLE_GCODE_VIEWER
}
void Bed3D::calc_triangles(const ExPolygon& poly)
{
Polygons triangles;
- poly.triangulate(&triangles);
+ poly.triangulate_p2t(&triangles);
if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true))
printf("Unable to create bed triangles\n");
@@ -309,15 +390,13 @@ void Bed3D::calc_triangles(const ExPolygon& poly)
void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
{
Polylines axes_lines;
- for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0))
- {
+ for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) {
Polyline line;
line.append(Point(x, bed_bbox.min(1)));
line.append(Point(x, bed_bbox.max(1)));
axes_lines.push_back(line);
}
- for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0))
- {
+ for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) {
Polyline line;
line.append(Point(bed_bbox.min(0), y));
line.append(Point(bed_bbox.max(0), y));
@@ -335,6 +414,7 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
printf("Unable to create bed grid lines\n");
}
+#if !ENABLE_GCODE_VIEWER
static std::string system_print_bed_model(const Preset &preset)
{
std::string out;
@@ -352,23 +432,25 @@ static std::string system_print_bed_texture(const Preset &preset)
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
return out;
}
+#endif // !ENABLE_GCODE_VIEWER
std::tuple<Bed3D::EType, std::string, std::string> Bed3D::detect_type(const Pointfs& shape) const
{
auto bundle = wxGetApp().preset_bundle;
- if (bundle != nullptr)
- {
+ if (bundle != nullptr) {
const Preset* curr = &bundle->printers.get_selected_preset();
- while (curr != nullptr)
- {
- if (curr->config.has("bed_shape"))
- {
- if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values)
- {
+ while (curr != nullptr) {
+ if (curr->config.has("bed_shape")) {
+ if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values) {
+#if ENABLE_GCODE_VIEWER
+ std::string model_filename = PresetUtils::system_printer_bed_model(*curr);
+ std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr);
+#else
std::string model_filename = system_print_bed_model(*curr);
std::string texture_filename = system_print_bed_texture(*curr);
+#endif // ENABLE_GCODE_VIEWER
if (!model_filename.empty() && !texture_filename.empty())
- return std::make_tuple(System, model_filename, texture_filename);
+ return { System, model_filename, texture_filename };
}
}
@@ -376,7 +458,7 @@ std::tuple<Bed3D::EType, std::string, std::string> Bed3D::detect_type(const Poin
}
}
- return std::make_tuple(Custom, "", "");
+ return { Custom, "", "" };
}
void Bed3D::render_axes() const
@@ -396,26 +478,21 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
{
- if (m_texture_filename.empty())
- {
+ if (m_texture_filename.empty()) {
m_texture.reset();
render_default(bottom);
return;
}
- if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename))
- {
+ if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) {
m_texture.reset();
- if (boost::algorithm::iends_with(m_texture_filename, ".svg"))
- {
+ if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
// use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
- if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename))
- {
+ if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
- if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8))
- {
+ if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
render_default(bottom);
return;
}
@@ -423,19 +500,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
// starts generating the main texture, compression will run asynchronously
- if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size))
- {
+ if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
render_default(bottom);
return;
}
- }
- else if (boost::algorithm::iends_with(m_texture_filename, ".png"))
- {
+ }
+ else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
- if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename))
- {
- if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false))
- {
+ if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) {
+ if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) {
render_default(bottom);
return;
}
@@ -443,20 +516,17 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
// starts generating the main texture, compression will run asynchronously
- if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true))
- {
+ if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
render_default(bottom);
return;
}
}
- else
- {
+ else {
render_default(bottom);
return;
}
}
- else if (m_texture.unsent_compressed_data_available())
- {
+ else if (m_texture.unsent_compressed_data_available()) {
// sends to gpu the already available compressed levels of the main texture
m_texture.send_compressed_data_to_gpu();
@@ -468,19 +538,14 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
- if (m_triangles.get_vertices_count() > 0)
- {
- if (m_shader.get_shader_program_id() == 0)
- m_shader.init("printbed.vs", "printbed.fs");
-
- if (m_shader.is_initialized())
- {
- m_shader.start_using();
- m_shader.set_uniform("transparent_background", bottom);
- m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
+ if (m_triangles.get_vertices_count() > 0) {
+ GLShaderProgram* shader = wxGetApp().get_shader("printbed");
+ if (shader != nullptr) {
+ shader->start_using();
+ shader->set_uniform("transparent_background", bottom);
+ shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
- if (m_vbo_id == 0)
- {
+ if (m_vbo_id == 0) {
glsafe(::glGenBuffers(1, &m_vbo_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
@@ -498,8 +563,8 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
unsigned int stride = m_triangles.get_vertex_data_size();
- GLint position_id = m_shader.get_attrib_location("v_position");
- GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords");
+ GLint position_id = shader->get_attrib_location("v_position");
+ GLint tex_coords_id = shader->get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
@@ -509,13 +574,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
- if (position_id != -1)
- {
+ if (position_id != -1) {
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
- if (tex_coords_id != -1)
- {
+ if (tex_coords_id != -1) {
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
@@ -537,7 +600,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
- m_shader.stop_using();
+ shader->stop_using();
}
}
}
@@ -547,29 +610,41 @@ void Bed3D::render_model() const
if (m_model_filename.empty())
return;
- if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename))
- {
+ if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) {
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
Vec3d shift = m_bounding_box.center();
shift(2) = -0.03;
+#if ENABLE_GCODE_VIEWER
+ m_model_offset = shift;
+#else
m_model.set_offset(shift);
+#endif // ENABLE_GCODE_VIEWER
// update extended bounding box
calc_bounding_boxes();
}
- if (!m_model.get_filename().empty())
- {
- glsafe(::glEnable(GL_LIGHTING));
- m_model.render();
- glsafe(::glDisable(GL_LIGHTING));
+ if (!m_model.get_filename().empty()) {
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+ if (shader != nullptr) {
+ shader->start_using();
+#if ENABLE_GCODE_VIEWER
+ shader->set_uniform("uniform_color", m_model_color);
+ ::glPushMatrix();
+ ::glTranslated(m_model_offset(0), m_model_offset(1), m_model_offset(2));
+#endif // ENABLE_GCODE_VIEWER
+ m_model.render();
+#if ENABLE_GCODE_VIEWER
+ ::glPopMatrix();
+#endif // ENABLE_GCODE_VIEWER
+ shader->stop_using();
+ }
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
- if (m_texture_filename.empty() && m_model_filename.empty())
- {
+ if (m_texture_filename.empty() && m_model_filename.empty()) {
render_default(bottom);
return;
}
@@ -586,8 +661,7 @@ void Bed3D::render_default(bool bottom) const
m_texture.reset();
unsigned int triangles_vcount = m_triangles.get_vertices_count();
- if (triangles_vcount > 0)
- {
+ if (triangles_vcount > 0) {
bool has_model = !m_model.get_filename().empty();
glsafe(::glEnable(GL_DEPTH_TEST));
@@ -596,11 +670,14 @@ void Bed3D::render_default(bool bottom) const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
- if (!has_model && !bottom)
- {
+ if (!has_model && !bottom) {
// draw background
glsafe(::glDepthMask(GL_FALSE));
+#if ENABLE_GCODE_VIEWER
+ glsafe(::glColor4fv(m_model_color.data()));
+#else
glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f));
+#endif // ENABLE_GCODE_VIEWER
glsafe(::glNormal3d(0.0f, 0.0f, 1.0f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data()));
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
@@ -608,11 +685,11 @@ void Bed3D::render_default(bool bottom) const
}
// draw grid
- glsafe(::glLineWidth(3.0f * m_scale_factor));
+ glsafe(::glLineWidth(1.5f * m_scale_factor));
if (has_model && !bottom)
- glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f));
+ glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f));
else
- glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f));
+ glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()));
glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count()));
@@ -624,8 +701,7 @@ void Bed3D::render_default(bool bottom) const
void Bed3D::reset()
{
- if (m_vbo_id > 0)
- {
+ if (m_vbo_id > 0) {
glsafe(::glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
}
diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp
index abdfca1fe..fbfc3078c 100644
--- a/src/slic3r/GUI/3DBed.hpp
+++ b/src/slic3r/GUI/3DBed.hpp
@@ -3,12 +3,19 @@
#include "GLTexture.hpp"
#include "3DScene.hpp"
-#include "GLShader.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GLModel.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include <tuple>
+#if ENABLE_GCODE_VIEWER
+#include <array>
+#endif // ENABLE_GCODE_VIEWER
+#if !ENABLE_GCODE_VIEWER
class GLUquadric;
typedef class GLUquadric GLUquadricObj;
+#endif // !ENABLE_GCODE_VIEWER
namespace Slic3r {
namespace GUI {
@@ -45,22 +52,53 @@ public:
class Bed3D
{
+#if ENABLE_GCODE_VIEWER
+ class Axes
+ {
+ public:
+ static const float DefaultStemRadius;
+ static const float DefaultStemLength;
+ static const float DefaultTipRadius;
+ static const float DefaultTipLength;
+
+ private:
+#else
struct Axes
{
static const double Radius;
static const double ArrowBaseRadius;
static const double ArrowLength;
+#endif // ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+ Vec3d m_origin{ Vec3d::Zero() };
+ float m_stem_length{ DefaultStemLength };
+ mutable GLModel m_arrow;
+
+ public:
+#else
Vec3d origin;
Vec3d length;
GLUquadricObj* m_quadric;
+#endif // ENABLE_GCODE_VIEWER
+#if !ENABLE_GCODE_VIEWER
Axes();
~Axes();
-
+#endif // !ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+ const Vec3d& get_origin() const { return m_origin; }
+ void set_origin(const Vec3d& origin) { m_origin = origin; }
+ void set_stem_length(float length);
+ float get_total_length() const { return m_stem_length + DefaultTipLength; }
+#endif // ENABLE_GCODE_VIEWER
void render() const;
+#if !ENABLE_GCODE_VIEWER
private:
void render_axis(double length) const;
+#endif // !ENABLE_GCODE_VIEWER
};
public:
@@ -82,10 +120,15 @@ private:
GeometryBuffer m_triangles;
GeometryBuffer m_gridlines;
mutable GLTexture m_texture;
+#if ENABLE_GCODE_VIEWER
+ mutable GLModel m_model;
+ mutable Vec3d m_model_offset{ Vec3d::Zero() };
+ std::array<float, 4> m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f };
+#else
mutable GLBed m_model;
+#endif // ENABLE_GCODE_VIEWER
// temporary texture shown until the main texture has still no levels compressed
mutable GLTexture m_temp_texture;
- mutable Shader m_shader;
mutable unsigned int m_vbo_id;
Axes m_axes;
@@ -101,7 +144,7 @@ public:
const Pointfs& get_shape() const { return m_shape; }
// Return true if the bed shape changed, so the calee will update the UI.
- bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
+ bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
const BoundingBoxf3& get_bounding_box(bool extended) const {
return extended ? m_extended_bounding_box : m_bounding_box;
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index d683fd90c..70fec670c 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -7,19 +7,24 @@
#endif // ENABLE_SMOOTH_NORMALS
#include "3DScene.hpp"
-#if ENABLE_ENVIRONMENT_MAP
+#include "GLShader.hpp"
#include "GUI_App.hpp"
+#if ENABLE_ENVIRONMENT_MAP
#include "Plater.hpp"
#endif // ENABLE_ENVIRONMENT_MAP
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp"
#include "libslic3r/Geometry.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Slicing.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/Analyzer.hpp"
+#endif // !ENABLE_GCODE_VIEWER
#include "slic3r/GUI/BitmapCache.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Utils.hpp"
@@ -442,7 +447,6 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &
bounding_box().transformed(trafo);
}
-
void GLVolume::set_range(double min_z, double max_z)
{
this->qverts_range.first = 0;
@@ -747,6 +751,10 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func) const
{
+ GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
+ if (shader == nullptr)
+ return;
+
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
@@ -757,80 +765,32 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
- GLint current_program_id;
- glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
- GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
- GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1;
- GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1;
-
- GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1;
- GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1;
- GLint print_box_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.actived") : -1;
- GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
-
-#if ENABLE_SLOPE_RENDERING
- GLint slope_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.actived") : -1;
- GLint slope_normal_matrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.volume_world_normal_matrix") : -1;
- GLint slope_z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.z_range") : -1;
-#endif // ENABLE_SLOPE_RENDERING
-
-#if ENABLE_ENVIRONMENT_MAP
- GLint use_environment_tex_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "use_environment_tex") : -1;
-#endif // ENABLE_ENVIRONMENT_MAP
- glcheck();
-
- if (print_box_min_id != -1)
- glsafe(::glUniform3fv(print_box_min_id, 1, (const GLfloat*)m_print_box_min));
-
- if (print_box_max_id != -1)
- glsafe(::glUniform3fv(print_box_max_id, 1, (const GLfloat*)m_print_box_max));
-
- if (z_range_id != -1)
- glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)m_z_range));
-
- if (clipping_plane_id != -1)
- glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)m_clipping_plane));
-
+ shader->set_uniform("print_box.min", m_print_box_min, 3);
+ shader->set_uniform("print_box.max", m_print_box_max, 3);
+ shader->set_uniform("z_range", m_z_range, 2);
+ shader->set_uniform("clipping_plane", m_clipping_plane, 4);
#if ENABLE_SLOPE_RENDERING
- if (slope_z_range_id != -1)
- glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data()));
+ shader->set_uniform("slope.z_range", m_slope.z_range);
#endif // ENABLE_SLOPE_RENDERING
#if ENABLE_ENVIRONMENT_MAP
unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id();
- bool use_environment_texture = current_program_id > 0 && environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1";
-
- if (use_environment_tex_id != -1)
- {
- glsafe(::glUniform1i(use_environment_tex_id, use_environment_texture ? 1 : 0));
- if (use_environment_texture)
- glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id));
- }
+ bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1";
+ shader->set_uniform("use_environment_tex", use_environment_texture);
+ if (use_environment_texture)
+ glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id));
#endif // ENABLE_ENVIRONMENT_MAP
+ glcheck();
GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithIdAndZ& volume : to_render) {
volume.first->set_render_color();
#if ENABLE_SLOPE_RENDERING
- if (color_id >= 0)
- glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)volume.first->render_color));
- else
- glsafe(::glColor4fv(volume.first->render_color));
-
- if (print_box_active_id != -1)
- glsafe(::glUniform1i(print_box_active_id, volume.first->shader_outside_printer_detection_enabled ? 1 : 0));
-
- if (print_box_worldmatrix_id != -1)
- glsafe(::glUniformMatrix4fv(print_box_worldmatrix_id, 1, GL_FALSE, (const GLfloat*)volume.first->world_matrix().cast<float>().data()));
-
- if (slope_active_id != -1)
- glsafe(::glUniform1i(slope_active_id, m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower ? 1 : 0));
-
- if (slope_normal_matrix_id != -1)
- {
- Matrix3f normal_matrix = volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>();
- glsafe(::glUniformMatrix3fv(slope_normal_matrix_id, 1, GL_FALSE, (const GLfloat*)normal_matrix.data()));
- }
+ shader->set_uniform("uniform_color", volume.first->render_color, 4);
+ shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled);
+ shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix());
+ shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower);
+ shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
volume.first->render();
#else
@@ -839,7 +799,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
}
#if ENABLE_ENVIRONMENT_MAP
- if (use_environment_tex_id != -1 && use_environment_texture)
+ if (use_environment_texture)
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
#endif // ENABLE_ENVIRONMENT_MAP
@@ -1057,6 +1017,7 @@ bool GLVolumeCollection::has_toolpaths_to_export() const
return false;
}
+#if !ENABLE_GCODE_VIEWER
void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
{
if (filename == nullptr)
@@ -1338,6 +1299,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
fclose(fp);
}
+#endif // !ENABLE_GCODE_VIEWER
// caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array(
@@ -1985,6 +1947,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height
thick_point_to_verts(point, width, height, volume);
}
+#if !ENABLE_GCODE_VIEWER
GLModel::GLModel()
: m_filename("")
{
@@ -2040,6 +2003,10 @@ void GLModel::reset()
void GLModel::render() const
{
+ GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
+ if (shader == nullptr)
+ return;
+
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
@@ -2047,17 +2014,8 @@ void GLModel::render() const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
- GLint current_program_id;
- glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
- GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
- glcheck();
-
#if ENABLE_SLOPE_RENDERING
- if (color_id >= 0)
- glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_volume.render_color));
- else
- glsafe(::glColor4fv(m_volume.render_color));
-
+ shader->set_uniform("uniform_color", m_volume.render_color, 4);
m_volume.render();
#else
m_volume.render(color_id, -1, -1);
@@ -2274,5 +2232,6 @@ bool GLBed::on_init_from_file(const std::string& filename)
return true;
}
+#endif // !ENABLE_GCODE_VIEWER
} // namespace Slic3r
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index f935f0fb4..7e8ae6fe3 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -26,12 +26,6 @@
#endif // HAS_GLSAFE
namespace Slic3r {
-namespace GUI {
-class Bed3D;
-struct Camera;
-class GLToolbar;
-} // namespace GUI
-
class SLAPrintObject;
enum SLAPrintObjectStep : unsigned int;
class DynamicPrintConfig;
@@ -603,8 +597,10 @@ public:
std::string log_memory_info() const;
bool has_toolpaths_to_export() const;
+#if !ENABLE_GCODE_VIEWER
// Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format
void export_toolpaths_to_obj(const char* filename) const;
+#endif // !ENABLE_GCODE_VIEWER
private:
GLVolumeCollection(const GLVolumeCollection &other);
@@ -613,6 +609,7 @@ private:
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
+#if !ENABLE_GCODE_VIEWER
class GLModel
{
protected:
@@ -672,6 +669,7 @@ class GLBed : public GLModel
protected:
bool on_init_from_file(const std::string& filename) override;
};
+#endif // !ENABLE_GCODE_VIEWER
struct _3DScene
{
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 7309654a8..9675db10e 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -19,7 +19,9 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/libslic3r.h"
@@ -92,7 +94,11 @@ void BackgroundSlicingProcess::process_fff()
wxCommandEvent evt(m_event_slicing_completed_id);
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
- m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
+#if ENABLE_GCODE_VIEWER
+ m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb);
+#else
+ m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
+#endif // ENABLE_GCODE_VIEWER
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
@@ -382,6 +388,17 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
assert(m_print != nullptr);
assert(config.opt_enum<PrinterTechnology>("printer_technology") == m_print->technology());
Print::ApplyStatus invalidated = m_print->apply(model, config);
+#if ENABLE_GCODE_VIEWER
+ if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
+ !this->m_fff_print->is_step_done(psGCodeExport))
+ {
+ // Some FFF status was invalidated, and the G-code was not exported yet.
+ // Let the G-code preview UI know that the final G-code preview is not valid.
+ // In addition, this early memory deallocation reduces memory footprint.
+ if (m_gcode_result != nullptr)
+ m_gcode_result->reset();
+ }
+#else
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
m_gcode_preview_data != nullptr && ! this->m_fff_print->is_step_done(psGCodeExport)) {
// Some FFF status was invalidated, and the G-code was not exported yet.
@@ -389,6 +406,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
// In addition, this early memory deallocation reduces memory footprint.
m_gcode_preview_data->reset();
}
+#endif // ENABLE_GCODE_VIEWER
return invalidated;
}
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index c4672f1b4..9fe1157b6 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -11,6 +11,9 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+#endif // ENABLE_GCODE_VIEWER
namespace boost { namespace filesystem { class path; } }
@@ -18,7 +21,9 @@ namespace boost { namespace filesystem { class path; } }
namespace Slic3r {
class DynamicPrintConfig;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
class Model;
class SLAPrint;
@@ -50,8 +55,12 @@ public:
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
- void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
- void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
+ void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
+#if ENABLE_GCODE_VIEWER
+ void set_gcode_result(GCodeProcessor::Result* result) { m_gcode_result = result; }
+#else
+ void set_gcode_preview_data(GCodePreviewData* gpd) { m_gcode_preview_data = gpd; }
+#endif // ENABLE_GCODE_VIEWER
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
// and the background processing will transition into G-code export.
@@ -157,12 +166,17 @@ private:
// Non-owned pointers to Print instances.
Print *m_fff_print = nullptr;
SLAPrint *m_sla_print = nullptr;
+#if ENABLE_GCODE_VIEWER
+ // Data structure, to which the G-code export writes its annotations.
+ GCodeProcessor::Result *m_gcode_result = nullptr;
+#else
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = nullptr;
- // Callback function, used to write thumbnails into gcode.
- ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
- SL1Archive m_sla_archive;
- // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
+#endif // ENABLE_GCODE_VIEWER
+ // Callback function, used to write thumbnails into gcode.
+ ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
+ SL1Archive m_sla_archive;
+ // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,
// but once set, it cannot be re-set.
diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp
index 74a4bc1c8..cc4c831ae 100644
--- a/src/slic3r/GUI/BitmapCache.cpp
+++ b/src/slic3r/GUI/BitmapCache.cpp
@@ -3,6 +3,9 @@
#include "libslic3r/Utils.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "GUI.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "GUI_Utils.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include <boost/filesystem.hpp>
@@ -355,6 +358,7 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi
}
+#if !ENABLE_GCODE_VIEWER
static inline int hex_digit_to_int(const char c)
{
return
@@ -362,6 +366,7 @@ static inline int hex_digit_to_int(const char c)
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
+#endif // !ENABLE_GCODE_VIEWER
bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out)
{
diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp
index c1bf8e825..3bd22590f 100644
--- a/src/slic3r/GUI/Camera.cpp
+++ b/src/slic3r/GUI/Camera.cpp
@@ -5,6 +5,7 @@
#include "GUI_App.hpp"
#if ENABLE_CAMERA_STATISTICS
#include "Mouse3DController.hpp"
+#include "Plater.hpp"
#endif // ENABLE_CAMERA_STATISTICS
#include <GL/glew.h>
diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp
index ece999c07..6e4256235 100644
--- a/src/slic3r/GUI/Camera.hpp
+++ b/src/slic3r/GUI/Camera.hpp
@@ -84,6 +84,7 @@ public:
double get_near_z() const { return m_frustrum_zs.first; }
double get_far_z() const { return m_frustrum_zs.second; }
+ const std::pair<double, double>& get_z_range() const { return m_frustrum_zs; }
double get_fov() const;
diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp
index a4b65c1b8..8a9ac34ea 100644
--- a/src/slic3r/GUI/DoubleSlider.cpp
+++ b/src/slic3r/GUI/DoubleSlider.cpp
@@ -1,5 +1,11 @@
+#include "libslic3r/libslic3r.h"
+#if ENABLE_GCODE_VIEWER
+#include "DoubleSlider.hpp"
+#include "libslic3r/GCode.hpp"
+#else
#include "wxExtensions.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
@@ -15,7 +21,9 @@
#include <wx/bmpcbox.h>
#include <wx/statline.h>
#include <wx/dcclient.h>
+#if !ENABLE_GCODE_VIEWER
#include <wx/numformatter.h>
+#endif // !ENABLE_GCODE_VIEWER
#include <wx/colordlg.h>
#include <cmath>
@@ -72,8 +80,13 @@ Control::Control( wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
+#if ENABLE_GCODE_VIEWER
+ m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up"));
+ m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down"));
+#else
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
+#endif // ENABLE_GCODE_VIEWER
m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
@@ -275,14 +288,14 @@ wxCoord Control::get_position_from_value(const int value)
return wxCoord(SLIDER_MARGIN + int(val*step + 0.5));
}
-wxSize Control::get_size()
+wxSize Control::get_size() const
{
int w, h;
get_size(&w, &h);
return wxSize(w, h);
}
-void Control::get_size(int *w, int *h)
+void Control::get_size(int* w, int* h) const
{
GetSize(w, h);
is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim;
@@ -302,14 +315,22 @@ double Control::get_double_value(const SelectedSlider& selection)
Info Control::GetTicksValues() const
{
Info custom_gcode_per_print_z;
+#if ENABLE_GCODE_VIEWER
+ std::vector<CustomGCode::Item>& values = custom_gcode_per_print_z.gcodes;
+#else
std::vector<Item>& values = custom_gcode_per_print_z.gcodes;
+#endif // ENABLE_GCODE_VIEWER
const int val_size = m_values.size();
if (!m_values.empty())
for (const TickCode& tick : m_ticks.ticks) {
if (tick.tick > val_size)
break;
+#if ENABLE_GCODE_VIEWER
+ values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra });
+#else
values.emplace_back(Item{m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra});
+#endif // ENABLE_GCODE_VIEWER
}
if (m_force_mode_apply)
@@ -329,7 +350,11 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
const bool was_empty = m_ticks.empty();
m_ticks.ticks.clear();
+#if ENABLE_GCODE_VIEWER
+ const std::vector<CustomGCode::Item>& heights = custom_gcode_per_print_z.gcodes;
+#else
const std::vector<Item>& heights = custom_gcode_per_print_z.gcodes;
+#endif // ENABLE_GCODE_VIEWER
for (auto h : heights) {
auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon());
@@ -401,7 +426,15 @@ void Control::draw_focus_rect()
void Control::render()
{
+#if ENABLE_GCODE_VIEWER
+#ifdef _WIN32
+ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+#else
SetBackgroundColour(GetParent()->GetBackgroundColour());
+#endif // _WIN32
+#else
+ SetBackgroundColour(GetParent()->GetBackgroundColour());
+#endif // ENABLE_GCODE_VIEWER
draw_focus_rect();
wxPaintDC dc(this);
@@ -417,22 +450,22 @@ void Control::render()
// draw line
draw_scroll_line(dc, lower_pos, higher_pos);
- //draw color print ticks
+ // draw color print ticks
draw_ticks(dc);
// draw both sliders
draw_thumbs(dc, lower_pos, higher_pos);
- //draw lock/unlock
+ // draw lock/unlock
draw_one_layer_icon(dc);
- //draw revert bitmap (if it's shown)
+ // draw revert bitmap (if it's shown)
draw_revert_icon(dc);
- //draw cog bitmap (if it's shown)
+ // draw cog bitmap (if it's shown)
draw_cog_icon(dc);
- //draw mouse position
+ // draw mouse position
draw_tick_on_mouse_position(dc);
}
@@ -544,10 +577,21 @@ wxString Control::get_label(int tick) const
if (value >= m_values.size())
return "ErrVal";
- const wxString str = m_values.empty() ?
- wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) :
- wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
- return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value+1);
+#if ENABLE_GCODE_VIEWER
+ if (m_draw_mode == dmSequentialGCodeView)
+ return wxString::Format("%d", static_cast<unsigned int>(m_values[value]));
+ else {
+ const wxString str = m_values.empty() ?
+ wxString::Format("%.*f", 2, m_label_koef * value) :
+ wxString::Format("%.*f", 2, m_values[value]);
+ return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1);
+ }
+#else
+ const wxString str = m_values.empty() ?
+ wxNumberFormatter::ToString(m_label_koef * value, 2, wxNumberFormatter::Style_None) :
+ wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
+ return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1);
+#endif // ENABLE_GCODE_VIEWER
}
void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side/*=true*/) const
@@ -556,13 +600,36 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_
const wxString label = get_label(tick);
dc.GetMultiLineTextExtent(label, &text_width, &text_height);
wxPoint text_pos;
- if (right_side)
- text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) :
- wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1);
- else
- text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) :
- wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1);
- dc.DrawText(label, text_pos);
+ if (right_side) {
+ if (is_horizontal()) {
+ int width;
+ int height;
+ get_size(&width, &height);
+
+ int x_right = pos.x + 1 + text_width;
+ int xx = (x_right < width) ? pos.x + 1 : pos.x - text_width - 1;
+ text_pos = wxPoint(xx, pos.y + m_thumb_size.x / 2 + 1);
+ }
+ else
+ text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1);
+
+ // update text rectangle
+ m_rect_lower_thumb_text = wxRect(text_pos, wxSize(text_width, text_height));
+ }
+ else {
+ if (is_horizontal()) {
+ int x = pos.x - text_width - 1;
+ int xx = (x > 0) ? x : pos.x + 1;
+ text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1);
+ }
+ else
+ text_pos = wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1);
+
+ // update text rectangle
+ m_rect_higher_thumb_text = wxRect(text_pos, wxSize(text_width, text_height));
+ }
+
+ dc.DrawText(label, text_pos);
}
void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
@@ -572,6 +639,10 @@ void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider
void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
{
+#if ENABLE_GCODE_VIEWER
+ wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x);
+ wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y);
+#else
wxCoord x_draw, y_draw;
if (selection == ssLower) {
if (is_horizontal()) {
@@ -583,7 +654,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
- else{
+ else {
if (is_horizontal()) {
x_draw = pos.x;
y_draw = pos.y - int(0.5*m_thumb_size.y);
@@ -593,6 +664,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
+#endif // ENABLE_GCODE_VIEWER
dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
// Update thumb rect
@@ -756,7 +828,15 @@ void Control::draw_colored_band(wxDC& dc)
// don't color a band for MultiExtruder mode
if (m_ticks.empty() || m_mode == MultiExtruder)
{
+#if ENABLE_GCODE_VIEWER
+#ifdef _WIN32
+ draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band);
+#else
draw_band(dc, GetParent()->GetBackgroundColour(), main_band);
+#endif // _WIN32
+#else
+ draw_band(dc, GetParent()->GetBackgroundColour(), main_band);
+#endif // ENABLE_GCODE_VIEWER
return;
}
@@ -788,6 +868,11 @@ void Control::draw_colored_band(wxDC& dc)
void Control::draw_one_layer_icon(wxDC& dc)
{
+#if ENABLE_GCODE_VIEWER
+ if (m_draw_mode == dmSequentialGCodeView)
+ return;
+#endif // ENABLE_GCODE_VIEWER
+
const wxBitmap& icon = m_is_one_layer ?
m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() :
m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp();
@@ -829,8 +914,20 @@ void Control::draw_cog_icon(wxDC& dc)
get_size(&width, &height);
wxCoord x_draw, y_draw;
- is_horizontal() ? x_draw = width-2 : x_draw = width - m_cog_icon_dim - 2;
- is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height-2;
+#if ENABLE_GCODE_VIEWER
+ if (m_draw_mode == dmSequentialGCodeView)
+ {
+ is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim;
+ is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2;
+ }
+ else
+ {
+#endif // ENABLE_GCODE_VIEWER
+ is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2;
+ is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2;
+#if ENABLE_GCODE_VIEWER
+ }
+#endif // ENABLE_GCODE_VIEWER
dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw);
@@ -838,9 +935,12 @@ void Control::draw_cog_icon(wxDC& dc)
m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim);
}
-void Control::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
+void Control::update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection)
{
- const wxRect& rect = wxRect(begin_x, begin_y + (selection == ssLower ? int(m_thumb_size.y * 0.5) : 0), m_thumb_size.x, int(m_thumb_size.y*0.5));
+ const wxRect rect = is_horizontal() ?
+ wxRect(begin_x + (selection == ssHigher ? m_thumb_size.x / 2 : 0), begin_y, m_thumb_size.x / 2, m_thumb_size.y) :
+ wxRect(begin_x, begin_y + (selection == ssLower ? m_thumb_size.y / 2 : 0), m_thumb_size.x, m_thumb_size.y / 2);
+
if (selection == ssLower)
m_rect_lower_thumb = rect;
else
@@ -968,10 +1068,19 @@ wxString Control::get_tooltip(int tick/*=-1*/)
if (m_focus == fiRevertIcon)
return _L("Discard all custom changes");
if (m_focus == fiCogIcon)
- return m_mode == MultiAsSingle ?
+#if ENABLE_GCODE_VIEWER
+ {
+ if (m_draw_mode == dmSequentialGCodeView)
+ return _L("Jump to move") + " (Shift + G)";
+ else
+#endif // ENABLE_GCODE_VIEWER
+ return m_mode == MultiAsSingle ?
GUI::from_u8((boost::format(_u8L("Jump to height %s or "
- "Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) :
- _L("Jump to height") + " (Shift + G)";
+ "Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) :
+ _L("Jump to height") + " (Shift + G)";
+#if ENABLE_GCODE_VIEWER
+ }
+#endif // ENABLE_GCODE_VIEWER
if (m_focus == fiColorBand)
return m_mode != SingleExtruder ? "" :
_L("Edit current color - Right click the colored slider segment");
@@ -1099,6 +1208,14 @@ void Control::OnMotion(wxMouseEvent& event)
else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) &&
get_edited_tick_for_position(pos) >= 0 )
m_focus = fiColorBand;
+ else if (is_point_in_rect(pos, m_rect_lower_thumb))
+ m_focus = fiLowerThumb;
+ else if (is_point_in_rect(pos, m_rect_higher_thumb))
+ m_focus = fiHigherThumb;
+ else if (is_point_in_rect(pos, m_rect_lower_thumb_text))
+ m_focus = fiLowerThumbText;
+ else if (is_point_in_rect(pos, m_rect_higher_thumb_text))
+ m_focus = fiHigherThumbText;
else {
m_focus = fiTick;
tick = get_tick_near_point(pos);
@@ -1223,7 +1340,11 @@ void Control::OnLeftUp(wxMouseEvent& event)
if (m_mode == MultiAsSingle && m_draw_mode == dmRegular)
show_cog_icon_context_menu();
else
+#if ENABLE_GCODE_VIEWER
+ jump_to_value();
+#else
jump_to_print_z();
+#endif // ENABLE_GCODE_VIEWER
break;
case maOneLayerIconClick:
switch_one_layer_mode();
@@ -1262,6 +1383,15 @@ void Control::move_current_thumb(const bool condition)
if (is_horizontal())
delta *= -1;
+ // accelerators
+ int accelerator = 0;
+ if (wxGetKeyState(WXK_SHIFT))
+ accelerator += 5;
+ if (wxGetKeyState(WXK_CONTROL))
+ accelerator += 5;
+ if (accelerator > 0)
+ delta *= accelerator;
+
if (m_selection == ssLower) {
m_lower_value -= delta;
correct_lower_value();
@@ -1295,12 +1425,32 @@ void Control::OnWheel(wxMouseEvent& event)
ssLower : ssHigher;
}
+#if ENABLE_GCODE_VIEWER
+ move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0);
+#else
move_current_thumb(event.GetWheelRotation() > 0);
+#endif // ENABLE_GCODE_VIEWER
}
void Control::OnKeyDown(wxKeyEvent &event)
{
const int key = event.GetKeyCode();
+#if ENABLE_GCODE_VIEWER
+ if (m_draw_mode != dmSequentialGCodeView && key == WXK_NUMPAD_ADD) {
+ // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice.
+ // To avoid this case we should suppress second add_tick() call.
+ m_ticks.suppress_plus(true);
+ add_current_tick(true);
+ }
+ else if (m_draw_mode != dmSequentialGCodeView && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK)) {
+ // OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice.
+ // To avoid this case we should suppress second delete_tick() call.
+ m_ticks.suppress_minus(true);
+ delete_current_tick();
+ }
+ else if (m_draw_mode != dmSequentialGCodeView && event.GetKeyCode() == WXK_SHIFT)
+ UseDefaultColors(false);
+#else
if (key == WXK_NUMPAD_ADD) {
// OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice.
// To avoid this case we should suppress second add_tick() call.
@@ -1315,22 +1465,37 @@ void Control::OnKeyDown(wxKeyEvent &event)
}
else if (event.GetKeyCode() == WXK_SHIFT)
UseDefaultColors(false);
+#endif // ENABLE_GCODE_VIEWER
else if (is_horizontal())
{
- if (key == WXK_LEFT || key == WXK_RIGHT)
- move_current_thumb(key == WXK_LEFT);
- else if (key == WXK_UP || key == WXK_DOWN) {
- m_selection = key == WXK_UP ? ssHigher : ssLower;
- Refresh();
+#if ENABLE_GCODE_VIEWER
+ if (m_is_focused)
+ {
+#endif // ENABLE_GCODE_VIEWER
+ if (key == WXK_LEFT || key == WXK_RIGHT)
+ move_current_thumb(key == WXK_LEFT);
+ else if (key == WXK_UP || key == WXK_DOWN) {
+ m_selection = key == WXK_UP ? ssHigher : ssLower;
+ Refresh();
+ }
+#if ENABLE_GCODE_VIEWER
+ }
+#endif // ENABLE_GCODE_VIEWER
}
- }
else {
- if (key == WXK_LEFT || key == WXK_RIGHT) {
- m_selection = key == WXK_LEFT ? ssHigher : ssLower;
- Refresh();
+#if ENABLE_GCODE_VIEWER
+ if (m_is_focused)
+ {
+#endif // ENABLE_GCODE_VIEWER
+ if (key == WXK_LEFT || key == WXK_RIGHT) {
+ m_selection = key == WXK_LEFT ? ssHigher : ssLower;
+ Refresh();
+ }
+ else if (key == WXK_UP || key == WXK_DOWN)
+ move_current_thumb(key == WXK_UP);
+#if ENABLE_GCODE_VIEWER
}
- else if (key == WXK_UP || key == WXK_DOWN)
- move_current_thumb(key == WXK_UP);
+#endif // ENABLE_GCODE_VIEWER
}
event.Skip(); // !Needed to have EVT_CHAR generated as well
@@ -1351,16 +1516,27 @@ void Control::OnKeyUp(wxKeyEvent &event)
void Control::OnChar(wxKeyEvent& event)
{
const int key = event.GetKeyCode();
- if (key == '+' && !m_ticks.suppressed_plus()) {
- add_current_tick(true);
- m_ticks.suppress_plus(false);
- }
- else if (key == '-' && !m_ticks.suppressed_minus()) {
- delete_current_tick();
- m_ticks.suppress_minus(false);
+#if ENABLE_GCODE_VIEWER
+ if (m_draw_mode != dmSequentialGCodeView)
+ {
+#endif // ENABLE_GCODE_VIEWER
+ if (key == '+' && !m_ticks.suppressed_plus()) {
+ add_current_tick(true);
+ m_ticks.suppress_plus(false);
+ }
+ else if (key == '-' && !m_ticks.suppressed_minus()) {
+ delete_current_tick();
+ m_ticks.suppress_minus(false);
+ }
+#if ENABLE_GCODE_VIEWER
}
+#endif // ENABLE_GCODE_VIEWER
if (key == 'G')
+#if ENABLE_GCODE_VIEWER
+ jump_to_value();
+#else
jump_to_print_z();
+#endif // ENABLE_GCODE_VIEWER
}
void Control::OnRightDown(wxMouseEvent& event)
@@ -1550,7 +1726,11 @@ void Control::show_cog_icon_context_menu()
wxMenu menu;
append_menu_item(&menu, wxID_ANY, _(L("Jump to height")) + " (Shift+G)", "",
- [this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu);
+#if ENABLE_GCODE_VIEWER
+ [this](wxCommandEvent&) { jump_to_value(); }, "", & menu);
+#else
+ [this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu);
+#endif // ENABLE_GCODE_VIEWER
append_menu_item(&menu, wxID_ANY, _(L("Set extruder sequence for the entire print")), "",
[this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu);
@@ -1670,11 +1850,21 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height)
return into_u8(dlg.GetValue());
}
+#if ENABLE_GCODE_VIEWER
+static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode)
+#else
static double get_print_z_to_jump(double active_print_z, double min_z, double max_z)
+#endif // ENABLE_GCODE_VIEWER
{
+#if ENABLE_GCODE_VIEWER
+ wxString msg_text = (mode == dmSequentialGCodeView) ? _L("Enter the move you want to jump to") + ":" : _L("Enter the height you want to jump to") + ":";
+ wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height");
+ wxString msg_in = GUI::double_to_string(active_value);
+#else
wxString msg_text = _(L("Enter the height you want to jump to")) + ":";
wxString msg_header = _(L("Jump to height"));
wxString msg_in = GUI::double_to_string(active_print_z);
+#endif // ENABLE_GCODE_VIEWER
// get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle);
@@ -1883,6 +2073,23 @@ void Control::edit_extruder_sequence()
post_ticks_changed_event(ToolChange);
}
+#if ENABLE_GCODE_VIEWER
+void Control::jump_to_value()
+{
+ double value = get_value_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value],
+ m_values[m_min_value], m_values[m_max_value], m_draw_mode);
+ if (value < 0.0)
+ return;
+
+ auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
+ int tick_value = it - m_values.begin();
+
+ if (m_selection == ssLower)
+ SetLowerValue(tick_value);
+ else
+ SetHigherValue(tick_value);
+}
+#else
void Control::jump_to_print_z()
{
double print_z = get_print_z_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value],
@@ -1898,6 +2105,7 @@ void Control::jump_to_print_z()
else
SetHigherValue(tick_value);
}
+#endif // ENABLE_GCODE_VIEWER
void Control::post_ticks_changed_event(Type type /*= Custom*/)
{
@@ -1968,7 +2176,11 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int
{
if (mode == SingleExtruder && type == ColorChange && m_use_default_colors)
{
+#if ENABLE_GCODE_VIEWER
+ const std::vector<std::string>& colors = ColorPrintColors::get();
+#else
const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
+#endif // ENABLE_GCODE_VIEWER
if (ticks.empty())
return colors[0];
m_default_color_idx++;
diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp
index e39db6fb4..fb87ac4a9 100644
--- a/src/slic3r/GUI/DoubleSlider.hpp
+++ b/src/slic3r/GUI/DoubleSlider.hpp
@@ -4,7 +4,9 @@
#include "libslic3r/CustomGCode.hpp"
#include "wxExtensions.hpp"
+#if !ENABLE_GCODE_VIEWER
#include <wx/wx.h>
+#endif // !ENABLE_GCODE_VIEWER
#include <wx/window.h>
#include <wx/control.h>
#include <wx/dc.h>
@@ -42,6 +44,10 @@ enum FocusedItem {
fiCogIcon,
fiColorBand,
fiActionIcon,
+ fiLowerThumb,
+ fiHigherThumb,
+ fiLowerThumbText,
+ fiHigherThumbText,
fiTick
};
@@ -73,6 +79,9 @@ enum DrawMode
dmRegular,
dmSlaPrint,
dmSequentialFffPrint,
+#if ENABLE_GCODE_VIEWER
+ dmSequentialGCodeView,
+#endif // ENABLE_GCODE_VIEWER
};
struct TickCode
@@ -210,6 +219,9 @@ public:
void SetTicksValues(const Info &custom_gcode_per_print_z);
void SetDrawMode(bool is_sla_print, bool is_sequential_print);
+#if ENABLE_GCODE_VIEWER
+ void SetDrawMode(DrawMode mode) { m_draw_mode = mode; }
+#endif // ENABLE_GCODE_VIEWER
void SetManipulationMode(Mode mode) { m_mode = mode; }
Mode GetManipulationMode() const { return m_mode; }
@@ -222,7 +234,7 @@ public:
bool is_higher_at_max() const { return m_higher_value == m_max_value; }
bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); }
- void OnPaint(wxPaintEvent& ) { render();}
+ void OnPaint(wxPaintEvent& ) { render(); }
void OnLeftDown(wxMouseEvent& event);
void OnMotion(wxMouseEvent& event);
void OnLeftUp(wxMouseEvent& event);
@@ -246,7 +258,11 @@ public:
void discard_all_thicks();
void move_current_thumb_to_pos(wxPoint pos);
void edit_extruder_sequence();
+#if ENABLE_GCODE_VIEWER
+ void jump_to_value();
+#else
void jump_to_print_z();
+#endif // ENABLE_GCODE_VIEWER
void show_add_context_menu();
void show_edit_context_menu();
void show_cog_icon_context_menu();
@@ -272,7 +288,7 @@ protected:
void draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side = true) const;
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
- void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
+ void update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection);
bool detect_selected_slider(const wxPoint& pt);
void correct_lower_value();
void correct_higher_value();
@@ -290,8 +306,8 @@ private:
int get_value_from_position(const wxCoord x, const wxCoord y);
int get_value_from_position(const wxPoint pos) { return get_value_from_position(pos.x, pos.y); }
wxCoord get_position_from_value(const int value);
- wxSize get_size();
- void get_size(int *w, int *h);
+ wxSize get_size() const;
+ void get_size(int* w, int* h) const;
double get_double_value(const SelectedSlider& selection);
wxString get_tooltip(int tick = -1);
int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange);
@@ -348,6 +364,8 @@ private:
wxRect m_rect_lower_thumb;
wxRect m_rect_higher_thumb;
+ mutable wxRect m_rect_lower_thumb_text;
+ mutable wxRect m_rect_higher_thumb_text;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxRect m_rect_revert_icon;
diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp
new file mode 100644
index 000000000..24ed5be9b
--- /dev/null
+++ b/src/slic3r/GUI/GCodeViewer.cpp
@@ -0,0 +1,2350 @@
+#include "libslic3r/libslic3r.h"
+#include "GCodeViewer.hpp"
+
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/Print.hpp"
+#include "libslic3r/Geometry.hpp"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Utils.hpp"
+#include "GUI_App.hpp"
+#include "MainFrame.hpp"
+#include "Plater.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "Camera.hpp"
+#include "I18N.hpp"
+#include "GUI_Utils.hpp"
+#include "DoubleSlider.hpp"
+#include "GLCanvas3D.hpp"
+#include "GLToolbar.hpp"
+#include "GUI_Preview.hpp"
+#include <imgui/imgui_internal.h>
+
+#include <GL/glew.h>
+#include <boost/log/trivial.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+#include <array>
+#include <algorithm>
+#include <chrono>
+
+namespace Slic3r {
+namespace GUI {
+
+static unsigned char buffer_id(EMoveType type) {
+ return static_cast<unsigned char>(type) - static_cast<unsigned char>(EMoveType::Retract);
+}
+
+static EMoveType buffer_type(unsigned char id) {
+ return static_cast<EMoveType>(static_cast<unsigned char>(EMoveType::Retract) + id);
+}
+
+static std::array<float, 3> decode_color(const std::string& color) {
+ static const float INV_255 = 1.0f / 255.0f;
+
+ std::array<float, 3> ret = { 0.0f, 0.0f, 0.0f };
+ const char* c = color.data() + 1;
+ if (color.size() == 7 && color.front() == '#') {
+ for (size_t j = 0; j < 3; ++j) {
+ int digit1 = hex_digit_to_int(*c++);
+ int digit2 = hex_digit_to_int(*c++);
+ if (digit1 == -1 || digit2 == -1)
+ break;
+
+ ret[j] = float(digit1 * 16 + digit2) * INV_255;
+ }
+ }
+ return ret;
+}
+
+static std::vector<std::array<float, 3>> decode_colors(const std::vector<std::string>& colors) {
+ std::vector<std::array<float, 3>> output(colors.size(), { 0.0f, 0.0f, 0.0f });
+ for (size_t i = 0; i < colors.size(); ++i) {
+ output[i] = decode_color(colors[i]);
+ }
+ return output;
+}
+
+static float round_to_nearest(float value, unsigned int decimals)
+{
+ float res = 0.0f;
+ if (decimals == 0)
+ res = std::round(value);
+ else {
+ char buf[64];
+ sprintf(buf, "%.*g", decimals, value);
+ res = std::stof(buf);
+ }
+ return res;
+}
+
+void GCodeViewer::VBuffer::reset()
+{
+ // release gpu memory
+ if (id > 0) {
+ glsafe(::glDeleteBuffers(1, &id));
+ id = 0;
+ }
+
+ count = 0;
+}
+
+void GCodeViewer::IBuffer::reset()
+{
+ // release gpu memory
+ if (id > 0) {
+ glsafe(::glDeleteBuffers(1, &id));
+ id = 0;
+ }
+
+ count = 0;
+}
+
+bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const
+{
+ switch (move.type)
+ {
+ case EMoveType::Tool_change:
+ case EMoveType::Color_change:
+ case EMoveType::Pause_Print:
+ case EMoveType::Custom_GCode:
+ case EMoveType::Retract:
+ case EMoveType::Unretract:
+ case EMoveType::Extrude:
+ {
+ // use rounding to reduce the number of generated paths
+ return type == move.type && role == move.extrusion_role && height == round_to_nearest(move.height, 2) &&
+ width == round_to_nearest(move.width, 2) && feedrate == move.feedrate && fan_speed == move.fan_speed &&
+ volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id &&
+ cp_color_id == move.cp_color_id;
+ }
+ case EMoveType::Travel:
+ {
+ return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id;
+ }
+ default: { return false; }
+ }
+}
+
+void GCodeViewer::TBuffer::reset()
+{
+ // release gpu memory
+ vertices.reset();
+ indices.reset();
+
+ // release cpu memory
+ paths = std::vector<Path>();
+ render_paths = std::vector<RenderPath>();
+}
+
+void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id)
+{
+ Path::Endpoint endpoint = { i_id, s_id, move.position };
+ // use rounding to reduce the number of generated paths
+ paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder,
+ round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed,
+ round_to_nearest(move.volumetric_rate(), 2), move.extruder_id, move.cp_color_id });
+}
+
+GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const
+{
+ // Input value scaled to the colors range
+ const float step = step_size();
+ const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f
+
+ const size_t color_max_idx = Range_Colors.size() - 1;
+
+ // Compute the two colors just below (low) and above (high) the input value
+ const size_t color_low_idx = std::clamp<size_t>(static_cast<size_t>(global_t), 0, color_max_idx);
+ const size_t color_high_idx = std::clamp<size_t>(color_low_idx + 1, 0, color_max_idx);
+
+ // Compute how far the value is between the low and high colors so that they can be interpolated
+ const float local_t = std::clamp(global_t - static_cast<float>(color_low_idx), 0.0f, 1.0f);
+
+ // Interpolate between the low and high colors to find exactly which color the input value should get
+ Color ret;
+ for (unsigned int i = 0; i < 3; ++i) {
+ ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t);
+ }
+ return ret;
+}
+
+void GCodeViewer::SequentialView::Marker::init()
+{
+ m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f));
+}
+
+void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position)
+{
+ m_world_position = position;
+ m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast<double>()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast<float>();
+}
+
+void GCodeViewer::SequentialView::Marker::render() const
+{
+ if (!m_visible)
+ return;
+
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+
+ shader->start_using();
+ shader->set_uniform("uniform_color", m_color);
+
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixf(m_world_transform.data()));
+
+ m_model.render();
+
+ glsafe(::glPopMatrix());
+
+ shader->stop_using();
+
+ glsafe(::glDisable(GL_BLEND));
+
+ static float last_window_width = 0.0f;
+ static size_t last_text_length = 0;
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
+ imgui.set_next_window_pos(0.5f * static_cast<float>(cnv_size.get_width()), static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+ ImGui::SetNextWindowBgAlpha(0.25f);
+ imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":");
+ ImGui::SameLine();
+ char buf[1024];
+ sprintf(buf, "X: %.2f, Y: %.2f, Z: %.2f", m_world_position(0), m_world_position(1), m_world_position(2));
+ imgui.text(std::string(buf));
+
+ // force extra frame to automatically update window size
+ float width = ImGui::GetWindowWidth();
+ size_t length = strlen(buf);
+ if (width != last_window_width || length != last_text_length) {
+ last_window_width = width;
+ last_text_length = length;
+ wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
+ wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
+ }
+
+ imgui.end();
+ ImGui::PopStyleVar();
+}
+
+const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{
+ { 0.75f, 0.75f, 0.75f }, // erNone
+ { 1.00f, 0.90f, 0.43f }, // erPerimeter
+ { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter
+ { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter
+ { 0.69f, 0.19f, 0.16f }, // erInternalInfill
+ { 0.59f, 0.33f, 0.80f }, // erSolidInfill
+ { 0.94f, 0.33f, 0.33f }, // erTopSolidInfill
+ { 1.00f, 0.55f, 0.41f }, // erIroning
+ { 0.30f, 0.50f, 0.73f }, // erBridgeInfill
+ { 1.00f, 1.00f, 1.00f }, // erGapFill
+ { 0.00f, 0.53f, 0.43f }, // erSkirt
+ { 0.00f, 1.00f, 0.00f }, // erSupportMaterial
+ { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface
+ { 0.70f, 0.89f, 0.67f }, // erWipeTower
+ { 0.37f, 0.82f, 0.58f }, // erCustom
+ { 0.00f, 0.00f, 0.00f } // erMixed
+}};
+
+const std::vector<GCodeViewer::Color> GCodeViewer::Options_Colors {{
+ { 0.803f, 0.135f, 0.839f }, // Retractions
+ { 0.287f, 0.679f, 0.810f }, // Unretractions
+ { 0.758f, 0.744f, 0.389f }, // ToolChanges
+ { 0.856f, 0.582f, 0.546f }, // ColorChanges
+ { 0.322f, 0.942f, 0.512f }, // PausePrints
+ { 0.886f, 0.825f, 0.262f } // CustomGCodes
+}};
+
+const std::vector<GCodeViewer::Color> GCodeViewer::Travel_Colors {{
+ { 0.219f, 0.282f, 0.609f }, // Move
+ { 0.112f, 0.422f, 0.103f }, // Extrude
+ { 0.505f, 0.064f, 0.028f } // Retract
+}};
+
+const std::vector<GCodeViewer::Color> GCodeViewer::Range_Colors {{
+ { 0.043f, 0.173f, 0.478f }, // bluish
+ { 0.075f, 0.349f, 0.522f },
+ { 0.110f, 0.533f, 0.569f },
+ { 0.016f, 0.839f, 0.059f },
+ { 0.667f, 0.949f, 0.000f },
+ { 0.988f, 0.975f, 0.012f },
+ { 0.961f, 0.808f, 0.039f },
+ { 0.890f, 0.533f, 0.125f },
+ { 0.820f, 0.408f, 0.188f },
+ { 0.761f, 0.322f, 0.235f },
+ { 0.581f, 0.149f, 0.087f } // reddish
+}};
+
+bool GCodeViewer::init()
+{
+ for (size_t i = 0; i < m_buffers.size(); ++i)
+ {
+ TBuffer& buffer = m_buffers[i];
+ switch (buffer_type(i))
+ {
+ default: { break; }
+ case EMoveType::Tool_change:
+ case EMoveType::Color_change:
+ case EMoveType::Pause_Print:
+ case EMoveType::Custom_GCode:
+ case EMoveType::Retract:
+ case EMoveType::Unretract:
+ {
+ buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
+ buffer.vertices.format = VBuffer::EFormat::Position;
+ break;
+ }
+ case EMoveType::Extrude:
+ {
+ buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
+ buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
+ break;
+ }
+ case EMoveType::Travel:
+ {
+ buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
+ buffer.vertices.format = VBuffer::EFormat::PositionNormal1;
+ break;
+ }
+ }
+ }
+
+ set_toolpath_move_type_visible(EMoveType::Extrude, true);
+ m_sequential_view.marker.init();
+ init_shaders();
+
+ std::array<int, 2> point_sizes;
+ ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data());
+ m_detected_point_sizes = { static_cast<float>(point_sizes[0]), static_cast<float>(point_sizes[1]) };
+
+ return true;
+}
+
+void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized)
+{
+ // avoid processing if called with the same gcode_result
+ if (m_last_result_id == gcode_result.id)
+ return;
+
+ m_last_result_id = gcode_result.id;
+
+ // release gpu memory, if used
+ reset();
+
+ load_toolpaths(gcode_result);
+ if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer)
+ load_shells(print, initialized);
+ else {
+ Pointfs bed_shape;
+ std::string texture;
+ std::string model;
+
+ if (!gcode_result.bed_shape.empty()) {
+ // bed shape detected in the gcode
+ bed_shape = gcode_result.bed_shape;
+ auto bundle = wxGetApp().preset_bundle;
+ if (bundle != nullptr && !gcode_result.printer_settings_id.empty()) {
+ const Preset* preset = bundle->printers.find_preset(gcode_result.printer_settings_id);
+ if (preset != nullptr) {
+ model = PresetUtils::system_printer_bed_model(*preset);
+ texture = PresetUtils::system_printer_bed_texture(*preset);
+ }
+ }
+ }
+ else {
+ // adjust printbed size in dependence of toolpaths bbox
+ const double margin = 10.0;
+ Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin);
+ Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin);
+
+ Vec2d size = max - min;
+ bed_shape = {
+ { min(0), min(1) },
+ { max(0), min(1) },
+ { max(0), min(1) + 0.442265 * size[1]},
+ { max(0) - 10.0, min(1) + 0.4711325 * size[1]},
+ { max(0) + 10.0, min(1) + 0.5288675 * size[1]},
+ { max(0), min(1) + 0.557735 * size[1]},
+ { max(0), max(1) },
+ { min(0) + 0.557735 * size[0], max(1)},
+ { min(0) + 0.5288675 * size[0], max(1) - 10.0},
+ { min(0) + 0.4711325 * size[0], max(1) + 10.0},
+ { min(0) + 0.442265 * size[0], max(1)},
+ { min(0), max(1) } };
+ }
+
+ wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty());
+ }
+
+ m_time_statistics = gcode_result.time_statistics;
+}
+
+void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors)
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ if (m_vertices_count == 0)
+ return;
+
+ wxBusyCursor busy;
+
+ if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty())
+ // update tool colors from config stored in the gcode
+ m_tool_colors = decode_colors(gcode_result.extruder_colors);
+ else
+ // update tool colors
+ m_tool_colors = decode_colors(str_tool_colors);
+
+ // update ranges for coloring / legend
+ m_extrusions.reset_ranges();
+ for (size_t i = 0; i < m_vertices_count; ++i) {
+ // skip first vertex
+ if (i == 0)
+ continue;
+
+ const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
+
+ switch (curr.type)
+ {
+ case EMoveType::Extrude:
+ {
+ m_extrusions.ranges.height.update_from(round_to_nearest(curr.height, 2));
+ m_extrusions.ranges.width.update_from(round_to_nearest(curr.width, 2));
+ m_extrusions.ranges.fan_speed.update_from(curr.fan_speed);
+ m_extrusions.ranges.volumetric_rate.update_from(round_to_nearest(curr.volumetric_rate(), 2));
+ [[fallthrough]];
+ }
+ case EMoveType::Travel:
+ {
+ if (m_buffers[buffer_id(curr.type)].visible)
+ m_extrusions.ranges.feedrate.update_from(curr.feedrate);
+
+ break;
+ }
+ default: { break; }
+ }
+ }
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.refresh_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ // update buffers' render paths
+ refresh_render_paths(false, false);
+}
+
+void GCodeViewer::reset()
+{
+ m_vertices_count = 0;
+ for (TBuffer& buffer : m_buffers) {
+ buffer.reset();
+ }
+
+ m_paths_bounding_box = BoundingBoxf3();
+ m_max_bounding_box = BoundingBoxf3();
+ m_tool_colors = std::vector<Color>();
+ m_extruder_ids = std::vector<unsigned char>();
+ m_extrusions.reset_role_visibility_flags();
+ m_extrusions.reset_ranges();
+ m_shells.volumes.clear();
+ m_layers_zs = std::vector<double>();
+ m_layers_z_range = { 0.0, 0.0 };
+ m_roles = std::vector<ExtrusionRole>();
+ m_time_statistics.reset();
+ m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal;
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.reset_all();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+void GCodeViewer::render() const
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.reset_opengl();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ if (m_roles.empty())
+ return;
+
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ render_toolpaths();
+ m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
+ m_sequential_view.marker.render();
+ render_shells();
+ render_legend();
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ render_statistics();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const
+{
+ size_t id = static_cast<size_t>(buffer_id(type));
+ return (id < m_buffers.size()) ? m_buffers[id].visible : false;
+}
+
+void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible)
+{
+ size_t id = static_cast<size_t>(buffer_id(type));
+ if (id < m_buffers.size())
+ m_buffers[id].visible = visible;
+}
+
+unsigned int GCodeViewer::get_options_visibility_flags() const
+{
+ auto set_flag = [](unsigned int flags, unsigned int flag, bool active) {
+ return active ? (flags | (1 << flag)) : flags;
+ };
+
+ unsigned int flags = 0;
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode));
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Shells), m_shells.visible);
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible());
+ flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Legend), is_legend_enabled());
+ return flags;
+}
+
+void GCodeViewer::set_options_visibility_from_flags(unsigned int flags)
+{
+ auto is_flag_set = [flags](unsigned int flag) {
+ return (flags & (1 << flag)) != 0;
+ };
+
+ set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Travel)));
+ set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Retractions)));
+ set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Unretractions)));
+ set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolChanges)));
+ set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
+ set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
+ set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast<unsigned int>(Preview::OptionType::CustomGCodes)));
+ m_shells.visible = is_flag_set(static_cast<unsigned int>(Preview::OptionType::Shells));
+ m_sequential_view.marker.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolMarker)));
+ enable_legend(is_flag_set(static_cast<unsigned int>(Preview::OptionType::Legend)));
+}
+
+void GCodeViewer::set_layers_z_range(const std::array<double, 2>& layers_z_range)
+{
+ bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0];
+ bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1];
+ m_layers_z_range = layers_z_range;
+ refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last);
+ wxGetApp().plater()->update_preview_moves_slider();
+}
+
+void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
+{
+ if (filename == nullptr)
+ return;
+
+ if (!has_data())
+ return;
+
+ wxBusyCursor busy;
+
+ // the data needed is contained into the Extrude TBuffer
+ const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)];
+ if (buffer.vertices.id == 0 || buffer.indices.id == 0)
+ return;
+
+ // collect color information to generate materials
+ std::vector<Color> colors;
+ for (const RenderPath& path : buffer.render_paths) {
+ colors.push_back(path.color);
+ }
+
+ // save materials file
+ boost::filesystem::path mat_filename(filename);
+ mat_filename.replace_extension("mtl");
+ FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
+ if (fp == nullptr) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
+ return;
+ }
+
+ fprintf(fp, "# G-Code Toolpaths Materials\n");
+ fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
+
+ unsigned int colors_count = 1;
+ for (const Color& color : colors) {
+ fprintf(fp, "\nnewmtl material_%d\n", colors_count++);
+ fprintf(fp, "Ka 1 1 1\n");
+ fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]);
+ fprintf(fp, "Ks 0 0 0\n");
+ }
+
+ fclose(fp);
+
+ // save geometry file
+ fp = boost::nowide::fopen(filename, "w");
+ if (fp == nullptr) {
+ BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing";
+ return;
+ }
+
+ fprintf(fp, "# G-Code Toolpaths\n");
+ fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
+ fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str());
+
+ // get vertices data from vertex buffer on gpu
+ size_t floats_per_vertex = buffer.vertices.vertex_size_floats();
+ std::vector<float> vertices = std::vector<float>(buffer.vertices.count * floats_per_vertex);
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
+ glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer.vertices.data_size_bytes(), vertices.data()));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+ auto get_vertex = [&vertices, floats_per_vertex](size_t id) {
+ // extract vertex from vector of floats
+ size_t base_id = id * floats_per_vertex;
+ return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]);
+ };
+
+ struct Segment
+ {
+ Vec3f v1;
+ Vec3f v2;
+ Vec3f dir;
+ Vec3f right;
+ Vec3f up;
+ Vec3f rl_displacement;
+ Vec3f tb_displacement;
+ float length;
+ };
+
+ auto generate_segment = [get_vertex](size_t start_id, float half_width, float half_height) {
+ auto local_basis = [](const Vec3f& dir) {
+ // calculate local basis (dir, right, up) on given segment
+ std::array<Vec3f, 3> ret;
+ ret[0] = dir.normalized();
+ if (std::abs(ret[0][2]) < EPSILON) {
+ // segment parallel to XY plane
+ ret[1] = { ret[0][1], -ret[0][0], 0.0f };
+ ret[2] = Vec3f::UnitZ();
+ }
+ else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) {
+ // segment parallel to Z axis
+ ret[1] = Vec3f::UnitX();
+ ret[2] = Vec3f::UnitY();
+ }
+ else {
+ ret[0] = dir.normalized();
+ ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized();
+ ret[2] = ret[1].cross(ret[0]);
+ }
+ return ret;
+ };
+
+ Vec3f v1 = get_vertex(start_id) - half_height * Vec3f::UnitZ();
+ Vec3f v2 = get_vertex(start_id + 1) - half_height * Vec3f::UnitZ();
+ float length = (v2 - v1).norm();
+ const auto&& [dir, right, up] = local_basis(v2 - v1);
+ return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length });
+ };
+
+ size_t out_vertices_count = 0;
+
+ for (size_t i = 0; i < buffer.render_paths.size(); ++i) {
+ // get paths segments from buffer paths
+ const RenderPath& render_path = buffer.render_paths[i];
+ const Path& path = buffer.paths[render_path.path_id];
+ float half_width = 0.5f * path.width;
+ // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar
+ float half_height = std::max(0.5f * path.height, 0.005f);
+
+ // generates vertices/normals/triangles
+ std::vector<Vec3f> out_vertices;
+ std::vector<Vec3f> out_normals;
+ using Triangle = std::array<size_t, 3>;
+ std::vector<Triangle> out_triangles;
+ for (size_t j = 0; j < render_path.offsets.size(); ++j) {
+ unsigned int start = static_cast<unsigned int>(render_path.offsets[j] / sizeof(unsigned int));
+ unsigned int end = start + render_path.sizes[j];
+
+ for (size_t k = start; k < end; k += 2) {
+ Segment curr = generate_segment(k, half_width, half_height);
+
+ if (k == start) {
+ // starting endpoint vertices/normals
+ out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
+ out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top
+ out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
+ out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
+ out_vertices_count += 4;
+
+ // starting cap triangles
+ size_t base_id = out_vertices_count - 4 + 1;
+ out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 });
+ out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 });
+ }
+ else {
+ // for the endpoint shared by the current and the previous segments
+ // we keep the top and bottom vertices of the previous vertices
+ // and add new left/right vertices for the current segment
+ out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
+ out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
+ out_vertices_count += 2;
+
+ Segment prev = generate_segment(k - 2, half_width, half_height);
+ Vec3f med_dir = (prev.dir + curr.dir).normalized();
+ float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f)));
+ Vec3f disp_vec = disp * prev.dir;
+
+ bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f;
+ if (prev.dir.dot(curr.dir) < 0.7071068f) {
+ // if the angle between two consecutive segments is greater than 45 degrees
+ // we add a cap in the outside corner
+ // and displace the vertices in the inside corner to the same position, if possible
+ if (is_right_turn) {
+ // corner cap triangles (left)
+ size_t base_id = out_vertices_count - 6 + 1;
+ out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 });
+ out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 });
+
+ // update right vertices
+ if (disp < prev.length && disp < curr.length) {
+ base_id = out_vertices.size() - 6;
+ out_vertices[base_id + 0] -= disp_vec;
+ out_vertices[base_id + 4] = out_vertices[base_id + 0];
+ }
+ }
+ else {
+ // corner cap triangles (right)
+ size_t base_id = out_vertices_count - 6 + 1;
+ out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 });
+ out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 });
+
+ // update left vertices
+ if (disp < prev.length && disp < curr.length) {
+ base_id = out_vertices.size() - 6;
+ out_vertices[base_id + 2] -= disp_vec;
+ out_vertices[base_id + 5] = out_vertices[base_id + 2];
+ }
+ }
+ }
+ else {
+ // if the angle between two consecutive segments is lesser than 45 degrees
+ // displace the vertices to the same position
+ if (is_right_turn) {
+ size_t base_id = out_vertices.size() - 6;
+ // right
+ out_vertices[base_id + 0] -= disp_vec;
+ out_vertices[base_id + 4] = out_vertices[base_id + 0];
+ // left
+ out_vertices[base_id + 2] += disp_vec;
+ out_vertices[base_id + 5] = out_vertices[base_id + 2];
+ }
+ else {
+ size_t base_id = out_vertices.size() - 6;
+ // right
+ out_vertices[base_id + 0] += disp_vec;
+ out_vertices[base_id + 4] = out_vertices[base_id + 0];
+ // left
+ out_vertices[base_id + 2] -= disp_vec;
+ out_vertices[base_id + 5] = out_vertices[base_id + 2];
+ }
+ }
+ }
+
+ // current second endpoint vertices/normals
+ out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right
+ out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top
+ out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
+ out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
+ out_vertices_count += 4;
+
+ // sides triangles
+ if (k == start) {
+ size_t base_id = out_vertices_count - 8 + 1;
+ out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 });
+ out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 });
+ out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 });
+ out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 });
+ out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 });
+ out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 });
+ out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 });
+ out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 });
+ }
+ else {
+ size_t base_id = out_vertices_count - 10 + 1;
+ out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 });
+ out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 });
+ out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 });
+ out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 });
+ out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 });
+ out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 });
+ out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 });
+ out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 });
+ }
+
+ if (k + 2 == end) {
+ // ending cap triangles
+ size_t base_id = out_vertices_count - 4 + 1;
+ out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 });
+ out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 });
+ }
+ }
+ }
+
+ // save to file
+ fprintf(fp, "\n# vertices path %zu\n", i + 1);
+ for (const Vec3f& v : out_vertices) {
+ fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]);
+ }
+
+ fprintf(fp, "\n# normals path %zu\n", i + 1);
+ for (const Vec3f& n : out_normals) {
+ fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]);
+ }
+
+ fprintf(fp, "\n# material path %zu\n", i + 1);
+ fprintf(fp, "usemtl material_%zu\n", i + 1);
+
+ fprintf(fp, "\n# triangles path %zu\n", i + 1);
+ for (const Triangle& t : out_triangles) {
+ fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", t[0], t[0], t[1], t[1], t[2], t[2]);
+ }
+ }
+
+ fclose(fp);
+}
+
+void GCodeViewer::init_shaders()
+{
+ unsigned char begin_id = buffer_id(EMoveType::Retract);
+ unsigned char end_id = buffer_id(EMoveType::Count);
+
+ bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20);
+ for (unsigned char i = begin_id; i < end_id; ++i) {
+ switch (buffer_type(i))
+ {
+ case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; }
+ case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; }
+ case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; }
+ default: { break; }
+ }
+ }
+}
+
+void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+ m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex);
+ m_statistics.results_time = gcode_result.time;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ // vertices data
+ m_vertices_count = gcode_result.moves.size();
+ if (m_vertices_count == 0)
+ return;
+
+ for (size_t i = 0; i < m_vertices_count; ++i) {
+ const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
+ if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer)
+ // for the gcode viewer we need all moves to correctly size the printbed
+ m_paths_bounding_box.merge(move.position.cast<double>());
+ else {
+ if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f)
+ m_paths_bounding_box.merge(move.position.cast<double>());
+ }
+ }
+
+ // add origin
+ m_paths_bounding_box.merge(Vec3d::Zero());
+
+ // max bounding box (account for tool marker)
+ m_max_bounding_box = m_paths_bounding_box;
+ m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ());
+
+ // format data into the buffers to be rendered as points
+ auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+ std::vector<float>& buffer_vertices, std::vector<unsigned int>& buffer_indices, size_t move_id) {
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(curr.position[j]);
+ }
+ buffer.add_path(curr, static_cast<unsigned int>(buffer_indices.size()), static_cast<unsigned int>(move_id));
+ buffer_indices.push_back(static_cast<unsigned int>(buffer_indices.size()));
+ };
+
+ // format data into the buffers to be rendered as lines
+ auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+ std::vector<float>& buffer_vertices, std::vector<unsigned int>& buffer_indices, size_t move_id) {
+ // x component of the normal to the current segment (the normal is parallel to the XY plane)
+ float normal_x = (curr.position - prev.position).normalized()[1];
+
+ if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
+ // add starting vertex position
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(prev.position[j]);
+ }
+ // add starting vertex normal x component
+ buffer_vertices.push_back(normal_x);
+ // add starting index
+ buffer_indices.push_back(static_cast<unsigned int>(buffer_indices.size()));
+ buffer.add_path(curr, static_cast<unsigned int>(buffer_indices.size() - 1), static_cast<unsigned int>(move_id - 1));
+ buffer.paths.back().first.position = prev.position;
+ }
+
+ Path& last_path = buffer.paths.back();
+ if (last_path.first.i_id != last_path.last.i_id) {
+ // add previous vertex position
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(prev.position[j]);
+ }
+ // add previous vertex normal x component
+ buffer_vertices.push_back(normal_x);
+ // add previous index
+ buffer_indices.push_back(static_cast<unsigned int>(buffer_indices.size()));
+ }
+
+ // add current vertex position
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(curr.position[j]);
+ }
+ // add current vertex normal x component
+ buffer_vertices.push_back(normal_x);
+ // add current index
+ buffer_indices.push_back(static_cast<unsigned int>(buffer_indices.size()));
+ last_path.last = { static_cast<unsigned int>(buffer_indices.size() - 1), static_cast<unsigned int>(move_id), curr.position };
+ };
+
+ // format data into the buffers to be rendered as solid
+ auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer,
+ std::vector<float>& buffer_vertices, std::vector<unsigned int>& buffer_indices, size_t move_id) {
+ static Vec3f prev_dir;
+ static Vec3f prev_up;
+ static float prev_length;
+ auto store_vertex = [](std::vector<float>& buffer_vertices, const Vec3f& position, const Vec3f& normal) {
+ // append position
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(position[j]);
+ }
+ // append normal
+ for (int j = 0; j < 3; ++j) {
+ buffer_vertices.push_back(normal[j]);
+ }
+ };
+ auto store_triangle = [](std::vector<unsigned int>& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) {
+ buffer_indices.push_back(i1);
+ buffer_indices.push_back(i2);
+ buffer_indices.push_back(i3);
+ };
+ auto extract_position_at = [](const std::vector<float>& vertices, size_t id) {
+ return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]);
+ };
+ auto update_position_at = [](std::vector<float>& vertices, size_t id, const Vec3f& position) {
+ vertices[id + 0] = position[0];
+ vertices[id + 1] = position[1];
+ vertices[id + 2] = position[2];
+ };
+ auto append_dummy_cap = [store_triangle](std::vector<unsigned int>& buffer_indices, unsigned int id) {
+ store_triangle(buffer_indices, id, id, id);
+ store_triangle(buffer_indices, id, id, id);
+ };
+
+ if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
+ buffer.add_path(curr, static_cast<unsigned int>(buffer_indices.size()), static_cast<unsigned int>(move_id - 1));
+ buffer.paths.back().first.position = prev.position;
+ }
+
+ unsigned int starting_vertices_size = static_cast<unsigned int>(buffer_vertices.size() / buffer.vertices.vertex_size_floats());
+
+ Vec3f dir = (curr.position - prev.position).normalized();
+ Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized();
+ Vec3f left = -right;
+ Vec3f up = right.cross(dir);
+ Vec3f bottom = -up;
+
+ Path& last_path = buffer.paths.back();
+
+ float half_width = 0.5f * last_path.width;
+ float half_height = 0.5f * last_path.height;
+
+ Vec3f prev_pos = prev.position - half_height * up;
+ Vec3f curr_pos = curr.position - half_height * up;
+
+ float length = (curr_pos - prev_pos).norm();
+ if (last_path.vertices_count() == 1) {
+ // 1st segment
+
+ // vertices 1st endpoint
+ store_vertex(buffer_vertices, prev_pos + half_height * up, up);
+ store_vertex(buffer_vertices, prev_pos + half_width * right, right);
+ store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom);
+ store_vertex(buffer_vertices, prev_pos + half_width * left, left);
+
+ // vertices 2nd endpoint
+ store_vertex(buffer_vertices, curr_pos + half_height * up, up);
+ store_vertex(buffer_vertices, curr_pos + half_width * right, right);
+ store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom);
+ store_vertex(buffer_vertices, curr_pos + half_width * left, left);
+
+ // triangles starting cap
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1);
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
+
+ // dummy triangles outer corner cap
+ append_dummy_cap(buffer_indices, starting_vertices_size);
+
+ // triangles sides
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4);
+ store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
+ store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5);
+ store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5);
+ store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6);
+ store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6);
+ store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7);
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7);
+
+ // triangles ending cap
+ store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7);
+ store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6);
+ }
+ else {
+ // any other segment
+ Vec3f med_dir = (prev_dir + dir).normalized();
+ float displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f)));
+ Vec3f displacement_vec = displacement * prev_dir;
+ bool can_displace = displacement < prev_length && displacement < length;
+
+ size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats();
+ size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats();
+ Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id);
+ Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id);
+
+ bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f;
+ // whether the angle between adjacent segments is greater than 45 degrees
+ bool is_sharp = prev_dir.dot(dir) < 0.7071068f;
+
+ bool right_displaced = false;
+ bool left_displaced = false;
+
+ // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible
+ if (can_displace) {
+ if (is_right_turn) {
+ prev_right_pos -= displacement_vec;
+ update_position_at(buffer_vertices, prev_right_id, prev_right_pos);
+ right_displaced = true;
+ }
+ else {
+ prev_left_pos -= displacement_vec;
+ update_position_at(buffer_vertices, prev_left_id, prev_left_pos);
+ left_displaced = true;
+ }
+ }
+
+ if (!is_sharp) {
+ // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible
+ if (can_displace) {
+ if (is_right_turn) {
+ prev_left_pos += displacement_vec;
+ update_position_at(buffer_vertices, prev_left_id, prev_left_pos);
+ left_displaced = true;
+ }
+ else {
+ prev_right_pos += displacement_vec;
+ update_position_at(buffer_vertices, prev_right_id, prev_right_pos);
+ right_displaced = true;
+ }
+ }
+
+ // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint)
+ // vertices position matches that of the previous segment 2nd endpoint, if displaced
+ store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right);
+ store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left);
+ }
+ else {
+ // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint)
+ // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced
+ if (is_right_turn) {
+ store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right);
+ store_vertex(buffer_vertices, prev_pos + half_width * left, left);
+ }
+ else {
+ store_vertex(buffer_vertices, prev_pos + half_width * right, right);
+ store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left);
+ }
+ }
+
+ // vertices 2nd endpoint
+ store_vertex(buffer_vertices, curr_pos + half_height * up, up);
+ store_vertex(buffer_vertices, curr_pos + half_width * right, right);
+ store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom);
+ store_vertex(buffer_vertices, curr_pos + half_width * left, left);
+
+ // triangles starting cap
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0);
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2);
+
+ // triangles outer corner cap
+ if (is_right_turn) {
+ if (left_displaced)
+ // dummy triangles
+ append_dummy_cap(buffer_indices, starting_vertices_size);
+ else {
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1);
+ store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1);
+ }
+ }
+ else {
+ if (right_displaced)
+ // dummy triangles
+ append_dummy_cap(buffer_indices, starting_vertices_size);
+ else {
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0);
+ store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0);
+ }
+ }
+
+ // triangles sides
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2);
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2);
+ store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3);
+ store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3);
+ store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4);
+ store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4);
+ store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5);
+ store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5);
+
+ // triangles ending cap
+ store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5);
+ store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4);
+ }
+
+ last_path.last = { static_cast<unsigned int>(buffer_indices.size() - 1), static_cast<unsigned int>(move_id), curr.position };
+ prev_dir = dir;
+ prev_up = up;
+ prev_length = length;
+ };
+
+ // toolpaths data -> extract from result
+ std::vector<std::vector<float>> vertices(m_buffers.size());
+ std::vector<std::vector<unsigned int>> indices(m_buffers.size());
+ for (size_t i = 0; i < m_vertices_count; ++i) {
+ // skip first vertex
+ if (i == 0)
+ continue;
+
+ const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1];
+ const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
+
+ unsigned char id = buffer_id(curr.type);
+ TBuffer& buffer = m_buffers[id];
+ std::vector<float>& buffer_vertices = vertices[id];
+ std::vector<unsigned int>& buffer_indices = indices[id];
+
+ switch (curr.type)
+ {
+ case EMoveType::Tool_change:
+ case EMoveType::Color_change:
+ case EMoveType::Pause_Print:
+ case EMoveType::Custom_GCode:
+ case EMoveType::Retract:
+ case EMoveType::Unretract:
+ {
+ add_as_point(curr, buffer, buffer_vertices, buffer_indices, i);
+ break;
+ }
+ case EMoveType::Extrude:
+ {
+ add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i);
+ break;
+ }
+ case EMoveType::Travel:
+ {
+ add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i);
+ break;
+ }
+ default: { break; }
+ }
+ }
+
+ // toolpaths data -> send data to gpu
+ for (size_t i = 0; i < m_buffers.size(); ++i) {
+ TBuffer& buffer = m_buffers[i];
+
+ // vertices
+ const std::vector<float>& buffer_vertices = vertices[i];
+ buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats();
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float);
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ glsafe(::glGenBuffers(1, &buffer.vertices.id));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
+ glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+ // indices
+ const std::vector<unsigned int>& buffer_indices = indices[i];
+ buffer.indices.count = buffer_indices.size();
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int);
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ if (buffer.indices.count > 0) {
+ glsafe(::glGenBuffers(1, &buffer.indices.id));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id));
+ glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ }
+ }
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ for (const TBuffer& buffer : m_buffers) {
+ m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path);
+ }
+ unsigned int travel_buffer_id = buffer_id(EMoveType::Travel);
+ m_statistics.travel_segments_count = indices[travel_buffer_id].size() / m_buffers[travel_buffer_id].indices_per_segment();
+ unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude);
+ m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / m_buffers[extrude_buffer_id].indices_per_segment();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ // layers zs / roles / extruder ids / cp color ids -> extract from result
+ for (size_t i = 0; i < m_vertices_count; ++i) {
+ const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
+ if (move.type == EMoveType::Extrude)
+ m_layers_zs.emplace_back(static_cast<double>(move.position[2]));
+
+ m_extruder_ids.emplace_back(move.extruder_id);
+
+ if (i > 0)
+ m_roles.emplace_back(move.extrusion_role);
+ }
+
+ // layers zs -> replace intervals of layers with similar top positions with their average value.
+ std::sort(m_layers_zs.begin(), m_layers_zs.end());
+ int n = int(m_layers_zs.size());
+ int k = 0;
+ for (int i = 0; i < n;) {
+ int j = i + 1;
+ double zmax = m_layers_zs[i] + EPSILON;
+ for (; j < n && m_layers_zs[j] <= zmax; ++j);
+ m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i];
+ i = j;
+ }
+ if (k < n)
+ m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end());
+
+ // set layers z range
+ m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() };
+
+ // roles -> remove duplicates
+ std::sort(m_roles.begin(), m_roles.end());
+ m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end());
+
+ // extruder ids -> remove duplicates
+ std::sort(m_extruder_ids.begin(), m_extruder_ids.end());
+ m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end());
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.load_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+void GCodeViewer::load_shells(const Print& print, bool initialized)
+{
+ if (print.objects().empty())
+ // no shells, return
+ return;
+
+ // adds objects' volumes
+ int object_id = 0;
+ for (const PrintObject* obj : print.objects()) {
+ const ModelObject* model_obj = obj->model_object();
+
+ std::vector<int> instance_ids(model_obj->instances.size());
+ for (int i = 0; i < (int)model_obj->instances.size(); ++i) {
+ instance_ids[i] = i;
+ }
+
+ m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized);
+
+ ++object_id;
+ }
+
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
+ // adds wipe tower's volume
+ double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2);
+ const PrintConfig& config = print.config();
+ size_t extruders_count = config.nozzle_diameter.size();
+ if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) {
+ const DynamicPrintConfig& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+ double layer_height = print_config.opt_float("layer_height");
+ double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
+ double nozzle_diameter = print.config().nozzle_diameter.values[0];
+ float depth = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth;
+ float brim_width = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width;
+
+ m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
+ !print.is_step_done(psWipeTower), brim_width, initialized);
+ }
+ }
+
+ // remove modifiers
+ while (true) {
+ GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; });
+ if (it != m_shells.volumes.volumes.end()) {
+ delete (*it);
+ m_shells.volumes.volumes.erase(it);
+ }
+ else
+ break;
+ }
+
+ for (GLVolume* volume : m_shells.volumes.volumes) {
+ volume->zoom_to_volumes = false;
+ volume->color[3] = 0.25f;
+ volume->force_native_color = true;
+ volume->set_render_color();
+ }
+}
+
+void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
+{
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ auto extrusion_color = [this](const Path& path) {
+ Color color;
+ switch (m_view_type)
+ {
+ case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast<unsigned int>(path.role)]; break; }
+ case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; }
+ case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; }
+ case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; }
+ case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; }
+ case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; }
+ case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; }
+ case EViewType::ColorPrint: { color = m_tool_colors[path.cp_color_id]; break; }
+ default: { color = { 1.0f, 1.0f, 1.0f }; break; }
+ }
+ return color;
+ };
+
+ auto travel_color = [this](const Path& path) {
+ return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ :
+ ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ :
+ Travel_Colors[0] /* Move */);
+ };
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ m_statistics.render_paths_size = 0;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+ m_sequential_view.endpoints.first = m_vertices_count;
+ m_sequential_view.endpoints.last = 0;
+ if (!keep_sequential_current_first)
+ m_sequential_view.current.first = 0;
+ if (!keep_sequential_current_last)
+ m_sequential_view.current.last = m_vertices_count;
+
+ // first pass: collect visible paths and update sequential view data
+ std::vector<std::pair<TBuffer*, size_t>> paths;
+ for (TBuffer& buffer : m_buffers) {
+ // reset render paths
+ buffer.render_paths.clear();
+
+ if (!buffer.visible)
+ continue;
+
+ for (size_t i = 0; i < buffer.paths.size(); ++i) {
+ const Path& path = buffer.paths[i];
+ if (path.type == EMoveType::Travel) {
+ if (!is_travel_in_z_range(i))
+ continue;
+ }
+ else if (!is_in_z_range(path))
+ continue;
+
+ if (path.type == EMoveType::Extrude && !is_visible(path))
+ continue;
+
+ // store valid path
+ paths.push_back({ &buffer, i });
+
+ m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id);
+ m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id);
+ }
+ }
+
+ // update current sequential position
+ m_sequential_view.current.first = keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first;
+ m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.last;
+
+ // get the world position from gpu
+ bool found = false;
+ for (const TBuffer& buffer : m_buffers) {
+ // searches the path containing the current position
+ for (const Path& path : buffer.paths) {
+ if (path.contains(m_sequential_view.current.last)) {
+ unsigned int offset = m_sequential_view.current.last - path.first.s_id;
+ if (offset > 0) {
+ if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line)
+ offset = 2 * offset - 1;
+ else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
+ unsigned int indices_count = buffer.indices_per_segment();
+ offset = indices_count * (offset - 1) + (indices_count - 6);
+ }
+ }
+ offset += path.first.i_id;
+
+ // gets the index from the index buffer on gpu
+ unsigned int index = 0;
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id));
+ glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>(offset * sizeof(unsigned int)), static_cast<GLsizeiptr>(sizeof(unsigned int)), static_cast<void*>(&index)));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+ // gets the position from the vertices buffer on gpu
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
+ glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(m_sequential_view.current_position.data())));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+
+ // second pass: filter paths by sequential data and collect them by color
+ for (const auto& [buffer, id] : paths) {
+ const Path& path = buffer->paths[id];
+ if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first)
+ continue;
+
+ Color color;
+ switch (path.type)
+ {
+ case EMoveType::Extrude: { color = extrusion_color(path); break; }
+ case EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; }
+ default: { color = { 0.0f, 0.0f, 0.0f }; break; }
+ }
+
+ auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; });
+ if (it == buffer->render_paths.end()) {
+ it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath());
+ it->color = color;
+ it->path_id = id;
+ }
+
+ unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1;
+ unsigned int size_in_indices = 0;
+ switch (buffer->render_primitive_type)
+ {
+ case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = size_in_vertices; break; }
+ case TBuffer::ERenderPrimitiveType::Line:
+ case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (size_in_vertices - 1); break; }
+ }
+ it->sizes.push_back(size_in_indices);
+
+ unsigned int delta_1st = 0;
+ if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id)
+ delta_1st = m_sequential_view.current.first - path.first.s_id;
+
+ if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle)
+ delta_1st *= buffer->indices_per_segment();
+
+ it->offsets.push_back(static_cast<size_t>((path.first.i_id + delta_1st) * sizeof(unsigned int)));
+ }
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ for (const TBuffer& buffer : m_buffers) {
+ m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath);
+ for (const RenderPath& path : buffer.render_paths) {
+ m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
+ m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
+ }
+ }
+ m_statistics.refresh_paths_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+}
+
+void GCodeViewer::render_toolpaths() const
+{
+ float point_size = 0.8f;
+ std::array<float, 4> light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f };
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ double zoom = camera.get_zoom();
+ const std::array<int, 4>& viewport = camera.get_viewport();
+ float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
+ static_cast<float>(viewport[3]) * 0.0005;
+
+ auto set_uniform_color = [](const std::array<float, 3>& color, GLShaderProgram& shader) {
+ std::array<float, 4> color4 = { color[0], color[1], color[2], 1.0f };
+ shader.set_uniform("uniform_color", color4);
+ };
+
+ auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) {
+ set_uniform_color(Options_Colors[static_cast<unsigned int>(color_id)], shader);
+ shader.set_uniform("zoom", zoom);
+ shader.set_uniform("percent_outline_radius", 0.0f);
+ shader.set_uniform("percent_center_radius", 0.33f);
+ shader.set_uniform("point_size", point_size);
+ shader.set_uniform("near_plane_height", near_plane_height);
+
+ glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE));
+ glsafe(::glEnable(GL_POINT_SPRITE));
+
+ for (const RenderPath& path : buffer.render_paths) {
+ glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ ++m_statistics.gl_multi_points_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ }
+
+ glsafe(::glDisable(GL_POINT_SPRITE));
+ glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
+ };
+
+ auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) {
+ shader.set_uniform("light_intensity", light_intensity);
+ for (const RenderPath& path : buffer.render_paths) {
+ set_uniform_color(path.color, shader);
+ glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ ++m_statistics.gl_multi_lines_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ }
+ };
+
+ auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) {
+ for (const RenderPath& path : buffer.render_paths) {
+ set_uniform_color(path.color, shader);
+ glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ ++m_statistics.gl_multi_triangles_calls_count;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ }
+ };
+
+ auto line_width = [](double zoom) {
+ return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0));
+ };
+
+ glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
+
+ unsigned char begin_id = buffer_id(EMoveType::Retract);
+ unsigned char end_id = buffer_id(EMoveType::Count);
+
+ for (unsigned char i = begin_id; i < end_id; ++i) {
+ const TBuffer& buffer = m_buffers[i];
+ if (!buffer.visible)
+ continue;
+
+ if (buffer.vertices.id == 0 || buffer.indices.id == 0)
+ continue;
+
+ GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str());
+ if (shader != nullptr) {
+ shader->start_using();
+
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
+ glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size()));
+ glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
+ bool has_normals = buffer.vertices.normal_size_floats() > 0;
+ if (has_normals) {
+ glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size()));
+ glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
+ }
+
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id));
+
+ switch (buffer.render_primitive_type)
+ {
+ case TBuffer::ERenderPrimitiveType::Point:
+ {
+ EOptionsColors color;
+ switch (buffer_type(i))
+ {
+ case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; }
+ case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; }
+ case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; }
+ case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; }
+ case EMoveType::Retract: { color = EOptionsColors::Retractions; break; }
+ case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; }
+ }
+ render_as_points(buffer, color, *shader);
+ break;
+ }
+ case TBuffer::ERenderPrimitiveType::Line:
+ {
+ render_as_lines(buffer, *shader);
+ break;
+ }
+ case TBuffer::ERenderPrimitiveType::Triangle:
+ {
+ render_as_triangles(buffer, *shader);
+ break;
+ }
+ }
+
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+ if (has_normals)
+ glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
+
+ glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+ shader->stop_using();
+ }
+ }
+}
+
+void GCodeViewer::render_shells() const
+{
+ if (!m_shells.visible || m_shells.volumes.empty())
+ return;
+
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+// glsafe(::glDepthMask(GL_FALSE));
+
+ shader->start_using();
+ m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix());
+ shader->stop_using();
+
+// glsafe(::glDepthMask(GL_TRUE));
+}
+
+void GCodeViewer::render_legend() const
+{
+ if (!m_legend_enabled)
+ return;
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+
+ imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+ ImGui::SetNextWindowBgAlpha(0.6f);
+ imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ enum class EItemType : unsigned char
+ {
+ Rect,
+ Circle,
+ Hexagon,
+ Line
+ };
+
+ const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
+
+ float icon_size = ImGui::GetTextLineHeight();
+ float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
+
+ auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label,
+ bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 2>& offsets = { 0.0f, 0.0f },
+ std::function<void()> callback = nullptr) {
+ if (!visible)
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
+ ImVec2 pos = ImGui::GetCursorScreenPos();
+ switch (type)
+ {
+ default:
+ case EItemType::Rect:
+ {
+ draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
+ ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
+ break;
+ }
+ case EItemType::Circle:
+ {
+ ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
+ if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") {
+ draw_list->AddCircleFilled(center, 0.5f * icon_size,
+ ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
+ float radius = 0.5f * icon_size;
+ draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
+ radius = 0.5f * icon_size * 0.01f * 33.0f;
+ draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
+ }
+ else
+ draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
+
+ break;
+ }
+ case EItemType::Hexagon:
+ {
+ ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
+ draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6);
+ break;
+ }
+ case EItemType::Line:
+ {
+ draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
+ break;
+ }
+ }
+
+ // draw text
+ ImGui::Dummy({ icon_size, icon_size });
+ ImGui::SameLine();
+ if (callback != nullptr) {
+ if (ImGui::MenuItem(label.c_str()))
+ callback();
+ else {
+ // show tooltip
+ if (ImGui::IsItemHovered()) {
+ if (!visible)
+ ImGui::PopStyleVar();
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
+ ImGui::BeginTooltip();
+ imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor();
+ if (!visible)
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
+
+ // to avoid the tooltip to change size when moving the mouse
+ wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
+ wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
+ }
+ }
+
+ if (!time.empty()) {
+ ImGui::SameLine(offsets[0]);
+ imgui.text(time);
+ ImGui::SameLine(offsets[1]);
+ pos = ImGui::GetCursorScreenPos();
+ float width = std::max(1.0f, percent_bar_size * percent / max_percent);
+ draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
+ ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
+ ImGui::Dummy({ percent_bar_size, icon_size });
+ ImGui::SameLine();
+ char buf[64];
+ ::sprintf(buf, "%.1f%%", 100.0f * percent);
+ ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
+ }
+ }
+ else
+ imgui.text(label);
+
+ if (!visible)
+ ImGui::PopStyleVar();
+ };
+
+ auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) {
+ auto append_range_item = [this, draw_list, &imgui, append_item](int i, float value, unsigned int decimals) {
+ char buf[1024];
+ ::sprintf(buf, "%.*f", decimals, value);
+ append_item(EItemType::Rect, Range_Colors[i], buf);
+ };
+
+ if (range.count == 1)
+ // single item use case
+ append_range_item(0, range.min, decimals);
+ else if (range.count == 2) {
+ append_range_item(static_cast<int>(Range_Colors.size()) - 1, range.max, decimals);
+ append_range_item(0, range.min, decimals);
+ }
+ else {
+ float step_size = range.step_size();
+ for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
+ append_range_item(i, range.min + static_cast<float>(i) * step_size, decimals);
+ }
+ }
+ };
+
+ auto append_headers = [&imgui](const std::array<std::string, 3>& texts, const std::array<float, 2>& offsets) {
+ imgui.text(texts[0]);
+ ImGui::SameLine(offsets[0]);
+ imgui.text(texts[1]);
+ ImGui::SameLine(offsets[1]);
+ imgui.text(texts[2]);
+ ImGui::Separator();
+ };
+
+ auto max_width = [](const std::vector<std::string>& items, const std::string& title, float extra_size = 0.0f) {
+ float ret = ImGui::CalcTextSize(title.c_str()).x;
+ for (const std::string& item : items) {
+ ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x);
+ }
+ return ret;
+ };
+
+ auto calculate_offsets = [max_width](const std::vector<std::string>& labels, const std::vector<std::string>& times,
+ const std::array<std::string, 2>& titles, float extra_size = 0.0f) {
+ const ImGuiStyle& style = ImGui::GetStyle();
+ std::array<float, 2> ret = { 0.0f, 0.0f };
+ ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x;
+ ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x;
+ return ret;
+ };
+
+ auto color_print_ranges = [this](unsigned char extruder_id, const std::vector<CustomGCode::Item>& custom_gcode_per_print_z) {
+ std::vector<std::pair<Color, std::pair<double, double>>> ret;
+ ret.reserve(custom_gcode_per_print_z.size());
+
+ for (const auto& item : custom_gcode_per_print_z) {
+ if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
+ continue;
+
+ if (item.type != ColorChange)
+ continue;
+
+ auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon());
+
+ if (lower_b == m_layers_zs.end())
+ continue;
+
+ double current_z = *lower_b;
+ double previous_z = lower_b == m_layers_zs.begin() ? 0.0 : *(--lower_b);
+
+ // to avoid duplicate values, check adding values
+ if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z))
+ ret.push_back({ decode_color(item.color), { previous_z, current_z } });
+ }
+
+ return ret;
+ };
+
+ auto upto_label = [](double z) {
+ char buf[64];
+ ::sprintf(buf, "%.2f", z);
+ return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm");
+ };
+
+ auto above_label = [](double z) {
+ char buf[64];
+ ::sprintf(buf, "%.2f", z);
+ return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm");
+ };
+
+ auto fromto_label = [](double z1, double z2) {
+ char buf1[64];
+ ::sprintf(buf1, "%.2f", z1);
+ char buf2[64];
+ ::sprintf(buf2, "%.2f", z2);
+ return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
+ };
+
+ auto role_time_and_percent = [this, time_mode](ExtrusionRole role) {
+ auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<ExtrusionRole, float>& item) { return role == item.first; });
+ return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
+ };
+
+ // data used to properly align items in columns when showing time
+ std::array<float, 2> offsets = { 0.0f, 0.0f };
+ std::vector<std::string> labels;
+ std::vector<std::string> times;
+ std::vector<float> percents;
+ float max_percent = 0.0f;
+
+ if (m_view_type == EViewType::FeatureType) {
+ // calculate offsets to align time/percentage data
+ for (size_t i = 0; i < m_roles.size(); ++i) {
+ ExtrusionRole role = m_roles[i];
+ if (role < erCount) {
+ labels.push_back(_u8L(ExtrusionEntity::role_to_string(role)));
+ auto [time, percent] = role_time_and_percent(role);
+ times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : "");
+ percents.push_back(percent);
+ max_percent = std::max(max_percent, percent);
+ }
+ }
+
+ offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size);
+ }
+
+ // total estimated printing time section
+ if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
+ (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) {
+ ImGui::AlignTextToFramePadding();
+ switch (m_time_estimate_mode)
+ {
+ case PrintEstimatedTimeStatistics::ETimeMode::Normal:
+ {
+ imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:");
+ break;
+ }
+ case PrintEstimatedTimeStatistics::ETimeMode::Stealth:
+ {
+ imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:");
+ break;
+ }
+ }
+ ImGui::SameLine();
+ imgui.text(short_time(get_time_dhms(time_mode.time)));
+
+ auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) {
+ bool show = false;
+ for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) {
+ if (i != static_cast<size_t>(mode) &&
+ short_time(get_time_dhms(m_time_statistics.modes[static_cast<size_t>(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) {
+ show = true;
+ break;
+ }
+ }
+ if (show && m_time_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
+ if (imgui.button(label)) {
+ m_time_estimate_mode = mode;
+ wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
+ wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
+ }
+ }
+ };
+
+ switch (m_time_estimate_mode)
+ {
+ case PrintEstimatedTimeStatistics::ETimeMode::Normal:
+ {
+ show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth);
+ break;
+ }
+ case PrintEstimatedTimeStatistics::ETimeMode::Stealth:
+ {
+ show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal);
+ break;
+ }
+ }
+ ImGui::Spacing();
+ }
+
+ // extrusion paths section -> title
+ switch (m_view_type)
+ {
+ case EViewType::FeatureType:
+ {
+ append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets);
+ break;
+ }
+ case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; }
+ case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; }
+ case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; }
+ case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; }
+ case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; }
+ case EViewType::Tool: { imgui.title(_u8L("Tool")); break; }
+ case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; }
+ default: { break; }
+ }
+
+ // extrusion paths section -> items
+ switch (m_view_type)
+ {
+ case EViewType::FeatureType:
+ {
+ for (size_t i = 0; i < m_roles.size(); ++i) {
+ ExtrusionRole role = m_roles[i];
+ if (role >= erCount)
+ continue;
+ bool visible = is_visible(role);
+ append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(role)], labels[i],
+ visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() {
+ m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role);
+ // update buffers' render paths
+ refresh_render_paths(false, false);
+ wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
+ wxGetApp().plater()->update_preview_bottom_toolbar();
+ }
+ );
+ }
+ break;
+ }
+ case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; }
+ case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; }
+ case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; }
+ case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; }
+ case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; }
+ case EViewType::Tool:
+ {
+ // shows only extruders actually used
+ for (unsigned char i : m_extruder_ids) {
+ append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1));
+ }
+ break;
+ }
+ case EViewType::ColorPrint:
+ {
+ const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
+ const int extruders_count = wxGetApp().extruders_edited_cnt();
+ if (extruders_count == 1) { // single extruder use case
+ std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z);
+ const int items_cnt = static_cast<int>(cp_values.size());
+ if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
+ append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color"));
+ }
+ else {
+ for (int i = items_cnt; i >= 0; --i) {
+ // create label for color change item
+ if (i == 0) {
+ append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first));
+ break;
+ }
+ else if (i == items_cnt) {
+ append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second));
+ continue;
+ }
+ append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first));
+ }
+ }
+ }
+ else // multi extruder use case
+ {
+ // shows only extruders actually used
+ for (unsigned char i : m_extruder_ids) {
+ std::vector<std::pair<Color, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z);
+ const int items_cnt = static_cast<int>(cp_values.size());
+ if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode
+ append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color"));
+ }
+ else {
+ for (int j = items_cnt; j >= 0; --j) {
+ // create label for color change item
+ std::string label = _u8L("Extruder") + " " + std::to_string(i + 1);
+ if (j == 0) {
+ label += " " + upto_label(cp_values.front().second.first);
+ append_item(EItemType::Rect, m_tool_colors[i], label);
+ break;
+ }
+ else if (j == items_cnt) {
+ label += " " + above_label(cp_values[j - 1].second.second);
+ append_item(EItemType::Rect, cp_values[j - 1].first, label);
+ continue;
+ }
+
+ label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first);
+ append_item(EItemType::Rect, cp_values[j - 1].first, label);
+ }
+ }
+ }
+ }
+
+ break;
+ }
+ default: { break; }
+ }
+
+ // partial estimated printing time section
+ if (m_view_type == EViewType::ColorPrint) {
+ using Times = std::pair<float, float>;
+ using TimesList = std::vector<std::pair<CustomGCode::Type, Times>>;
+
+ // helper structure containig the data needed to render the time items
+ struct PartialTime
+ {
+ enum class EType : unsigned char
+ {
+ Print,
+ ColorChange,
+ Pause
+ };
+ EType type;
+ int extruder_id;
+ Color color1;
+ Color color2;
+ Times times;
+ };
+ using PartialTimes = std::vector<PartialTime>;
+
+ auto generate_partial_times = [this](const TimesList& times) {
+ PartialTimes items;
+
+ std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
+ int extruders_count = wxGetApp().extruders_edited_cnt();
+ std::vector<Color> last_color(extruders_count);
+ for (int i = 0; i < extruders_count; ++i) {
+ last_color[i] = m_tool_colors[i];
+ }
+ int last_extruder_id = 1;
+ for (const auto& time_rec : times) {
+ switch (time_rec.first)
+ {
+ case CustomGCode::PausePrint:
+ {
+ auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
+ if (it != custom_gcode_per_print_z.end()) {
+ items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second });
+ items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second });
+ custom_gcode_per_print_z.erase(it);
+ }
+ break;
+ }
+ case CustomGCode::ColorChange:
+ {
+ auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
+ if (it != custom_gcode_per_print_z.end()) {
+ items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second });
+ items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second });
+ last_color[it->extruder - 1] = decode_color(it->color);
+ last_extruder_id = it->extruder;
+ custom_gcode_per_print_z.erase(it);
+ }
+ else
+ items.push_back({ PartialTime::EType::Print, last_extruder_id, Color(), Color(), time_rec.second });
+
+ break;
+ }
+ default: { break; }
+ }
+ }
+
+ return items;
+ };
+
+ auto append_color = [this, &imgui](const Color& color1, const Color& color2, std::array<float, 2>& offsets, const Times& times) {
+ imgui.text(_u8L("Color change"));
+ ImGui::SameLine();
+
+ float icon_size = ImGui::GetTextLineHeight();
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+ ImVec2 pos = ImGui::GetCursorScreenPos();
+ pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x;
+
+ draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
+ ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }));
+ pos.x += icon_size;
+ draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
+ ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }));
+
+ ImGui::SameLine(offsets[0]);
+ imgui.text(short_time(get_time_dhms(times.second - times.first)));
+ };
+
+ PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times);
+ if (!partial_times.empty()) {
+ labels.clear();
+ times.clear();
+
+ for (const PartialTime& item : partial_times) {
+ switch (item.type)
+ {
+ case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; }
+ case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
+ case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
+ }
+ times.push_back(short_time(get_time_dhms(item.times.second)));
+ }
+ offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size);
+
+ ImGui::Spacing();
+ append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets);
+ for (const PartialTime& item : partial_times) {
+ switch (item.type)
+ {
+ case PartialTime::EType::Print:
+ {
+ imgui.text(_u8L("Print"));
+ ImGui::SameLine(offsets[0]);
+ imgui.text(short_time(get_time_dhms(item.times.second)));
+ ImGui::SameLine(offsets[1]);
+ imgui.text(short_time(get_time_dhms(item.times.first)));
+ break;
+ }
+ case PartialTime::EType::Pause:
+ {
+ imgui.text(_u8L("Pause"));
+ ImGui::SameLine(offsets[0]);
+ imgui.text(short_time(get_time_dhms(item.times.second - item.times.first)));
+ break;
+ }
+ case PartialTime::EType::ColorChange:
+ {
+ append_color(item.color1, item.color2, offsets, item.times);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // travel paths section
+ if (m_buffers[buffer_id(EMoveType::Travel)].visible) {
+ switch (m_view_type)
+ {
+ case EViewType::Feedrate:
+ case EViewType::Tool:
+ case EViewType::ColorPrint:
+ {
+ break;
+ }
+ default:
+ {
+ // title
+ ImGui::Spacing();
+ imgui.title(_u8L("Travel"));
+
+ // items
+ append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement"));
+ append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion"));
+ append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction"));
+
+ break;
+ }
+ }
+ }
+
+ auto any_option_available = [this]() {
+ auto available = [this](EMoveType type) {
+ const TBuffer& buffer = m_buffers[buffer_id(type)];
+ return buffer.visible && buffer.indices.count > 0;
+ };
+
+ return available(EMoveType::Color_change) ||
+ available(EMoveType::Custom_GCode) ||
+ available(EMoveType::Pause_Print) ||
+ available(EMoveType::Retract) ||
+ available(EMoveType::Tool_change) ||
+ available(EMoveType::Unretract);
+ };
+
+ auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) {
+ const TBuffer& buffer = m_buffers[buffer_id(move_type)];
+ if (buffer.visible && buffer.indices.count > 0)
+ append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast<unsigned int>(color)], text);
+ };
+
+ // options section
+ if (any_option_available()) {
+ // title
+ ImGui::Spacing();
+ imgui.title(_u8L("Options"));
+
+ // items
+ add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions"));
+ add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Unretractions"));
+ add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes"));
+ add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes"));
+ add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Pause prints"));
+ add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes"));
+ }
+
+ imgui.end();
+ ImGui::PopStyleVar();
+}
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+void GCodeViewer::render_statistics() const
+{
+ static const float offset = 230.0f;
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+
+ imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f);
+ imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
+ ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor time:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.results_time) + " ms");
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Load time:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.load_time) + " ms");
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh time:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.refresh_time) + " ms");
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh paths time:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms");
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_POINTS calls:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count));
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINES calls:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.gl_multi_lines_calls_count));
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_TRIANGLES calls:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.gl_multi_triangles_calls_count));
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor results:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.results_size) + " bytes");
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Paths CPU:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.paths_size) + " bytes");
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Render paths CPU:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes");
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes");
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Indices GPU:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes");
+
+ ImGui::Separator();
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Travel segments count:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.travel_segments_count));
+
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments count:"));
+ ImGui::SameLine(offset);
+ imgui.text(std::to_string(m_statistics.extrude_segments_count));
+
+ imgui.end();
+}
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+bool GCodeViewer::is_travel_in_z_range(size_t id) const
+{
+ const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
+ if (id >= buffer.paths.size())
+ return false;
+
+ Path path = buffer.paths[id];
+ int first = static_cast<int>(id);
+ unsigned int last = static_cast<unsigned int>(id);
+
+ // check adjacent paths
+ while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) {
+ --first;
+ path.first = buffer.paths[first].first;
+ }
+ while (last < static_cast<unsigned int>(buffer.paths.size() - 1) && path.last.position.isApprox(buffer.paths[last + 1].first.position)) {
+ ++last;
+ path.last = buffer.paths[last].last;
+ }
+
+ return is_in_z_range(path);
+}
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // ENABLE_GCODE_VIEWER
diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp
new file mode 100644
index 000000000..302296c41
--- /dev/null
+++ b/src/slic3r/GUI/GCodeViewer.hpp
@@ -0,0 +1,459 @@
+#ifndef slic3r_GCodeViewer_hpp_
+#define slic3r_GCodeViewer_hpp_
+
+#if ENABLE_GCODE_VIEWER
+#include "3DScene.hpp"
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+#include "GLModel.hpp"
+
+#include <float.h>
+
+namespace Slic3r {
+
+class Print;
+class TriangleMesh;
+
+namespace GUI {
+
+class GCodeViewer
+{
+ using Color = std::array<float, 3>;
+ static const std::vector<Color> Extrusion_Role_Colors;
+ static const std::vector<Color> Options_Colors;
+ static const std::vector<Color> Travel_Colors;
+ static const std::vector<Color> Range_Colors;
+
+ enum class EOptionsColors : unsigned char
+ {
+ Retractions,
+ Unretractions,
+ ToolChanges,
+ ColorChanges,
+ PausePrints,
+ CustomGCodes
+ };
+
+ // vbo buffer containing vertices data used to rendder a specific toolpath type
+ struct VBuffer
+ {
+ enum class EFormat : unsigned char
+ {
+ // vertex format: 3 floats -> position.x|position.y|position.z
+ Position,
+ // vertex format: 4 floats -> position.x|position.y|position.z|normal.x
+ PositionNormal1,
+ // vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z
+ PositionNormal3
+ };
+
+ EFormat format{ EFormat::Position };
+ // vbo id
+ unsigned int id{ 0 };
+ // count of vertices, updated after data are sent to gpu
+ size_t count{ 0 };
+
+ size_t data_size_bytes() const { return count * vertex_size_bytes(); }
+
+ size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); }
+ size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); }
+
+ size_t position_offset_floats() const { return 0; }
+ size_t position_offset_size() const { return position_offset_floats() * sizeof(float); }
+ size_t position_size_floats() const
+ {
+ switch (format)
+ {
+ case EFormat::Position:
+ case EFormat::PositionNormal3: { return 3; }
+ case EFormat::PositionNormal1: { return 4; }
+ default: { return 0; }
+ }
+ }
+ size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
+
+ size_t normal_offset_floats() const
+ {
+ switch (format)
+ {
+ case EFormat::Position:
+ case EFormat::PositionNormal1: { return 0; }
+ case EFormat::PositionNormal3: { return 3; }
+ default: { return 0; }
+ }
+ }
+ size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); }
+ size_t normal_size_floats() const {
+ switch (format)
+ {
+ default:
+ case EFormat::Position:
+ case EFormat::PositionNormal1: { return 0; }
+ case EFormat::PositionNormal3: { return 3; }
+ }
+ }
+ size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); }
+
+ void reset();
+ };
+
+ // ibo buffer containing indices data (lines/triangles) used to render a specific toolpath type
+ struct IBuffer
+ {
+ // ibo id
+ unsigned int id{ 0 };
+ // count of indices, updated after data are sent to gpu
+ size_t count{ 0 };
+
+ void reset();
+ };
+
+ // Used to identify different toolpath sub-types inside a IBuffer
+ struct Path
+ {
+ struct Endpoint
+ {
+ // index into the indices buffer
+ unsigned int i_id{ 0u };
+ // sequential id
+ unsigned int s_id{ 0u };
+ Vec3f position{ Vec3f::Zero() };
+ };
+
+ EMoveType type{ EMoveType::Noop };
+ ExtrusionRole role{ erNone };
+ Endpoint first;
+ Endpoint last;
+ float delta_extruder{ 0.0f };
+ float height{ 0.0f };
+ float width{ 0.0f };
+ float feedrate{ 0.0f };
+ float fan_speed{ 0.0f };
+ float volumetric_rate{ 0.0f };
+ unsigned char extruder_id{ 0 };
+ unsigned char cp_color_id{ 0 };
+
+ bool matches(const GCodeProcessor::MoveVertex& move) const;
+ size_t vertices_count() const { return last.s_id - first.s_id + 1; }
+ bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; }
+ };
+
+ // Used to batch the indices needed to render paths
+ struct RenderPath
+ {
+ Color color;
+ size_t path_id;
+ std::vector<unsigned int> sizes;
+ std::vector<size_t> offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements())
+ };
+
+ // buffer containing data for rendering a specific toolpath type
+ struct TBuffer
+ {
+ enum class ERenderPrimitiveType : unsigned char
+ {
+ Point,
+ Line,
+ Triangle
+ };
+
+ ERenderPrimitiveType render_primitive_type;
+ VBuffer vertices;
+ IBuffer indices;
+
+ std::string shader;
+ std::vector<Path> paths;
+ std::vector<RenderPath> render_paths;
+ bool visible{ false };
+
+ void reset();
+ void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id);
+ unsigned int indices_per_segment() const {
+ switch (render_primitive_type)
+ {
+ case ERenderPrimitiveType::Point: { return 1; }
+ case ERenderPrimitiveType::Line: { return 2; }
+ case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles
+ default: { return 0; }
+ }
+ }
+ };
+
+ // helper to render shells
+ struct Shells
+ {
+ GLVolumeCollection volumes;
+ bool visible{ false };
+ };
+
+ // helper to render extrusion paths
+ struct Extrusions
+ {
+ struct Range
+ {
+ float min;
+ float max;
+ unsigned int count;
+
+ Range() { reset(); }
+
+ void update_from(const float value) {
+ if (value != max && value != min)
+ ++count;
+ min = std::min(min, value);
+ max = std::max(max, value);
+ }
+ void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; }
+
+ float step_size() const { return (max - min) / (static_cast<float>(Range_Colors.size()) - 1.0f); }
+ Color get_color_at(float value) const;
+ };
+
+ struct Ranges
+ {
+ // Color mapping by layer height.
+ Range height;
+ // Color mapping by extrusion width.
+ Range width;
+ // Color mapping by feedrate.
+ Range feedrate;
+ // Color mapping by fan speed.
+ Range fan_speed;
+ // Color mapping by volumetric extrusion rate.
+ Range volumetric_rate;
+
+ void reset() {
+ height.reset();
+ width.reset();
+ feedrate.reset();
+ fan_speed.reset();
+ volumetric_rate.reset();
+ }
+ };
+
+ unsigned int role_visibility_flags{ 0 };
+ Ranges ranges;
+
+ void reset_role_visibility_flags() {
+ role_visibility_flags = 0;
+ for (unsigned int i = 0; i < erCount; ++i) {
+ role_visibility_flags |= 1 << i;
+ }
+ }
+
+ void reset_ranges() { ranges.reset(); }
+ };
+
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ struct Statistics
+ {
+ // times
+ long long results_time{ 0 };
+ long long load_time{ 0 };
+ long long refresh_time{ 0 };
+ long long refresh_paths_time{ 0 };
+ // opengl calls
+ long long gl_multi_points_calls_count{ 0 };
+ long long gl_multi_lines_calls_count{ 0 };
+ long long gl_multi_triangles_calls_count{ 0 };
+ // memory
+ long long results_size{ 0 };
+ long long vertices_gpu_size{ 0 };
+ long long indices_gpu_size{ 0 };
+ long long paths_size{ 0 };
+ long long render_paths_size{ 0 };
+ // others
+ long long travel_segments_count{ 0 };
+ long long extrude_segments_count{ 0 };
+
+ void reset_all() {
+ reset_times();
+ reset_opengl();
+ reset_sizes();
+ reset_counters();
+ }
+
+ void reset_times() {
+ results_time = 0;
+ load_time = 0;
+ refresh_time = 0;
+ refresh_paths_time = 0;
+ }
+
+ void reset_opengl() {
+ gl_multi_points_calls_count = 0;
+ gl_multi_lines_calls_count = 0;
+ gl_multi_triangles_calls_count = 0;
+ }
+
+ void reset_sizes() {
+ results_size = 0;
+ vertices_gpu_size = 0;
+ indices_gpu_size = 0;
+ paths_size = 0;
+ render_paths_size = 0;
+ }
+
+ void reset_counters() {
+ travel_segments_count = 0;
+ extrude_segments_count = 0;
+ }
+ };
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+
+public:
+ struct SequentialView
+ {
+ class Marker
+ {
+ GLModel m_model;
+ Vec3f m_world_position;
+ Transform3f m_world_transform;
+ float m_z_offset{ 0.5f };
+ std::array<float, 4> m_color{ 1.0f, 1.0f, 1.0f, 1.0f };
+ bool m_visible{ false };
+
+ public:
+ void init();
+
+ const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
+
+ void set_world_position(const Vec3f& position);
+ void set_color(const std::array<float, 4>& color) { m_color = color; }
+
+ bool is_visible() const { return m_visible; }
+ void set_visible(bool visible) { m_visible = visible; }
+
+ void render() const;
+ };
+
+ struct Endpoints
+ {
+ unsigned int first{ 0 };
+ unsigned int last{ 0 };
+ };
+
+ Endpoints endpoints;
+ Endpoints current;
+ Vec3f current_position{ Vec3f::Zero() };
+ Marker marker;
+ };
+
+ enum class EViewType : unsigned char
+ {
+ FeatureType,
+ Height,
+ Width,
+ Feedrate,
+ FanSpeed,
+ VolumetricRate,
+ Tool,
+ ColorPrint,
+ Count
+ };
+
+private:
+ unsigned int m_last_result_id{ 0 };
+ size_t m_vertices_count{ 0 };
+ mutable std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
+ // bounding box of toolpaths
+ BoundingBoxf3 m_paths_bounding_box;
+ // bounding box of toolpaths + marker tools
+ BoundingBoxf3 m_max_bounding_box;
+ std::vector<Color> m_tool_colors;
+ std::vector<double> m_layers_zs;
+ std::array<double, 2> m_layers_z_range;
+ std::vector<ExtrusionRole> m_roles;
+ std::vector<unsigned char> m_extruder_ids;
+ mutable Extrusions m_extrusions;
+ mutable SequentialView m_sequential_view;
+ Shells m_shells;
+ EViewType m_view_type{ EViewType::FeatureType };
+ bool m_legend_enabled{ true };
+ PrintEstimatedTimeStatistics m_time_statistics;
+ mutable PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal };
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ mutable Statistics m_statistics;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
+
+public:
+ GCodeViewer() = default;
+ ~GCodeViewer() { reset(); }
+
+ bool init();
+
+ // extract rendering data from the given parameters
+ void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized);
+ // recalculate ranges in dependence of what is visible and sets tool/print colors
+ void refresh(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors);
+
+ void reset();
+ void render() const;
+
+ bool has_data() const { return !m_roles.empty(); }
+
+ const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; }
+ const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; }
+ const std::vector<double>& get_layers_zs() const { return m_layers_zs; };
+
+ const SequentialView& get_sequential_view() const { return m_sequential_view; }
+ void update_sequential_view_current(unsigned int first, unsigned int last)
+ {
+ m_sequential_view.current.first = first;
+ m_sequential_view.current.last = last;
+ refresh_render_paths(true, true);
+ }
+
+ EViewType get_view_type() const { return m_view_type; }
+ void set_view_type(EViewType type) {
+ if (type == EViewType::Count)
+ type = EViewType::FeatureType;
+
+ m_view_type = type;
+ }
+
+ bool is_toolpath_move_type_visible(EMoveType type) const;
+ void set_toolpath_move_type_visible(EMoveType type, bool visible);
+ unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; }
+ void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; }
+ unsigned int get_options_visibility_flags() const;
+ void set_options_visibility_from_flags(unsigned int flags);
+ void set_layers_z_range(const std::array<double, 2>& layers_z_range);
+
+ bool is_legend_enabled() const { return m_legend_enabled; }
+ void enable_legend(bool enable) { m_legend_enabled = enable; }
+
+ void export_toolpaths_to_obj(const char* filename) const;
+
+private:
+ void init_shaders();
+ void load_toolpaths(const GCodeProcessor::Result& gcode_result);
+ void load_shells(const Print& print, bool initialized);
+ void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
+ void render_toolpaths() const;
+ void render_shells() const;
+ void render_legend() const;
+#if ENABLE_GCODE_VIEWER_STATISTICS
+ void render_statistics() const;
+#endif // ENABLE_GCODE_VIEWER_STATISTICS
+ bool is_visible(ExtrusionRole role) const {
+ return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0;
+ }
+ bool is_visible(const Path& path) const { return is_visible(path.role); }
+ bool is_in_z_range(const Path& path) const {
+ auto in_z_range = [this](double z) {
+ return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON;
+ };
+
+ return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]);
+ }
+ bool is_travel_in_z_range(size_t id) const;
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // ENABLE_GCODE_VIEWER
+
+#endif // slic3r_GCodeViewer_hpp_
+
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 94f6f6ef3..7ae6f4294 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -5,7 +5,9 @@
#include "polypartition.h"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
+#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
@@ -158,11 +160,8 @@ GLCanvas3D::LayersEditing::~LayersEditing()
const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f;
-bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+void GLCanvas3D::LayersEditing::init()
{
- if (!m_shader.init(vertex_shader_filename, fragment_shader_filename))
- return false;
-
glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
@@ -171,8 +170,6 @@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename,
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
-
- return true;
}
void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
@@ -205,7 +202,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
bool GLCanvas3D::LayersEditing::is_allowed() const
{
- return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0;
+ return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0;
}
bool GLCanvas3D::LayersEditing::is_enabled() const
@@ -223,8 +220,6 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
if (!m_enabled)
return;
- static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
-
const Size& cnv_size = canvas.get_canvas_size();
ImGuiWrapper& imgui = *wxGetApp().imgui();
@@ -233,23 +228,23 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
- imgui.text_colored(ORANGE, _L("Left mouse button:"));
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:"));
ImGui::SameLine();
imgui.text(_L("Add detail"));
- imgui.text_colored(ORANGE, _L("Right mouse button:"));
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:"));
ImGui::SameLine();
imgui.text(_L("Remove detail"));
- imgui.text_colored(ORANGE, _L("Shift + Left mouse button:"));
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:"));
ImGui::SameLine();
imgui.text(_L("Reset to base"));
- imgui.text_colored(ORANGE, _L("Shift + Right mouse button:"));
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:"));
ImGui::SameLine();
imgui.text(_L("Smoothing"));
- imgui.text_colored(ORANGE, _L("Mouse wheel:"));
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:"));
ImGui::SameLine();
imgui.text(_L("Increase/decrease edit area"));
@@ -351,7 +346,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
bool GLCanvas3D::LayersEditing::is_initialized() const
{
- return m_shader.is_initialized();
+ return wxGetApp().get_shader("variable_layer_height") != nullptr;
}
std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const
@@ -385,13 +380,17 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con
void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const
{
- m_shader.start_using();
+ GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
- m_shader.set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z));
- m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height);
- m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas));
- m_shader.set_uniform("z_cursor_band_width", band_width);
- m_shader.set_uniform("object_max_z", m_object_max_z);
+ shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z));
+ shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height);
+ shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas));
+ shader->set_uniform("z_cursor_band_width", band_width);
+ shader->set_uniform("object_max_z", m_object_max_z);
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
@@ -411,7 +410,7 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3
glsafe(::glEnd());
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
- m_shader.stop_using();
+ shader->stop_using();
}
void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const
@@ -445,73 +444,50 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G
{
assert(this->is_allowed());
assert(this->last_object_id != -1);
- GLint shader_id = m_shader.get_shader()->shader_program_id;
- assert(shader_id > 0);
+ GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
+ if (shader == nullptr)
+ return;
- GLint current_program_id;
- glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
- if (shader_id > 0 && shader_id != current_program_id)
+ GLShaderProgram* current_shader = wxGetApp().get_current_shader();
+ if (shader->get_id() != current_shader->get_id())
// The layer editing shader is not yet active. Activate it.
- glsafe(::glUseProgram(shader_id));
+ shader->start_using();
else
// The layer editing shader was already active.
- current_program_id = -1;
+ current_shader = nullptr;
- GLint z_to_texture_row_id = ::glGetUniformLocation(shader_id, "z_to_texture_row");
- GLint z_texture_row_to_normalized_id = ::glGetUniformLocation(shader_id, "z_texture_row_to_normalized");
- GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor");
- GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width");
- GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix");
- GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z");
- glcheck();
+ const_cast<LayersEditing*>(this)->generate_layer_height_texture();
- if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1)
- {
- const_cast<LayersEditing*>(this)->generate_layer_height_texture();
-
- // Uniforms were resolved, go ahead using the layer editing shader.
- glsafe(::glUniform1f(z_to_texture_row_id, GLfloat(m_layers_texture.cells - 1) / (GLfloat(m_layers_texture.width) * GLfloat(m_object_max_z))));
- glsafe(::glUniform1f(z_texture_row_to_normalized_id, GLfloat(1.0f / m_layers_texture.height)));
- glsafe(::glUniform1f(z_cursor_id, GLfloat(m_object_max_z) * GLfloat(this->get_cursor_z_relative(canvas))));
- glsafe(::glUniform1f(z_cursor_band_width_id, GLfloat(this->band_width)));
- // Initialize the layer height texture mapping.
- GLsizei w = (GLsizei)m_layers_texture.width;
- GLsizei h = (GLsizei)m_layers_texture.height;
- GLsizei half_w = w / 2;
- GLsizei half_h = h / 2;
- glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
- glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
- glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
- glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
- glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data()));
- glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4));
- for (const GLVolume* glvolume : volumes.volumes) {
- // Render the object using the layer editing shader and texture.
- if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier)
- continue;
- if (world_matrix_id != -1)
- glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast<float>().data()));
- if (object_max_z_id != -1)
- glsafe(::glUniform1f(object_max_z_id, GLfloat(0)));
- glvolume->render();
- }
- // Revert back to the previous shader.
- glBindTexture(GL_TEXTURE_2D, 0);
- if (current_program_id > 0)
- glsafe(::glUseProgram(current_program_id));
- }
- else
- {
- // Something went wrong. Just render the object.
- assert(false);
- for (const GLVolume* glvolume : volumes.volumes) {
- // Render the object using the layer editing shader and texture.
- if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier)
- continue;
- glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast<float>().data()));
- glvolume->render();
- }
- }
+ // Uniforms were resolved, go ahead using the layer editing shader.
+ shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z)));
+ shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height));
+ shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas)));
+ shader->set_uniform("z_cursor_band_width", float(this->band_width));
+
+ // Initialize the layer height texture mapping.
+ GLsizei w = (GLsizei)m_layers_texture.width;
+ GLsizei h = (GLsizei)m_layers_texture.height;
+ GLsizei half_w = w / 2;
+ GLsizei half_h = h / 2;
+ glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
+ glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
+ glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+ glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
+ glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data()));
+ glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4));
+ for (const GLVolume* glvolume : volumes.volumes) {
+ // Render the object using the layer editing shader and texture.
+ if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier)
+ continue;
+
+ shader->set_uniform("volume_world_matrix", glvolume->world_matrix());
+ shader->set_uniform("object_max_z", GLfloat(0));
+ glvolume->render();
+ }
+ // Revert back to the previous shader.
+ glBindTexture(GL_TEXTURE_2D, 0);
+ if (current_shader != nullptr)
+ current_shader->start_using();
}
void GLCanvas3D::LayersEditing::adjust_layer_height_profile()
@@ -899,6 +875,7 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas)
generate(m_msg_text, canvas, true, m_is_colored_red);
}
+#if !ENABLE_GCODE_VIEWER
const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
const unsigned char GLCanvas3D::LegendTexture::Default_Background_Color[3] = { (unsigned char)(DEFAULT_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[2] * 255.0f) };
const unsigned char GLCanvas3D::LegendTexture::Error_Background_Color[3] = { (unsigned char)(ERROR_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[2] * 255.0f) };
@@ -1257,6 +1234,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
}
}
+#endif // !ENABLE_GCODE_VIEWER
void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_instances) const
{
@@ -1528,7 +1506,11 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
+#if ENABLE_GCODE_VIEWER
+wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, wxKeyEvent);
+#else
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
+#endif // ENABLE_GCODE_VIEWER
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@@ -1559,7 +1541,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
, m_dirty(true)
, m_initialized(false)
, m_apply_zoom_to_volumes_filter(false)
+#if !ENABLE_GCODE_VIEWER
, m_legend_texture_enabled(false)
+#endif // !ENABLE_GCODE_VIEWER
, m_picking_enabled(false)
, m_moving_enabled(false)
, m_dynamic_background_enabled(false)
@@ -1650,17 +1634,16 @@ bool GLCanvas3D::init()
if (m_multisample_allowed)
glsafe(::glEnable(GL_MULTISAMPLE));
- if (!m_shader.init("gouraud.vs", "gouraud.fs"))
- {
- std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl;
- return false;
- }
+ if (m_main_toolbar.is_enabled())
+ m_layers_editing.init();
- if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
+#if ENABLE_GCODE_VIEWER
+ if (!m_main_toolbar.is_enabled())
{
- std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl;
- return false;
+ if (!m_gcode_viewer.init())
+ return false;
}
+#endif // ENABLE_GCODE_VIEWER
// on linux the gl context is not valid until the canvas is not shown on screen
// we defer the geometry finalization of volumes until the first call to render()
@@ -1892,7 +1875,11 @@ void GLCanvas3D::enable_layers_editing(bool enable)
void GLCanvas3D::enable_legend_texture(bool enable)
{
+#if ENABLE_GCODE_VIEWER
+ m_gcode_viewer.enable_legend(enable);
+#else
m_legend_texture_enabled = enable;
+#endif // ENABLE_GCODE_VIEWER
}
void GLCanvas3D::enable_picking(bool enable)
@@ -1954,6 +1941,13 @@ void GLCanvas3D::zoom_to_selection()
_zoom_to_box(m_selection.get_bounding_box());
}
+#if ENABLE_GCODE_VIEWER
+void GLCanvas3D::zoom_to_gcode()
+{
+ _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05);
+}
+#endif // ENABLE_GCODE_VIEWER
+
void GLCanvas3D::select_view(const std::string& direction)
{
wxGetApp().plater()->get_camera().select_view(direction);
@@ -2049,6 +2043,10 @@ void GLCanvas3D::render()
_render_background();
_render_objects();
+#if ENABLE_GCODE_VIEWER
+ if (!m_main_toolbar.is_enabled())
+ _render_gcode();
+#endif // ENABLE_GCODE_VIEWER
_render_sla_slices();
_render_selection();
_render_bed(!camera.is_looking_downward(), true);
@@ -2060,6 +2058,9 @@ void GLCanvas3D::render()
// we need to set the mouse's scene position here because the depth buffer
// could be invalidated by the following gizmo render methods
// this position is used later into on_mouse() to drag the objects
+#if ENABLE_GCODE_VIEWER
+ if (m_picking_enabled)
+#endif // ENABLE_GCODE_VIEWER
m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<coord_t>());
_render_current_gizmo();
@@ -2097,7 +2098,7 @@ void GLCanvas3D::render()
#endif // ENABLE_RENDER_STATISTICS
#if ENABLE_CAMERA_STATISTICS
- m_camera.debug_render();
+ camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
std::string tooltip;
@@ -2204,6 +2205,40 @@ void GLCanvas3D::ensure_on_bed(unsigned int object_idx)
}
}
+
+#if ENABLE_GCODE_VIEWER
+const std::vector<double>& GLCanvas3D::get_gcode_layers_zs() const
+{
+ return m_gcode_viewer.get_layers_zs();
+}
+
+std::vector<double> GLCanvas3D::get_volumes_print_zs(bool active_only) const
+{
+ return m_volumes.get_current_print_zs(active_only);
+}
+
+void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags)
+{
+ m_gcode_viewer.set_options_visibility_from_flags(flags);
+}
+
+void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags)
+{
+ m_gcode_viewer.set_toolpath_role_visibility_flags(flags);
+}
+
+void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type)
+{
+ m_gcode_viewer.set_view_type(type);
+}
+
+void GLCanvas3D::set_toolpaths_z_range(const std::array<double, 2>& range)
+{
+ m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6);
+ if (m_gcode_viewer.has_data())
+ m_gcode_viewer.set_layers_z_range(range);
+}
+#else
std::vector<double> GLCanvas3D::get_current_print_zs(bool active_only) const
{
return m_volumes.get_current_print_zs(active_only);
@@ -2213,6 +2248,7 @@ void GLCanvas3D::set_toolpaths_range(double low, double high)
{
m_volumes.set_range(low, high);
}
+#endif // ENABLE_GCODE_VIEWER
std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs)
{
@@ -2655,6 +2691,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume&
vol_old.finalize_geometry(gl_initialized);
}
+#if !ENABLE_GCODE_VIEWER
static void load_gcode_retractions(const GCodePreviewData::Retraction& retractions, GLCanvas3D::GCodePreviewVolumeIndex::EType extrusion_type, GLVolumeCollection &volumes, GLCanvas3D::GCodePreviewVolumeIndex &volume_index, bool gl_initialized)
{
// nothing to render, return
@@ -2685,7 +2722,23 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio
}
volume->indexed_vertex_array.finalize_geometry(gl_initialized);
}
+#endif // !ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result)
+{
+ m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized);
+ if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer)
+ _show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
+}
+void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors)
+{
+ m_gcode_viewer.refresh(gcode_result, str_tool_colors);
+ set_as_dirty();
+ request_extra_frame();
+}
+#else
void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)
{
const Print *print = this->fff_print();
@@ -2754,6 +2807,7 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
_generate_legend_texture(preview_data, tool_colors);
}
}
+#endif // ENABLE_GCODE_VIEWER
void GLCanvas3D::load_sla_preview()
{
@@ -2787,6 +2841,7 @@ void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, c
_update_toolpath_volumes_outside_state();
_show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
+#if !ENABLE_GCODE_VIEWER
if (color_print_values.empty())
reset_legend_texture();
else {
@@ -2795,6 +2850,7 @@ void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, c
const std::vector<float> tool_colors = _parse_colors(str_tool_colors);
_generate_legend_texture(preview_data, tool_colors);
}
+#endif // !ENABLE_GCODE_VIEWER
}
void GLCanvas3D::bind_event_handlers()
@@ -3032,18 +3088,46 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'i': { _update_camera_zoom(1.0); break; }
case 'K':
case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
+#if ENABLE_GCODE_VIEWER
+ case 'L':
+ case 'l': {
+ if (!m_main_toolbar.is_enabled()) {
+ m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled());
+ m_dirty = true;
+ wxGetApp().plater()->update_preview_bottom_toolbar();
+ }
+ break;
+ }
+#endif // ENABLE_GCODE_VIEWER
case 'O':
case 'o': { _update_camera_zoom(-1.0); break; }
#if ENABLE_RENDER_PICKING_PASS
- case 'T':
- case 't': {
+ case 'P':
+ case 'p': {
m_show_picking_texture = !m_show_picking_texture;
- m_dirty = true;
+ m_dirty = true;
break;
}
#endif // ENABLE_RENDER_PICKING_PASS
case 'Z':
+#if ENABLE_GCODE_VIEWER
+ case 'z':
+ {
+ if (!m_selection.is_empty())
+ zoom_to_selection();
+ else
+ {
+ if (!m_volumes.empty())
+ zoom_to_volumes();
+ else
+ _zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
+ }
+
+ break;
+ }
+#else
case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; }
+#endif // ENABLE_GCODE_VIEWER
default: { evt.Skip(); break; }
}
}
@@ -3286,7 +3370,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
keyCode == WXK_DOWN)
{
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
+#if ENABLE_GCODE_VIEWER
+ post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, evt));
+#else
post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt));
+#endif // ENABLE_GCODE_VIEWER
}
}
}
@@ -3850,9 +3938,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_selection.is_empty())
m_gizmos.reset_all_states();
+#if ENABLE_GCODE_VIEWER
+ m_dirty = true;
+#else
// Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
//if (m_picking_enabled)
m_dirty = true;
+#endif // ENABLE_GCODE_VIEWER
}
else
evt.Skip();
@@ -3913,6 +4005,7 @@ Vec2d GLCanvas3D::get_local_mouse_position() const
return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y);
}
+#if !ENABLE_GCODE_VIEWER
void GLCanvas3D::reset_legend_texture()
{
if (m_legend_texture.get_id() != 0)
@@ -3921,6 +4014,7 @@ void GLCanvas3D::reset_legend_texture()
m_legend_texture.reset();
}
}
+#endif // !ENABLE_GCODE_VIEWER
void GLCanvas3D::set_tooltip(const std::string& tooltip) const
{
@@ -4210,12 +4304,15 @@ void GLCanvas3D::update_ui_from_settings()
}
#endif // ENABLE_RETINA_GL
+#if ENABLE_GCODE_VIEWER
+ if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer)
+ wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1");
+#else
bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1";
wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse);
+#endif // ENABLE_GCODE_VIEWER
}
-
-
GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
{
WipeTowerInfo wti;
@@ -4277,12 +4374,20 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar()
bool GLCanvas3D::has_toolpaths_to_export() const
{
+#if ENABLE_GCODE_VIEWER
+ return m_gcode_viewer.has_data();
+#else
return m_volumes.has_toolpaths_to_export();
+#endif // ENABLE_GCODE_VIEWER
}
void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
{
+#if ENABLE_GCODE_VIEWER
+ m_gcode_viewer.export_toolpaths_to_obj(filename);
+#else
m_volumes.export_toolpaths_to_obj(filename);
+#endif // ENABLE_GCODE_VIEWER
}
void GLCanvas3D::mouse_up_cleanup()
@@ -4432,8 +4537,8 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
return ret;
};
- static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f };
- static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f };
+ static const std::array<float, 4> orange = { 0.923f, 0.504f, 0.264f, 1.0f };
+ static const std::array<float, 4> gray = { 0.64f, 0.64f, 0.64f, 1.0f };
GLVolumePtrs visible_volumes;
@@ -4477,39 +4582,33 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
camera.apply_projection(box, near_z, far_z);
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
+ if (shader == nullptr)
+ return;
+
if (transparent_background)
glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
- m_shader.start_using();
-
- GLint shader_id = m_shader.get_shader_program_id();
- GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color");
- GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection");
- glcheck();
-
- if (print_box_detection_id != -1)
- glsafe(::glUniform1i(print_box_detection_id, 0));
+ shader->start_using();
+ shader->set_uniform("print_box.volume_detection", 0);
for (const GLVolume* vol : visible_volumes)
{
- if (color_id >= 0)
- glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray));
- else
- glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray));
-
+ shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
vol->render();
}
- m_shader.stop_using();
+ shader->stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
if (show_bed)
_render_bed(!camera.is_looking_downward(), false);
+ // restore background color
if (transparent_background)
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
}
@@ -5120,6 +5219,12 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
}
bb.merge(wxGetApp().plater()->get_bed().get_bounding_box(include_bed_model));
+
+#if ENABLE_GCODE_VIEWER
+ if (!m_main_toolbar.is_enabled())
+ bb.merge(m_gcode_viewer.get_max_bounding_box());
+#endif // ENABLE_GCODE_VIEWER
+
return bb;
}
@@ -5280,8 +5385,40 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const
_update_volumes_hover_state();
}
+#if ENABLE_GCODE_VIEWER
+static BoundingBoxf3 print_volume(const DynamicPrintConfig& config)
+{
+ // tolerance to avoid false detection at bed edges
+ const double tolerance_x = 0.05;
+ const double tolerance_y = 0.05;
+
+ BoundingBoxf3 ret;
+ const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config.option("bed_shape"));
+ if (opt != nullptr) {
+ BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
+ ret = BoundingBoxf3(Vec3d(unscale<double>(bed_box_2D.min(0)) - tolerance_x, unscale<double>(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale<double>(bed_box_2D.max(0)) + tolerance_x, unscale<double>(bed_box_2D.max(1)) + tolerance_y, config.opt_float("max_print_height")));
+ // Allow the objects to protrude below the print bed
+ ret.min(2) = -1e10;
+ }
+ return ret;
+}
+#endif // ENABLE_GCODE_VIEWER
+
void GLCanvas3D::_render_background() const
{
+#if ENABLE_GCODE_VIEWER
+ bool use_error_color = false;
+ if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) {
+ use_error_color = m_dynamic_background_enabled;
+ if (!m_volumes.empty())
+ use_error_color &= _is_any_volume_outside();
+ else {
+ BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
+ use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_paths_bounding_box()) : false;
+ }
+ }
+#endif // ENABLE_GCODE_VIEWER
+
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
glsafe(::glMatrixMode(GL_PROJECTION));
@@ -5292,7 +5429,11 @@ void GLCanvas3D::_render_background() const
glsafe(::glDisable(GL_DEPTH_TEST));
::glBegin(GL_QUADS);
+#if ENABLE_GCODE_VIEWER
+ if (use_error_color)
+#else
if (m_dynamic_background_enabled && _is_any_volume_outside())
+#endif // ENABLE_GCODE_VIEWER
::glColor3fv(ERROR_BG_DARK_COLOR);
else
::glColor3fv(DEFAULT_BG_DARK_COLOR);
@@ -5300,8 +5441,12 @@ void GLCanvas3D::_render_background() const
::glVertex2f(-1.0f, -1.0f);
::glVertex2f(1.0f, -1.0f);
+#if ENABLE_GCODE_VIEWER
+ if (use_error_color)
+#else
if (m_dynamic_background_enabled && _is_any_volume_outside())
- ::glColor3fv(ERROR_BG_LIGHT_COLOR);
+#endif // ENABLE_GCODE_VIEWER
+::glColor3fv(ERROR_BG_LIGHT_COLOR);
else
::glColor3fv(DEFAULT_BG_LIGHT_COLOR);
@@ -5339,13 +5484,11 @@ void GLCanvas3D::_render_objects() const
m_camera_clipping_plane = m_gizmos.get_clipping_plane();
- if (m_picking_enabled)
- {
+ if (m_picking_enabled) {
// Update the layer editing selection to the first object selected, update the current object maximum Z.
const_cast<LayersEditing&>(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1);
- if (m_config != nullptr)
- {
+ if (m_config != nullptr) {
const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false);
m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height"));
m_volumes.check_outside_state(m_config, nullptr);
@@ -5359,28 +5502,39 @@ void GLCanvas3D::_render_objects() const
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
- m_shader.start_using();
- if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
- int object_id = m_layers_editing.last_object_id;
- m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
- // Which volume to paint without the layer height profile shader?
- return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
- });
- // Let LayersEditing handle rendering of the active object using the layer height profile shader.
- m_layers_editing.render_volumes(*this, this->m_volumes);
- } else {
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
+ if (shader != nullptr) {
+ shader->start_using();
+
+ if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
+ int object_id = m_layers_editing.last_object_id;
+ m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
+ // Which volume to paint without the layer height profile shader?
+ return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
+ });
+ // Let LayersEditing handle rendering of the active object using the layer height profile shader.
+ m_layers_editing.render_volumes(*this, this->m_volumes);
+ } else {
// do not cull backfaces to show broken geometry, if any
m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
});
- }
+ }
- m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
- m_shader.stop_using();
+ m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
+ shader->stop_using();
+ }
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
}
+#if ENABLE_GCODE_VIEWER
+void GLCanvas3D::_render_gcode() const
+{
+ m_gcode_viewer.render();
+}
+#endif // ENABLE_GCODE_VIEWER
+
void GLCanvas3D::_render_selection() const
{
float scale_factor = 1.0;
@@ -5457,7 +5611,9 @@ void GLCanvas3D::_render_overlays() const
_render_gizmos_overlay();
_render_warning_texture();
+#if !ENABLE_GCODE_VIEWER
_render_legend_texture();
+#endif // !ENABLE_GCODE_VIEWER
// main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
// to correctly place them
@@ -5504,6 +5660,7 @@ void GLCanvas3D::_render_warning_texture() const
m_warning_texture.render(*this);
}
+#if !ENABLE_GCODE_VIEWER
void GLCanvas3D::_render_legend_texture() const
{
if (!m_legend_texture_enabled)
@@ -5511,6 +5668,7 @@ void GLCanvas3D::_render_legend_texture() const
m_legend_texture.render(*this);
}
+#endif // !ENABLE_GCODE_VIEWER
void GLCanvas3D::_render_volumes_for_picking() const
{
@@ -5788,7 +5946,7 @@ void GLCanvas3D::_render_sla_slices() const
void GLCanvas3D::_render_selection_sidebar_hints() const
{
- m_selection.render_sidebar_hints(m_sidebar_field, m_shader);
+ m_selection.render_sidebar_hints(m_sidebar_field);
}
void GLCanvas3D::_update_volumes_hover_state() const
@@ -6458,6 +6616,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info();
}
+#if !ENABLE_GCODE_VIEWER
static inline int hex_digit_to_int(const char c)
{
return
@@ -6785,6 +6944,7 @@ void GLCanvas3D::_load_fff_shells()
}
}
}
+#endif // !ENABLE_GCODE_VIEWER
// While it looks like we can call
// this->reload_scene(true, true)
@@ -6842,6 +7002,7 @@ void GLCanvas3D::_load_sla_shells()
update_volumes_colors_by_extruder();
}
+#if !ENABLE_GCODE_VIEWER
void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
{
unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
@@ -6899,9 +7060,13 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe
}
}
}
+#endif // !ENABLE_GCODE_VIEWER
void GLCanvas3D::_update_toolpath_volumes_outside_state()
{
+#if ENABLE_GCODE_VIEWER
+ BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
+#else
// tolerance to avoid false detection at bed edges
static const double tolerance_x = 0.05;
static const double tolerance_y = 0.05;
@@ -6918,15 +7083,23 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state()
print_volume.min(2) = -1e10;
}
}
+#endif // ENABLE_GCODE_VIEWER
for (GLVolume* volume : m_volumes.volumes)
{
+#if ENABLE_GCODE_VIEWER
+ volume->is_outside = ((test_volume.radius() > 0.0) && volume->is_extrusion_path) ? !test_volume.contains(volume->bounding_box()) : false;
+#else
volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false;
+#endif // ENABLE_GCODE_VIEWER
}
}
void GLCanvas3D::_update_sla_shells_outside_state()
{
+#if ENABLE_GCODE_VIEWER
+ BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
+#else
// tolerance to avoid false detection at bed edges
static const double tolerance_x = 0.05;
static const double tolerance_y = 0.05;
@@ -6943,17 +7116,37 @@ void GLCanvas3D::_update_sla_shells_outside_state()
print_volume.min(2) = -1e10;
}
}
+#endif // ENABLE_GCODE_VIEWER
for (GLVolume* volume : m_volumes.volumes)
{
+#if ENABLE_GCODE_VIEWER
+ volume->is_outside = ((test_volume.radius() > 0.0) && volume->shader_outside_printer_detection_enabled) ? !test_volume.contains(volume->transformed_convex_hull_bounding_box()) : false;
+#else
volume->is_outside = ((print_volume.radius() > 0.0) && volume->shader_outside_printer_detection_enabled) ? !print_volume.contains(volume->transformed_convex_hull_bounding_box()) : false;
+#endif // ENABLE_GCODE_VIEWER
}
}
void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning)
{
_set_current();
+#if ENABLE_GCODE_VIEWER
+ bool show = false;
+ if (!m_volumes.empty())
+ show = _is_any_volume_outside();
+ else {
+ if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) {
+ BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
+ const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box();
+ if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0)
+ show = !test_volume.contains(paths_volume);
+ }
+ }
+ _set_warning_texture(warning, show);
+#else
_set_warning_texture(warning, _is_any_volume_outside());
+#endif // ENABLE_GCODE_VIEWER
}
std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
@@ -6981,10 +7174,12 @@ std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& col
return output;
}
+#if !ENABLE_GCODE_VIEWER
void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
{
m_legend_texture.generate(preview_data, tool_colors, *this, true);
}
+#endif // !ENABLE_GCODE_VIEWER
void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state)
{
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index c9433a10e..03d42089b 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -6,13 +6,16 @@
#include <chrono>
#include "GLToolbar.hpp"
-#include "GLShader.hpp"
#include "Event.hpp"
#include "Selection.hpp"
#include "Gizmos/GLGizmosManager.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GLSelectionRectangle.hpp"
#include "MeshUtils.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+#include "GCodeViewer.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "libslic3r/Slicing.hpp"
@@ -36,7 +39,9 @@ namespace Slic3r {
struct Camera;
class BackgroundSlicingProcess;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
struct ThumbnailData;
class ModelObject;
class ModelInstance;
@@ -103,7 +108,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
+#if ENABLE_GCODE_VIEWER
+wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, wxKeyEvent);
+#else
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
+#endif // ENABLE_GCODE_VIEWER
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@@ -118,6 +127,7 @@ class GLCanvas3D
static const double DefaultCameraZoomToBoxMarginFactor;
public:
+#if !ENABLE_GCODE_VIEWER
struct GCodePreviewVolumeIndex
{
enum EType
@@ -144,6 +154,7 @@ public:
void reset() { first_volumes.clear(); }
};
+#endif // !ENABLE_GCODE_VIEWER
private:
class LayersEditing
@@ -161,7 +172,6 @@ private:
private:
bool m_enabled;
- Shader m_shader;
unsigned int m_z_texture_id;
// Not owned by LayersEditing.
const DynamicPrintConfig *m_config;
@@ -208,8 +218,9 @@ private:
LayersEditing();
~LayersEditing();
- bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
- void set_config(const DynamicPrintConfig* config);
+ void init();
+
+ void set_config(const DynamicPrintConfig* config);
void select_object(const Model &model, int object_id);
bool is_allowed() const;
@@ -339,6 +350,7 @@ private:
bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false);
};
+#if !ENABLE_GCODE_VIEWER
class LegendTexture : public GUI::GLTexture
{
static const int Px_Title_Offset = 5;
@@ -365,6 +377,7 @@ private:
void render(const GLCanvas3D& canvas) const;
};
+#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_STATISTICS
struct RenderStats
@@ -443,11 +456,12 @@ private:
std::unique_ptr<RetinaHelper> m_retina_helper;
#endif
bool m_in_render;
+#if !ENABLE_GCODE_VIEWER
LegendTexture m_legend_texture;
+#endif // !ENABLE_GCODE_VIEWER
WarningTexture m_warning_texture;
wxTimer m_timer;
LayersEditing m_layers_editing;
- Shader m_shader;
Mouse m_mouse;
mutable GLGizmosManager m_gizmos;
mutable GLToolbar m_main_toolbar;
@@ -462,6 +476,10 @@ private:
bool m_extra_frame_requested;
mutable GLVolumeCollection m_volumes;
+#if ENABLE_GCODE_VIEWER
+ GCodeViewer m_gcode_viewer;
+#endif // ENABLE_GCODE_VIEWER
+
Selection m_selection;
const DynamicPrintConfig* m_config;
Model* m_model;
@@ -472,7 +490,9 @@ private:
bool m_initialized;
bool m_apply_zoom_to_volumes_filter;
mutable std::vector<int> m_hover_volume_idxs;
+#if !ENABLE_GCODE_VIEWER
bool m_legend_texture_enabled;
+#endif // !ENABLE_GCODE_VIEWER
bool m_picking_enabled;
bool m_moving_enabled;
bool m_dynamic_background_enabled;
@@ -490,7 +510,9 @@ private:
bool m_reload_delayed;
+#if !ENABLE_GCODE_VIEWER
GCodePreviewVolumeIndex m_gcode_preview_volume_index;
+#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_PICKING_PASS
bool m_show_picking_texture;
@@ -532,6 +554,12 @@ public:
void reset_volumes();
int check_volumes_outside_state() const;
+#if ENABLE_GCODE_VIEWER
+ void reset_gcode_toolpaths() { m_gcode_viewer.reset(); }
+ const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); }
+ void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
+#endif // ENABLE_GCODE_VIEWER
+
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void update_instance_printable_state_for_object(size_t obj_idx);
@@ -564,7 +592,6 @@ public:
void set_color_by(const std::string& value);
void refresh_camera_scene_box();
- const Shader& get_shader() const { return m_shader; }
BoundingBoxf3 volumes_bounding_box() const;
BoundingBoxf3 scene_bounding_box() const;
@@ -597,6 +624,9 @@ public:
void zoom_to_bed();
void zoom_to_volumes();
void zoom_to_selection();
+#if ENABLE_GCODE_VIEWER
+ void zoom_to_gcode();
+#endif // ENABLE_GCODE_VIEWER
void select_view(const std::string& direction);
void update_volumes_colors_by_extruder();
@@ -613,7 +643,20 @@ public:
void delete_selected();
void ensure_on_bed(unsigned int object_idx);
+#if ENABLE_GCODE_VIEWER
+ bool is_gcode_legend_enabled() const { return m_gcode_viewer.is_legend_enabled(); }
+ GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); }
+ const std::vector<double>& get_gcode_layers_zs() const;
+ std::vector<double> get_volumes_print_zs(bool active_only) const;
+ unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); }
+ void set_gcode_options_visibility_from_flags(unsigned int flags);
+ unsigned int get_toolpath_role_visibility_flags() const { return m_gcode_viewer.get_toolpath_role_visibility_flags(); }
+ void set_toolpath_role_visibility_flags(unsigned int flags);
+ void set_toolpath_view_type(GCodeViewer::EViewType type);
+ void set_toolpaths_z_range(const std::array<double, 2>& range);
+#else
std::vector<double> get_current_print_zs(bool active_only) const;
+#endif // ENABLE_GCODE_VIEWER
void set_toolpaths_range(double low, double high);
std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
@@ -623,7 +666,14 @@ public:
void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false);
+#if ENABLE_GCODE_VIEWER
+ void load_gcode_preview(const GCodeProcessor::Result& gcode_result);
+ void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors);
+ void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); }
+ GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); }
+#else
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
+#endif // ENABLE_GCODE_VIEWER
void load_sla_preview();
void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values);
void bind_event_handlers();
@@ -642,7 +692,9 @@ public:
Size get_canvas_size() const;
Vec2d get_local_mouse_position() const;
+#if !ENABLE_GCODE_VIEWER
void reset_legend_texture();
+#endif // !ENABLE_GCODE_VIEWER
void set_tooltip(const std::string& tooltip) const;
@@ -744,6 +796,9 @@ private:
void _render_background() const;
void _render_bed(bool bottom, bool show_axes) const;
void _render_objects() const;
+#if ENABLE_GCODE_VIEWER
+ void _render_gcode() const;
+#endif // ENABLE_GCODE_VIEWER
void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER
void _render_selection_center() const;
@@ -751,7 +806,9 @@ private:
void _check_and_update_toolbar_icon_scale() const;
void _render_overlays() const;
void _render_warning_texture() const;
+#if !ENABLE_GCODE_VIEWER
void _render_legend_texture() const;
+#endif // !ENABLE_GCODE_VIEWER
void _render_volumes_for_picking() const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
@@ -799,22 +856,28 @@ private:
// Create 3D thick extrusion lines for wipe tower extrusions
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
+#if !ENABLE_GCODE_VIEWER
// generates gcode extrusion paths geometry
void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode travel paths geometry
void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates objects and wipe tower geometry
void _load_fff_shells();
+#endif // !ENABLE_GCODE_VIEWER
// Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished.
void _load_sla_shells();
+#if !ENABLE_GCODE_VIEWER
// sets gcode geometry visibility according to user selection
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
+#endif // !ENABLE_GCODE_VIEWER
void _update_toolpath_volumes_outside_state();
void _update_sla_shells_outside_state();
void _show_warning_texture_if_needed(WarningTexture::Warning warning);
+#if !ENABLE_GCODE_VIEWER
// generates the legend texture in dependence of the current shown view type
void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
+#endif // !ENABLE_GCODE_VIEWER
// generates a warning texture containing the given message
void _set_warning_texture(WarningTexture::Warning warning, bool state);
diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp
new file mode 100644
index 000000000..e738aa3c4
--- /dev/null
+++ b/src/slic3r/GUI/GLModel.cpp
@@ -0,0 +1,531 @@
+#include "libslic3r/libslic3r.h"
+#include "GLModel.hpp"
+
+#include "3DScene.hpp"
+#include "libslic3r/TriangleMesh.hpp"
+#include "libslic3r/Model.hpp"
+
+#include <boost/filesystem/operations.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <GL/glew.h>
+
+namespace Slic3r {
+namespace GUI {
+
+void GLModel::init_from(const GLModelInitializationData& data)
+{
+ assert(!data.positions.empty() && !data.triangles.empty());
+ assert(data.positions.size() == data.normals.size());
+
+ if (m_vbo_id > 0) // call reset() if you want to reuse this model
+ return;
+
+ // vertices/normals data
+ std::vector<float> vertices(6 * data.positions.size());
+ for (size_t i = 0; i < data.positions.size(); ++i) {
+ size_t offset = i * 6;
+ ::memcpy(static_cast<void*>(&vertices[offset]), static_cast<const void*>(data.positions[i].data()), 3 * sizeof(float));
+ ::memcpy(static_cast<void*>(&vertices[3 + offset]), static_cast<const void*>(data.normals[i].data()), 3 * sizeof(float));
+ }
+
+ // indices data
+ std::vector<unsigned int> indices(3 * data.triangles.size());
+ for (size_t i = 0; i < data.triangles.size(); ++i) {
+ for (size_t j = 0; j < 3; ++j) {
+ indices[i * 3 + j] = static_cast<unsigned int>(data.triangles[i][j]);
+ }
+ }
+
+ m_indices_count = static_cast<unsigned int>(indices.size());
+ m_bounding_box = BoundingBoxf3();
+ for (size_t i = 0; i < data.positions.size(); ++i) {
+ m_bounding_box.merge(data.positions[i].cast<double>());
+ }
+
+ send_to_gpu(vertices, indices);
+}
+
+void GLModel::init_from(const TriangleMesh& mesh)
+{
+ if (m_vbo_id > 0) // call reset() if you want to reuse this model
+ return;
+
+ std::vector<float> vertices = std::vector<float>(18 * mesh.stl.stats.number_of_facets);
+ std::vector<unsigned int> indices = std::vector<unsigned int>(3 * mesh.stl.stats.number_of_facets);
+
+ unsigned int vertices_count = 0;
+ for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
+ const stl_facet& facet = mesh.stl.facet_start[i];
+ for (uint32_t j = 0; j < 3; ++j) {
+ uint32_t offset = i * 18 + j * 6;
+ ::memcpy(static_cast<void*>(&vertices[offset]), static_cast<const void*>(facet.vertex[j].data()), 3 * sizeof(float));
+ ::memcpy(static_cast<void*>(&vertices[3 + offset]), static_cast<const void*>(facet.normal.data()), 3 * sizeof(float));
+ }
+ for (uint32_t j = 0; j < 3; ++j) {
+ indices[i * 3 + j] = vertices_count + j;
+ }
+ vertices_count += 3;
+ }
+
+ m_indices_count = static_cast<unsigned int>(indices.size());
+ m_bounding_box = mesh.bounding_box();
+
+ send_to_gpu(vertices, indices);
+}
+
+bool GLModel::init_from_file(const std::string& filename)
+{
+ if (!boost::filesystem::exists(filename))
+ return false;
+
+ if (!boost::algorithm::iends_with(filename, ".stl"))
+ return false;
+
+ Model model;
+ try
+ {
+ model = Model::read_from_file(filename);
+ }
+ catch (std::exception&)
+ {
+ return false;
+ }
+
+ init_from(model.mesh());
+
+ m_filename = filename;
+
+ return true;
+}
+
+void GLModel::reset()
+{
+ // release gpu memory
+ if (m_ibo_id > 0) {
+ glsafe(::glDeleteBuffers(1, &m_ibo_id));
+ m_ibo_id = 0;
+ }
+
+ if (m_vbo_id > 0) {
+ glsafe(::glDeleteBuffers(1, &m_vbo_id));
+ m_vbo_id = 0;
+ }
+
+ m_indices_count = 0;
+ m_bounding_box = BoundingBoxf3();
+ m_filename = std::string();
+}
+
+void GLModel::render() const
+{
+ if (m_vbo_id == 0 || m_ibo_id == 0)
+ return;
+
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
+ glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0));
+ glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))));
+
+ glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
+ glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
+
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
+ glsafe(::glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indices_count), GL_UNSIGNED_INT, (const void*)0));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+ glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
+ glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
+
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+}
+
+void GLModel::send_to_gpu(const std::vector<float>& vertices, const std::vector<unsigned int>& indices)
+{
+ // vertex data -> send to gpu
+ glsafe(::glGenBuffers(1, &m_vbo_id));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
+ glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+ // indices data -> send to gpu
+ glsafe(::glGenBuffers(1, &m_ibo_id));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
+ glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+}
+
+GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height)
+{
+ auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
+ data.positions.emplace_back(position);
+ data.normals.emplace_back(normal);
+ };
+
+ resolution = std::max(4, resolution);
+
+ GLModelInitializationData data;
+
+ const float angle_step = 2.0f * M_PI / static_cast<float>(resolution);
+ std::vector<float> cosines(resolution);
+ std::vector<float> sines(resolution);
+
+ for (int i = 0; i < resolution; ++i)
+ {
+ float angle = angle_step * static_cast<float>(i);
+ cosines[i] = ::cos(angle);
+ sines[i] = -::sin(angle);
+ }
+
+ const float total_height = tip_height + stem_height;
+
+ // tip vertices/normals
+ append_vertex(data, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ());
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f });
+ }
+
+ // tip triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ int v3 = (i < resolution - 1) ? i + 2 : 1;
+ data.triangles.emplace_back(0, i + 1, v3);
+ }
+
+ // tip cap outer perimeter vertices
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ());
+ }
+
+ // tip cap inner perimeter vertices
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ());
+ }
+
+ // tip cap triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1;
+ int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1;
+ data.triangles.emplace_back(i + resolution + 1, v3, v2);
+ data.triangles.emplace_back(i + resolution + 1, i + 2 * resolution + 1, v3);
+ }
+
+ // stem bottom vertices
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f });
+ }
+
+ // stem top vertices
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f });
+ }
+
+ // stem triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1;
+ int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1;
+ data.triangles.emplace_back(i + 3 * resolution + 1, v3, v2);
+ data.triangles.emplace_back(i + 3 * resolution + 1, i + 4 * resolution + 1, v3);
+ }
+
+ // stem cap vertices
+ append_vertex(data, Vec3f::Zero(), -Vec3f::UnitZ());
+ for (int i = 0; i < resolution; ++i)
+ {
+ append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ());
+ }
+
+ // stem cap triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2;
+ data.triangles.emplace_back(5 * resolution + 1, v3, i + 5 * resolution + 2);
+ }
+
+ return data;
+}
+
+GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness)
+{
+ auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
+ data.positions.emplace_back(position);
+ data.normals.emplace_back(normal);
+ };
+
+ resolution = std::max(2, resolution);
+
+ GLModelInitializationData data;
+
+ const float half_thickness = 0.5f * thickness;
+ const float half_stem_width = 0.5f * stem_width;
+ const float half_tip_width = 0.5f * tip_width;
+
+ const float outer_radius = radius + half_stem_width;
+ const float inner_radius = radius - half_stem_width;
+ const float step_angle = 0.5f * PI / static_cast<float>(resolution);
+
+ // tip
+ // top face vertices
+ append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { -tip_height, radius, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ());
+
+ // top face triangles
+ data.triangles.emplace_back(0, 1, 2);
+ data.triangles.emplace_back(0, 2, 4);
+ data.triangles.emplace_back(4, 2, 3);
+
+ // bottom face vertices
+ append_vertex(data, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ());
+
+ // bottom face triangles
+ data.triangles.emplace_back(5, 7, 6);
+ data.triangles.emplace_back(5, 9, 7);
+ data.triangles.emplace_back(9, 8, 7);
+
+ // side faces vertices
+ append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX());
+
+ Vec3f normal(-half_tip_width, tip_height, 0.0f);
+ normal.normalize();
+ append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, normal);
+ append_vertex(data, { -tip_height, radius, -half_thickness }, normal);
+ append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, normal);
+ append_vertex(data, { -tip_height, radius, half_thickness }, normal);
+
+ normal = Vec3f(-half_tip_width, -tip_height, 0.0f);
+ normal.normalize();
+ append_vertex(data, { -tip_height, radius, -half_thickness }, normal);
+ append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, normal);
+ append_vertex(data, { -tip_height, radius, half_thickness }, normal);
+ append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, normal);
+
+ append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX());
+
+ // side face triangles
+ for (int i = 0; i < 4; ++i)
+ {
+ int ii = i * 4;
+ data.triangles.emplace_back(10 + ii, 11 + ii, 13 + ii);
+ data.triangles.emplace_back(10 + ii, 13 + ii, 12 + ii);
+ }
+
+ // stem
+ // top face vertices
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ());
+ }
+
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ());
+ }
+
+ // top face triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ data.triangles.emplace_back(26 + i, 27 + i, 27 + resolution + i);
+ data.triangles.emplace_back(27 + i, 28 + resolution + i, 27 + resolution + i);
+ }
+
+ // bottom face vertices
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ());
+ }
+
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ());
+ }
+
+ // bottom face triangles
+ for (int i = 0; i < resolution; ++i)
+ {
+ data.triangles.emplace_back(28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i);
+ data.triangles.emplace_back(29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i);
+ }
+
+ // side faces vertices and triangles
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ float c = ::cos(angle);
+ float s = ::sin(angle);
+ append_vertex(data, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f });
+ }
+
+ for (int i = 0; i <= resolution; ++i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ float c = ::cos(angle);
+ float s = ::sin(angle);
+ append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f });
+ }
+
+ int first_id = 26 + 4 * (resolution + 1);
+ for (int i = 0; i < resolution; ++i)
+ {
+ int ii = first_id + i;
+ data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2);
+ data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1);
+ }
+
+ append_vertex(data, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY());
+
+ first_id = 26 + 6 * (resolution + 1);
+ data.triangles.emplace_back(first_id, first_id + 1, first_id + 3);
+ data.triangles.emplace_back(first_id, first_id + 3, first_id + 2);
+
+ for (int i = resolution; i >= 0; --i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ float c = ::cos(angle);
+ float s = ::sin(angle);
+ append_vertex(data, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f });
+ }
+
+ for (int i = resolution; i >= 0; --i)
+ {
+ float angle = static_cast<float>(i) * step_angle;
+ float c = ::cos(angle);
+ float s = ::sin(angle);
+ append_vertex(data, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f });
+ }
+
+ first_id = 30 + 6 * (resolution + 1);
+ for (int i = 0; i < resolution; ++i)
+ {
+ int ii = first_id + i;
+ data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2);
+ data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1);
+ }
+
+ return data;
+}
+
+GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness)
+{
+ auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
+ data.positions.emplace_back(position);
+ data.normals.emplace_back(normal);
+ };
+
+ GLModelInitializationData data;
+
+ const float half_thickness = 0.5f * thickness;
+ const float half_stem_width = 0.5f * stem_width;
+ const float half_tip_width = 0.5f * tip_width;
+ const float total_height = tip_height + stem_height;
+
+ // top face vertices
+ append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { 0.0, total_height, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ());
+ append_vertex(data, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ());
+
+ // top face triangles
+ data.triangles.emplace_back(0, 1, 6);
+ data.triangles.emplace_back(6, 1, 5);
+ data.triangles.emplace_back(4, 5, 3);
+ data.triangles.emplace_back(5, 1, 3);
+ data.triangles.emplace_back(1, 2, 3);
+
+ // bottom face vertices
+ append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
+ append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ());
+
+ // bottom face triangles
+ data.triangles.emplace_back(7, 13, 8);
+ data.triangles.emplace_back(13, 12, 8);
+ data.triangles.emplace_back(12, 11, 10);
+ data.triangles.emplace_back(8, 12, 10);
+ data.triangles.emplace_back(9, 8, 10);
+
+ // side faces vertices
+ append_vertex(data, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX());
+ append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX());
+
+ append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY());
+
+ Vec3f normal(tip_height, half_tip_width, 0.0f);
+ normal.normalize();
+ append_vertex(data, { half_tip_width, stem_height, -half_thickness }, normal);
+ append_vertex(data, { 0.0, total_height, -half_thickness }, normal);
+ append_vertex(data, { half_tip_width, stem_height, half_thickness }, normal);
+ append_vertex(data, { 0.0, total_height, half_thickness }, normal);
+
+ normal = Vec3f(-tip_height, half_tip_width, 0.0f);
+ normal.normalize();
+ append_vertex(data, { 0.0, total_height, -half_thickness }, normal);
+ append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, normal);
+ append_vertex(data, { 0.0, total_height, half_thickness }, normal);
+ append_vertex(data, { -half_tip_width, stem_height, half_thickness }, normal);
+
+ append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY());
+
+ append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX());
+ append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX());
+ append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX());
+ append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX());
+
+ append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY());
+ append_vertex(data, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY());
+
+ // side face triangles
+ for (int i = 0; i < 7; ++i)
+ {
+ int ii = i * 4;
+ data.triangles.emplace_back(14 + ii, 15 + ii, 17 + ii);
+ data.triangles.emplace_back(14 + ii, 17 + ii, 16 + ii);
+ }
+
+ return data;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp
new file mode 100644
index 000000000..0b4a69bdb
--- /dev/null
+++ b/src/slic3r/GUI/GLModel.hpp
@@ -0,0 +1,68 @@
+#ifndef slic3r_GLModel_hpp_
+#define slic3r_GLModel_hpp_
+
+#include "libslic3r/Point.hpp"
+#include "libslic3r/BoundingBox.hpp"
+#include <vector>
+#include <string>
+
+namespace Slic3r {
+
+class TriangleMesh;
+
+namespace GUI {
+
+ struct GLModelInitializationData
+ {
+ std::vector<Vec3f> positions;
+ std::vector<Vec3f> normals;
+ std::vector<Vec3i> triangles;
+ };
+
+ class GLModel
+ {
+ unsigned int m_vbo_id{ 0 };
+ unsigned int m_ibo_id{ 0 };
+ size_t m_indices_count{ 0 };
+
+ BoundingBoxf3 m_bounding_box;
+ std::string m_filename;
+
+ public:
+ virtual ~GLModel() { reset(); }
+
+ void init_from(const GLModelInitializationData& data);
+ void init_from(const TriangleMesh& mesh);
+ bool init_from_file(const std::string& filename);
+ void reset();
+ void render() const;
+
+ const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
+
+ const std::string& get_filename() const { return m_filename; }
+
+ private:
+ void send_to_gpu(const std::vector<float>& vertices, const std::vector<unsigned int>& indices);
+ };
+
+
+ // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution
+ // the origin of the arrow is in the center of the stem cap
+ // the arrow has its axis of symmetry along the Z axis and is pointing upward
+ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height);
+
+ // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution
+ // the origin of the arrow is in the center of the circle
+ // the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise
+ GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness);
+
+ // create an arrow with the given dimensions
+ // the origin of the arrow is in the center of the stem cap
+ // the arrow is contained in XY plane and has its main axis along the Y axis
+ GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness);
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLModel_hpp_
+
diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp
index c31076060..3c2612b45 100644
--- a/src/slic3r/GUI/GLShader.cpp
+++ b/src/slic3r/GUI/GLShader.cpp
@@ -1,366 +1,348 @@
-#include <GL/glew.h>
-
+#include "libslic3r/libslic3r.h"
#include "GLShader.hpp"
-#include "libslic3r/Utils.hpp"
#include "3DScene.hpp"
+#include "libslic3r/Utils.hpp"
+
#include <boost/nowide/fstream.hpp>
+#include <GL/glew.h>
+#include <cassert>
-#include <string>
-#include <utility>
-#include <assert.h>
+#include <boost/log/trivial.hpp>
namespace Slic3r {
-GLShader::~GLShader()
+GLShaderProgram::~GLShaderProgram()
{
- assert(fragment_program_id == 0);
- assert(vertex_program_id == 0);
- assert(shader_program_id == 0);
+ if (m_id > 0)
+ glsafe(::glDeleteProgram(m_id));
}
-// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr.
-inline std::string gl_get_string_safe(GLenum param)
+bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilenames& filenames)
{
- const char *value = (const char*)glGetString(param);
- return std::string(value ? value : "N/A");
+ auto load_from_file = [](const std::string& filename) {
+ std::string path = resources_dir() + "/shaders/" + filename;
+ boost::nowide::ifstream s(path, boost::nowide::ifstream::binary);
+ if (!s.good()) {
+ BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'";
+ return std::string();
+ }
+
+ s.seekg(0, s.end);
+ int file_length = static_cast<int>(s.tellg());
+ s.seekg(0, s.beg);
+ std::string source(file_length, '\0');
+ s.read(source.data(), file_length);
+ if (!s.good()) {
+ BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'";
+ return std::string();
+ }
+
+ s.close();
+ return source;
+ };
+
+ ShaderSources sources = {};
+ for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
+ sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i]);
+ }
+
+ bool valid = !sources[static_cast<size_t>(EShaderType::Vertex)].empty() && !sources[static_cast<size_t>(EShaderType::Fragment)].empty() && sources[static_cast<size_t>(EShaderType::Compute)].empty();
+ valid |= !sources[static_cast<size_t>(EShaderType::Compute)].empty() && sources[static_cast<size_t>(EShaderType::Vertex)].empty() && sources[static_cast<size_t>(EShaderType::Fragment)].empty() &&
+ sources[static_cast<size_t>(EShaderType::Geometry)].empty() && sources[static_cast<size_t>(EShaderType::TessEvaluation)].empty() && sources[static_cast<size_t>(EShaderType::TessControl)].empty();
+
+ return valid ? init_from_texts(name, sources) : false;
}
-bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
+bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSources& sources)
{
- std::string gl_version = gl_get_string_safe(GL_VERSION);
- int major = atoi(gl_version.c_str());
- //int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1);
- if (major < 2) {
- // Cannot create a shader object on OpenGL 1.x.
- // Form an error message.
- std::string gl_vendor = gl_get_string_safe(GL_VENDOR);
- std::string gl_renderer = gl_get_string_safe(GL_RENDERER);
- std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION);
- last_error = "Your computer does not support OpenGL shaders.\n";
-#ifdef _WIN32
- if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") {
- last_error = "Windows is using a software OpenGL renderer.\n"
- "You are either connected over remote desktop,\n"
- "or a hardware acceleration is not available.\n";
+ auto shader_type_as_string = [](EShaderType type) {
+ switch (type)
+ {
+ case EShaderType::Vertex: { return "vertex"; }
+ case EShaderType::Fragment: { return "fragment"; }
+ case EShaderType::Geometry: { return "geometry"; }
+ case EShaderType::TessEvaluation: { return "tesselation evaluation"; }
+ case EShaderType::TessControl: { return "tesselation control"; }
+ case EShaderType::Compute: { return "compute"; }
+ default: { return "unknown"; }
}
-#endif
- last_error += "GL version: " + gl_version + "\n";
- last_error += "vendor: " + gl_vendor + "\n";
- last_error += "renderer: " + gl_renderer + "\n";
- last_error += "GLSL version: " + glsl_version + "\n";
- return false;
- }
+ };
- if (fragment_shader != nullptr) {
- this->fragment_program_id = ::glCreateShader(GL_FRAGMENT_SHADER);
- glcheck();
- if (this->fragment_program_id == 0) {
- last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed.";
- return false;
+ auto create_shader = [](EShaderType type) {
+ GLuint id = 0;
+ switch (type)
+ {
+ case EShaderType::Vertex: { id = ::glCreateShader(GL_VERTEX_SHADER); glcheck(); break; }
+ case EShaderType::Fragment: { id = ::glCreateShader(GL_FRAGMENT_SHADER); glcheck(); break; }
+ case EShaderType::Geometry: { id = ::glCreateShader(GL_GEOMETRY_SHADER); glcheck(); break; }
+ case EShaderType::TessEvaluation: { id = ::glCreateShader(GL_TESS_EVALUATION_SHADER); glcheck(); break; }
+ case EShaderType::TessControl: { id = ::glCreateShader(GL_TESS_CONTROL_SHADER); glcheck(); break; }
+ case EShaderType::Compute: { id = ::glCreateShader(GL_COMPUTE_SHADER); glcheck(); break; }
+ default: { break; }
}
- GLint len = (GLint)strlen(fragment_shader);
- glsafe(::glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len));
- glsafe(::glCompileShader(this->fragment_program_id));
- GLint params;
- glsafe(::glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, &params));
- if (params == GL_FALSE) {
- // Compilation failed. Get the log.
- glsafe(::glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, &params));
- std::vector<char> msg(params);
- glsafe(::glGetShaderInfoLog(this->fragment_program_id, params, &params, msg.data()));
- this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data();
- this->release();
- return false;
+
+ return (id == 0) ? std::make_pair(false, GLuint(0)) : std::make_pair(true, id);
+ };
+
+ auto release_shaders = [](const std::array<GLuint, static_cast<size_t>(EShaderType::Count)>& shader_ids) {
+ for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
+ if (shader_ids[i] > 0)
+ glsafe(::glDeleteShader(shader_ids[i]));
}
- }
+ };
- if (vertex_shader != nullptr) {
- this->vertex_program_id = ::glCreateShader(GL_VERTEX_SHADER);
- glcheck();
- if (this->vertex_program_id == 0) {
- last_error = "glCreateShader(GL_VERTEX_SHADER) failed.";
- this->release();
- return false;
- }
- GLint len = (GLint)strlen(vertex_shader);
- glsafe(::glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len));
- glsafe(::glCompileShader(this->vertex_program_id));
- GLint params;
- glsafe(::glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, &params));
- if (params == GL_FALSE) {
- // Compilation failed. Get the log.
- glsafe(::glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params));
- std::vector<char> msg(params);
- glsafe(::glGetShaderInfoLog(this->vertex_program_id, params, &params, msg.data()));
- this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data();
- this->release();
- return false;
+ assert(m_id == 0);
+
+ m_name = name;
+
+ std::array<GLuint, static_cast<size_t>(EShaderType::Count)> shader_ids = { 0 };
+
+ for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
+ const std::string& source = sources[i];
+ if (!source.empty())
+ {
+ EShaderType type = static_cast<EShaderType>(i);
+ auto [result, id] = create_shader(type);
+ if (result)
+ shader_ids[i] = id;
+ else {
+ BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'";
+
+ // release shaders
+ release_shaders(shader_ids);
+ return false;
+ }
+
+ const char* source_ptr = source.c_str();
+ glsafe(::glShaderSource(id, 1, &source_ptr, nullptr));
+ glsafe(::glCompileShader(id));
+ GLint params;
+ glsafe(::glGetShaderiv(id, GL_COMPILE_STATUS, &params));
+ if (params == GL_FALSE) {
+ // Compilation failed.
+ glsafe(::glGetShaderiv(id, GL_INFO_LOG_LENGTH, &params));
+ std::vector<char> msg(params);
+ glsafe(::glGetShaderInfoLog(id, params, &params, msg.data()));
+ BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data();
+
+ // release shaders
+ release_shaders(shader_ids);
+ return false;
+ }
}
}
- // Link shaders
- this->shader_program_id = ::glCreateProgram();
+ m_id = ::glCreateProgram();
glcheck();
- if (this->shader_program_id == 0) {
- last_error = "glCreateProgram() failed.";
- this->release();
+ if (m_id == 0) {
+ BOOST_LOG_TRIVIAL(error) << "glCreateProgram() failed for shader program '" << name << "'";
+
+ // release shaders
+ release_shaders(shader_ids);
return false;
}
- if (this->fragment_program_id)
- glsafe(::glAttachShader(this->shader_program_id, this->fragment_program_id));
- if (this->vertex_program_id)
- glsafe(::glAttachShader(this->shader_program_id, this->vertex_program_id));
- glsafe(::glLinkProgram(this->shader_program_id));
+ for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
+ if (shader_ids[i] > 0)
+ glsafe(::glAttachShader(m_id, shader_ids[i]));
+ }
+ glsafe(::glLinkProgram(m_id));
GLint params;
- glsafe(::glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, &params));
+ glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, &params));
if (params == GL_FALSE) {
- // Linking failed. Get the log.
- glsafe(::glGetProgramiv(this->shader_program_id, GL_INFO_LOG_LENGTH, &params));
+ // Linking failed.
+ glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
- glsafe(::glGetProgramInfoLog(this->shader_program_id, params, &params, msg.data()));
- this->last_error = std::string("Shader linking failed:\n") + msg.data();
- this->release();
- return false;
- }
+ glsafe(::glGetProgramInfoLog(m_id, params, &params, msg.data()));
+ BOOST_LOG_TRIVIAL(error) << "Unable to link shader program '" << name << "':\n" << msg.data();
- last_error.clear();
- return true;
-}
+ // release shaders
+ release_shaders(shader_ids);
-bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
-{
- const std::string& path = resources_dir() + "/shaders/";
-
- boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
- if (!vs.good())
- return false;
+ // release shader program
+ glsafe(::glDeleteProgram(m_id));
+ m_id = 0;
- vs.seekg(0, vs.end);
- int file_length = (int)vs.tellg();
- vs.seekg(0, vs.beg);
- std::string vertex_shader(file_length, '\0');
- vs.read(vertex_shader.data(), file_length);
- if (!vs.good())
return false;
-
- vs.close();
-
- boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary);
- if (!fs.good())
- return false;
-
- fs.seekg(0, fs.end);
- file_length = (int)fs.tellg();
- fs.seekg(0, fs.beg);
- std::string fragment_shader(file_length, '\0');
- fs.read(fragment_shader.data(), file_length);
- if (!fs.good())
- return false;
-
- fs.close();
-
- return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
-}
-
-void GLShader::release()
-{
- if (this->shader_program_id) {
- if (this->vertex_program_id)
- glsafe(::glDetachShader(this->shader_program_id, this->vertex_program_id));
- if (this->fragment_program_id)
- glsafe(::glDetachShader(this->shader_program_id, this->fragment_program_id));
- glsafe(::glDeleteProgram(this->shader_program_id));
- this->shader_program_id = 0;
}
- if (this->vertex_program_id) {
- glsafe(::glDeleteShader(this->vertex_program_id));
- this->vertex_program_id = 0;
- }
- if (this->fragment_program_id) {
- glsafe(::glDeleteShader(this->fragment_program_id));
- this->fragment_program_id = 0;
- }
+ // release shaders, they are no more needed
+ release_shaders(shader_ids);
+
+ return true;
}
-void GLShader::enable() const
+void GLShaderProgram::start_using() const
{
- glsafe(::glUseProgram(this->shader_program_id));
+ assert(m_id > 0);
+ glsafe(::glUseProgram(m_id));
}
-void GLShader::disable() const
+void GLShaderProgram::stop_using() const
{
glsafe(::glUseProgram(0));
}
-// Return shader vertex attribute ID
-int GLShader::get_attrib_location(const char *name) const
+bool GLShaderProgram::set_uniform(const char* name, int value) const
{
- return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1;
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform1i(id, static_cast<GLint>(value)));
+ return true;
+ }
+ return false;
}
-// Return shader uniform variable ID
-int GLShader::get_uniform_location(const char *name) const
+bool GLShaderProgram::set_uniform(const char* name, bool value) const
{
- return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1;
+ return set_uniform(name, value ? 1 : 0);
}
-bool GLShader::set_uniform(const char *name, float value) const
+bool GLShaderProgram::set_uniform(const char* name, float value) const
{
- int id = this->get_uniform_location(name);
- if (id >= 0) {
- glsafe(::glUniform1fARB(id, value));
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform1f(id, static_cast<GLfloat>(value)));
return true;
}
return false;
}
-bool GLShader::set_uniform(const char* name, const float* matrix) const
+bool GLShaderProgram::set_uniform(const char* name, double value) const
+{
+ return set_uniform(name, static_cast<float>(value));
+}
+
+bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 2>& value) const
{
int id = get_uniform_location(name);
- if (id >= 0)
- {
- glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix));
+ if (id >= 0) {
+ glsafe(::glUniform2iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
-bool GLShader::set_uniform(const char* name, int value) const
+bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 3>& value) const
{
int id = get_uniform_location(name);
- if (id >= 0)
- {
- glsafe(::glUniform1i(id, value));
+ if (id >= 0) {
+ glsafe(::glUniform3iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
-/*
-# Set shader vector
-sub SetVector
-{
- my($self,$var,@values) = @_;
-
- my $id = $self->Map($var);
- return 'Unable to map $var' if (!defined($id));
-
- my $count = scalar(@values);
- eval('glUniform'.$count.'fARB($id,@values)');
-
- return '';
-}
-
-# Set shader 4x4 matrix
-sub SetMatrix
-{
- my($self,$var,$oga) = @_;
-
- my $id = $self->Map($var);
- return 'Unable to map $var' if (!defined($id));
-
- glUniformMatrix4fvARB_c($id,1,0,$oga->ptr());
- return '';
-}
-*/
-
-Shader::Shader()
- : m_shader(nullptr)
-{
-}
-
-Shader::~Shader()
+bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 4>& value) const
{
- reset();
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform4iv(id, 1, static_cast<const GLint*>(value.data())));
+ return true;
+ }
+ return false;
}
-bool Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
+bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 2>& value) const
{
- if (is_initialized())
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
-
- m_shader = new GLShader();
- if (m_shader != nullptr)
- {
- if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str()))
- {
- std::cout << "Compilaton of shader failed:" << std::endl;
- std::cout << m_shader->last_error << std::endl;
- reset();
- return false;
- }
}
-
- return true;
+ return false;
}
-bool Shader::is_initialized() const
+bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 3>& value) const
{
- return (m_shader != nullptr);
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
+ return true;
+ }
+ return false;
}
-bool Shader::start_using() const
+bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 4>& value) const
{
- if (is_initialized())
- {
- m_shader->enable();
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
- else
- return false;
+ return false;
}
-void Shader::stop_using() const
+bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const
{
- if (m_shader != nullptr)
- m_shader->disable();
+ if (size == 1)
+ return set_uniform(name, value[0]);
+ else if (size < 5) {
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ if (size == 2)
+ glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value)));
+ else if (size == 3)
+ glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value)));
+ else
+ glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value)));
+
+ return true;
+ }
+ }
+ return false;
}
-int Shader::get_attrib_location(const std::string& name) const
+bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const
{
- return (m_shader != nullptr) ? m_shader->get_attrib_location(name.c_str()) : -1;
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.matrix().data())));
+ return true;
+ }
+ return false;
}
-int Shader::get_uniform_location(const std::string& name) const
+bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const
{
- return (m_shader != nullptr) ? m_shader->get_uniform_location(name.c_str()) : -1;
+ return set_uniform(name, value.cast<float>());
}
-void Shader::set_uniform(const std::string& name, float value) const
+bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const
{
- if (m_shader != nullptr)
- m_shader->set_uniform(name.c_str(), value);
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.data())));
+ return true;
+ }
+ return false;
}
-void Shader::set_uniform(const std::string& name, const float* matrix) const
+bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const
{
- if (m_shader != nullptr)
- m_shader->set_uniform(name.c_str(), matrix);
+ int id = get_uniform_location(name);
+ if (id >= 0) {
+ glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
+ return true;
+ }
+ return false;
}
-void Shader::set_uniform(const std::string& name, bool value) const
+bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const
{
- if (m_shader != nullptr)
- m_shader->set_uniform(name.c_str(), value ? 1 : 0);
+ return set_uniform(name, static_cast<Vec3f>(value.cast<float>()));
}
-unsigned int Shader::get_shader_program_id() const
+int GLShaderProgram::get_attrib_location(const char* name) const
{
- return (m_shader != nullptr) ? m_shader->shader_program_id : 0;
+ return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1;
}
-void Shader::reset()
+int GLShaderProgram::get_uniform_location(const char* name) const
{
- if (m_shader != nullptr)
- {
- m_shader->release();
- delete m_shader;
- m_shader = nullptr;
- }
+ return (m_id > 0) ? ::glGetUniformLocation(m_id, name) : -1;
}
} // namespace Slic3r
diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp
index df2a23f15..a1160f8e9 100644
--- a/src/slic3r/GUI/GLShader.hpp
+++ b/src/slic3r/GUI/GLShader.hpp
@@ -1,71 +1,67 @@
#ifndef slic3r_GLShader_hpp_
#define slic3r_GLShader_hpp_
-#include "libslic3r/libslic3r.h"
-#include "libslic3r/Point.hpp"
+#include <array>
+#include <string>
namespace Slic3r {
-class GLShader
+class GLShaderProgram
{
public:
- GLShader() :
- fragment_program_id(0),
- vertex_program_id(0),
- shader_program_id(0)
- {}
- ~GLShader();
+ enum class EShaderType
+ {
+ Vertex,
+ Fragment,
+ Geometry,
+ TessEvaluation,
+ TessControl,
+ Compute,
+ Count
+ };
+
+ typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderFilenames;
+ typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderSources;
- bool load_from_text(const char *fragment_shader, const char *vertex_shader);
- bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
-
- void release();
-
- int get_attrib_location(const char *name) const;
- int get_uniform_location(const char *name) const;
-
- bool set_uniform(const char *name, float value) const;
- bool set_uniform(const char* name, const float* matrix) const;
- bool set_uniform(const char* name, int value) const;
-
- void enable() const;
- void disable() const;
-
- unsigned int fragment_program_id;
- unsigned int vertex_program_id;
- unsigned int shader_program_id;
- std::string last_error;
-};
-
-class Shader
-{
- GLShader* m_shader;
+private:
+ std::string m_name;
+ unsigned int m_id{ 0 };
public:
- Shader();
- ~Shader();
+ ~GLShaderProgram();
- bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
+ bool init_from_files(const std::string& name, const ShaderFilenames& filenames);
+ bool init_from_texts(const std::string& name, const ShaderSources& sources);
- bool is_initialized() const;
+ const std::string& get_name() const { return m_name; }
+ unsigned int get_id() const { return m_id; }
- bool start_using() const;
+ void start_using() const;
void stop_using() const;
- int get_attrib_location(const std::string& name) const;
- int get_uniform_location(const std::string& name) const;
-
- void set_uniform(const std::string& name, float value) const;
- void set_uniform(const std::string& name, const float* matrix) const;
- void set_uniform(const std::string& name, bool value) const;
-
- const GLShader* get_shader() const { return m_shader; }
- unsigned int get_shader_program_id() const;
-
-private:
- void reset();
+ bool set_uniform(const char* name, int value) const;
+ bool set_uniform(const char* name, bool value) const;
+ bool set_uniform(const char* name, float value) const;
+ bool set_uniform(const char* name, double value) const;
+ bool set_uniform(const char* name, const std::array<int, 2>& value) const;
+ bool set_uniform(const char* name, const std::array<int, 3>& value) const;
+ bool set_uniform(const char* name, const std::array<int, 4>& value) const;
+ bool set_uniform(const char* name, const std::array<float, 2>& value) const;
+ bool set_uniform(const char* name, const std::array<float, 3>& value) const;
+ bool set_uniform(const char* name, const std::array<float, 4>& value) const;
+ bool set_uniform(const char* name, const float* value, size_t size) const;
+ bool set_uniform(const char* name, const Transform3f& value) const;
+ bool set_uniform(const char* name, const Transform3d& value) const;
+ bool set_uniform(const char* name, const Matrix3f& value) const;
+ bool set_uniform(const char* name, const Vec3f& value) const;
+ bool set_uniform(const char* name, const Vec3d& value) const;
+
+ // returns -1 if not found
+ int get_attrib_location(const char* name) const;
+ // returns -1 if not found
+ int get_uniform_location(const char* name) const;
};
-}
+} // namespace Slic3r
#endif /* slic3r_GLShader_hpp_ */
diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp
new file mode 100644
index 000000000..1041faa3d
--- /dev/null
+++ b/src/slic3r/GUI/GLShadersManager.cpp
@@ -0,0 +1,75 @@
+#include "libslic3r/libslic3r.h"
+#include "GLShadersManager.hpp"
+#include "3DScene.hpp"
+#include "GUI_App.hpp"
+
+#include <cassert>
+#include <algorithm>
+
+#include <GL/glew.h>
+
+namespace Slic3r {
+
+std::pair<bool, std::string> GLShadersManager::init()
+{
+ std::string error;
+
+ auto append_shader = [this, &error](const std::string& name, const GLShaderProgram::ShaderFilenames& filenames) {
+ m_shaders.push_back(std::make_unique<GLShaderProgram>());
+ if (!m_shaders.back()->init_from_files(name, filenames)) {
+ error += name + "\n";
+ // if any error happens while initializating the shader, we remove it from the list
+ m_shaders.pop_back();
+ return false;
+ }
+ return true;
+ };
+
+ assert(m_shaders.empty());
+
+ bool valid = true;
+
+ // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells
+ valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" });
+ // used to render printbed
+ valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" });
+ // used to render options in gcode preview
+ valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" });
+ if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20))
+ valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" });
+ // used to render extrusion and travel paths as lines in gcode preview
+ valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
+ // used to render objects in 3d editor
+ valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" });
+ // used to render variable layers heights in 3d editor
+ valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
+
+ return { valid, error };
+}
+
+void GLShadersManager::shutdown()
+{
+ for (std::unique_ptr<GLShaderProgram>& shader : m_shaders) {
+ shader.reset();
+ }
+}
+
+GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name)
+{
+ auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [shader_name](std::unique_ptr<GLShaderProgram>& p) { return p->get_name() == shader_name; });
+ return (it != m_shaders.end()) ? it->get() : nullptr;
+}
+
+GLShaderProgram* GLShadersManager::get_current_shader()
+{
+ GLint id = 0;
+ glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id));
+ if (id == 0)
+ return nullptr;
+
+ auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr<GLShaderProgram>& p) { return static_cast<GLint>(p->get_id()) == id; });
+ return (it != m_shaders.end()) ? it->get() : nullptr;
+}
+
+} // namespace Slic3r
+
diff --git a/src/slic3r/GUI/GLShadersManager.hpp b/src/slic3r/GUI/GLShadersManager.hpp
new file mode 100644
index 000000000..b2bbc140b
--- /dev/null
+++ b/src/slic3r/GUI/GLShadersManager.hpp
@@ -0,0 +1,30 @@
+#ifndef slic3r_GLShadersManager_hpp_
+#define slic3r_GLShadersManager_hpp_
+
+#include "GLShader.hpp"
+
+#include <vector>
+#include <string>
+#include <memory>
+
+namespace Slic3r {
+
+class GLShadersManager
+{
+ std::vector<std::unique_ptr<GLShaderProgram>> m_shaders;
+
+public:
+ std::pair<bool, std::string> init();
+ // call this method before to release the OpenGL context
+ void shutdown();
+
+ // returns nullptr if not found
+ GLShaderProgram* get_shader(const std::string& shader_name);
+
+ // returns currently active shader, nullptr if none
+ GLShaderProgram* get_current_shader();
+};
+
+} // namespace Slic3r
+
+#endif // slic3r_GLShadersManager_hpp_
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index 4ab282b06..46371b037 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -230,24 +230,13 @@ void GLToolbar::set_icons_size(float size)
void GLToolbar::set_scale(float scale)
{
- if (m_layout.scale != scale)
- {
+ if (m_layout.scale != scale) {
m_layout.scale = scale;
m_layout.dirty = true;
m_icons_texture_dirty = true;
}
}
-bool GLToolbar::is_enabled() const
-{
- return m_enabled;
-}
-
-void GLToolbar::set_enabled(bool enable)
-{
- m_enabled = enable;//true; etFIXME
-}
-
bool GLToolbar::add_item(const GLToolbarItem::Data& data)
{
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);
diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp
index 41c2735c9..74e18de97 100644
--- a/src/slic3r/GUI/GLToolbar.hpp
+++ b/src/slic3r/GUI/GLToolbar.hpp
@@ -276,8 +276,8 @@ public:
void set_icons_size(float size);
void set_scale(float scale);
- bool is_enabled() const;
- void set_enabled(bool enable);
+ bool is_enabled() const { return m_enabled; }
+ void set_enabled(bool enable) { m_enabled = enable; }
bool add_item(const GLToolbarItem::Data& data);
bool add_separator();
diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp
index 30d44b9ab..913716dfd 100644
--- a/src/slic3r/GUI/GUI.cpp
+++ b/src/slic3r/GUI/GUI.cpp
@@ -255,7 +255,7 @@ void warning_catcher(wxWindow* parent, const wxString& message)
msg.ShowModal();
}
-void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
+void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
{
if (comboCtrl == nullptr)
return;
@@ -266,41 +266,59 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
comboCtrl->UseAltPopupWindow();
+ int max_width = 0;
+
// the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3
// comboCtrl->EnablePopupAnimation(false);
comboCtrl->SetPopupControl(popup);
- popup->SetStringValue(from_u8(text));
- popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
- popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
+ wxString title = from_u8(text);
+ max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x);
+ popup->SetStringValue(title);
+ popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
+ popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
std::vector<std::string> items_str;
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
- for (const std::string& item : items_str) {
- popup->Append(from_u8(item));
- }
+ // each item must be composed by 2 parts
+ assert(items_str.size() %2 == 0);
- for (unsigned int i = 0; i < popup->GetCount(); ++i) {
- popup->Check(i, initial_value);
- }
- }
+ for (size_t i = 0; i < items_str.size(); i += 2) {
+ wxString label = from_u8(items_str[i]);
+ max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x);
+ popup->Append(label);
+ popup->Check(i / 2, items_str[i + 1] == "1");
+ }
+
+ comboCtrl->SetMinClientSize(wxSize(max_width, -1));
+ }
}
-int combochecklist_get_flags(wxComboCtrl* comboCtrl)
+unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
{
- int flags = 0;
+ unsigned int flags = 0;
- wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
- if (popup != nullptr) {
- for (unsigned int i = 0; i < popup->GetCount(); ++i) {
- if (popup->IsChecked(i))
- flags |= 1 << i;
- }
- }
+ wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
+ if (popup != nullptr) {
+ for (unsigned int i = 0; i < popup->GetCount(); ++i) {
+ if (popup->IsChecked(i))
+ flags |= 1 << i;
+ }
+ }
+
+ return flags;
+}
- return flags;
+void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags)
+{
+ wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
+ if (popup != nullptr) {
+ for (unsigned int i = 0; i < popup->GetCount(); ++i) {
+ popup->Check(i, (flags & (1 << i)) != 0);
+ }
+ }
}
AppConfig* get_app_config()
diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp
index a54288df4..cf133971e 100644
--- a/src/slic3r/GUI/GUI.hpp
+++ b/src/slic3r/GUI/GUI.hpp
@@ -49,13 +49,17 @@ inline void show_info(wxWindow* parent, const std::string& message,const std::st
void warning_catcher(wxWindow* parent, const wxString& message);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
-// Items are all initialized to the given value.
-// Items must be separated by '|', for example "Item1|Item2|Item3", and so on.
-void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value);
+// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
+// For example "Item1|0|Item2|1|Item3|0", and so on.
+void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items);
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
-// encoded inside an int.
-int combochecklist_get_flags(wxComboCtrl* comboCtrl);
+// encoded inside an unsigned int.
+unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl);
+
+// Sets the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
+// with the flags encoded in the given unsigned int.
+void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags);
// wxString conversions:
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 82c2861bc..18c5fd4f5 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -789,6 +789,20 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
dialog.GetPaths(input_files);
}
+#if ENABLE_GCODE_VIEWER
+void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
+{
+ input_file.Clear();
+ wxFileDialog dialog(parent ? parent : GetTopWindow(),
+ _(L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):")),
+ app_config->get_last_dir(), "",
+ file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+ if (dialog.ShowModal() == wxID_OK)
+ input_file = dialog.GetPath();
+}
+#endif // ENABLE_GCODE_VIEWER
+
bool GUI_App::switch_language()
{
if (select_language()) {
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 82073c549..191d7c264 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -157,6 +157,10 @@ public:
void keyboard_shortcuts();
void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files) const;
+#if ENABLE_GCODE_VIEWER
+ void load_gcode(wxWindow* parent, wxString& input_file) const;
+#endif // ENABLE_GCODE_VIEWER
+
static bool catch_error(std::function<void()> cb, const std::string& err);
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
@@ -231,6 +235,12 @@ public:
void gcode_thumbnails_debug();
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
+ GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); }
+ GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); }
+
+ bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); }
+ bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); }
+
private:
bool on_init_inner();
void init_app_config();
@@ -249,6 +259,6 @@ private:
DECLARE_APP(GUI_App)
} // GUI
-} //Slic3r
+} // Slic3r
#endif // slic3r_GUI_App_hpp_
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index c7c21b20f..5dcd26a87 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -1,5 +1,7 @@
#include "libslic3r/libslic3r.h"
+#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // !ENABLE_GCODE_VIEWER
#include "GUI_Preview.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
@@ -11,6 +13,9 @@
#include "libslic3r/PresetBundle.hpp"
#include "DoubleSlider.hpp"
#include "Plater.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "MainFrame.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include <wx/notebook.h>
#include <wx/glcanvas.h>
@@ -169,28 +174,51 @@ void View3D::render()
m_canvas->set_as_dirty();
}
+#if ENABLE_GCODE_VIEWER
+Preview::Preview(
+ wxWindow* parent, Model* model, DynamicPrintConfig* config,
+ BackgroundSlicingProcess* process, GCodeProcessor::Result* gcode_result, std::function<void()> schedule_background_process_func)
+#else
Preview::Preview(
wxWindow* parent, Model* model, DynamicPrintConfig* config,
BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
+#endif // ENABLE_GCODE_VIEWER
: m_canvas_widget(nullptr)
, m_canvas(nullptr)
+#if ENABLE_GCODE_VIEWER
+ , m_left_sizer(nullptr)
+ , m_layers_slider_sizer(nullptr)
+ , m_bottom_toolbar_panel(nullptr)
+#else
, m_double_slider_sizer(nullptr)
+#endif // ENABLE_GCODE_VIEWER
, m_label_view_type(nullptr)
, m_choice_view_type(nullptr)
- , m_label_show_features(nullptr)
+ , m_label_show(nullptr)
, m_combochecklist_features(nullptr)
+#if ENABLE_GCODE_VIEWER
+ , m_combochecklist_features_pos(0)
+ , m_combochecklist_options(nullptr)
+#else
, m_checkbox_travel(nullptr)
, m_checkbox_retractions(nullptr)
, m_checkbox_unretractions(nullptr)
, m_checkbox_shells(nullptr)
, m_checkbox_legend(nullptr)
+#endif // ENABLE_GCODE_VIEWER
, m_config(config)
, m_process(process)
+#if ENABLE_GCODE_VIEWER
+ , m_gcode_result(gcode_result)
+#else
, m_gcode_preview_data(gcode_preview_data)
+#endif // ENABLE_GCODE_VIEWER
, m_number_extruders(1)
, m_preferred_color_mode("feature")
, m_loaded(false)
+#if !ENABLE_GCODE_VIEWER
, m_enabled(false)
+#endif // !ENABLE_GCODE_VIEWER
, m_schedule_background_process(schedule_background_process_func)
#ifdef __linux__
, m_volumes_cleanup_required(false)
@@ -198,7 +226,9 @@ Preview::Preview(
{
if (init(parent, model))
{
+#if !ENABLE_GCODE_VIEWER
show_hide_ui_elements("none");
+#endif // !ENABLE_GCODE_VIEWER
load_print();
}
}
@@ -208,6 +238,15 @@ bool Preview::init(wxWindow* parent, Model* model)
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
return false;
+#if ENABLE_GCODE_VIEWER
+ // to match the background of the sliders
+#ifdef _WIN32
+ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+#else
+ SetBackgroundColour(GetParent()->GetBackgroundColour());
+#endif // _WIN32
+#endif // ENABLE_GCODE_VIEWER
+
m_canvas_widget = OpenGLManager::create_wxglcanvas(*this);
if (m_canvas_widget == nullptr)
return false;
@@ -222,52 +261,120 @@ bool Preview::init(wxWindow* parent, Model* model)
m_canvas->enable_legend_texture(true);
m_canvas->enable_dynamic_background(true);
+#if ENABLE_GCODE_VIEWER
+ m_layers_slider_sizer = create_layers_slider_sizer();
+
+ m_bottom_toolbar_panel = new wxPanel(this);
+ m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View"));
+ m_choice_view_type = new wxChoice(m_bottom_toolbar_panel, wxID_ANY);
+#else
m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
create_double_slider();
- m_label_view_type = new wxStaticText(this, wxID_ANY, _(L("View")));
+ m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View"));
m_choice_view_type = new wxChoice(this, wxID_ANY);
- m_choice_view_type->Append(_(L("Feature type")));
- m_choice_view_type->Append(_(L("Height")));
- m_choice_view_type->Append(_(L("Width")));
- m_choice_view_type->Append(_(L("Speed")));
- m_choice_view_type->Append(_(L("Fan speed")));
- m_choice_view_type->Append(_(L("Volumetric flow rate")));
- m_choice_view_type->Append(_(L("Tool")));
- m_choice_view_type->Append(_(L("Color Print")));
+#endif // ENABLE_GCODE_VIEWER
+ m_choice_view_type->Append(_L("Feature type"));
+ m_choice_view_type->Append(_L("Height"));
+ m_choice_view_type->Append(_L("Width"));
+ m_choice_view_type->Append(_L("Speed"));
+ m_choice_view_type->Append(_L("Fan speed"));
+ m_choice_view_type->Append(_L("Volumetric flow rate"));
+ m_choice_view_type->Append(_L("Tool"));
+ m_choice_view_type->Append(_L("Color Print"));
m_choice_view_type->SetSelection(0);
- m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show")));
+#if ENABLE_GCODE_VIEWER
+ m_label_show = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("Show"));
+#else
+ m_label_show = new wxStaticText(this, wxID_ANY, _L("Show"));
+#endif // ENABLE_GCODE_VIEWER
m_combochecklist_features = new wxComboCtrl();
- m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY);
- std::string feature_text = GUI::into_u8(_(L("Feature types")));
+#if ENABLE_GCODE_VIEWER
+ m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+#else
+ m_combochecklist_features->Create(this, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+#endif // ENABLE_GCODE_VIEWER
std::string feature_items = GUI::into_u8(
- _(L("Perimeter")) + "|" +
- _(L("External perimeter")) + "|" +
- _(L("Overhang perimeter")) + "|" +
- _(L("Internal infill")) + "|" +
- _(L("Solid infill")) + "|" +
- _(L("Top solid infill")) + "|" +
- _(L("Ironing")) + "|" +
- _(L("Bridge infill")) + "|" +
- _(L("Gap fill")) + "|" +
- _(L("Skirt")) + "|" +
- _(L("Support material")) + "|" +
- _(L("Support material interface")) + "|" +
- _(L("Wipe tower")) + "|" +
- _(L("Custom"))
+#if ENABLE_GCODE_VIEWER
+ _L("Unknown") + "|1|" +
+#endif // ENABLE_GCODE_VIEWER
+ _L("Perimeter") + "|1|" +
+ _L("External perimeter") + "|1|" +
+ _L("Overhang perimeter") + "|1|" +
+ _L("Internal infill") + "|1|" +
+ _L("Solid infill") + "|1|" +
+ _L("Top solid infill") + "|1|" +
+ _L("Ironing") + "|1|" +
+ _L("Bridge infill") + "|1|" +
+ _L("Gap fill") + "|1|" +
+ _L("Skirt") + "|1|" +
+ _L("Support material") + "|1|" +
+ _L("Support material interface") + "|1|" +
+ _L("Wipe tower") + "|1|" +
+ _L("Custom") + "|1"
);
- Slic3r::GUI::create_combochecklist(m_combochecklist_features, feature_text, feature_items, true);
-
+ Slic3r::GUI::create_combochecklist(m_combochecklist_features, GUI::into_u8(_L("Feature types")), feature_items);
+
+#if ENABLE_GCODE_VIEWER
+ m_combochecklist_options = new wxComboCtrl();
+ m_combochecklist_options->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
+ std::string options_items = GUI::into_u8(
+ get_option_type_string(OptionType::Travel) + "|0|" +
+ get_option_type_string(OptionType::Retractions) + "|0|" +
+ get_option_type_string(OptionType::Unretractions) + "|0|" +
+ get_option_type_string(OptionType::ToolChanges) + "|0|" +
+ get_option_type_string(OptionType::ColorChanges) + "|0|" +
+ get_option_type_string(OptionType::PausePrints) + "|0|" +
+ get_option_type_string(OptionType::CustomGCodes) + "|0|" +
+ get_option_type_string(OptionType::Shells) + "|0|" +
+ get_option_type_string(OptionType::ToolMarker) + "|0|" +
+ get_option_type_string(OptionType::Legend) + "|1"
+);
+ Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items);
+#else
m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel")));
m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions")));
m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions")));
m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells")));
m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend")));
m_checkbox_legend->SetValue(true);
-
+#endif // ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+ m_left_sizer = new wxBoxSizer(wxVERTICAL);
+ m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
+
+ wxBoxSizer* right_sizer = new wxBoxSizer(wxVERTICAL);
+ right_sizer->Add(m_layers_slider_sizer, 1, wxEXPAND, 0);
+
+ m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL);
+ m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView);
+
+ wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
+ bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
+ bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0);
+ // change the following number if editing the layout of the bottom toolbar sizer. It is used into update_bottom_toolbar()
+ m_combochecklist_features_pos = 6;
+ bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
+ bottom_toolbar_sizer->Hide(m_combochecklist_features);
+ bottom_toolbar_sizer->AddSpacer(5);
+ bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0);
+ m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer);
+
+ m_left_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0);
+ m_left_sizer->Hide(m_bottom_toolbar_panel);
+
+ wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
+ main_sizer->Add(m_left_sizer, 1, wxALL | wxEXPAND, 0);
+ main_sizer->Add(right_sizer, 0, wxALL | wxEXPAND, 0);
+#else
wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0);
@@ -276,7 +383,7 @@ bool Preview::init(wxWindow* parent, Model* model)
bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(10);
- bottom_sizer->Add(m_label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5);
+ bottom_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5);
bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(20);
bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5);
@@ -292,6 +399,7 @@ bool Preview::init(wxWindow* parent, Model* model)
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0);
main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0);
+#endif // ENABLE_GCODE_VIEWER
SetSizer(main_sizer);
SetMinSize(GetSize());
@@ -299,6 +407,7 @@ bool Preview::init(wxWindow* parent, Model* model)
bind_event_handlers();
+#if !ENABLE_GCODE_VIEWER
// sets colors for gcode preview extrusion roles
std::vector<std::string> extrusion_roles_colors = {
"Perimeter", "FFFF66",
@@ -316,6 +425,7 @@ bool Preview::init(wxWindow* parent, Model* model)
"Custom", "28CC94"
};
m_gcode_preview_data->set_extrusion_paths_colors(extrusion_roles_colors);
+#endif // !ENABLE_GCODE_VIEWER
return true;
}
@@ -345,17 +455,24 @@ void Preview::set_number_extruders(unsigned int number_extruders)
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
m_choice_view_type->SetSelection(type);
+#if ENABLE_GCODE_VIEWER
+ if ((0 <= type) && (type < static_cast<int>(GCodeViewer::EViewType::Count)))
+ m_canvas->set_gcode_view_preview_type(static_cast<GCodeViewer::EViewType>(type));
+#else
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
+#endif // ENABLE_GCODE_VIEWER
m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature";
}
}
+#if !ENABLE_GCODE_VIEWER
void Preview::set_enabled(bool enabled)
{
m_enabled = enabled;
}
+#endif // !ENABLE_GCODE_VIEWER
void Preview::bed_shape_changed()
{
@@ -382,6 +499,9 @@ void Preview::load_print(bool keep_z_range)
else if (tech == ptSLA)
load_print_as_sla();
+#if ENABLE_GCODE_VIEWER
+ update_bottom_toolbar();
+#endif // ENABLE_GCODE_VIEWER
Layout();
}
@@ -403,7 +523,9 @@ void Preview::reload_print(bool keep_volumes)
!keep_volumes)
{
m_canvas->reset_volumes();
+#if !ENABLE_GCODE_VIEWER
m_canvas->reset_legend_texture();
+#endif // !ENABLE_GCODE_VIEWER
m_loaded = false;
#ifdef __linux__
m_volumes_cleanup_required = false;
@@ -426,7 +548,12 @@ void Preview::refresh_print()
void Preview::msw_rescale()
{
// rescale slider
+#if ENABLE_GCODE_VIEWER
+ if (m_layers_slider != nullptr) m_layers_slider->msw_rescale();
+ if (m_moves_slider != nullptr) m_moves_slider->msw_rescale();
+#else
if (m_slider) m_slider->msw_rescale();
+#endif // ENABLE_GCODE_VIEWER
// rescale warning legend on the canvas
get_canvas3d()->msw_rescale();
@@ -435,28 +562,47 @@ void Preview::msw_rescale()
refresh_print();
}
+#if ENABLE_GCODE_VIEWER
+void Preview::move_layers_slider(wxKeyEvent& evt)
+{
+ if (m_layers_slider != nullptr) m_layers_slider->OnKeyDown(evt);
+}
+#else
void Preview::move_double_slider(wxKeyEvent& evt)
{
- if (m_slider)
+ if (m_slider)
m_slider->OnKeyDown(evt);
}
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+void Preview::edit_layers_slider(wxKeyEvent& evt)
+{
+ if (m_layers_slider != nullptr) m_layers_slider->OnChar(evt);
+}
+#else
void Preview::edit_double_slider(wxKeyEvent& evt)
{
- if (m_slider)
+ if (m_slider)
m_slider->OnChar(evt);
}
+#endif // ENABLE_GCODE_VIEWER
void Preview::bind_event_handlers()
{
this->Bind(wxEVT_SIZE, &Preview::on_size, this);
m_choice_view_type->Bind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
+#if ENABLE_GCODE_VIEWER
+ m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
+ m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
+#else
m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this);
+#endif // ENABLE_GCODE_VIEWER
}
void Preview::unbind_event_handlers()
@@ -464,19 +610,25 @@ void Preview::unbind_event_handlers()
this->Unbind(wxEVT_SIZE, &Preview::on_size, this);
m_choice_view_type->Unbind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
+#if ENABLE_GCODE_VIEWER
+ m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
+ m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
+#else
m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this);
+#endif // ENABLE_GCODE_VIEWER
}
+#if !ENABLE_GCODE_VIEWER
void Preview::show_hide_ui_elements(const std::string& what)
{
bool enable = (what == "full");
- m_label_show_features->Enable(enable);
+ m_label_show->Enable(enable);
m_combochecklist_features->Enable(enable);
- m_checkbox_travel->Enable(enable);
+ m_checkbox_travel->Enable(enable);
m_checkbox_retractions->Enable(enable);
m_checkbox_unretractions->Enable(enable);
m_checkbox_shells->Enable(enable);
@@ -487,7 +639,7 @@ void Preview::show_hide_ui_elements(const std::string& what)
m_choice_view_type->Enable(enable);
bool visible = (what != "none");
- m_label_show_features->Show(visible);
+ m_label_show->Show(visible);
m_combochecklist_features->Show(visible);
m_checkbox_travel->Show(visible);
m_checkbox_retractions->Show(visible);
@@ -497,26 +649,37 @@ void Preview::show_hide_ui_elements(const std::string& what)
m_label_view_type->Show(visible);
m_choice_view_type->Show(visible);
}
+#endif // !ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+void Preview::hide_layers_slider()
+{
+ m_layers_slider_sizer->Hide((size_t)0);
+ Layout();
+}
+#else
void Preview::reset_sliders(bool reset_all)
{
m_enabled = false;
-// reset_double_slider();
+ // reset_double_slider();
if (reset_all)
m_double_slider_sizer->Hide((size_t)0);
else
m_double_slider_sizer->GetItem(size_t(0))->GetSizer()->Hide(1);
}
+#endif // ENABLE_GCODE_VIEWER
+#if !ENABLE_GCODE_VIEWER
void Preview::update_sliders(const std::vector<double>& layers_z, bool keep_z_range)
{
m_enabled = true;
-
update_double_slider(layers_z, keep_z_range);
+
m_double_slider_sizer->Show((size_t)0);
Layout();
}
+#endif // !ENABLE_GCODE_VIEWER
void Preview::on_size(wxSizeEvent& evt)
{
@@ -528,19 +691,56 @@ void Preview::on_choice_view_type(wxCommandEvent& evt)
{
m_preferred_color_mode = (m_choice_view_type->GetStringSelection() == L("Tool")) ? "tool" : "feature";
int selection = m_choice_view_type->GetCurrentSelection();
+#if ENABLE_GCODE_VIEWER
+ if (0 <= selection && selection < static_cast<int>(GCodeViewer::EViewType::Count))
+ m_canvas->set_toolpath_view_type(static_cast<GCodeViewer::EViewType>(selection));
+
+ refresh_print();
+#else
if ((0 <= selection) && (selection < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)selection;
reload_print();
+#endif // ENABLE_GCODE_VIEWER
}
void Preview::on_combochecklist_features(wxCommandEvent& evt)
{
- int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features);
- m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags;
+ unsigned int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features);
+#if ENABLE_GCODE_VIEWER
+ m_canvas->set_toolpath_role_visibility_flags(flags);
+#else
+ m_gcode_preview_data->extrusion.role_flags = flags;
+#endif // ENABLE_GCODE_VIEWER
refresh_print();
}
+#if ENABLE_GCODE_VIEWER
+void Preview::on_combochecklist_options(wxCommandEvent& evt)
+{
+ auto xored = [](unsigned int flags1, unsigned int flags2, unsigned int flag) {
+ auto is_flag_set = [](unsigned int flags, unsigned int flag) {
+ return (flags & (1 << flag)) != 0;
+ };
+ return !is_flag_set(flags1, flag) != !is_flag_set(flags2, flag);
+ };
+
+ unsigned int curr_flags = m_canvas->get_gcode_options_visibility_flags();
+ unsigned int new_flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options);
+ if (curr_flags == new_flags)
+ return;
+
+ m_canvas->set_gcode_options_visibility_from_flags(new_flags);
+
+ bool skip_refresh = xored(curr_flags, new_flags, static_cast<unsigned int>(OptionType::Shells)) ||
+ xored(curr_flags, new_flags, static_cast<unsigned int>(OptionType::ToolMarker));
+
+ if (!skip_refresh)
+ refresh_print();
+ else
+ m_canvas->set_as_dirty();
+}
+#else
void Preview::on_checkbox_travel(wxCommandEvent& evt)
{
m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked();
@@ -572,8 +772,9 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt)
m_canvas->enable_legend_texture(m_checkbox_legend->IsChecked());
m_canvas_widget->Refresh();
}
+#endif // ENABLE_GCODE_VIEWER
-void Preview::update_view_type(bool slice_completed)
+void Preview::update_view_type(bool keep_volumes)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config;
@@ -587,34 +788,112 @@ void Preview::update_view_type(bool slice_completed)
int type = m_choice_view_type->FindString(choice);
if (m_choice_view_type->GetSelection() != type) {
m_choice_view_type->SetSelection(type);
+#if ENABLE_GCODE_VIEWER
+ if ((0 <= type) && (type < static_cast<int>(GCodeViewer::EViewType::Count)))
+ m_canvas->set_gcode_view_preview_type(static_cast<GCodeViewer::EViewType>(type));
+#else
if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types)
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
+#endif // ENABLE_GCODE_VIEWER
m_preferred_color_mode = "feature";
}
+#if ENABLE_GCODE_VIEWER
+ reload_print(keep_volumes);
+#else
reload_print();
+#endif // ENABLE_GCODE_VIEWER
+}
+
+#if ENABLE_GCODE_VIEWER
+void Preview::update_bottom_toolbar()
+{
+ combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags());
+ combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags());
+
+ // updates visibility of features combobox
+ if (m_bottom_toolbar_panel->IsShown())
+ {
+ wxSizer* sizer = m_bottom_toolbar_panel->GetSizer();
+ bool show = !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType;
+
+ if (show)
+ {
+ if (sizer->GetItem(m_combochecklist_features) == nullptr)
+ {
+ sizer->Insert(m_combochecklist_features_pos, m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
+ sizer->Show(m_combochecklist_features);
+ sizer->Layout();
+ Refresh();
+ }
+ }
+ else
+ {
+ if (sizer->GetItem(m_combochecklist_features) != nullptr)
+ {
+ sizer->Hide(m_combochecklist_features);
+ sizer->Detach(m_combochecklist_features);
+ sizer->Layout();
+ Refresh();
+ }
+ }
+ }
}
+#endif // ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+wxBoxSizer* Preview::create_layers_slider_sizer()
+{
+ wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
+ m_layers_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100);
+
+ m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA,
+ wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"));
+
+ sizer->Add(m_layers_slider, 0, wxEXPAND, 0);
+
+ // sizer, m_canvas_widget
+ m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_layers_slider_from_canvas, this);
+ m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) {
+ if (event.GetKeyCode() == WXK_SHIFT)
+ m_layers_slider->UseDefaultColors(true);
+ event.Skip();
+ });
+
+ m_layers_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_layers_slider_scroll_changed, this);
+
+ Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
+ Model& model = wxGetApp().plater()->model();
+ model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues();
+ m_schedule_background_process();
+ update_view_type(false);
+ });
+
+ return sizer;
+}
+#else
void Preview::create_double_slider()
{
m_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100);
+
bool sla_print_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA;
bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects");
m_slider->SetDrawMode(sla_print_technology, sequential_print);
m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0);
+
// sizer, m_canvas_widget
m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_double_slider_from_canvas, this);
m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) {
if (event.GetKeyCode() == WXK_SHIFT)
m_slider->UseDefaultColors(true);
event.Skip();
- });
+ });
m_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_sliders_scroll_changed, this);
-
Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
Model& model = wxGetApp().plater()->model();
model.custom_gcode_per_print_z = m_slider->GetTicksValues();
@@ -623,6 +902,7 @@ void Preview::create_double_slider()
update_view_type(false);
});
}
+#endif // ENABLE_GCODE_VIEWER
// Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
// Returns -1 if there is no such member.
@@ -651,8 +931,12 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double
return -1;
}
+#if ENABLE_GCODE_VIEWER
+void Preview::check_layers_slider_values(std::vector<CustomGCode::Item>& ticks_from_model, const std::vector<double>& layers_z)
+#else
void Preview::check_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
const std::vector<double>& layers_z)
+#endif // ENABLE_GCODE_VIEWER
{
// All ticks that would end up outside the slider range should be erased.
// TODO: this should be placed into more appropriate part of code,
@@ -669,12 +953,66 @@ void Preview::check_slider_values(std::vector<CustomGCode::Item>& ticks_from_mod
m_schedule_background_process();
}
-void Preview::update_double_slider(const std::vector<double>& layers_z, bool keep_z_range)
+#if ENABLE_GCODE_VIEWER
+void Preview::update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range)
+{
+ // Save the initial slider span.
+ double z_low = m_layers_slider->GetLowerValueD();
+ double z_high = m_layers_slider->GetHigherValueD();
+ bool was_empty = m_layers_slider->GetMaxValue() == 0;
+
+ bool force_sliders_full_range = was_empty;
+ if (!keep_z_range)
+ {
+ bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_layers_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/;
+ force_sliders_full_range |= span_changed;
+ }
+ bool snap_to_min = force_sliders_full_range || m_layers_slider->is_lower_at_min();
+ bool snap_to_max = force_sliders_full_range || m_layers_slider->is_higher_at_max();
+
+ // Detect and set manipulation mode for double slider
+ update_layers_slider_mode();
+
+ CustomGCode::Info& ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z;
+ check_layers_slider_values(ticks_info_from_model.gcodes, layers_z);
+
+ m_layers_slider->SetSliderValues(layers_z);
+ assert(m_layers_slider->GetMinValue() == 0);
+ m_layers_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1);
+
+ int idx_low = 0;
+ int idx_high = m_layers_slider->GetMaxValue();
+ if (!layers_z.empty()) {
+ if (!snap_to_min) {
+ int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/);
+ if (idx_new != -1)
+ idx_low = idx_new;
+ }
+ if (!snap_to_max) {
+ int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/);
+ if (idx_new != -1)
+ idx_high = idx_new;
+ }
+ }
+ m_layers_slider->SetSelectionSpan(idx_low, idx_high);
+ m_layers_slider->SetTicksValues(ticks_info_from_model);
+
+ bool sla_print_technology = wxGetApp().plater()->printer_technology() == ptSLA;
+ bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects");
+ m_layers_slider->SetDrawMode(sla_print_technology, sequential_print);
+ m_layers_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config());
+
+ m_layers_slider_sizer->Show((size_t)0);
+ Layout();
+}
+#else
+void Preview::update_double_slider(const std::vector<double> & layers_z, bool keep_z_range)
{
// Save the initial slider span.
double z_low = m_slider->GetLowerValueD();
double z_high = m_slider->GetHigherValueD();
bool was_empty = m_slider->GetMaxValue() == 0;
+
bool force_sliders_full_range = was_empty;
if (!keep_z_range)
{
@@ -682,27 +1020,27 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
force_sliders_full_range |= span_changed;
}
bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min();
- bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max();
+ bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max();
// Detect and set manipulation mode for double slider
update_double_slider_mode();
- CustomGCode::Info &ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z;
+ CustomGCode::Info& ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z;
check_slider_values(ticks_info_from_model.gcodes, layers_z);
m_slider->SetSliderValues(layers_z);
assert(m_slider->GetMinValue() == 0);
m_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1);
- int idx_low = 0;
+ int idx_low = 0;
int idx_high = m_slider->GetMaxValue();
- if (! layers_z.empty()) {
- if (! snap_to_min) {
+ if (!layers_z.empty()) {
+ if (!snap_to_min) {
int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/);
if (idx_new != -1)
idx_low = idx_new;
}
- if (! snap_to_max) {
+ if (!snap_to_max) {
int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/);
if (idx_new != -1)
idx_high = idx_new;
@@ -718,8 +1056,13 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
m_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config());
}
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+void Preview::update_layers_slider_mode()
+#else
void Preview::update_double_slider_mode()
+#endif // ENABLE_GCODE_VIEWER
{
// true -> single-extruder printer profile OR
// multi-extruder printer profile , but whole model is printed by only one extruder
@@ -768,16 +1111,70 @@ void Preview::update_double_slider_mode()
}
}
+#if ENABLE_GCODE_VIEWER
+ m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
+#else
m_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
+#endif // ENABLE_GCODE_VIEWER
}
+#if ENABLE_GCODE_VIEWER
+void Preview::reset_layers_slider()
+{
+ m_layers_slider->SetHigherValue(0);
+ m_layers_slider->SetLowerValue(0);
+}
+#else
void Preview::reset_double_slider()
{
m_slider->SetHigherValue(0);
m_slider->SetLowerValue(0);
}
+#endif // ENABLE_GCODE_VIEWER
-void Preview::update_double_slider_from_canvas(wxKeyEvent& event)
+#if ENABLE_GCODE_VIEWER
+void Preview::update_layers_slider_from_canvas(wxKeyEvent& event)
+{
+ if (event.HasModifiers()) {
+ event.Skip();
+ return;
+ }
+
+ const auto key = event.GetKeyCode();
+
+ if (key == 'U' || key == 'D') {
+ const int new_pos = key == 'U' ? m_layers_slider->GetHigherValue() + 1 : m_layers_slider->GetHigherValue() - 1;
+ m_layers_slider->SetHigherValue(new_pos);
+ if (event.ShiftDown() || m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
+ }
+ else if (key == 'S')
+ m_layers_slider->ChangeOneLayerLock();
+ else if (key == WXK_SHIFT)
+ m_layers_slider->UseDefaultColors(false);
+ else
+ event.Skip();
+}
+
+void Preview::update_moves_slider()
+{
+ const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view();
+ // this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan
+ if (view.endpoints.last < view.endpoints.first)
+ return;
+
+ std::vector<double> values(view.endpoints.last - view.endpoints.first + 1);
+ unsigned int count = 0;
+ for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i)
+ {
+ values[count++] = static_cast<double>(i + 1);
+ }
+
+ m_moves_slider->SetSliderValues(values);
+ m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first);
+ m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first);
+}
+#else
+void Preview::update_double_slider_from_canvas(wxKeyEvent & event)
{
if (event.HasModifiers()) {
event.Skip();
@@ -803,9 +1200,16 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event)
else
event.Skip();
}
+#endif // ENABLE_GCODE_VIEWER
void Preview::load_print_as_fff(bool keep_z_range)
{
+#if ENABLE_GCODE_VIEWER
+ if (wxGetApp().mainframe == nullptr)
+ // avoid proessing while mainframe is being constructed
+ return;
+#endif // ENABLE_GCODE_VIEWER
+
if (m_loaded || m_process->current_printer_technology() != ptFFF)
return;
@@ -829,10 +1233,21 @@ void Preview::load_print_as_fff(bool keep_z_range)
}
}
+#if ENABLE_GCODE_VIEWER
+ if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers)
+#else
if (! has_layers)
+#endif // ENABLE_GCODE_VIEWER
{
+#if ENABLE_GCODE_VIEWER
+ hide_layers_slider();
+ m_left_sizer->Hide(m_bottom_toolbar_panel);
+ m_left_sizer->Layout();
+ Refresh();
+#else
reset_sliders(true);
m_canvas->reset_legend_texture();
+#endif // ENABLE_GCODE_VIEWER
m_canvas_widget->Refresh();
return;
}
@@ -845,26 +1260,46 @@ void Preview::load_print_as_fff(bool keep_z_range)
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
m_choice_view_type->SetSelection(type);
+#if ENABLE_GCODE_VIEWER
+ if (0 <= type && type < static_cast<int>(GCodeViewer::EViewType::Count))
+ m_canvas->set_gcode_view_preview_type(static_cast<GCodeViewer::EViewType>(type));
+#else
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
+#endif // ENABLE_GCODE_VIEWER
// If the->SetSelection changed the following line, revert it to "decide yourself".
m_preferred_color_mode = "tool_or_feature";
}
+#if ENABLE_GCODE_VIEWER
+ GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type();
+ bool gcode_preview_data_valid = !m_gcode_result->moves.empty();
+#else
bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty();
+#endif // ENABLE_GCODE_VIEWER
// Collect colors per extruder.
std::vector<std::string> colors;
std::vector<CustomGCode::Item> color_print_values = {};
// set color print values, if it si selected "ColorPrint" view type
+#if ENABLE_GCODE_VIEWER
+ if (gcode_view_type == GCodeViewer::EViewType::ColorPrint)
+#else
if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)
+#endif // ENABLE_GCODE_VIEWER
{
colors = wxGetApp().plater()->get_colors_for_color_print();
+#if !ENABLE_GCODE_VIEWER
colors.push_back("#808080"); // gray color for pause print or custom G-code
+#endif // !ENABLE_GCODE_VIEWER
if (!gcode_preview_data_valid)
color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
}
+#if ENABLE_GCODE_VIEWER
+ else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool)
+#else
else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) )
+#endif // ENABLE_GCODE_VIEWER
{
colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
color_print_values.clear();
@@ -872,24 +1307,52 @@ void Preview::load_print_as_fff(bool keep_z_range)
if (IsShown())
{
+#if ENABLE_GCODE_VIEWER
+ std::vector<double> zs;
+#endif // ENABLE_GCODE_VIEWER
+
m_canvas->set_selected_extruder(0);
if (gcode_preview_data_valid) {
// Load the real G-code preview.
+#if ENABLE_GCODE_VIEWER
+ m_canvas->load_gcode_preview(*m_gcode_result);
+ m_canvas->refresh_gcode_preview(*m_gcode_result, colors);
+ m_left_sizer->Show(m_bottom_toolbar_panel);
+ m_left_sizer->Layout();
+ Refresh();
+ zs = m_canvas->get_gcode_layers_zs();
+#else
m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
+#endif // ENABLE_GCODE_VIEWER
m_loaded = true;
} else {
// Load the initial preview based on slices, not the final G-code.
m_canvas->load_preview(colors, color_print_values);
+#if ENABLE_GCODE_VIEWER
+ m_left_sizer->Hide(m_bottom_toolbar_panel);
+ m_left_sizer->Layout();
+ Refresh();
+ zs = m_canvas->get_volumes_print_zs(true);
+#endif // ENABLE_GCODE_VIEWER
}
+#if !ENABLE_GCODE_VIEWER
show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple");
- // recalculates zs and update sliders accordingly
std::vector<double> zs = m_canvas->get_current_print_zs(true);
+#endif // !ENABLE_GCODE_VIEWER
if (zs.empty()) {
// all layers filtered out
+#if ENABLE_GCODE_VIEWER
+ hide_layers_slider();
+#else
reset_sliders(true);
+#endif // ENABLE_GCODE_VIEWER
m_canvas_widget->Refresh();
} else
+#if ENABLE_GCODE_VIEWER
+ update_layers_slider(zs, keep_z_range);
+#else
update_sliders(zs, keep_z_range);
+#endif // ENABLE_GCODE_VIEWER
}
}
@@ -917,43 +1380,98 @@ void Preview::load_print_as_sla()
n_layers = (unsigned int)zs.size();
if (n_layers == 0)
{
+#if ENABLE_GCODE_VIEWER
+ hide_layers_slider();
+#else
reset_sliders(true);
+#endif // ENABLE_GCODE_VIEWER
m_canvas_widget->Refresh();
}
if (IsShown())
{
m_canvas->load_sla_preview();
+#if ENABLE_GCODE_VIEWER
+ m_left_sizer->Hide(m_bottom_toolbar_panel);
+ m_left_sizer->Hide(m_bottom_toolbar_panel);
+ m_left_sizer->Layout();
+ Refresh();
+#else
show_hide_ui_elements("none");
+#endif // ENABLE_GCODE_VIEWER
if (n_layers > 0)
+#if ENABLE_GCODE_VIEWER
+ update_layers_slider(zs);
+#else
update_sliders(zs);
+#endif // ENABLE_GCODE_VIEWER
m_loaded = true;
}
}
+#if ENABLE_GCODE_VIEWER
+void Preview::on_layers_slider_scroll_changed(wxCommandEvent& event)
+#else
void Preview::on_sliders_scroll_changed(wxCommandEvent& event)
+#endif // ENABLE_GCODE_VIEWER
{
if (IsShown())
{
PrinterTechnology tech = m_process->current_printer_technology();
if (tech == ptFFF)
{
+#if ENABLE_GCODE_VIEWER
+ m_canvas->set_toolpaths_z_range({ m_layers_slider->GetLowerValueD(), m_layers_slider->GetHigherValueD() });
+ m_canvas->set_as_dirty();
+#else
m_canvas->set_toolpaths_range(m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6);
m_canvas->render();
m_canvas->set_use_clipping_planes(false);
+#endif // ENABLE_GCODE_VIEWER
}
else if (tech == ptSLA)
{
+#if ENABLE_GCODE_VIEWER
+ m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_layers_slider->GetLowerValueD()));
+ m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_layers_slider->GetHigherValueD()));
+ m_canvas->set_use_clipping_planes(m_layers_slider->GetHigherValue() != 0);
+#else
m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD()));
m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD()));
m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0);
+#endif // ENABLE_GCODE_VIEWER
m_canvas->render();
}
}
}
+#if ENABLE_GCODE_VIEWER
+void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event)
+{
+ m_canvas->update_gcode_sequential_view_current(static_cast<unsigned int>(m_moves_slider->GetLowerValueD() - 1.0), static_cast<unsigned int>(m_moves_slider->GetHigherValueD() - 1.0));
+ m_canvas->render();
+}
+
+wxString Preview::get_option_type_string(OptionType type) const
+{
+ switch (type)
+ {
+ case OptionType::Travel: { return _L("Travel"); }
+ case OptionType::Retractions: { return _L("Retractions"); }
+ case OptionType::Unretractions: { return _L("Unretractions"); }
+ case OptionType::ToolChanges: { return _L("Tool changes"); }
+ case OptionType::ColorChanges: { return _L("Color changes"); }
+ case OptionType::PausePrints: { return _L("Pause prints"); }
+ case OptionType::CustomGCodes: { return _L("Custom GCodes"); }
+ case OptionType::Shells: { return _L("Shells"); }
+ case OptionType::ToolMarker: { return _L("Tool marker"); }
+ case OptionType::Legend: { return _L("Legend/Estimated printing time"); }
+ default: { return ""; }
+ }
+}
+#endif // ENABLE_GCODE_VIEWER
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp
index bbf2774b8..d9ce44bd6 100644
--- a/src/slic3r/GUI/GUI_Preview.hpp
+++ b/src/slic3r/GUI/GUI_Preview.hpp
@@ -7,7 +7,9 @@
#include "libslic3r/CustomGCode.hpp"
#include <string>
-
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+#endif // ENABLE_GCODE_VIEWER
class wxNotebook;
class wxGLCanvas;
@@ -23,7 +25,9 @@ namespace Slic3r {
class DynamicPrintConfig;
class Print;
class BackgroundSlicingProcess;
+#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
+#endif // !ENABLE_GCODE_VIEWER
class Model;
namespace DoubleSlider {
@@ -79,20 +83,35 @@ class Preview : public wxPanel
{
wxGLCanvas* m_canvas_widget;
GLCanvas3D* m_canvas;
+#if ENABLE_GCODE_VIEWER
+ wxBoxSizer* m_left_sizer;
+ wxBoxSizer* m_layers_slider_sizer;
+ wxPanel* m_bottom_toolbar_panel;
+#else
wxBoxSizer* m_double_slider_sizer;
+#endif // ENABLE_GCODE_VIEWER
wxStaticText* m_label_view_type;
wxChoice* m_choice_view_type;
- wxStaticText* m_label_show_features;
+ wxStaticText* m_label_show;
wxComboCtrl* m_combochecklist_features;
+#if ENABLE_GCODE_VIEWER
+ size_t m_combochecklist_features_pos;
+ wxComboCtrl* m_combochecklist_options;
+#else
wxCheckBox* m_checkbox_travel;
wxCheckBox* m_checkbox_retractions;
wxCheckBox* m_checkbox_unretractions;
wxCheckBox* m_checkbox_shells;
wxCheckBox* m_checkbox_legend;
+#endif // ENABLE_GCODE_VIEWER
DynamicPrintConfig* m_config;
BackgroundSlicingProcess* m_process;
+#if ENABLE_GCODE_VIEWER
+ GCodeProcessor::Result* m_gcode_result;
+#else
GCodePreviewData* m_gcode_preview_data;
+#endif // ENABLE_GCODE_VIEWER
#ifdef __linux__
// We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955.
@@ -107,13 +126,39 @@ class Preview : public wxPanel
std::string m_preferred_color_mode;
bool m_loaded;
+#if !ENABLE_GCODE_VIEWER
bool m_enabled;
+#endif // !ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+ DoubleSlider::Control* m_layers_slider{ nullptr };
+ DoubleSlider::Control* m_moves_slider{ nullptr };
+#else
DoubleSlider::Control* m_slider {nullptr};
+#endif // ENABLE_GCODE_VIEWER
public:
- Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
+#if ENABLE_GCODE_VIEWER
+ enum class OptionType : unsigned int
+ {
+ Travel,
+ Retractions,
+ Unretractions,
+ ToolChanges,
+ ColorChanges,
+ PausePrints,
+ CustomGCodes,
+ Shells,
+ ToolMarker,
+ Legend
+ };
+
+Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process,
+ GCodeProcessor::Result* gcode_result, std::function<void()> schedule_background_process = []() {});
+#else
+Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = []() {});
+#endif // ENABLE_GCODE_VIEWER
virtual ~Preview();
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
@@ -122,7 +167,9 @@ public:
void set_as_dirty();
void set_number_extruders(unsigned int number_extruders);
+#if !ENABLE_GCODE_VIEWER
void set_enabled(bool enabled);
+#endif // !ENABLE_GCODE_VIEWER
void bed_shape_changed();
void select_view(const std::string& direction);
void set_drop_target(wxDropTarget* target);
@@ -132,47 +179,83 @@ public:
void refresh_print();
void msw_rescale();
+#if ENABLE_GCODE_VIEWER
+ void move_layers_slider(wxKeyEvent& evt);
+ void edit_layers_slider(wxKeyEvent& evt);
+#else
void move_double_slider(wxKeyEvent& evt);
void edit_double_slider(wxKeyEvent& evt);
+#endif // ENABLE_GCODE_VIEWER
- void update_view_type(bool slice_completed);
+ void update_view_type(bool keep_volumes);
bool is_loaded() const { return m_loaded; }
+#if ENABLE_GCODE_VIEWER
+ void update_bottom_toolbar();
+ void update_moves_slider();
+#endif // ENABLE_GCODE_VIEWER
+
private:
bool init(wxWindow* parent, Model* model);
void bind_event_handlers();
void unbind_event_handlers();
+#if ENABLE_GCODE_VIEWER
+ void hide_layers_slider();
+#else
void show_hide_ui_elements(const std::string& what);
void reset_sliders(bool reset_all);
void update_sliders(const std::vector<double>& layers_z, bool keep_z_range = false);
+#endif // ENABLE_GCODE_VIEWER
void on_size(wxSizeEvent& evt);
void on_choice_view_type(wxCommandEvent& evt);
void on_combochecklist_features(wxCommandEvent& evt);
+#if ENABLE_GCODE_VIEWER
+ void on_combochecklist_options(wxCommandEvent& evt);
+#else
void on_checkbox_travel(wxCommandEvent& evt);
void on_checkbox_retractions(wxCommandEvent& evt);
void on_checkbox_unretractions(wxCommandEvent& evt);
void on_checkbox_shells(wxCommandEvent& evt);
void on_checkbox_legend(wxCommandEvent& evt);
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+ // Create/Update/Reset double slider on 3dPreview
+ wxBoxSizer* create_layers_slider_sizer();
+ void check_layers_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
+ const std::vector<double>& layers_z);
+ void reset_layers_slider();
+ void update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
+ void update_layers_slider_mode();
+ // update vertical DoubleSlider after keyDown in canvas
+ void update_layers_slider_from_canvas(wxKeyEvent& event);
+#else
// Create/Update/Reset double slider on 3dPreview
void create_double_slider();
- void check_slider_values(std::vector<CustomGCode::Item> &ticks_from_model,
- const std::vector<double> &layers_z);
+ void check_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
+ const std::vector<double>& layers_z);
void reset_double_slider();
void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
void update_double_slider_mode();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
+#endif // ENABLE_GCODE_VIEWER
void load_print_as_fff(bool keep_z_range = false);
void load_print_as_sla();
+#if ENABLE_GCODE_VIEWER
+ void on_layers_slider_scroll_changed(wxCommandEvent& event);
+ void on_moves_slider_scroll_changed(wxCommandEvent& event);
+ wxString get_option_type_string(OptionType type) const;
+#else
void on_sliders_scroll_changed(wxCommandEvent& event);
+#endif // ENABLE_GCODE_VIEWER
};
} // namespace GUI
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index 6ce3f62a6..96b24524c 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -387,6 +387,15 @@ public:
std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics);
+#if ENABLE_GCODE_VIEWER
+inline int hex_digit_to_int(const char c)
+{
+ return
+ (c >= '0' && c <= '9') ? int(c - '0') :
+ (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+ (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+}
+#endif // ENABLE_GCODE_VIEWER
}}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index e5a648d11..bd52e9ec6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -535,8 +535,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
window_width = std::max(window_width, button_width);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
- static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
- m_imgui->text_colored(ORANGE, caption);
+ m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
ImGui::SameLine(caption_max);
m_imgui->text(text);
};
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index 104680f50..7c2754502 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -50,6 +50,15 @@ static const std::map<const char, std::string> font_icons = {
{ImGui::ErrorMarker , "flag_red" }
};
+const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f };
+const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f };
+const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f };
+const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f };
+const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f };
+const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = { 0.233f, 0.233f, 0.233f, 1.0f };
+const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = { 0.433f, 0.433f, 0.433f, 1.8f };
+const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED;
+
ImGuiWrapper::ImGuiWrapper()
: m_glyph_ranges(nullptr)
, m_font_cjk(false)
@@ -792,6 +801,12 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co
check_box(_L("Search in English"), view_params.english);
}
+void ImGuiWrapper::title(const std::string& str)
+{
+ text(str);
+ ImGui::Separator();
+}
+
void ImGuiWrapper::disabled_begin(bool disabled)
{
wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");
@@ -1011,23 +1026,13 @@ void ImGuiWrapper::init_style()
{
ImGuiStyle &style = ImGui::GetStyle();
- auto set_color = [&](ImGuiCol_ col, unsigned hex_color) {
- style.Colors[col] = ImVec4(
- ((hex_color >> 24) & 0xff) / 255.0f,
- ((hex_color >> 16) & 0xff) / 255.0f,
- ((hex_color >> 8) & 0xff) / 255.0f,
- (hex_color & 0xff) / 255.0f);
+ auto set_color = [&](ImGuiCol_ entity, ImVec4 color) {
+ style.Colors[entity] = color;
};
- static const unsigned COL_WINDOW_BACKGROND = 0x222222cc;
- static const unsigned COL_GREY_DARK = 0x555555ff;
- static const unsigned COL_GREY_LIGHT = 0x666666ff;
- static const unsigned COL_ORANGE_DARK = 0xc16737ff;
- static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff;
-
// Window
style.WindowRounding = 4.0f;
- set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND);
+ set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROUND);
set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK);
// Generics
@@ -1039,9 +1044,9 @@ void ImGuiWrapper::init_style()
set_color(ImGuiCol_TextSelectedBg, COL_ORANGE_DARK);
// Buttons
- set_color(ImGuiCol_Button, COL_ORANGE_DARK);
- set_color(ImGuiCol_ButtonHovered, COL_ORANGE_LIGHT);
- set_color(ImGuiCol_ButtonActive, COL_ORANGE_LIGHT);
+ set_color(ImGuiCol_Button, COL_BUTTON_BACKGROUND);
+ set_color(ImGuiCol_ButtonHovered, COL_BUTTON_HOVERED);
+ set_color(ImGuiCol_ButtonActive, COL_BUTTON_ACTIVE);
// Checkbox
set_color(ImGuiCol_CheckMark, COL_ORANGE_LIGHT);
@@ -1057,6 +1062,13 @@ void ImGuiWrapper::init_style()
// Separator
set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT);
+
+ // Tabs
+ set_color(ImGuiCol_Tab, COL_ORANGE_DARK);
+ set_color(ImGuiCol_TabHovered, COL_ORANGE_LIGHT);
+ set_color(ImGuiCol_TabActive, COL_ORANGE_LIGHT);
+ set_color(ImGuiCol_TabUnfocused, COL_GREY_DARK);
+ set_color(ImGuiCol_TabUnfocusedActive, COL_GREY_LIGHT);
}
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index ee553c4b6..5484e46c6 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -86,6 +86,7 @@ public:
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel);
void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str,
Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized);
+ void title(const std::string& str);
void disabled_begin(bool disabled);
void disabled_end();
@@ -95,6 +96,15 @@ public:
bool want_text_input() const;
bool want_any_input() const;
+ static const ImVec4 COL_GREY_DARK;
+ static const ImVec4 COL_GREY_LIGHT;
+ static const ImVec4 COL_ORANGE_DARK;
+ static const ImVec4 COL_ORANGE_LIGHT;
+ static const ImVec4 COL_WINDOW_BACKGROUND;
+ static const ImVec4 COL_BUTTON_BACKGROUND;
+ static const ImVec4 COL_BUTTON_HOVERED;
+ static const ImVec4 COL_BUTTON_ACTIVE;
+
private:
void init_font(bool compress);
void init_input();
diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp
index 556b610e9..1eceea22e 100644
--- a/src/slic3r/GUI/KBShortcutsDialog.cpp
+++ b/src/slic3r/GUI/KBShortcutsDialog.cpp
@@ -1,3 +1,4 @@
+#include "libslic3r/libslic3r.h"
#include "KBShortcutsDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
@@ -6,6 +7,9 @@
#include <wx/display.h>
#include "GUI_App.hpp"
#include "wxExtensions.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "MainFrame.hpp"
+#endif // ENABLE_GCODE_VIEWER
#define NOTEBOOK_TOP 1
#define NOTEBOOK_LEFT 2
@@ -29,12 +33,8 @@ namespace Slic3r {
namespace GUI {
KBShortcutsDialog::KBShortcutsDialog()
- : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")),
-#if ENABLE_SCROLLABLE
+ : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
-#else
- wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
-#endif // ENABLE_SCROLLABLE
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@@ -65,13 +65,9 @@ main_sizer->Add(book, 1, wxEXPAND | wxALL, 10);
fill_shortcuts();
for (size_t i = 0; i < m_full_shortcuts.size(); ++i)
{
-#if ENABLE_SCROLLABLE
wxPanel* page = create_page(book, m_full_shortcuts[i], font, bold_font);
m_pages.push_back(page);
book->AddPage(page, m_full_shortcuts[i].first, i == 0);
-#else
- book->AddPage(create_page(book, m_full_shortcuts[i], font, bold_font), m_full_shortcuts[i].first, i == 0);
-#endif // ENABLE_SCROLLABLE
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
@@ -98,114 +94,122 @@ void KBShortcutsDialog::fill_shortcuts()
const std::string& ctrl = GUI::shortkey_ctrl_prefix();
const std::string& alt = GUI::shortkey_alt_prefix();
- Shortcuts commands_shortcuts = {
- // File
- { ctrl + "N", L("New project, clear plater") },
- { ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") },
- { ctrl + "S", L("Save project (3mf)") },
- { ctrl + alt + "S", L("Save project as (3mf)") },
- { ctrl + "R", L("(Re)slice") },
- // File>Import
- { ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") },
- { ctrl + "L", L("Import Config from ini/amf/3mf/gcode") },
- { ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") },
- // File>Export
- { ctrl + "G", L("Export G-code") },
- { ctrl + "Shift+" + "G", L("Send G-code") },
- { ctrl + "E", L("Export config") },
- { ctrl + "U", L("Export to SD card / Flash drive") },
- { ctrl + "T", L("Eject SD card / Flash drive") },
- // Edit
- { ctrl + "A", L("Select all objects") },
- { "Esc", L("Deselect all") },
- { "Del", L("Delete selected") },
- { ctrl + "Del", L("Delete all") },
- { ctrl + "Z", L("Undo") },
- { ctrl + "Y", L("Redo") },
- { ctrl + "C", L("Copy to clipboard") },
- { ctrl + "V", L("Paste from clipboard") },
- { "F5", L("Reload plater from disk") },
- { ctrl + "F", L("Search") },
- // Window
- { ctrl + "1", L("Select Plater Tab") },
- { ctrl + "2", L("Select Print Settings Tab") },
- { ctrl + "3", L("Select Filament Settings Tab") },
- { ctrl + "4", L("Select Printer Settings Tab") },
- { ctrl + "5", L("Switch to 3D") },
- { ctrl + "6", L("Switch to Preview") },
- { ctrl + "J", L("Print host upload queue") },
- // View
- { "0-6", L("Camera view") },
- { "E", L("Show/Hide object/instance labels") },
+#if ENABLE_GCODE_VIEWER
+ bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer;
+
+ if (!is_gcode_viewer) {
+#endif // ENABLE_GCODE_VIEWER
+ Shortcuts commands_shortcuts = {
+ // File
+ { ctrl + "N", L("New project, clear plater") },
+ { ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") },
+ { ctrl + "S", L("Save project (3mf)") },
+ { ctrl + alt + "S", L("Save project as (3mf)") },
+ { ctrl + "R", L("(Re)slice") },
+ // File>Import
+ { ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") },
+ { ctrl + "L", L("Import Config from ini/amf/3mf/gcode") },
+ { ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") },
+ // File>Export
+ { ctrl + "G", L("Export G-code") },
+ { ctrl + "Shift+" + "G", L("Send G-code") },
+ { ctrl + "E", L("Export config") },
+ { ctrl + "U", L("Export to SD card / Flash drive") },
+ { ctrl + "T", L("Eject SD card / Flash drive") },
+ // Edit
+ { ctrl + "A", L("Select all objects") },
+ { "Esc", L("Deselect all") },
+ { "Del", L("Delete selected") },
+ { ctrl + "Del", L("Delete all") },
+ { ctrl + "Z", L("Undo") },
+ { ctrl + "Y", L("Redo") },
+ { ctrl + "C", L("Copy to clipboard") },
+ { ctrl + "V", L("Paste from clipboard") },
+ { "F5", L("Reload plater from disk") },
+ { ctrl + "F", L("Search") },
+ // Window
+ { ctrl + "1", L("Select Plater Tab") },
+ { ctrl + "2", L("Select Print Settings Tab") },
+ { ctrl + "3", L("Select Filament Settings Tab") },
+ { ctrl + "4", L("Select Printer Settings Tab") },
+ { ctrl + "5", L("Switch to 3D") },
+ { ctrl + "6", L("Switch to Preview") },
+ { ctrl + "J", L("Print host upload queue") },
+ // View
+ { "0-6", L("Camera view") },
+ { "E", L("Show/Hide object/instance labels") },
#if ENABLE_SLOPE_RENDERING
- { "D", L("Turn On/Off facets' slope rendering") },
+ { "D", L("Turn On/Off facets' slope rendering") },
#endif // ENABLE_SLOPE_RENDERING
- // Configuration
- { ctrl + "P", L("Preferences") },
- // Help
- { "?", L("Show keyboard shortcuts list") }
- };
-
- m_full_shortcuts.push_back(std::make_pair(_(L("Commands")), commands_shortcuts));
-
- Shortcuts plater_shortcuts = {
- { "A", L("Arrange") },
- { "Shift+A", L("Arrange selection") },
- { "+", L("Add Instance of the selected object") },
- { "-", L("Remove Instance of the selected object") },
- { ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") },
- { "Shift+", L("Press to activate selection rectangle") },
- { alt, L("Press to activate deselection rectangle") },
- { L("Arrow Up"), L("Move selection 10 mm in positive Y direction") },
- { L("Arrow Down"), L("Move selection 10 mm in negative Y direction") },
- { L("Arrow Left"), L("Move selection 10 mm in negative X direction") },
- { L("Arrow Right"), L("Move selection 10 mm in positive X direction") },
- { std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") },
- { ctrl + L("Any arrow"), L("Movement in camera space") },
- { L("Page Up"), L("Rotate selection 45 degrees CCW") },
- { L("Page Down"), L("Rotate selection 45 degrees CW") },
- { "M", L("Gizmo move") },
- { "S", L("Gizmo scale") },
- { "R", L("Gizmo rotate") },
- { "C", L("Gizmo cut") },
- { "F", L("Gizmo Place face on bed") },
- { "H", L("Gizmo SLA hollow") },
- { "L", L("Gizmo SLA support points") },
- { "Esc", L("Unselect gizmo or clear selection") },
- { "K", L("Change camera type (perspective, orthographic)") },
- { "B", L("Zoom to Bed") },
- { "Z", L("Zoom to selected object\nor all objects in scene, if none selected") },
- { "I", L("Zoom in") },
- { "O", L("Zoom out") },
+ // Configuration
+ { ctrl + "P", L("Preferences") },
+ // Help
+ { "?", L("Show keyboard shortcuts list") }
+ };
+
+ m_full_shortcuts.push_back(std::make_pair(_L("Commands"), commands_shortcuts));
+
+ Shortcuts plater_shortcuts = {
+ { "A", L("Arrange") },
+ { "Shift+A", L("Arrange selection") },
+ { "+", L("Add Instance of the selected object") },
+ { "-", L("Remove Instance of the selected object") },
+ { ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") },
+ { "Shift+", L("Press to activate selection rectangle") },
+ { alt, L("Press to activate deselection rectangle") },
+ { L("Arrow Up"), L("Move selection 10 mm in positive Y direction") },
+ { L("Arrow Down"), L("Move selection 10 mm in negative Y direction") },
+ { L("Arrow Left"), L("Move selection 10 mm in negative X direction") },
+ { L("Arrow Right"), L("Move selection 10 mm in positive X direction") },
+ { std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") },
+ { ctrl + L("Any arrow"), L("Movement in camera space") },
+ { L("Page Up"), L("Rotate selection 45 degrees CCW") },
+ { L("Page Down"), L("Rotate selection 45 degrees CW") },
+ { "M", L("Gizmo move") },
+ { "S", L("Gizmo scale") },
+ { "R", L("Gizmo rotate") },
+ { "C", L("Gizmo cut") },
+ { "F", L("Gizmo Place face on bed") },
+ { "H", L("Gizmo SLA hollow") },
+ { "L", L("Gizmo SLA support points") },
+ { "Esc", L("Unselect gizmo or clear selection") },
+ { "K", L("Change camera type (perspective, orthographic)") },
+ { "B", L("Zoom to Bed") },
+ { "Z", L("Zoom to selected object\nor all objects in scene, if none selected") },
+ { "I", L("Zoom in") },
+ { "O", L("Zoom out") },
#ifdef __linux__
- { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
+ { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
#endif // __linux__
#if ENABLE_RENDER_PICKING_PASS
- // Don't localize debugging texts.
- { "T", "Toggle picking pass texture rendering on/off" },
+ // Don't localize debugging texts.
+ { "P", "Toggle picking pass texture rendering on/off" },
#endif // ENABLE_RENDER_PICKING_PASS
- };
+ };
- m_full_shortcuts.push_back(std::make_pair(_(L("Plater")), plater_shortcuts));
+ m_full_shortcuts.push_back(std::make_pair(_L("Plater"), plater_shortcuts));
- Shortcuts gizmos_shortcuts = {
- { "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") },
- { "F", L("Scale selection to fit print volume\nin Gizmo scale") },
- { ctrl, L("Press to activate one direction scaling in Gizmo scale") },
- { alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") },
- };
+ Shortcuts gizmos_shortcuts = {
+ { "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") },
+ { "F", L("Scale selection to fit print volume\nin Gizmo scale") },
+ { ctrl, L("Press to activate one direction scaling in Gizmo scale") },
+ { alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") },
+ };
- m_full_shortcuts.push_back(std::make_pair(_(L("Gizmos")), gizmos_shortcuts));
+ m_full_shortcuts.push_back(std::make_pair(_L("Gizmos"), gizmos_shortcuts));
+#if ENABLE_GCODE_VIEWER
+ }
+#endif // ENABLE_GCODE_VIEWER
Shortcuts preview_shortcuts = {
{ L("Arrow Up"), L("Upper Layer") },
{ L("Arrow Down"), L("Lower Layer") },
{ "U", L("Upper Layer") },
{ "D", L("Lower Layer") },
- { "L", L("Show/Hide Legend") }
+ { "L", L("Show/Hide Legend/Estimated printing time") },
};
- m_full_shortcuts.push_back(std::make_pair(_(L("Preview")), preview_shortcuts));
+ m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts));
Shortcuts layers_slider_shortcuts = {
{ L("Arrow Up"), L("Move current slider thumb Up") },
@@ -213,10 +217,23 @@ void KBShortcutsDialog::fill_shortcuts()
{ L("Arrow Left"), L("Set upper thumb to current slider thumb") },
{ L("Arrow Right"), L("Set lower thumb to current slider thumb") },
{ "+", L("Add color change marker for current layer") },
- { "-", L("Delete color change marker for current layer") }
+ { "-", L("Delete color change marker for current layer") },
+ { "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
+ { ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
};
- m_full_shortcuts.push_back(std::make_pair(_(L("Layers Slider")), layers_slider_shortcuts));
+ m_full_shortcuts.push_back(std::make_pair(_L("Layers Slider"), layers_slider_shortcuts));
+
+#if ENABLE_GCODE_VIEWER
+ Shortcuts sequential_slider_shortcuts = {
+ { L("Arrow Left"), L("Move current slider thumb Left") },
+ { L("Arrow Right"), L("Move current slider thumb Right") },
+ { "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
+ { ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
+ };
+
+ m_full_shortcuts.push_back(std::make_pair(_L("Sequential Slider"), sequential_slider_shortcuts));
+#endif // ENABLE_GCODE_VIEWER
}
wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_font)
@@ -239,7 +256,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f
sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
// text
- wxStaticText* text = new wxStaticText(panel, wxID_ANY, _(L("Keyboard shortcuts")));
+ wxStaticText* text = new wxStaticText(panel, wxID_ANY, _L("Keyboard shortcuts"));
text->SetFont(header_font);
sizer->Add(text, 0, wxALIGN_CENTER_VERTICAL);
@@ -254,13 +271,9 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const std::pair<wxStri
static const int max_items_per_column = 20;
int columns_count = 1 + (int)shortcuts.second.size() / max_items_per_column;
-#if ENABLE_SCROLLABLE
wxScrolledWindow* page = new wxScrolledWindow(parent);
page->SetScrollbars(20, 20, 50, 50);
page->SetInitialSize(wxSize(850, 450));
-#else
- wxPanel* page = new wxPanel(parent);
-#endif // ENABLE_SCROLLABLE
#if (BOOK_TYPE == LISTBOOK_TOP) || (BOOK_TYPE == LISTBOOK_LEFT)
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, page, " " + shortcuts.first + " ");
diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp
index 70820ae77..a8ec4e426 100644
--- a/src/slic3r/GUI/KBShortcutsDialog.hpp
+++ b/src/slic3r/GUI/KBShortcutsDialog.hpp
@@ -8,8 +8,6 @@
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
-#define ENABLE_SCROLLABLE 1
-
namespace Slic3r {
namespace GUI {
@@ -22,9 +20,7 @@ class KBShortcutsDialog : public DPIDialog
ShortcutsVec m_full_shortcuts;
ScalableBitmap m_logo_bmp;
wxStaticBitmap* m_header_bitmap;
-#if ENABLE_SCROLLABLE
std::vector<wxPanel*> m_pages;
-#endif // ENABLE_SCROLLABLE
public:
KBShortcutsDialog();
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 8f2aeef8a..886e96e1a 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -92,16 +92,41 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
#endif
// Font is already set in DPIFrame constructor
*/
- // Load the icon either from the exe, or from the ico file.
-#if _WIN32
- {
- TCHAR szExeFileName[MAX_PATH];
- GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
- SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
- }
+
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+ if (wxTaskBarIcon::IsAvailable()) {
+#if defined(__WXOSX__) && wxOSX_USE_COCOA
+ m_taskbar_icon = new wxTaskBarIcon(wxTBI_DOCK);
#else
+ m_taskbar_icon = new wxTaskBarIcon();
+#endif
+ m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer");
+
+ m_taskbar_icon->Bind(wxEVT_TASKBAR_CLICK, [this](wxTaskBarIconEvent& evt) {
+ wxString msg = _L("You pressed the icon in taskbar for ") + "\n";
+ if (m_mode == EMode::Editor)
+ msg += wxString(SLIC3R_APP_NAME);
+ else
+ msg += wxString(SLIC3R_APP_NAME) + "-GCode viewer";
+
+ wxMessageDialog dialog(nullptr, msg, _("Taskbar icon clicked"), wxOK);
+ dialog.ShowModal();
+ });
+ }
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
+
SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
-#endif // _WIN32
+// // Load the icon either from the exe, or from the ico file.
+//#if _WIN32
+// {
+//
+// TCHAR szExeFileName[MAX_PATH];
+// GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
+// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
+// }
+//#else
+// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
+//#endif // _WIN32
// initialize status bar
m_statusbar = std::make_shared<ProgressStatusBar>(this);
@@ -113,7 +138,25 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
// initialize tabpanel and menubar
init_tabpanel();
+#if ENABLE_GCODE_VIEWER
+ init_editor_menubar();
+ init_gcodeviewer_menubar();
+
+#if _WIN32
+ // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
+ wxAcceleratorEntry entries[6];
+ entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1);
+ entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2);
+ entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3);
+ entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4);
+ entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5);
+ entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6);
+ wxAcceleratorTable accel(6, entries);
+ SetAcceleratorTable(accel);
+#endif // _WIN32
+#else
init_menubar();
+#endif // ENABLE_GCODE_VIEWER
// set default tooltip timer in msec
// SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
@@ -226,10 +269,21 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
update_ui_from_settings(); // FIXME (?)
- if (m_plater != nullptr)
+ if (m_plater != nullptr) {
+#if ENABLE_GCODE_VIEWER
+ m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1");
+#endif // ENABLE_GCODE_VIEWER
m_plater->show_action_buttons(true);
+ }
}
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+MainFrame::~MainFrame()
+{
+ delete m_taskbar_icon;
+}
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
+
void MainFrame::update_layout()
{
auto restore_to_creation = [this]() {
@@ -271,9 +325,16 @@ void MainFrame::update_layout()
Layout();
};
+#if ENABLE_GCODE_VIEWER
+ ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer :
+ (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old :
+ wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New :
+ wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old);
+#else
ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old :
wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New :
wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old;
+#endif // ENABLE_GCODE_VIEWER
if (m_layout == layout)
return;
@@ -294,6 +355,10 @@ void MainFrame::update_layout()
// Set new settings
switch (m_layout)
{
+ case ESettingsLayout::Unknown:
+ {
+ break;
+ }
case ESettingsLayout::Old:
{
m_plater->Reparent(m_tabpanel);
@@ -325,6 +390,14 @@ void MainFrame::update_layout()
m_plater->Show();
break;
}
+#if ENABLE_GCODE_VIEWER
+ case ESettingsLayout::GCodeViewer:
+ {
+ m_main_sizer->Add(m_plater, 1, wxEXPAND);
+ m_plater->Show();
+ break;
+ }
+#endif // ENABLE_GCODE_VIEWER
}
//#ifdef __APPLE__
@@ -359,6 +432,20 @@ void MainFrame::shutdown()
}
#endif // _WIN32
+#if ENABLE_GCODE_VIEWER
+ if (m_plater != nullptr) {
+ m_plater->stop_jobs();
+
+ // Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
+ // when closing the application using Command+Q, a mouse event is triggered after this lambda is completed,
+ // causing a crash
+ m_plater->unbind_canvas_event_handlers();
+
+ // Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours
+ // see: https://github.com/prusa3d/PrusaSlicer/issues/3964
+ m_plater->reset_canvas_volumes();
+ }
+#else
if (m_plater)
m_plater->stop_jobs();
@@ -370,6 +457,7 @@ void MainFrame::shutdown()
// Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours
// see: https://github.com/prusa3d/PrusaSlicer/issues/3964
if (m_plater) m_plater->reset_canvas_volumes();
+#endif // ENABLE_GCODE_VIEWER
// Weird things happen as the Paint messages are floating around the windows being destructed.
// Avoid the Paint messages by hiding the main window.
@@ -381,11 +469,22 @@ void MainFrame::shutdown()
// call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
m_settings_dialog.Close();
- // Stop the background thread (Windows and Linux).
- // Disconnect from a 3DConnextion driver (OSX).
- m_plater->get_mouse3d_controller().shutdown();
- // Store the device parameter database back to appconfig.
- m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config);
+ if (m_plater != nullptr) {
+#if ENABLE_GCODE_VIEWER
+ // restore sidebar if it was hidden when switching to gcode viewer mode
+ if (m_restore_from_gcode_viewer.collapsed_sidebar)
+ m_plater->collapse_sidebar(false);
+
+ // restore sla printer if it was deselected when switching to gcode viewer mode
+ if (m_restore_from_gcode_viewer.sla_technology)
+ m_plater->set_printer_technology(ptSLA);
+#endif // ENABLE_GCODE_VIEWER
+ // Stop the background thread (Windows and Linux).
+ // Disconnect from a 3DConnextion driver (OSX).
+ m_plater->get_mouse3d_controller().shutdown();
+ // Store the device parameter database back to appconfig.
+ m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config);
+ }
// Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
wxGetApp().removable_drive_manager()->shutdown();
@@ -713,7 +812,81 @@ void MainFrame::on_sys_color_changed()
msw_rescale_menu(menu_bar->GetMenu(id));
}
+#if ENABLE_GCODE_VIEWER
+#ifdef _MSC_VER
+ // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
+ // as the simple numeric accelerators spoil all numeric data entry.
+static const wxString sep = "\t\xA0";
+static const wxString sep_space = "\xA0";
+#else
+static const wxString sep = " - ";
+static const wxString sep_space = "";
+#endif
+
+static wxMenu* generate_help_menu()
+{
+ wxMenu* helpMenu = new wxMenu();
+ append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
+ [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
+ append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")),
+ [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); });
+//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
+//# wxTheApp->check_version(1);
+//# });
+//# $versioncheck->Enable(wxTheApp->have_version_check);
+ append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME),
+ wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME),
+ [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
+// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME),
+// wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME),
+// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
+ helpMenu->AppendSeparator();
+ append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
+ [](wxCommandEvent&) { wxGetApp().system_info(); });
+ append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
+ [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
+ append_menu_item(helpMenu, wxID_ANY, _(L"Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
+ [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
+ append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
+ [](wxCommandEvent&) { Slic3r::GUI::about(); });
+ helpMenu->AppendSeparator();
+ append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
+ [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
+#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
+ helpMenu->AppendSeparator();
+ append_menu_item(helpMenu, wxID_ANY, "DEBUG gcode thumbnails", "DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails",
+ [](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
+#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
+
+ return helpMenu;
+}
+
+static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, std::function<bool(void)> can_change_view)
+{
+ // The camera control accelerators are captured by GLCanvas3D::on_char().
+ append_menu_item(view_menu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("iso"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ view_menu->AppendSeparator();
+ //TRN To be shown in the main menu View->Top
+ append_menu_item(view_menu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ //TRN To be shown in the main menu View->Bottom
+ append_menu_item(view_menu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ append_menu_item(view_menu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ append_menu_item(view_menu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ append_menu_item(view_menu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("left"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+ append_menu_item(view_menu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("right"); },
+ "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
+}
+
+void MainFrame::init_editor_menubar()
+#else
void MainFrame::init_menubar()
+#endif // ENABLE_GCODE_VIEWER
{
#ifdef __APPLE__
wxMenuBar::SetAutoWindowMenu(false);
@@ -722,15 +895,15 @@ void MainFrame::init_menubar()
// File menu
wxMenu* fileMenu = new wxMenu;
{
- append_menu_item(fileMenu, wxID_ANY, _(L("&New Project")) + "\tCtrl+N", _(L("Start a new project")),
+ append_menu_item(fileMenu, wxID_ANY, _L("&New Project") + "\tCtrl+N", _L("Start a new project"),
[this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr,
[this](){return m_plater != nullptr && can_start_new_project(); }, this);
- append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")),
+ append_menu_item(fileMenu, wxID_ANY, _L("&Open Project") + dots + "\tCtrl+O", _L("Open a project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open", nullptr,
[this](){return m_plater != nullptr; }, this);
wxMenu* recent_projects_menu = new wxMenu();
- wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), "");
+ wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _L("Recent projects"), "");
m_recent_projects.UseMenu(recent_projects_menu);
Bind(wxEVT_MENU, [this](wxCommandEvent& evt) {
size_t file_id = evt.GetId() - wxID_FILE1;
@@ -739,7 +912,7 @@ void MainFrame::init_menubar()
m_plater->load_project(filename);
else
{
- wxMessageDialog msg(this, _(L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?")), _(L("Error")), wxYES_NO | wxYES_DEFAULT);
+ wxMessageDialog msg(this, _L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?"), _L("Error"), wxYES_NO | wxYES_DEFAULT);
if (msg.ShowModal() == wxID_YES)
{
m_recent_projects.RemoveFileFromHistory(file_id);
@@ -764,13 +937,13 @@ void MainFrame::init_menubar()
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId());
- append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")),
+ append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#ifdef __APPLE__
- append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Shift+S", _(L("Save current project file as")),
+ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
#else
- append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")),
+ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
#endif // __APPLE__
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
@@ -778,7 +951,7 @@ void MainFrame::init_menubar()
fileMenu->AppendSeparator();
wxMenu* import_menu = new wxMenu();
- append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
+ append_menu_item(import_menu, wxID_ANY, _L("Import STL/OBJ/AM&F/3MF") + dots + "\tCtrl+I", _L("Load a model"),
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
@@ -786,59 +959,59 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
- append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
+ append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 output archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
import_menu->AppendSeparator();
- append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
+ append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"),
[this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr,
[this]() {return true; }, this);
- append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")),
+ append_menu_item(import_menu, wxID_ANY, _L("Import Config from &project") + dots +"\tCtrl+Alt+L", _L("Load configuration from project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config", nullptr,
[this]() {return true; }, this);
import_menu->AppendSeparator();
- append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")),
+ append_menu_item(import_menu, wxID_ANY, _L("Import Config &Bundle") + dots, _L("Load presets from a bundle"),
[this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle", nullptr,
[this]() {return true; }, this);
- append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), "");
+ append_submenu(fileMenu, import_menu, wxID_ANY, _L("&Import"), "");
wxMenu* export_menu = new wxMenu();
- wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")),
+ wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _L("Export &G-code") + dots +"\tCtrl+G", _L("Export current plate as G-code"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr,
[this](){return can_export_gcode(); }, this);
m_changeable_menu_items.push_back(item_export_gcode);
- wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")),
+ wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _L("S&end G-code") + dots +"\tCtrl+Shift+G", _L("Send to print current plate as G-code"),
[this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, "export_gcode", nullptr,
[this](){return can_send_gcode(); }, this);
m_changeable_menu_items.push_back(item_send_gcode);
- append_menu_item(export_menu, wxID_ANY, _(L("Export G-code to SD card / Flash drive")) + dots + "\tCtrl+U", _(L("Export current plate as G-code to SD card / Flash drive")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export G-code to SD card / Flash drive") + dots + "\tCtrl+U", _L("Export current plate as G-code to SD card / Flash drive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(true); }, "export_to_sd", nullptr,
[this]() {return can_export_gcode_sd(); }, this);
export_menu->AppendSeparator();
- append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export plate as &STL") + dots, _L("Export current plate as STL"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr,
[this](){return can_export_model(); }, this);
- append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export plate as STL &including supports") + dots, _L("Export current plate as STL including supports"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr,
[this](){return can_export_supports(); }, this);
- append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export plate as &AMF") + dots, _L("Export current plate as AMF"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr,
[this](){return can_export_model(); }, this);
export_menu->AppendSeparator();
- append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr,
[this]() {return can_export_toolpaths(); }, this);
export_menu->AppendSeparator();
- append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export &Config") + dots +"\tCtrl+E", _L("Export current configuration to file"),
[this](wxCommandEvent&) { export_config(); }, "export_config", nullptr,
[this]() {return true; }, this);
- append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")),
+ append_menu_item(export_menu, wxID_ANY, _L("Export Config &Bundle") + dots, _L("Export all presets to file"),
[this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle", nullptr,
[this]() {return true; }, this);
- append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), "");
+ append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), "");
- append_menu_item(fileMenu, wxID_ANY, _(L("Ejec&t SD card / Flash drive")) + dots + "\tCtrl+T", _(L("Eject SD card / Flash drive after the G-code was exported to it.")),
+ append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD card / Flash drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."),
[this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr,
[this]() {return can_eject(); }, this);
@@ -846,19 +1019,19 @@ void MainFrame::init_menubar()
#if 0
m_menu_item_repeat = nullptr;
- append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice")) +dots+ "\tCtrl+U", _(L("Slice a file into a G-code")),
+ append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice") +dots+ "\tCtrl+U", _L("Slice a file into a G-code"),
[this](wxCommandEvent&) {
wxTheApp->CallAfter([this]() {
quick_slice();
m_menu_item_repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
- append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save As")) +dots +"\tCtrl+Alt+U", _(L("Slice a file into a G-code, save as")),
+ append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice and Save As") +dots +"\tCtrl+Alt+U", _L("Slice a file into a G-code, save as"),
[this](wxCommandEvent&) {
wxTheApp->CallAfter([this]() {
quick_slice(qsSaveAs);
m_menu_item_repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
- m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _(L("Repeat Last Quick Slice")) +"\tCtrl+Shift+U", _(L("Repeat last quick slice")),
+ m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _L("Repeat Last Quick Slice") +"\tCtrl+Shift+U", _L("Repeat last quick slice"),
[this](wxCommandEvent&) {
wxTheApp->CallAfter([this]() {
quick_slice(qsReslice);
@@ -866,18 +1039,29 @@ void MainFrame::init_menubar()
m_menu_item_repeat->Enable(false);
fileMenu->AppendSeparator();
#endif
- m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice No&w")) + "\tCtrl+R", _(L("Start new slicing process")),
+ m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _L("(Re)Slice No&w") + "\tCtrl+R", _L("Start new slicing process"),
[this](wxCommandEvent&) { reslice_now(); }, "re_slice", nullptr,
- [this](){return m_plater != nullptr && can_reslice(); }, this);
+ [this]() { return m_plater != nullptr && can_reslice(); }, this);
fileMenu->AppendSeparator();
- append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")),
+ append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"),
[this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr,
- [this]() {return true; }, this);
+ [this]() { return true; }, this);
+#if ENABLE_GCODE_VIEWER
+ fileMenu->AppendSeparator();
+ append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"),
+ [this](wxCommandEvent&) {
+ if (m_plater->model().objects.empty() ||
+ wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"),
+ wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES)
+ set_mode(EMode::GCodeViewer);
+ }, "", nullptr);
+#endif // ENABLE_GCODE_VIEWER
fileMenu->AppendSeparator();
- append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), wxString::Format(_(L("Quit %s")), SLIC3R_APP_NAME),
+ append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { Close(false); });
}
+#if !ENABLE_GCODE_VIEWER
#ifdef _MSC_VER
// \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
@@ -887,6 +1071,7 @@ void MainFrame::init_menubar()
wxString sep = " - ";
wxString sep_space = "";
#endif
+#endif // !ENABLE_GCODE_VIEWER
// Edit menu
wxMenu* editMenu = nullptr;
@@ -899,44 +1084,44 @@ void MainFrame::init_menubar()
#else
wxString hotkey_delete = "Del";
#endif
- append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A",
- _(L("Selects all objects")), [this](wxCommandEvent&) { m_plater->select_all(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Select all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A",
+ _L("Selects all objects"), [this](wxCommandEvent&) { m_plater->select_all(); },
"", nullptr, [this](){return can_select(); }, this);
- append_menu_item(editMenu, wxID_ANY, _(L("D&eselect all")) + sep + "Esc",
- _(L("Deselects all objects")), [this](wxCommandEvent&) { m_plater->deselect_all(); },
+ append_menu_item(editMenu, wxID_ANY, _L("D&eselect all") + sep + "Esc",
+ _L("Deselects all objects"), [this](wxCommandEvent&) { m_plater->deselect_all(); },
"", nullptr, [this](){return can_deselect(); }, this);
editMenu->AppendSeparator();
- append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete,
- _(L("Deletes the current selection")),[this](wxCommandEvent&) { m_plater->remove_selected(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Delete selected") + sep + hotkey_delete,
+ _L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); },
"remove_menu", nullptr, [this](){return can_delete(); }, this);
- append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete,
- _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
+ append_menu_item(editMenu, wxID_ANY, _L("Delete &all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete,
+ _L("Deletes all objects"), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
"delete_all_menu", nullptr, [this](){return can_delete_all(); }, this);
editMenu->AppendSeparator();
- append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
- _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Undo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
+ _L("Undo"), [this](wxCommandEvent&) { m_plater->undo(); },
"undo_menu", nullptr, [this](){return m_plater->can_undo(); }, this);
- append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
- _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Redo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
+ _L("Redo"), [this](wxCommandEvent&) { m_plater->redo(); },
"redo_menu", nullptr, [this](){return m_plater->can_redo(); }, this);
editMenu->AppendSeparator();
- append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
- _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Copy") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
+ _L("Copy selection to clipboard"), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
"copy_menu", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
- append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V",
- _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
+ append_menu_item(editMenu, wxID_ANY, _L("&Paste") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V",
+ _L("Paste clipboard"), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
"paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
editMenu->AppendSeparator();
- append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5",
- _(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); },
+ append_menu_item(editMenu, wxID_ANY, _L("Re&load from disk") + sep + "F5",
+ _L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); },
"", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this);
editMenu->AppendSeparator();
- append_menu_item(editMenu, wxID_ANY, _(L("Searc&h")) + "\tCtrl+F",
- _(L("Find option")), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); },
+ append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F",
+ _L("Find option"), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); },
"search", nullptr, [this]() {return true; }, this);
}
@@ -944,32 +1129,33 @@ void MainFrame::init_menubar()
auto windowMenu = new wxMenu();
{
if (m_plater) {
- append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")),
+ append_menu_item(windowMenu, wxID_HIGHEST + 1, _L("&Plater Tab") + "\tCtrl+1", _L("Show the plater"),
[this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr,
[this]() {return true; }, this);
windowMenu->AppendSeparator();
}
- append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")),
+ append_menu_item(windowMenu, wxID_HIGHEST + 2, _L("P&rint Settings Tab") + "\tCtrl+2", _L("Show the print settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(1); }, "cog", nullptr,
[this]() {return true; }, this);
- wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")),
+ wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _L("&Filament Settings Tab") + "\tCtrl+3", _L("Show the filament settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(2); }, "spool", nullptr,
[this]() {return true; }, this);
m_changeable_menu_items.push_back(item_material_tab);
- wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")),
+ wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _L("Print&er Settings Tab") + "\tCtrl+4", _L("Show the printer settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(3); }, "printer", nullptr,
[this]() {return true; }, this);
m_changeable_menu_items.push_back(item_printer_tab);
if (m_plater) {
windowMenu->AppendSeparator();
- append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")),
+ append_menu_item(windowMenu, wxID_HIGHEST + 5, _L("3&D") + "\tCtrl+5", _L("Show the 3D editing view"),
[this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu", nullptr,
[this](){return can_change_view(); }, this);
- append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")),
+ append_menu_item(windowMenu, wxID_HIGHEST + 6, _L("Pre&view") + "\tCtrl+6", _L("Show the 3D slices preview"),
[this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu", nullptr,
[this](){return can_change_view(); }, this);
}
+#if !ENABLE_GCODE_VIEWER
#if _WIN32
// This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
wxAcceleratorEntry entries[6];
@@ -982,9 +1168,10 @@ void MainFrame::init_menubar()
wxAcceleratorTable accel(6, entries);
SetAcceleratorTable(accel);
#endif // _WIN32
+#endif // !ENABLE_GCODE_VIEWER
windowMenu->AppendSeparator();
- append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")),
+ append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"),
[this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr,
[this]() {return true; }, this);
@@ -1005,72 +1192,79 @@ void MainFrame::init_menubar()
wxMenu* viewMenu = nullptr;
if (m_plater) {
viewMenu = new wxMenu();
+#if ENABLE_GCODE_VIEWER
+ add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
+#else
// The camera control accelerators are captured by GLCanvas3D::on_char().
- append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")),[this](wxCommandEvent&) { select_view("iso"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [this](wxCommandEvent&) { select_view("iso"); },
"", nullptr, [this](){return can_change_view(); }, this);
viewMenu->AppendSeparator();
//TRN To be shown in the main menu View->Top
- append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [this](wxCommandEvent&) { select_view("top"); },
"", nullptr, [this](){return can_change_view(); }, this);
//TRN To be shown in the main menu View->Bottom
- append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [this](wxCommandEvent&) { select_view("bottom"); },
"", nullptr, [this](){return can_change_view(); }, this);
- append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [this](wxCommandEvent&) { select_view("front"); },
"", nullptr, [this](){return can_change_view(); }, this);
- append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [this](wxCommandEvent&) { select_view("rear"); },
"", nullptr, [this](){return can_change_view(); }, this);
- append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [this](wxCommandEvent&) { select_view("left"); },
"", nullptr, [this](){return can_change_view(); }, this);
- append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); },
+ append_menu_item(viewMenu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [this](wxCommandEvent&) { select_view("right"); },
"", nullptr, [this](){return can_change_view(); }, this);
+#endif // ENABLE_GCODE_VIEWER
viewMenu->AppendSeparator();
#if ENABLE_SLOPE_RENDERING
wxMenu* options_menu = new wxMenu();
- append_menu_check_item(options_menu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
+ append_menu_check_item(options_menu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"),
[this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
[this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
- append_menu_check_item(options_menu, wxID_ANY, _(L("Show &slope")) + sep + "D", _(L("Objects coloring using faces' slope")),
+ append_menu_check_item(options_menu, wxID_ANY, _L("Show &slope") + sep + "D", _L("Objects coloring using faces' slope"),
[this](wxCommandEvent&) { m_plater->show_view3D_slope(!m_plater->is_view3D_slope_shown()); }, this,
[this]() { return m_plater->is_view3D_shown() && !m_plater->is_view3D_layers_editing_enabled(); }, [this]() { return m_plater->is_view3D_slope_shown(); }, this);
- append_submenu(viewMenu, options_menu, wxID_ANY, _(L("&Options")), "");
+ append_submenu(viewMenu, options_menu, wxID_ANY, _L("&Options"), "");
#else
- append_menu_check_item(viewMenu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
+ append_menu_check_item(viewMenu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"),
[this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
[this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
#endif // ENABLE_SLOPE_RENDERING
- append_menu_check_item(viewMenu, wxID_ANY, _(L("&Collapse sidebar")), _(L("Collapse sidebar")),
+ append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar"), _L("Collapse sidebar"),
[this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this,
[this]() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this);
}
// Help menu
+#if ENABLE_GCODE_VIEWER
+ auto helpMenu = generate_help_menu();
+#else
auto helpMenu = new wxMenu();
{
- append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")),
+ append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
[this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
- append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")),
+ append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"),
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); });
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
//# wxTheApp->check_version(1);
//# });
//# $versioncheck->Enable(wxTheApp->have_version_check);
- append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Website")), SLIC3R_APP_NAME),
- wxString::Format(_(L("Open the %s website in your browser")), SLIC3R_APP_NAME),
+ append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME),
+ wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME),
// wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME),
// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
helpMenu->AppendSeparator();
- append_menu_item(helpMenu, wxID_ANY, _(L("System &Info")), _(L("Show system information")),
+ append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
[this](wxCommandEvent&) { wxGetApp().system_info(); });
- append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")),
+ append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
[this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
- append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME),
+ append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")),
[this](wxCommandEvent&) { Slic3r::GUI::about(); });
helpMenu->AppendSeparator();
- append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")),
+ append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
helpMenu->AppendSeparator();
@@ -1078,10 +1272,22 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
}
+#endif // ENABLE_GCODE_VIEWER
// menubar
// assign menubar to frame after appending items, otherwise special items
// will not be handled correctly
+#if ENABLE_GCODE_VIEWER
+ m_editor_menubar = new wxMenuBar();
+ m_editor_menubar->Append(fileMenu, _L("&File"));
+ if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit"));
+ m_editor_menubar->Append(windowMenu, _L("&Window"));
+ if (viewMenu) m_editor_menubar->Append(viewMenu, _L("&View"));
+ // Add additional menus from C++
+ wxGetApp().add_config_menu(m_editor_menubar);
+ m_editor_menubar->Append(helpMenu, _L("&Help"));
+ SetMenuBar(m_editor_menubar);
+#else
auto menubar = new wxMenuBar();
menubar->Append(fileMenu, _(L("&File")));
if (editMenu) menubar->Append(editMenu, _(L("&Edit")));
@@ -1091,11 +1297,16 @@ void MainFrame::init_menubar()
wxGetApp().add_config_menu(menubar);
menubar->Append(helpMenu, _(L("&Help")));
SetMenuBar(menubar);
+#endif // ENABLE_GCODE_VIEWER
#ifdef __APPLE__
// This fixes a bug on Mac OS where the quit command doesn't emit window close events
// wx bug: https://trac.wxwidgets.org/ticket/18328
+#if ENABLE_GCODE_VIEWER
+ wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu();
+#else
wxMenu *apple_menu = menubar->OSXGetAppleMenu();
+#endif // ENABLE_GCODE_VIEWER
if (apple_menu != nullptr) {
apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent &) {
Close();
@@ -1104,10 +1315,179 @@ void MainFrame::init_menubar()
#endif
if (plater()->printer_technology() == ptSLA)
+#if ENABLE_GCODE_VIEWER
+ update_editor_menubar();
+#else
update_menubar();
+#endif // ENABLE_GCODE_VIEWER
+}
+
+#if ENABLE_GCODE_VIEWER
+void MainFrame::init_gcodeviewer_menubar()
+{
+ wxMenu* fileMenu = new wxMenu;
+ {
+ append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"),
+ [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->load_gcode(); }, "open", nullptr,
+ [this]() {return m_plater != nullptr; }, this);
+ fileMenu->AppendSeparator();
+ append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"),
+ [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr,
+ [this]() {return can_export_toolpaths(); }, this);
+ fileMenu->AppendSeparator();
+ append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"),
+ [this](wxCommandEvent&) { set_mode(EMode::Editor); });
+ fileMenu->AppendSeparator();
+ append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME),
+ [this](wxCommandEvent&) { Close(false); });
+ }
+
+ // View menu
+ wxMenu* viewMenu = nullptr;
+ if (m_plater != nullptr) {
+ viewMenu = new wxMenu();
+ add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
+ }
+
+ // helpmenu
+ auto helpMenu = generate_help_menu();
+
+ m_gcodeviewer_menubar = new wxMenuBar();
+ m_gcodeviewer_menubar->Append(fileMenu, _L("&File"));
+ if ((viewMenu != nullptr))
+ m_gcodeviewer_menubar->Append(viewMenu, _L("&View"));
+ m_gcodeviewer_menubar->Append(helpMenu, _L("&Help"));
+}
+
+void MainFrame::set_mode(EMode mode)
+{
+ if (m_mode == mode)
+ return;
+
+ wxBusyCursor busy;
+
+ m_mode = mode;
+ switch (m_mode)
+ {
+ default:
+ case EMode::Editor:
+ {
+ update_layout();
+ select_tab(0);
+
+ m_plater->reset();
+ m_plater->reset_gcode_toolpaths();
+
+ m_plater->Freeze();
+
+ // reinitialize undo/redo stack
+ m_plater->clear_undo_redo_stack_main();
+ m_plater->take_snapshot(_L("New Project"));
+
+ // restore sla printer if it was deselected when switching to gcode viewer mode
+ if (m_restore_from_gcode_viewer.sla_technology) {
+ m_plater->set_printer_technology(ptSLA);
+ m_restore_from_gcode_viewer.sla_technology = false;
+ }
+
+ // switch view
+ m_plater->select_view_3D("3D");
+ m_plater->select_view("iso");
+
+ // switch printbed
+ m_plater->set_bed_shape();
+
+ // switch menubar
+ SetMenuBar(m_editor_menubar);
+
+ // show toolbars
+ m_plater->enable_view_toolbar(true);
+
+ if (m_restore_from_gcode_viewer.collapse_toolbar_enabled) {
+ m_plater->get_collapse_toolbar().set_enabled(true);
+ m_restore_from_gcode_viewer.collapse_toolbar_enabled = false;
+ }
+
+ // show sidebar
+ if (m_restore_from_gcode_viewer.collapsed_sidebar) {
+ m_plater->collapse_sidebar(false);
+ m_restore_from_gcode_viewer.collapsed_sidebar = false;
+ }
+
+ m_plater->Thaw();
+
+ SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+ if (m_taskbar_icon != nullptr) {
+ m_taskbar_icon->RemoveIcon();
+ m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer");
+ }
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
+
+ break;
+ }
+ case EMode::GCodeViewer:
+ {
+ update_layout();
+
+ m_plater->reset();
+ m_plater->reset_last_loaded_gcode();
+ m_plater->reset_gcode_toolpaths();
+
+ m_plater->Freeze();
+
+ // reinitialize undo/redo stack
+ m_plater->clear_undo_redo_stack_main();
+ m_plater->take_snapshot(_L("New Project"));
+
+ // switch to FFF printer mode
+ m_restore_from_gcode_viewer.sla_technology = m_plater->set_printer_technology(ptFFF);
+
+ // switch view
+ m_plater->select_view_3D("Preview");
+ m_plater->select_view("iso");
+
+ // switch printbed
+ m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true);
+
+ // switch menubar
+ SetMenuBar(m_gcodeviewer_menubar);
+
+ // hide toolbars
+ m_plater->enable_view_toolbar(false);
+
+ if (wxGetApp().app_config->get("show_collapse_button") == "1") {
+ m_plater->get_collapse_toolbar().set_enabled(false);
+ m_restore_from_gcode_viewer.collapse_toolbar_enabled = true;
+ }
+
+ // hide sidebar
+ if (wxGetApp().app_config->get("collapsed_sidebar") != "1") {
+ m_plater->collapse_sidebar(true);
+ m_restore_from_gcode_viewer.collapsed_sidebar = true;
+ }
+
+ m_plater->Thaw();
+
+ SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG));
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+ if (m_taskbar_icon != nullptr) {
+ m_taskbar_icon->RemoveIcon();
+ m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer");
+ }
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
+
+ break;
+ }
+ }
}
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
+void MainFrame::update_editor_menubar()
+#else
void MainFrame::update_menubar()
+#endif // ENABLE_GCODE_VIEWER
{
const bool is_fff = plater()->printer_technology() == ptFFF;
@@ -1600,16 +1980,19 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
this->SetFont(wxGetApp().normal_font());
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
- // Load the icon either from the exe, or from the ico file.
-#if _WIN32
- {
- TCHAR szExeFileName[MAX_PATH];
- GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
- SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
- }
-#else
- SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
-#endif // _WIN32
+
+ SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
+// // Load the icon either from the exe, or from the ico file.
+//#if _WIN32
+// {
+//
+// TCHAR szExeFileName[MAX_PATH];
+// GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
+// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
+// }
+//#else
+// SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
+//#endif // _WIN32
this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) {
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index 3c93f6b58..7777a053d 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -7,6 +7,9 @@
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/filehistory.h>
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+#include <wx/taskbar.h>
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
#include <string>
#include <map>
@@ -68,6 +71,20 @@ class MainFrame : public DPIFrame
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
wxString m_last_config = wxEmptyString;
+#if ENABLE_GCODE_VIEWER
+ wxMenuBar* m_editor_menubar{ nullptr };
+ wxMenuBar* m_gcodeviewer_menubar{ nullptr };
+
+ struct RestoreFromGCodeViewer
+ {
+ bool collapsed_sidebar{ false };
+ bool collapse_toolbar_enabled{ false };
+ bool sla_technology{ false };
+ };
+
+ RestoreFromGCodeViewer m_restore_from_gcode_viewer;
+#endif // ENABLE_GCODE_VIEWER
+
#if 0
wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now
#endif
@@ -121,17 +138,36 @@ class MainFrame : public DPIFrame
Old,
New,
Dlg,
+#if ENABLE_GCODE_VIEWER
+ GCodeViewer
+#endif // ENABLE_GCODE_VIEWER
};
ESettingsLayout m_layout{ ESettingsLayout::Unknown };
+#if ENABLE_GCODE_VIEWER
+public:
+ enum class EMode : unsigned char
+ {
+ Editor,
+ GCodeViewer
+ };
+
+private:
+ EMode m_mode{ EMode::Editor };
+#endif // ENABLE_GCODE_VIEWER
+
protected:
virtual void on_dpi_changed(const wxRect &suggested_rect);
virtual void on_sys_color_changed() override;
public:
MainFrame();
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+ ~MainFrame();
+#else
~MainFrame() = default;
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
void update_layout();
@@ -145,8 +181,17 @@ public:
void init_tabpanel();
void create_preset_tabs();
void add_created_tab(Tab* panel);
+#if ENABLE_GCODE_VIEWER
+ void init_editor_menubar();
+ void update_editor_menubar();
+ void init_gcodeviewer_menubar();
+
+ EMode get_mode() const { return m_mode; }
+ void set_mode(EMode mode);
+#else
void init_menubar();
void update_menubar();
+#endif // ENABLE_GCODE_VIEWER
void update_ui_from_settings();
bool is_loaded() const { return m_loaded; }
@@ -181,6 +226,10 @@ public:
wxProgressDialog* m_progress_dialog { nullptr };
std::shared_ptr<ProgressStatusBar> m_statusbar;
+#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
+ wxTaskBarIcon* m_taskbar_icon{ nullptr };
+#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
+
#ifdef _WIN32
void* m_hDeviceNotify { nullptr };
uint32_t m_ulSHChangeNotifyRegister { 0 };
diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp
index bdb005b1e..13c58f847 100644
--- a/src/slic3r/GUI/OpenGLManager.cpp
+++ b/src/slic3r/GUI/OpenGLManager.cpp
@@ -28,6 +28,13 @@
namespace Slic3r {
namespace GUI {
+// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr.
+inline std::string gl_get_string_safe(GLenum param, const std::string& default_value)
+{
+ const char* value = (const char*)::glGetString(param);
+ return std::string((value != nullptr) ? value : default_value);
+}
+
const std::string& OpenGLManager::GLInfo::get_version() const
{
if (!m_detected)
@@ -85,21 +92,10 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const
void OpenGLManager::GLInfo::detect() const
{
- const char* data = (const char*)::glGetString(GL_VERSION);
- if (data != nullptr)
- m_version = data;
-
- data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
- if (data != nullptr)
- m_glsl_version = data;
-
- data = (const char*)::glGetString(GL_VENDOR);
- if (data != nullptr)
- m_vendor = data;
-
- data = (const char*)::glGetString(GL_RENDERER);
- if (data != nullptr)
- m_renderer = data;
+ m_version = gl_get_string_safe(GL_VERSION, "N/A");
+ m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A");
+ m_vendor = gl_get_string_safe(GL_VENDOR, "N/A");
+ m_renderer = gl_get_string_safe(GL_RENDERER, "N/A");
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
@@ -114,13 +110,13 @@ void OpenGLManager::GLInfo::detect() const
m_detected = true;
}
-bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
+static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor)
{
- if (!m_detected)
- detect();
+ if (version == "N/A")
+ return false;
std::vector<std::string> tokens;
- boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on);
+ boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
if (tokens.empty())
return false;
@@ -145,6 +141,22 @@ bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, u
return gl_minor >= minor;
}
+bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
+{
+ if (!m_detected)
+ detect();
+
+ return version_greater_or_equal_to(m_version, major, minor);
+}
+
+bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
+{
+ if (!m_detected)
+ detect();
+
+ return version_greater_or_equal_to(m_glsl_version, major, minor);
+}
+
std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extensions) const
{
if (!m_detected)
@@ -159,15 +171,15 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension
std::string line_end = format_as_html ? "<br>" : "\n";
out << h2_start << "OpenGL installation" << h2_end << line_end;
- out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end;
- out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end;
- out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end;
- out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end;
+ out << b_start << "GL version: " << b_end << m_version << line_end;
+ out << b_start << "Vendor: " << b_end << m_vendor << line_end;
+ out << b_start << "Renderer: " << b_end << m_renderer << line_end;
+ out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end;
if (extensions)
{
std::vector<std::string> extensions_list;
- std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS);
+ std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, "");
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off);
if (!extensions_list.empty())
@@ -199,6 +211,8 @@ OpenGLManager::OSInfo OpenGLManager::s_os_info;
OpenGLManager::~OpenGLManager()
{
+ m_shaders_manager.shutdown();
+
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
#ifdef __APPLE__
// This is an ugly hack needed to solve the crash happening when closing the application on OSX 10.9.5 with newer wxWidgets
@@ -240,19 +254,30 @@ bool OpenGLManager::init_gl()
else
s_framebuffers_type = EFramebufferType::Unknown;
- if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) {
- // Complain about the OpenGL version.
+ bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0);
+ if (!valid_version) {
+ // Complain about the OpenGL version.
wxString message = from_u8((boost::format(
_utf8(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n"
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
- message += "\n";
+ message += "\n";
message += _L("You may need to update your graphics card driver.");
#ifdef _WIN32
- message += "\n";
+ message += "\n";
message += _L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter.");
#endif
wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR);
}
+
+ if (valid_version) {
+ // load shaders
+ auto [result, error] = m_shaders_manager.init();
+ if (!result) {
+ wxString message = from_u8((boost::format(
+ _utf8(L("Unable to load the following shaders:\n%s"))) % error).str());
+ wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR);
+ }
+ }
}
return true;
@@ -260,8 +285,7 @@ bool OpenGLManager::init_gl()
wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas)
{
- if (m_context == nullptr)
- {
+ if (m_context == nullptr) {
m_context = new wxGLContext(&canvas);
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp
index 9d7ee5bab..c89cdf3a6 100644
--- a/src/slic3r/GUI/OpenGLManager.hpp
+++ b/src/slic3r/GUI/OpenGLManager.hpp
@@ -1,6 +1,8 @@
#ifndef slic3r_OpenGLManager_hpp_
#define slic3r_OpenGLManager_hpp_
+#include "GLShadersManager.hpp"
+
class wxWindow;
class wxGLCanvas;
class wxGLContext;
@@ -41,6 +43,7 @@ public:
float get_max_anisotropy() const;
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
+ bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
@@ -70,6 +73,7 @@ private:
bool m_gl_initialized{ false };
wxGLContext* m_context{ nullptr };
+ GLShadersManager m_shaders_manager;
static GLInfo s_gl_info;
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
#ifdef __APPLE__
@@ -86,9 +90,11 @@ public:
~OpenGLManager();
bool init_gl();
-
wxGLContext* init_glcontext(wxGLCanvas& canvas);
+ GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); }
+ GLShaderProgram* get_current_shader() { return m_shaders_manager.get_current_shader(); }
+
static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; }
static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 027611750..1fe32fd2d 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -32,7 +32,11 @@
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
+#if ENABLE_GCODE_VIEWER
+#include "libslic3r/GCode/GCodeProcessor.hpp"
+#else
#include "libslic3r/GCode/PreviewData.hpp"
+#endif // ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
@@ -1161,35 +1165,39 @@ void Sidebar::update_sliced_info_sizer()
wxString::Format("%.2f", ps.total_cost);
p->sliced_info->SetTextAndShow(siCost, info_text, new_label);
+#if ENABLE_GCODE_VIEWER
+ // hide the estimate time
+ p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
+#else
if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A")
p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
else {
- new_label = _L("Estimated printing time") +":";
+ new_label = _L("Estimated printing time") + ":";
info_text = "";
wxString str_color = _L("Color");
wxString str_pause = _L("Pause");
- auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGCode::Type, std::string>>& times,
- wxString& new_label, wxString& info_text)
- {
- int color_change_count = 0;
- for (auto time : times)
- if (time.first == CustomGCode::ColorChange)
- color_change_count++;
-
- for (int i = (int)times.size() - 1; i >= 0; --i)
+ auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGCode::Type, std::string>>& times,
+ wxString& new_label, wxString& info_text)
{
- if (i == 0 || times[i - 1].first == CustomGCode::PausePrint)
- new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
- else if (times[i - 1].first == CustomGCode::ColorChange)
- new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
+ int color_change_count = 0;
+ for (auto time : times)
+ if (time.first == CustomGCode::ColorChange)
+ color_change_count++;
- if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint)
- new_label += format_wxstr(" -> %1%", str_pause);
+ for (int i = (int)times.size() - 1; i >= 0; --i)
+ {
+ if (i == 0 || times[i - 1].first == CustomGCode::PausePrint)
+ new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
+ else if (times[i - 1].first == CustomGCode::ColorChange)
+ new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
- info_text += format_wxstr("\n%1%", times[i].second);
- }
- };
+ if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint)
+ new_label += format_wxstr(" -> %1%", str_pause);
+
+ info_text += format_wxstr("\n%1%", times[i].second);
+ }
+ };
if (ps.estimated_normal_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("normal mode"));
@@ -1207,8 +1215,9 @@ void Sidebar::update_sliced_info_sizer()
info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time);
fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text);
}
- p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
+ p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
}
+#endif // !ENABLE_GCODE_VIEWER
// if there is a wipe tower, insert number of toolchanges info into the array:
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
@@ -1217,6 +1226,8 @@ void Sidebar::update_sliced_info_sizer()
p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
}
}
+
+ Layout();
}
void Sidebar::show_sliced_info_sizer(const bool show)
@@ -1338,21 +1349,78 @@ private:
Plater *plater;
static const std::regex pattern_drop;
+#if ENABLE_GCODE_VIEWER
+ static const std::regex pattern_gcode_drop;
+#endif // ENABLE_GCODE_VIEWER
};
const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
+#if ENABLE_GCODE_VIEWER
+const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode)", std::regex::icase);
+#endif // ENABLE_GCODE_VIEWER
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
std::vector<fs::path> paths;
- for (const auto &filename : filenames) {
+
+#if ENABLE_GCODE_VIEWER
+#ifdef WIN32
+ // hides the system icon
+ this->MSWUpdateDragImageOnLeave();
+#endif // WIN32
+
+ // gcode section
+ for (const auto& filename : filenames) {
fs::path path(into_path(filename));
- if (std::regex_match(path.string(), pattern_drop)) {
+ if (std::regex_match(path.string(), pattern_gcode_drop))
paths.push_back(std::move(path));
- } else {
+ }
+
+ if (paths.size() > 1) {
+ wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."),
+ wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
+ return false;
+ }
+ else if (paths.size() == 1) {
+ if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) {
+ plater->load_gcode(from_path(paths.front()));
+ return true;
+ }
+ else {
+ if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"),
+ wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) {
+
+ if (plater->model().objects.empty() ||
+ wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"),
+ wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) {
+ wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer);
+ plater->load_gcode(from_path(paths.front()));
+ return true;
+ }
+ }
return false;
}
}
+#endif // ENABLE_GCODE_VIEWER
+
+ // model section
+ for (const auto &filename : filenames) {
+ fs::path path(into_path(filename));
+ if (std::regex_match(path.string(), pattern_drop))
+ paths.push_back(std::move(path));
+ else
+ return false;
+ }
+
+#if ENABLE_GCODE_VIEWER
+ if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) {
+ if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"),
+ wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES)
+ wxGetApp().mainframe->set_mode(MainFrame::EMode::Editor);
+ else
+ return false;
+ }
+#endif // ENABLE_GCODE_VIEWER
wxString snapshot_label;
assert(! paths.empty());
@@ -1379,13 +1447,10 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
// because right now the plater is not cleared, we set the project file (from the latest imported .3mf or .amf file)
// only if not set yet
// if res is empty no data has been loaded
- if (!res.empty() && plater->get_project_filename().empty())
- {
- for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it)
- {
+ if (!res.empty() && plater->get_project_filename().empty()) {
+ for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
std::string filename = (*it).filename().string();
- if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf"))
- {
+ if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
plater->set_project_filename(from_path(*it));
break;
}
@@ -1426,7 +1491,11 @@ struct Plater::priv
Slic3r::SLAPrint sla_print;
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
+#if ENABLE_GCODE_VIEWER
+ Slic3r::GCodeProcessor::Result gcode_result;
+#else
Slic3r::GCodePreviewData gcode_preview_data;
+#endif // ENABLE_GCODE_VIEWER
// GUI elements
wxSizer* panel_sizer{ nullptr };
@@ -1539,6 +1608,15 @@ struct Plater::priv
bool init_view_toolbar();
bool init_collapse_toolbar();
+#if ENABLE_GCODE_VIEWER
+ void update_preview_bottom_toolbar();
+ void update_preview_moves_slider();
+#endif // ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+ void reset_gcode_toolpaths();
+#endif // ENABLE_GCODE_VIEWER
+
void reset_all_gizmos();
void update_ui_from_settings();
void update_main_toolbar_tooltips();
@@ -1659,7 +1737,7 @@ struct Plater::priv
// triangulate the bed and store the triangles into m_bed.m_triangles,
// fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement.
- void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
+ void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
bool can_delete() const;
bool can_delete_all() const;
@@ -1755,7 +1833,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_fff_print(&fff_print);
background_process.set_sla_print(&sla_print);
+#if ENABLE_GCODE_VIEWER
+ background_process.set_gcode_result(&gcode_result);
+#else
background_process.set_gcode_preview_data(&gcode_preview_data);
+#endif // ENABLE_GCODE_VIEWER
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
@@ -1780,7 +1862,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
view3D = new View3D(q, &model, config, &background_process);
+#if ENABLE_GCODE_VIEWER
+ preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); });
+#else
preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, [this]() { schedule_background_process(); });
+#endif // ENABLE_GCODE_VIEWER
#ifdef __APPLE__
// set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
@@ -1860,24 +1946,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
- view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
- {
- set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
- config->option<ConfigOptionString>("bed_custom_texture")->value,
- config->option<ConfigOptionString>("bed_custom_model")->value);
- });
+ view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
- preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
- {
- set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
- config->option<ConfigOptionString>("bed_custom_texture")->value,
- config->option<ConfigOptionString>("bed_custom_model")->value);
- });
+ preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
+#if ENABLE_GCODE_VIEWER
+ preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); });
+ preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); });
+#else
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
+#endif // ENABLE_GCODE_VIEWER
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
@@ -2015,6 +2096,10 @@ void Plater::priv::select_view_3D(const std::string& name)
set_current_panel(view3D);
else if (name == "Preview")
set_current_panel(preview);
+
+#if ENABLE_GCODE_VIEWER
+ wxGetApp().update_ui_from_settings();
+#endif // ENABLE_GCODE_VIEWER
}
void Plater::priv::select_next_view_3D()
@@ -2529,8 +2614,10 @@ void Plater::priv::deselect_all()
void Plater::priv::remove(size_t obj_idx)
{
+#if !ENABLE_GCODE_VIEWER
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
+#endif // !ENABLE_GCODE_VIEWER
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
@@ -2562,12 +2649,19 @@ void Plater::priv::reset()
set_project_filename(wxEmptyString);
+#if !ENABLE_GCODE_VIEWER
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
+#endif // !ENABLE_GCODE_VIEWER
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
+#if ENABLE_GCODE_VIEWER
+ reset_gcode_toolpaths();
+ gcode_result.reset();
+#endif // ENABLE_GCODE_VIEWER
+
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects();
@@ -2713,10 +2807,19 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
this->sidebar->show_sliced_info_sizer(false);
// Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
// Otherwise they will be just refreshed.
+#if ENABLE_GCODE_VIEWER
+ if (this->preview != nullptr) {
+ // If the preview is not visible, the following line just invalidates the preview,
+ // but the G-code paths or SLA preview are calculated first once the preview is made visible.
+ this->preview->get_canvas3d()->reset_gcode_toolpaths();
+ this->preview->reload_print();
+ }
+#else
if (this->preview != nullptr)
// If the preview is not visible, the following line just invalidates the preview,
// but the G-code paths or SLA preview are calculated first once the preview is made visible.
this->preview->reload_print();
+#endif // ENABLE_GCODE_VIEWER
// In FDM mode, we need to reload the 3D scene because of the wipe tower preview box.
// In SLA mode, we need to reload the 3D scene every time to show the support structures.
if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower")))
@@ -3952,6 +4055,25 @@ bool Plater::priv::init_collapse_toolbar()
return true;
}
+#if ENABLE_GCODE_VIEWER
+void Plater::priv::update_preview_bottom_toolbar()
+{
+ preview->update_bottom_toolbar();
+}
+
+void Plater::priv::update_preview_moves_slider()
+{
+ preview->update_moves_slider();
+}
+#endif // ENABLE_GCODE_VIEWER
+
+#if ENABLE_GCODE_VIEWER
+void Plater::priv::reset_gcode_toolpaths()
+{
+ preview->get_canvas3d()->reset_gcode_toolpaths();
+}
+#endif // ENABLE_GCODE_VIEWER
+
bool Plater::priv::can_set_instance_to_object() const
{
const int obj_idx = get_selected_object_idx();
@@ -4026,11 +4148,10 @@ bool Plater::priv::can_reload_from_disk() const
return !paths.empty();
}
-void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
+void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
{
- bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
- if (new_shape)
- {
+ bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom);
+ if (new_shape) {
if (view3D) view3D->bed_shape_changed();
if (preview) preview->bed_shape_changed();
}
@@ -4483,6 +4604,44 @@ void Plater::extract_config_from_project()
load_files(input_paths, false, true);
}
+#if ENABLE_GCODE_VIEWER
+void Plater::load_gcode()
+{
+ // Ask user for a gcode file name.
+ wxString input_file;
+ wxGetApp().load_gcode(this, input_file);
+ // And finally load the gcode file.
+ load_gcode(input_file);
+}
+
+void Plater::load_gcode(const wxString& filename)
+{
+ if (filename.empty() || m_last_loaded_gcode == filename)
+ return;
+
+ m_last_loaded_gcode = filename;
+
+ // cleanup view before to start loading/processing
+ p->gcode_result.reset();
+ reset_gcode_toolpaths();
+ p->preview->reload_print(false);
+ p->get_current_canvas3D()->render();
+
+ wxBusyCursor wait;
+
+ // process gcode
+ GCodeProcessor processor;
+ processor.enable_producers(true);
+ processor.enable_machine_envelope_processing(true);
+ processor.process_file(filename.ToUTF8().data());
+ p->gcode_result = std::move(processor.extract_result());
+
+ // show results
+ p->preview->reload_print(false);
+ p->preview->get_canvas3d()->zoom_to_gcode();
+}
+#endif // ENABLE_GCODE_VIEWER
+
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
// To be called when providing a list of files to the GUI slic3r on command line.
@@ -4983,6 +5142,9 @@ void Plater::reslice()
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return;
+#if ENABLE_GCODE_VIEWER
+ bool clean_gcode_toolpaths = true;
+#endif // ENABLE_GCODE_VIEWER
if (p->background_process.running())
{
if (wxGetApp().get_mode() == comSimple)
@@ -4995,9 +5157,19 @@ void Plater::reslice()
}
else if (!p->background_process.empty() && !p->background_process.idle())
p->show_action_buttons(true);
+#if ENABLE_GCODE_VIEWER
+ else
+ clean_gcode_toolpaths = false;
+
+ if (clean_gcode_toolpaths)
+ reset_gcode_toolpaths();
// update type of preview
+ p->preview->update_view_type(!clean_gcode_toolpaths);
+#else
+ // update type of preview
p->preview->update_view_type(true);
+#endif // ENABLE_GCODE_VIEWER
}
void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
@@ -5235,9 +5407,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
}
if (bed_shape_changed)
- p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
- p->config->option<ConfigOptionString>("bed_custom_texture")->value,
- p->config->option<ConfigOptionString>("bed_custom_model")->value);
+ set_bed_shape();
if (update_scheduled)
update();
@@ -5248,11 +5418,24 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
void Plater::set_bed_shape() const
{
- p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
+#if ENABLE_GCODE_VIEWER
+ set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
+ p->config->option<ConfigOptionString>("bed_custom_texture")->value,
+ p->config->option<ConfigOptionString>("bed_custom_model")->value);
+#else
+ p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
+#endif // ENABLE_GCODE_VIEWER
}
+#if ENABLE_GCODE_VIEWER
+void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const
+{
+ p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom);
+}
+#endif // ENABLE_GCODE_VIEWER
+
void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
@@ -5413,24 +5596,43 @@ PrinterTechnology Plater::printer_technology() const
const DynamicPrintConfig * Plater::config() const { return p->config; }
+#if ENABLE_GCODE_VIEWER
+bool Plater::set_printer_technology(PrinterTechnology printer_technology)
+#else
void Plater::set_printer_technology(PrinterTechnology printer_technology)
+#endif // ENABLE_GCODE_VIEWER
{
p->printer_technology = printer_technology;
+#if ENABLE_GCODE_VIEWER
+ bool ret = p->background_process.select_technology(printer_technology);
+ if (ret) {
+ // Update the active presets.
+ }
+#else
if (p->background_process.select_technology(printer_technology)) {
// Update the active presets.
}
+#endif // ENABLE_GCODE_VIEWER
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer");
- if (wxGetApp().mainframe)
+ if (wxGetApp().mainframe != nullptr)
+#if ENABLE_GCODE_VIEWER
+ wxGetApp().mainframe->update_editor_menubar();
+#else
wxGetApp().mainframe->update_menubar();
+#endif // ENABLE_GCODE_VIEWER
p->update_main_toolbar_tooltips();
p->sidebar->get_searcher().set_printer_technology(printer_technology);
+
+#if ENABLE_GCODE_VIEWER
+ return ret;
+#endif // ENABLE_GCODE_VIEWER
}
void Plater::changed_object(int obj_idx)
@@ -5578,11 +5780,25 @@ bool Plater::init_view_toolbar()
return p->init_view_toolbar();
}
+#if ENABLE_GCODE_VIEWER
+void Plater::enable_view_toolbar(bool enable)
+{
+ p->view_toolbar.set_enabled(enable);
+}
+#endif // ENABLE_GCODE_VIEWER
+
bool Plater::init_collapse_toolbar()
{
return p->init_collapse_toolbar();
}
+#if ENABLE_GCODE_VIEWER
+void Plater::enable_collapse_toolbar(bool enable)
+{
+ p->collapse_toolbar.set_enabled(enable);
+}
+#endif // ENABLE_GCODE_VIEWER
+
const Camera& Plater::get_camera() const
{
return p->camera;
@@ -5636,6 +5852,23 @@ GLToolbar& Plater::get_collapse_toolbar()
return p->collapse_toolbar;
}
+#if ENABLE_GCODE_VIEWER
+void Plater::update_preview_bottom_toolbar()
+{
+ p->update_preview_bottom_toolbar();
+}
+
+void Plater::update_preview_moves_slider()
+{
+ p->update_preview_moves_slider();
+}
+
+void Plater::reset_gcode_toolpaths()
+{
+ p->reset_gcode_toolpaths();
+}
+#endif // ENABLE_GCODE_VIEWER
+
const Mouse3DController& Plater::get_mouse3d_controller() const
{
return p->mouse3d_controller;
@@ -5703,6 +5936,9 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot();
bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
+#if ENABLE_GCODE_VIEWER
+void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
+#endif // ENABLE_GCODE_VIEWER
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 9b8d41cd3..cc8018620 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -140,6 +140,10 @@ public:
void add_model(bool imperial_units = false);
void import_sl1_archive();
void extract_config_from_project();
+#if ENABLE_GCODE_VIEWER
+ void load_gcode();
+ void load_gcode(const wxString& filename);
+#endif // ENABLE_GCODE_VIEWER
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
// To be called when providing a list of files to the GUI slic3r on command line.
@@ -219,6 +223,9 @@ public:
bool search_string_getter(int idx, const char** label, const char** tooltip);
// For the memory statistics.
const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const;
+#if ENABLE_GCODE_VIEWER
+ void clear_undo_redo_stack_main();
+#endif // ENABLE_GCODE_VIEWER
// Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
void enter_gizmos_stack();
void leave_gizmos_stack();
@@ -256,7 +263,11 @@ public:
PrinterTechnology printer_technology() const;
const DynamicPrintConfig * config() const;
+#if ENABLE_GCODE_VIEWER
+ bool set_printer_technology(PrinterTechnology printer_technology);
+#else
void set_printer_technology(PrinterTechnology printer_technology);
+#endif // ENABLE_GCODE_VIEWER
void copy_selection_to_clipboard();
void paste_from_clipboard();
@@ -282,7 +293,13 @@ public:
void sys_color_changed();
bool init_view_toolbar();
+#if ENABLE_GCODE_VIEWER
+ void enable_view_toolbar(bool enable);
+#endif // ENABLE_GCODE_VIEWER
bool init_collapse_toolbar();
+#if ENABLE_GCODE_VIEWER
+ void enable_collapse_toolbar(bool enable);
+#endif // ENABLE_GCODE_VIEWER
const Camera& get_camera() const;
Camera& get_camera();
@@ -301,10 +318,21 @@ public:
const GLToolbar& get_collapse_toolbar() const;
GLToolbar& get_collapse_toolbar();
+#if ENABLE_GCODE_VIEWER
+ void update_preview_bottom_toolbar();
+ void update_preview_moves_slider();
+
+ void reset_gcode_toolpaths();
+ void reset_last_loaded_gcode() { m_last_loaded_gcode = ""; }
+#endif // ENABLE_GCODE_VIEWER
+
const Mouse3DController& get_mouse3d_controller() const;
Mouse3DController& get_mouse3d_controller();
void set_bed_shape() const;
+#if ENABLE_GCODE_VIEWER
+ void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const;
+#endif // ENABLE_GCODE_VIEWER
const NotificationManager* get_notification_manager() const;
NotificationManager* get_notification_manager();
@@ -358,6 +386,10 @@ private:
bool m_tracking_popup_menu = false;
wxString m_tracking_popup_menu_error_message;
+#if ENABLE_GCODE_VIEWER
+ wxString m_last_loaded_gcode;
+#endif // ENABLE_GCODE_VIEWER
+
void suppress_snapshots();
void allow_snapshots();
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index e9250fe9e..bef0fd5d7 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -16,8 +16,11 @@
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
+#if ENABLE_GCODE_VIEWER
+#include <boost/log/trivial.hpp>
+#endif // ENABLE_GCODE_VIEWER
-static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f };
+static const float UNIFORM_SCALE_COLOR[4] = { 0.923f, 0.504f, 0.264f, 1.0f };
namespace Slic3r {
namespace GUI {
@@ -110,8 +113,10 @@ Selection::Selection()
, m_valid(false)
, m_scale_factor(1.0f)
{
+#if !ENABLE_GCODE_VIEWER
m_arrow.reset(new GLArrow);
m_curved_arrow.reset(new GLCurvedArrow(16));
+#endif // !ENABLE_GCODE_VIEWER
this->set_bounding_boxes_dirty();
#if ENABLE_RENDER_SELECTION_CENTER
@@ -138,6 +143,10 @@ void Selection::set_volumes(GLVolumePtrs* volumes)
// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized!
bool Selection::init()
{
+#if ENABLE_GCODE_VIEWER
+ m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f));
+ m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f));
+#else
if (!m_arrow->init())
return false;
@@ -147,6 +156,7 @@ bool Selection::init()
return false;
m_curved_arrow->set_scale(5.0 * Vec3d::Ones());
+#endif //ENABLE_GCODE_VIEWER
return true;
}
@@ -1261,40 +1271,40 @@ void Selection::render_center(bool gizmo_is_dragging) const
}
#endif // ENABLE_RENDER_SELECTION_CENTER
-void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const
+void Selection::render_sidebar_hints(const std::string& sidebar_field) const
{
if (sidebar_field.empty())
return;
+ GLShaderProgram* shader = nullptr;
+
if (!boost::starts_with(sidebar_field, "layer"))
{
- shader.start_using();
+ shader = wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
- glsafe(::glEnable(GL_LIGHTING));
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
- if (!boost::starts_with(sidebar_field, "layer"))
- {
+ if (!boost::starts_with(sidebar_field, "layer")) {
const Vec3d& center = get_bounding_box().center();
- if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates())
- {
+ if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
- if (!boost::starts_with(sidebar_field, "position"))
- {
+ if (!boost::starts_with(sidebar_field, "position")) {
Transform3d orient_matrix = Transform3d::Identity();
if (boost::starts_with(sidebar_field, "scale"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
- else if (boost::starts_with(sidebar_field, "rotation"))
- {
+ else if (boost::starts_with(sidebar_field, "rotation")) {
if (boost::ends_with(sidebar_field, "x"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
- else if (boost::ends_with(sidebar_field, "y"))
- {
+ else if (boost::ends_with(sidebar_field, "y")) {
const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
if (rotation(0) == 0.0)
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
@@ -1305,21 +1315,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha
glsafe(::glMultMatrixd(orient_matrix.data()));
}
- }
- else if (is_single_volume() || is_single_modifier())
- {
+ } else if (is_single_volume() || is_single_modifier()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
if (!boost::starts_with(sidebar_field, "position"))
orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
- }
- else
- {
+ } else {
glsafe(::glTranslated(center(0), center(1), center(2)));
- if (requires_local_axes())
- {
+ if (requires_local_axes()) {
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
@@ -1330,20 +1335,15 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha
render_sidebar_position_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "rotation"))
render_sidebar_rotation_hints(sidebar_field);
- else if (boost::starts_with(sidebar_field, "scale"))
+ else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size"))
render_sidebar_scale_hints(sidebar_field);
- else if (boost::starts_with(sidebar_field, "size"))
- render_sidebar_size_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "layer"))
render_sidebar_layers_hints(sidebar_field);
glsafe(::glPopMatrix());
if (!boost::starts_with(sidebar_field, "layer"))
- {
- glsafe(::glDisable(GL_LIGHTING));
- shader.stop_using();
- }
+ shader->stop_using();
}
bool Selection::requires_local_axes() const
@@ -1944,6 +1944,29 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons
glsafe(::glEnd());
}
+#if ENABLE_GCODE_VIEWER
+void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const
+{
+ auto set_color = [](Axis axis) {
+ GLShaderProgram* shader = wxGetApp().get_current_shader();
+ if (shader != nullptr)
+ shader->set_uniform("uniform_color", AXES_COLOR[axis], 4);
+ };
+
+ if (boost::ends_with(sidebar_field, "x")) {
+ set_color(X);
+ glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0));
+ m_arrow.render();
+ } else if (boost::ends_with(sidebar_field, "y")) {
+ set_color(Y);
+ m_arrow.render();
+ } else if (boost::ends_with(sidebar_field, "z")) {
+ set_color(Z);
+ glsafe(::glRotated(90.0, 1.0, 0.0, 0.0));
+ m_arrow.render();
+ }
+}
+#else
void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const
{
if (boost::ends_with(sidebar_field, "x"))
@@ -1959,9 +1982,39 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field)
render_sidebar_position_hint(Z);
}
}
+#endif // ENABLE_GCODE_VIEWER
+#if ENABLE_GCODE_VIEWER
void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const
{
+ auto set_color = [](Axis axis) {
+ GLShaderProgram* shader = wxGetApp().get_current_shader();
+ if (shader != nullptr)
+ shader->set_uniform("uniform_color", AXES_COLOR[axis], 4);
+ };
+
+ auto render_sidebar_rotation_hint = [this]() {
+ m_curved_arrow.render();
+ glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
+ m_curved_arrow.render();
+ };
+
+ if (boost::ends_with(sidebar_field, "x")) {
+ set_color(X);
+ glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
+ render_sidebar_rotation_hint();
+ } else if (boost::ends_with(sidebar_field, "y")) {
+ set_color(Y);
+ glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
+ render_sidebar_rotation_hint();
+ } else if (boost::ends_with(sidebar_field, "z")) {
+ set_color(Z);
+ render_sidebar_rotation_hint();
+ }
+}
+#else
+void Selection::render_sidebar_rotation_hints(const std::string & sidebar_field) const
+{
if (boost::ends_with(sidebar_field, "x"))
{
glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
@@ -1975,11 +2028,33 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field)
else if (boost::ends_with(sidebar_field, "z"))
render_sidebar_rotation_hint(Z);
}
+#endif // ENABLE_GCODE_VIEWER
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const
{
bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling();
+ auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) {
+ GLShaderProgram* shader = wxGetApp().get_current_shader();
+ if (shader != nullptr)
+ shader->set_uniform("uniform_color", uniform_scale ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4);
+
+ glsafe(::glTranslated(0.0, 5.0, 0.0));
+#if ENABLE_GCODE_VIEWER
+ m_arrow.render();
+#else
+ m_arrow->render();
+#endif // ENABLE_GCODE_VIEWER
+
+ glsafe(::glTranslated(0.0, -10.0, 0.0));
+ glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
+#if ENABLE_GCODE_VIEWER
+ m_arrow.render();
+#else
+ m_arrow->render();
+#endif // ENABLE_GCODE_VIEWER
+ };
+
if (boost::ends_with(sidebar_field, "x") || uniform_scale)
{
glsafe(::glPushMatrix());
@@ -2004,11 +2079,6 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con
}
}
-void Selection::render_sidebar_size_hints(const std::string& sidebar_field) const
-{
- render_sidebar_scale_hints(sidebar_field);
-}
-
void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const
{
static const double Margin = 10.0;
@@ -2081,6 +2151,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
glsafe(::glDisable(GL_BLEND));
}
+#if !ENABLE_GCODE_VIEWER
void Selection::render_sidebar_position_hint(Axis axis) const
{
m_arrow->set_color(AXES_COLOR[axis], 3);
@@ -2107,10 +2178,7 @@ void Selection::render_sidebar_scale_hint(Axis axis) const
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
m_arrow->render();
}
-
-void Selection::render_sidebar_size_hint(Axis axis, double length) const
-{
-}
+#endif // !ENABLE_GCODE_VIEWER
#ifndef NDEBUG
static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 7a929926c..2d87b9873 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -3,7 +3,9 @@
#include <set>
#include "libslic3r/Geometry.hpp"
-
+#if ENABLE_GCODE_VIEWER
+#include "GLModel.hpp"
+#endif // ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_SELECTION_CENTER
class GLUquadric;
@@ -19,6 +21,7 @@ class GLVolume;
class GLArrow;
class GLCurvedArrow;
class DynamicPrintConfig;
+class GLShaderProgram;
using GLVolumePtrs = std::vector<GLVolume*>;
using ModelObjectPtrs = std::vector<ModelObject*>;
@@ -218,10 +221,15 @@ private:
GLUquadricObj* m_quadric;
#endif // ENABLE_RENDER_SELECTION_CENTER
+#if ENABLE_GCODE_VIEWER
+ GLModel m_arrow;
+ GLModel m_curved_arrow;
+#else
// Arrows are saved through pointers to avoid including 3DScene.hpp.
// It also allows mutability.
std::unique_ptr<GLArrow> m_arrow;
std::unique_ptr<GLCurvedArrow> m_curved_arrow;
+#endif // ENABLE_GCODE_VIEWER
mutable float m_scale_factor;
@@ -336,7 +344,7 @@ public:
#if ENABLE_RENDER_SELECTION_CENTER
void render_center(bool gizmo_is_dragging) const;
#endif // ENABLE_RENDER_SELECTION_CENTER
- void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const;
+ void render_sidebar_hints(const std::string& sidebar_field) const;
bool requires_local_axes() const;
@@ -377,12 +385,12 @@ private:
void render_sidebar_position_hints(const std::string& sidebar_field) const;
void render_sidebar_rotation_hints(const std::string& sidebar_field) const;
void render_sidebar_scale_hints(const std::string& sidebar_field) const;
- void render_sidebar_size_hints(const std::string& sidebar_field) const;
void render_sidebar_layers_hints(const std::string& sidebar_field) const;
+#if !ENABLE_GCODE_VIEWER
void render_sidebar_position_hint(Axis axis) const;
void render_sidebar_rotation_hint(Axis axis) const;
void render_sidebar_scale_hint(Axis axis) const;
- void render_sidebar_size_hint(Axis axis, double length) const;
+#endif // !ENABLE_GCODE_VIEWER
public:
enum SyncRotationType {
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index 67b5a18f7..37794b5af 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -174,7 +174,6 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string,
const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
-const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18;
bool wxCheckListBoxComboPopup::Create(wxWindow* parent)
{
@@ -198,17 +197,22 @@ wxString wxCheckListBoxComboPopup::GetStringValue() const
wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{
- // matches owner wxComboCtrl's width
- // and sets height dinamically in dependence of contained items count
+ // set width dinamically in dependence of items text
+ // and set height dinamically in dependence of items count
wxComboCtrl* cmb = GetComboCtrl();
- if (cmb != nullptr)
- {
+ if (cmb != nullptr) {
wxSize size = GetComboCtrl()->GetSize();
unsigned int count = GetCount();
- if (count > 0)
- size.SetHeight(count * DefaultItemHeight);
+ if (count > 0) {
+ int max_width = size.x;
+ for (unsigned int i = 0; i < count; ++i) {
+ max_width = std::max(max_width, 60 + GetTextExtent(GetString(i)).x);
+ }
+ size.SetWidth(max_width);
+ size.SetHeight(count * cmb->GetCharHeight());
+ }
else
size.SetHeight(DefaultHeight);
diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp
index 9be3361bd..b2c71c4a0 100644
--- a/src/slic3r/GUI/wxExtensions.hpp
+++ b/src/slic3r/GUI/wxExtensions.hpp
@@ -63,7 +63,6 @@ class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
- static const unsigned int DefaultItemHeight;
wxString m_text;
diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp
index 70f82f4a5..09ca730ec 100644
--- a/tests/fff_print/test_data.cpp
+++ b/tests/fff_print/test_data.cpp
@@ -244,8 +244,12 @@ std::string gcode(Print & print)
boost::filesystem::path temp = boost::filesystem::unique_path();
print.set_status_silent();
print.process();
+#if ENABLE_GCODE_VIEWER
+ print.export_gcode(temp.string(), nullptr, nullptr);
+#else
print.export_gcode(temp.string(), nullptr);
- std::ifstream t(temp.string());
+#endif // ENABLE_GCODE_VIEWER
+ std::ifstream t(temp.string());
std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
boost::nowide::remove(temp.string().c_str());
return str;
diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp
index 6cb926621..45a080f31 100644
--- a/tests/fff_print/test_model.cpp
+++ b/tests/fff_print/test_model.cpp
@@ -50,7 +50,11 @@ SCENARIO("Model construction", "[Model]") {
print.apply(model, config);
print.process();
boost::filesystem::path temp = boost::filesystem::unique_path();
+#if ENABLE_GCODE_VIEWER
+ print.export_gcode(temp.string(), nullptr, nullptr);
+#else
print.export_gcode(temp.string(), nullptr);
+#endif // ENABLE_GCODE_VIEWER
REQUIRE(boost::filesystem::exists(temp));
REQUIRE(boost::filesystem::is_regular_file(temp));
REQUIRE(boost::filesystem::file_size(temp) > 0);
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index c3cd7e616..47961c623 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -15,7 +15,7 @@ REGISTER_CLASS(Filler, "Filler");
REGISTER_CLASS(Flow, "Flow");
REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer");
REGISTER_CLASS(GCode, "GCode");
-REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData");
+//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData");
// REGISTER_CLASS(GCodeSender, "GCode::Sender");
REGISTER_CLASS(Layer, "Layer");
REGISTER_CLASS(SupportLayer, "Layer::Support");
diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp
index 9e04edd4c..1536c874b 100644
--- a/xs/xsp/GCode.xsp
+++ b/xs/xsp/GCode.xsp
@@ -26,14 +26,14 @@
croak("%s\n", e.what());
}
%};
- void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data)
- %code%{
- try {
- THIS->do_export(print, path, preview_data);
- } catch (std::exception& e) {
- croak("%s\n", e.what());
- }
- %};
+// void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data)
+// %code%{
+// try {
+// THIS->do_export(print, path, preview_data);
+// } catch (std::exception& e) {
+// croak("%s\n", e.what());
+// }
+// %};
Ref<Vec2d> origin()
%code{% RETVAL = &(THIS->origin()); %};
@@ -60,26 +60,26 @@
%code{% RETVAL = const_cast<StaticPrintConfig*>(static_cast<const StaticPrintConfig*>(static_cast<const PrintObjectConfig*>(&THIS->config()))); %};
};
-%name{Slic3r::GCode::PreviewData} class GCodePreviewData {
- GCodePreviewData();
- ~GCodePreviewData();
- void reset();
- bool empty() const;
- void set_type(int type)
- %code%{
- if ((0 <= type) && (type < GCodePreviewData::Extrusion::Num_View_Types))
- THIS->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
- %};
- int type() %code%{ RETVAL = (int)THIS->extrusion.view_type; %};
- void set_extrusion_flags(int flags)
- %code%{ THIS->extrusion.role_flags = (unsigned int)flags; %};
- void set_travel_visible(bool visible)
- %code%{ THIS->travel.is_visible = visible; %};
- void set_retractions_visible(bool visible)
- %code%{ THIS->retraction.is_visible = visible; %};
- void set_unretractions_visible(bool visible)
- %code%{ THIS->unretraction.is_visible = visible; %};
- void set_shells_visible(bool visible)
- %code%{ THIS->shell.is_visible = visible; %};
- void set_extrusion_paths_colors(std::vector<std::string> colors);
-};
+//%name{Slic3r::GCode::PreviewData} class GCodePreviewData {
+// GCodePreviewData();
+// ~GCodePreviewData();
+// void reset();
+// bool empty() const;
+// void set_type(int type)
+// %code%{
+// if ((0 <= type) && (type < GCodePreviewData::Extrusion::Num_View_Types))
+// THIS->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
+// %};
+// int type() %code%{ RETVAL = (int)THIS->extrusion.view_type; %};
+// void set_extrusion_flags(int flags)
+// %code%{ THIS->extrusion.role_flags = (unsigned int)flags; %};
+// void set_travel_visible(bool visible)
+// %code%{ THIS->travel.is_visible = visible; %};
+// void set_retractions_visible(bool visible)
+// %code%{ THIS->retraction.is_visible = visible; %};
+// void set_unretractions_visible(bool visible)
+// %code%{ THIS->unretraction.is_visible = visible; %};
+// void set_shells_visible(bool visible)
+// %code%{ THIS->shell.is_visible = visible; %};
+// void set_extrusion_paths_colors(std::vector<std::string> colors);
+//};
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index 0952513ca..e4957c042 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -76,10 +76,10 @@ _constant()
%code%{ RETVAL = const_cast<ExtrusionEntityCollection*>(&THIS->skirt()); %};
Ref<ExtrusionEntityCollection> brim()
%code%{ RETVAL = const_cast<ExtrusionEntityCollection*>(&THIS->brim()); %};
- std::string estimated_normal_print_time()
- %code%{ RETVAL = THIS->print_statistics().estimated_normal_print_time; %};
- std::string estimated_silent_print_time()
- %code%{ RETVAL = THIS->print_statistics().estimated_silent_print_time; %};
+// std::string estimated_normal_print_time()
+// %code%{ RETVAL = THIS->print_statistics().estimated_normal_print_time; %};
+// std::string estimated_silent_print_time()
+// %code%{ RETVAL = THIS->print_statistics().estimated_silent_print_time; %};
double total_used_filament()
%code%{ RETVAL = THIS->print_statistics().total_used_filament; %};
double total_extruded_volume()
diff --git a/xs/xsp/my.map b/xs/xsp/my.map
index fd50d2975..7e51b237c 100644
--- a/xs/xsp/my.map
+++ b/xs/xsp/my.map
@@ -191,9 +191,9 @@ GCode* O_OBJECT_SLIC3R
Ref<GCode> O_OBJECT_SLIC3R_T
Clone<GCode> O_OBJECT_SLIC3R_T
-GCodePreviewData* O_OBJECT_SLIC3R
-Ref<GCodePreviewData> O_OBJECT_SLIC3R_T
-Clone<GCodePreviewData> O_OBJECT_SLIC3R_T
+//GCodePreviewData* O_OBJECT_SLIC3R
+//Ref<GCodePreviewData> O_OBJECT_SLIC3R_T
+//Clone<GCodePreviewData> O_OBJECT_SLIC3R_T
MotionPlanner* O_OBJECT_SLIC3R
Ref<MotionPlanner> O_OBJECT_SLIC3R_T
diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
index 7e277703b..385b50f1a 100644
--- a/xs/xsp/typemap.xspt
+++ b/xs/xsp/typemap.xspt
@@ -155,9 +155,9 @@
%typemap{Ref<GCode>}{simple};
%typemap{Clone<GCode>}{simple};
-%typemap{GCodePreviewData*};
-%typemap{Ref<GCodePreviewData>}{simple};
-%typemap{Clone<GCodePreviewData>}{simple};
+//%typemap{GCodePreviewData*};
+//%typemap{Ref<GCodePreviewData>}{simple};
+//%typemap{Clone<GCodePreviewData>}{simple};
%typemap{Points};
%typemap{Pointfs};