From c1a316124fd736ac0a004a6ec4a947eb4238632f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 3 Dec 2019 12:37:27 +0100 Subject: Tech ENABLE_THUMBNAIL_GENERATOR partially (only export to .gcode) ported from master --- resources/profiles/PrusaResearch.ini | 1 - src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 60 +++++ src/libslic3r/GCode.hpp | 11 + src/libslic3r/GCode/ThumbnailData.cpp | 36 +++ src/libslic3r/GCode/ThumbnailData.hpp | 31 +++ src/libslic3r/Point.hpp | 12 +- src/libslic3r/Print.cpp | 8 + src/libslic3r/Print.hpp | 7 + src/libslic3r/PrintConfig.cpp | 5 + src/libslic3r/Technologies.hpp | 9 + src/slic3r/GUI/3DBed.cpp | 10 +- src/slic3r/GUI/3DBed.hpp | 2 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 12 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 11 +- src/slic3r/GUI/Camera.cpp | 135 ++++++++++- src/slic3r/GUI/Camera.hpp | 23 +- src/slic3r/GUI/GLCanvas3D.cpp | 355 +++++++++++++++++++++++++++- src/slic3r/GUI/GLCanvas3D.hpp | 27 ++- src/slic3r/GUI/GLCanvas3DManager.cpp | 8 + src/slic3r/GUI/GLCanvas3DManager.hpp | 10 + src/slic3r/GUI/Plater.cpp | 39 +++ src/slic3r/GUI/Preset.cpp | 3 +- src/slic3r/GUI/PresetBundle.cpp | 1 + 24 files changed, 788 insertions(+), 30 deletions(-) create mode 100644 src/libslic3r/GCode/ThumbnailData.cpp create mode 100644 src/libslic3r/GCode/ThumbnailData.hpp diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 55e127921..36f097552 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -4031,7 +4031,6 @@ printer_model = SL1 printer_variant = default default_sla_material_profile = Prusa Orange Tough 0.05 default_sla_print_profile = 0.05 Normal -thumbnails = 400x400,800x480 bed_shape = 1.48x1.02,119.48x1.02,119.48x67.02,1.48x67.02 display_height = 68.04 display_orientation = portrait diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 97e0fc09b..154bb479a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -71,6 +71,8 @@ add_library(libslic3r STATIC Format/STL.hpp GCode/Analyzer.cpp GCode/Analyzer.hpp + GCode/ThumbnailData.cpp + GCode/ThumbnailData.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/PostProcessor.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ff62a5a03..19d4dc9e0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -17,6 +17,9 @@ #include #include #include +#if ENABLE_THUMBNAIL_GENERATOR +#include +#endif // ENABLE_THUMBNAIL_GENERATOR #include #include @@ -28,6 +31,8 @@ #include +#include "miniz_extension.hpp" + #if 0 // Enable debugging and asserts, even in the release build. #define DEBUG @@ -651,7 +656,11 @@ std::vector>> GCode::collec return layers_to_print; } +#if ENABLE_THUMBNAIL_GENERATOR +void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) +#else void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data) +#endif // ENABLE_THUMBNAIL_GENERATOR { PROFILE_CLEAR(); @@ -677,7 +686,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ try { m_placeholder_parser_failed_templates.clear(); +#if ENABLE_THUMBNAIL_GENERATOR + this->_do_export(*print, file, thumbnail_cb); +#else this->_do_export(*print, file); +#endif // ENABLE_THUMBNAIL_GENERATOR fflush(file); if (ferror(file)) { fclose(file); @@ -737,7 +750,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } +#if ENABLE_THUMBNAIL_GENERATOR +void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb) +#else void GCode::_do_export(Print &print, FILE *file) +#endif // ENABLE_THUMBNAIL_GENERATOR { PROFILE_FUNC(); @@ -925,6 +942,49 @@ void GCode::_do_export(Print &print, FILE *file) // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); + +#if ENABLE_THUMBNAIL_GENERATOR + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) + { + const size_t max_row_length = 78; + ThumbnailsList thumbnails; + thumbnail_cb(thumbnails, print.full_print_config().option("thumbnails")->values, true, true, true, true); + for (const ThumbnailData& data : thumbnails) + { + if (data.is_valid()) + { + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); + encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); + + _write_format(file, "\n;\n; thumbnail begin %dx%d %d\n", data.width, data.height, encoded.size()); + + unsigned int row_count = 0; + while (encoded.size() > max_row_length) + { + _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str()); + encoded = encoded.substr(max_row_length); + ++row_count; + } + + if (encoded.size() > 0) + _write_format(file, "; %s\n", encoded.c_str()); + + _write(file, "; thumbnail end\n;\n"); + + mz_free(png_data); + } + } + print.throw_if_canceled(); + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR + // Write notes (content of the Print Settings tab -> Notes) { std::list lines; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 83d61c483..c6257b81e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -17,6 +17,9 @@ #include "GCodeTimeEstimator.hpp" #include "EdgeGrid.hpp" #include "GCode/Analyzer.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include #include @@ -162,7 +165,11 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). +#if ENABLE_THUMBNAIL_GENERATOR + void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); +#else void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr); +#endif // ENABLE_THUMBNAIL_GENERATOR // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Vec2d& origin() const { return m_origin; } @@ -190,7 +197,11 @@ public: static void append_full_config(const Print& print, std::string& str); protected: +#if ENABLE_THUMBNAIL_GENERATOR + void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb); +#else void _do_export(Print &print, FILE *file); +#endif //ENABLE_THUMBNAIL_GENERATOR // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint diff --git a/src/libslic3r/GCode/ThumbnailData.cpp b/src/libslic3r/GCode/ThumbnailData.cpp new file mode 100644 index 000000000..80165916b --- /dev/null +++ b/src/libslic3r/GCode/ThumbnailData.cpp @@ -0,0 +1,36 @@ +#include "libslic3r/libslic3r.h" +#include "ThumbnailData.hpp" + +#if ENABLE_THUMBNAIL_GENERATOR + +namespace Slic3r { + +void ThumbnailData::set(unsigned int w, unsigned int h) +{ + if ((w == 0) || (h == 0)) + return; + + if ((width != w) || (height != h)) + { + width = w; + height = h; + // defaults to white texture + pixels = std::vector(width * height * 4, 255); + } +} + +void ThumbnailData::reset() +{ + width = 0; + height = 0; + pixels.clear(); +} + +bool ThumbnailData::is_valid() const +{ + return (width != 0) && (height != 0) && ((unsigned int)pixels.size() == 4 * width * height); +} + +} // namespace Slic3r + +#endif // ENABLE_THUMBNAIL_GENERATOR \ No newline at end of file diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp new file mode 100644 index 000000000..4acfe4374 --- /dev/null +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -0,0 +1,31 @@ +#ifndef slic3r_ThumbnailData_hpp_ +#define slic3r_ThumbnailData_hpp_ + +#if ENABLE_THUMBNAIL_GENERATOR + +#include +#include "libslic3r/Point.hpp" + +namespace Slic3r { + +struct ThumbnailData +{ + unsigned int width; + unsigned int height; + std::vector pixels; + + ThumbnailData() { reset(); } + void set(unsigned int w, unsigned int h); + void reset(); + + bool is_valid() const; +}; + +typedef std::vector ThumbnailsList; +typedef std::function ThumbnailsGeneratorCallback; + +} // namespace Slic3r + +#endif // ENABLE_THUMBNAIL_GENERATOR + +#endif // slic3r_ThumbnailData_hpp_ \ No newline at end of file diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 994f45e59..9631ba975 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -38,6 +38,7 @@ typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; typedef std::vector Points3; typedef std::vector Pointfs; +typedef std::vector Vec2ds; typedef std::vector Pointf3s; typedef Eigen::Matrix Matrix2f; @@ -87,11 +88,12 @@ class Point : public Vec2crd public: typedef coord_t coord_type; - Point() : Vec2crd() { (*this)(0) = 0; (*this)(1) = 0; } - Point(coord_t x, coord_t y) { (*this)(0) = x; (*this)(1) = y; } - Point(int64_t x, int64_t y) { (*this)(0) = coord_t(x); (*this)(1) = coord_t(y); } // for Clipper - Point(double x, double y) { (*this)(0) = coord_t(lrint(x)); (*this)(1) = coord_t(lrint(y)); } - Point(const Point &rhs) { *this = rhs; } + Point() : Vec2crd(0, 0) {} + Point(coord_t x, coord_t y) : Vec2crd(x, y) {} + Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} // for Clipper + Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} + Point(const Point& rhs) { *this = rhs; } + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} // This constructor allows you to construct Point from Eigen expressions template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 796215781..630020c1b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1525,7 +1525,11 @@ 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_THUMBNAIL_GENERATOR +std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) +#else std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) +#endif // ENABLE_THUMBNAIL_GENERATOR { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. @@ -1542,7 +1546,11 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa // The following line may die for multiple reasons. GCode gcode; +#if ENABLE_THUMBNAIL_GENERATOR + gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb); +#else gcode.do_export(this, path.c_str(), preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR return path.c_str(); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b6d7b678d..139048c5c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,6 +11,9 @@ #include "Slicing.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR namespace Slic3r { @@ -301,7 +304,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_THUMBNAIL_GENERATOR + std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); +#else std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8b73a2e82..f524a6d49 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -62,6 +62,11 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + def = this->add("thumbnails", coPoints); + def->label = L("Picture sizes to be stored into a .gcode and .sl1 files"); + def->mode = comExpert; + def->set_default_value(new ConfigOptionPoints()); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 51d092094..0c555c616 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -32,4 +32,13 @@ #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) +//============ +// 2.1.1 techs +//============ +#define ENABLE_2_1_1 1 + +// Enable thumbnail generator +#define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_1_1) + + #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index b2b6c167e..8533ba7b4 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -171,6 +171,7 @@ void Bed3D::Axes::render() const glsafe(::glPopMatrix()); glsafe(::glDisable(GL_LIGHTING)); + glsafe(::glDisable(GL_DEPTH_TEST)); } void Bed3D::Axes::render_axis(double length) const @@ -262,11 +263,14 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } -void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const +void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const { m_scale_factor = scale_factor; - render_axes(); + if (show_axes) + render_axes(); + + glsafe(::glEnable(GL_DEPTH_TEST)); switch (m_type) { @@ -277,6 +281,8 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const default: case Custom: { render_custom(canvas, theta > 90.0f); break; } } + + glsafe(::glDisable(GL_DEPTH_TEST)); } void Bed3D::calc_bounding_boxes() const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index c485b93e3..044654a97 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -111,7 +111,7 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D& canvas, float theta, float scale_factor) const; + void render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const; private: void calc_bounding_boxes() const; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index a1db6884e..49f0a8c01 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -10,6 +10,10 @@ #include #include +#if ENABLE_THUMBNAIL_GENERATOR +#include +#endif // ENABLE_THUMBNAIL_GENERATOR + // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" @@ -82,8 +86,12 @@ void BackgroundSlicingProcess::process_fff() assert(m_print == m_fff_print); m_print->process(); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); - m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); - if (this->set_step_started(bspsGCodeFinalize)) { +#if ENABLE_THUMBNAIL_GENERATOR + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); +#else + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR + if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index cf5edd55f..bd6d0ac44 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -49,7 +49,10 @@ public: void set_fff_print(Print *print) { m_fff_print = print; } void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } - // The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished +#if ENABLE_THUMBNAIL_GENERATOR + void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } +#endif // ENABLE_THUMBNAIL_GENERATOR + // The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished // and the background processing will transition into G-code export. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; } @@ -151,7 +154,11 @@ private: SLAPrint *m_sla_print = nullptr; // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; - // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. +#if ENABLE_THUMBNAIL_GENERATOR + // Callback function, used to write thumbnails into gcode. + ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; +#endif // ENABLE_THUMBNAIL_GENERATOR + // 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/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 8e3a6d1f1..053a1254d 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -1,7 +1,9 @@ #include "libslic3r/libslic3r.h" #include "Camera.hpp" +#if !ENABLE_THUMBNAIL_GENERATOR #include "3DScene.hpp" +#endif // !ENABLE_THUMBNAIL_GENERATOR #include "GUI_App.hpp" #include "AppConfig.hpp" @@ -22,6 +24,10 @@ namespace Slic3r { namespace GUI { const double Camera::DefaultDistance = 1000.0; +#if ENABLE_THUMBNAIL_GENERATOR +const double Camera::DefaultZoomToBoxMarginFactor = 1.025; +const double Camera::DefaultZoomToVolumesMarginFactor = 1.025; +#endif // ENABLE_THUMBNAIL_GENERATOR double Camera::FrustrumMinZRange = 50.0; double Camera::FrustrumMinNearZ = 100.0; double Camera::FrustrumZMargin = 10.0; @@ -184,7 +190,7 @@ void Camera::apply_view_matrix() const glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); } -void Camera::apply_projection(const BoundingBoxf3& box) const +void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const { set_distance(DefaultDistance); @@ -195,6 +201,12 @@ void Camera::apply_projection(const BoundingBoxf3& box) const { m_frustrum_zs = calc_tight_frustrum_zs_around(box); + if (near_z > 0.0) + m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z); + + if (far_z > 0.0) + m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); + w = 0.5 * (double)m_viewport[2]; h = 0.5 * (double)m_viewport[3]; @@ -266,10 +278,18 @@ void Camera::apply_projection(const BoundingBoxf3& box) const glsafe(::glMatrixMode(GL_MODELVIEW)); } +#if ENABLE_THUMBNAIL_GENERATOR +void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) +#else void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) +#endif // ENABLE_THUMBNAIL_GENERATOR { // Calculate the zoom factor needed to adjust the view around the given box. +#if ENABLE_THUMBNAIL_GENERATOR + double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor); +#else double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); +#endif // ENABLE_THUMBNAIL_GENERATOR if (zoom > 0.0) { m_zoom = zoom; @@ -278,6 +298,20 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) } } +#if ENABLE_THUMBNAIL_GENERATOR +void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor) +{ + Vec3d center; + double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor); + if (zoom > 0.0) + { + m_zoom = zoom; + // center view around the calculated center + m_target = center; + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + #if ENABLE_CAMERA_STATISTICS void Camera::debug_render() const { @@ -372,7 +406,11 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo return ret; } +#if ENABLE_THUMBNAIL_GENERATOR +double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const +#else double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const +#endif // ENABLE_THUMBNAIL_GENERATOR { double max_bb_size = box.max_size(); if (max_bb_size == 0.0) @@ -402,34 +440,111 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca vertices.push_back(box.max); vertices.emplace_back(box.min(0), box.max(1), box.max(2)); - double max_x = 0.0; - double max_y = 0.0; + double min_x = DBL_MAX; + double min_y = DBL_MAX; + double max_x = -DBL_MAX; + double max_y = -DBL_MAX; +#if !ENABLE_THUMBNAIL_GENERATOR // margin factor to give some empty space around the box double margin_factor = 1.25; +#endif // !ENABLE_THUMBNAIL_GENERATOR for (const Vec3d& v : vertices) { // project vertex on the plane perpendicular to camera forward axis - Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); + Vec3d pos = v - bb_center; Vec3d proj_on_plane = pos - pos.dot(forward) * forward; // calculates vertex coordinate along camera xy axes double x_on_plane = proj_on_plane.dot(right); double y_on_plane = proj_on_plane.dot(up); - max_x = std::max(max_x, std::abs(x_on_plane)); - max_y = std::max(max_y, std::abs(y_on_plane)); + min_x = std::min(min_x, x_on_plane); + min_y = std::min(min_y, y_on_plane); + max_x = std::max(max_x, x_on_plane); + max_y = std::max(max_y, y_on_plane); } - if ((max_x == 0.0) || (max_y == 0.0)) + double dx = max_x - min_x; + double dy = max_y - min_y; + if ((dx <= 0.0) || (dy <= 0.0)) return -1.0f; - max_x *= margin_factor; - max_y *= margin_factor; + double med_x = 0.5 * (max_x + min_x); + double med_y = 0.5 * (max_y + min_y); + + dx *= margin_factor; + dy *= margin_factor; + + return std::min((double)canvas_w / dx, (double)canvas_h / dy); +} + +#if ENABLE_THUMBNAIL_GENERATOR +double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const +{ + if (volumes.empty()) + return -1.0; + + // project the volumes vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // ensure that the view matrix is updated + apply_view_matrix(); + + Vec3d right = get_dir_right(); + Vec3d up = get_dir_up(); + Vec3d forward = get_dir_forward(); + + BoundingBoxf3 box; + for (const GLVolume* volume : volumes) + { + box.merge(volume->transformed_bounding_box()); + } + center = box.center(); + + double min_x = DBL_MAX; + double min_y = DBL_MAX; + double max_x = -DBL_MAX; + double max_y = -DBL_MAX; + + for (const GLVolume* volume : volumes) + { + const Transform3d& transform = volume->world_matrix(); + const TriangleMesh* hull = volume->convex_hull(); + if (hull == nullptr) + continue; + + for (const Vec3f& vertex : hull->its.vertices) + { + Vec3d v = transform * vertex.cast(); + + // project vertex on the plane perpendicular to camera forward axis + Vec3d pos = v - center; + Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + + // calculates vertex coordinate along camera xy axes + double x_on_plane = proj_on_plane.dot(right); + double y_on_plane = proj_on_plane.dot(up); + + min_x = std::min(min_x, x_on_plane); + min_y = std::min(min_y, y_on_plane); + max_x = std::max(max_x, x_on_plane); + max_y = std::max(max_y, y_on_plane); + } + } + + center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up; + + double dx = margin_factor * (max_x - min_x); + double dy = margin_factor * (max_y - min_y); + + if ((dx <= 0.0) || (dy <= 0.0)) + return -1.0f; - return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y)); + return std::min((double)canvas_w / dx, (double)canvas_h / dy); } +#endif // ENABLE_THUMBNAIL_GENERATOR void Camera::set_distance(double distance) const { diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 839d0d6cf..022ac76eb 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -2,6 +2,9 @@ #define slic3r_Camera_hpp_ #include "libslic3r/BoundingBox.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "3DScene.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include namespace Slic3r { @@ -10,6 +13,10 @@ namespace GUI { struct Camera { static const double DefaultDistance; +#if ENABLE_THUMBNAIL_GENERATOR + static const double DefaultZoomToBoxMarginFactor; + static const double DefaultZoomToVolumesMarginFactor; +#endif // ENABLE_THUMBNAIL_GENERATOR static double FrustrumMinZRange; static double FrustrumMinNearZ; static double FrustrumZMargin; @@ -88,9 +95,16 @@ public: void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; - void apply_projection(const BoundingBoxf3& box) const; - + // Calculates and applies the projection matrix tighting the frustrum z range around the given box. + // If larger z span is needed, pass the desired values of near and far z (negative values are ignored) + void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const; + +#if ENABLE_THUMBNAIL_GENERATOR + void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor); + void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor); +#else void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); +#endif // ENABLE_THUMBNAIL_GENERATOR #if ENABLE_CAMERA_STATISTICS void debug_render() const; @@ -100,7 +114,12 @@ private: // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; +#if ENABLE_THUMBNAIL_GENERATOR + double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const; + double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; +#else double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; +#endif // ENABLE_THUMBNAIL_GENERATOR void set_distance(double distance) const; }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 71bacd815..0bd5bb24b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7,6 +7,9 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r/GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/Geometry.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Utils.hpp" @@ -1119,6 +1122,10 @@ wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +#if ENABLE_THUMBNAIL_GENERATOR +const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; +#endif // ENABLE_THUMBNAIL_GENERATOR + GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) , m_context(nullptr) @@ -1585,7 +1592,7 @@ void GLCanvas3D::render() _render_objects(); _render_sla_slices(); _render_selection(); - _render_bed(theta); + _render_bed(theta, true); #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); @@ -1645,6 +1652,18 @@ void GLCanvas3D::render() #endif // ENABLE_RENDER_STATISTICS } +#if ENABLE_THUMBNAIL_GENERATOR +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + switch (GLCanvas3DManager::get_framebuffers_type()) + { + case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; } + case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; } + default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; } + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void GLCanvas3D::select_all() { m_selection.add_all(); @@ -3526,6 +3545,323 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) imgui->end(); } +#if ENABLE_THUMBNAIL_GENERATOR +void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + auto is_visible = [](const GLVolume& v) -> bool + { + bool ret = v.printable; + ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); + 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 }; + + GLVolumePtrs visible_volumes; + + for (GLVolume* vol : m_volumes.volumes) + { + if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0))) + { + if (!printable_only || is_visible(*vol)) + visible_volumes.push_back(vol); + } + } + + if (visible_volumes.empty()) + return; + + BoundingBoxf3 box; + for (const GLVolume* vol : visible_volumes) + { + box.merge(vol->transformed_bounding_box()); + } + + Camera camera; + camera.set_type(Camera::Ortho); + camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height); + camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.apply_view_matrix(); + + double near_z = -1.0; + double far_z = -1.0; + + if (show_bed) + { + // extends the near and far z of the frustrum to avoid the bed being clipped + + // box in eye space + BoundingBoxf3 t_bed_box = m_bed.get_bounding_box(true).transformed(camera.get_view_matrix()); + near_z = -t_bed_box.max(2); + far_z = -t_bed_box.min(2); + } + + camera.apply_projection(box, near_z, far_z); + + 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)); + + 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)); + + vol->render(); + } + + m_shader.stop_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + if (show_bed) + _render_bed(camera.get_theta(), false); + + if (transparent_background) + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) + { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); + } + else + { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + { + _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background); + + if (multisample) + { + GLuint resolve_fbo; + glsafe(::glGenFramebuffers(1, &resolve_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + { + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); + glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); + glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) + { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); + } + else + { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) + { + _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background); + + if (multisample) + { + GLuint resolve_fbo; + glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) + { + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); + glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + // check that thumbnail size does not exceed the default framebuffer size + const Size& cnv_size = get_canvas_size(); + unsigned int cnv_w = (unsigned int)cnv_size.get_width(); + unsigned int cnv_h = (unsigned int)cnv_size.get_height(); + if ((w > cnv_w) || (h > cnv_h)) + { + float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); + w = (unsigned int)(ratio * (float)w); + h = (unsigned int)(ratio * (float)h); + } + + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background); + + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + + // restore the default framebuffer size to avoid flickering on the 3D scene + m_camera.apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); +} +#endif // ENABLE_THUMBNAIL_GENERATOR + bool GLCanvas3D::_init_toolbars() { if (!_init_main_toolbar()) @@ -3837,12 +4173,21 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be return bb; } +#if ENABLE_THUMBNAIL_GENERATOR +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) +{ + const Size& cnv_size = get_canvas_size(); + m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor); + m_dirty = true; +} +#else void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box) { const Size& cnv_size = get_canvas_size(); m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height()); m_dirty = true; } +#endif // ENABLE_THUMBNAIL_GENERATOR void GLCanvas3D::_refresh_if_shown_on_screen() { @@ -4025,13 +4370,13 @@ void GLCanvas3D::_render_background() const glsafe(::glPopMatrix()); } -void GLCanvas3D::_render_bed(float theta) const +void GLCanvas3D::_render_bed(float theta, bool show_axes) const { float scale_factor = 1.0; #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - m_bed.render(const_cast(*this), theta, scale_factor); + m_bed.render(const_cast(*this), theta, scale_factor, show_axes); } void GLCanvas3D::_render_objects() const @@ -4039,7 +4384,9 @@ void GLCanvas3D::_render_objects() const if (m_volumes.empty()) return; +#if !ENABLE_THUMBNAIL_GENERATOR glsafe(::glEnable(GL_LIGHTING)); +#endif // !ENABLE_THUMBNAIL_GENERATOR glsafe(::glEnable(GL_DEPTH_TEST)); m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); @@ -4083,7 +4430,9 @@ void GLCanvas3D::_render_objects() const m_shader.stop_using(); m_camera_clipping_plane = ClippingPlane::ClipsNothing(); +#if !ENABLE_THUMBNAIL_GENERATOR glsafe(::glDisable(GL_LIGHTING)); +#endif // !ENABLE_THUMBNAIL_GENERATOR } void GLCanvas3D::_render_selection() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0beef094b..66ecf315a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -35,6 +35,9 @@ class GLShader; class ExPolygon; class BackgroundSlicingProcess; class GCodePreviewData; +#if ENABLE_THUMBNAIL_GENERATOR +struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR struct SlicingParameters; enum LayerHeightEditActionType : unsigned int; @@ -131,6 +134,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); class GLCanvas3D { +#if ENABLE_THUMBNAIL_GENERATOR + static const double DefaultCameraZoomToBoxMarginFactor; +#endif // ENABLE_THUMBNAIL_GENERATOR + public: struct GCodePreviewVolumeIndex { @@ -543,6 +550,11 @@ public: bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } void render(); +#if ENABLE_THUMBNAIL_GENERATOR + // printable_only == false -> render also non printable volumes as grayed + // parts_only == false -> render also sla support and pad + void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR void select_all(); void deselect_all(); @@ -662,14 +674,18 @@ private: BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const; +#if ENABLE_THUMBNAIL_GENERATOR + void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor); +#else void _zoom_to_box(const BoundingBoxf3& box); +#endif // ENABLE_THUMBNAIL_GENERATOR void _refresh_if_shown_on_screen(); void _picking_pass() const; void _rectangular_selection_picking_pass() const; void _render_background() const; - void _render_bed(float theta) const; + void _render_bed(float theta, bool show_axes) const; void _render_objects() const; void _render_selection() const; #if ENABLE_RENDER_SELECTION_CENTER @@ -690,6 +706,15 @@ private: void _render_sla_slices() const; void _render_selection_sidebar_hints() const; void _render_undo_redo_stack(const bool is_undo, float pos_x); +#if ENABLE_THUMBNAIL_GENERATOR + void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); + // render thumbnail using an off-screen framebuffer + void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); + // render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported + void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); + // render thumbnail using the default framebuffer + void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR void _update_volumes_hover_state() const; diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 59a34d8cd..1a887d543 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -187,6 +187,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; bool GLCanvas3DManager::s_compressed_textures_supported = false; +GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None; GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() @@ -267,6 +268,13 @@ void GLCanvas3DManager::init_gl() else s_compressed_textures_supported = false; + if (GLEW_ARB_framebuffer_object) + s_framebuffers_type = FB_Arb; + else if (GLEW_EXT_framebuffer_object) + s_framebuffers_type = FB_Ext; + else + s_framebuffers_type = FB_None; + if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) { // Complain about the OpenGL version. wxString message = wxString::Format( diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 760266a27..940e0230a 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -30,6 +30,13 @@ struct Camera; class GLCanvas3DManager { public: + enum EFramebufferType : unsigned char + { + FB_None, + FB_Arb, + FB_Ext + }; + class GLInfo { mutable bool m_detected; @@ -77,6 +84,7 @@ private: bool m_gl_initialized; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; + static EFramebufferType s_framebuffers_type; public: GLCanvas3DManager(); @@ -97,6 +105,8 @@ public: static bool can_multisample() { return s_multisample == MS_Enabled; } static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } + static bool are_framebuffers_supported() { return (s_framebuffers_type != FB_None); } + static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow *parent); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ef6a7d3c5..c3683b8b0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -31,6 +31,9 @@ #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r/GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" @@ -1901,6 +1904,11 @@ struct Plater::priv bool can_set_instance_to_object() const; bool can_mirror() const; +#if ENABLE_THUMBNAIL_GENERATOR + void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); + void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR + void msw_rescale_object_menu(); // returns the path to project file with the given extension (none if extension == wxEmptyString) @@ -1968,6 +1976,17 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); +#if ENABLE_THUMBNAIL_GENERATOR + 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 task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { + generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); + }); + std::future result = task.get_future(); + wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); }); + result.wait(); + }); +#endif // ENABLE_THUMBNAIL_GENERATOR background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); // Default printer technology for default config. @@ -3481,6 +3500,26 @@ bool Plater::priv::init_object_menu() return true; } +#if ENABLE_THUMBNAIL_GENERATOR +void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background); +} + +void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +{ + thumbnails.clear(); + for (const Vec2d& size : sizes) + { + thumbnails.push_back(ThumbnailData()); + Point isize(size); // round to ints + generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background); + if (!thumbnails.back().is_valid()) + thumbnails.pop_back(); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void Plater::priv::msw_rescale_object_menu() { for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu }) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index d2503d349..69e6c86d5 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -426,7 +426,8 @@ const std::vector& Preset::printer_options() "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", "machine_min_extruding_rate", "machine_min_travel_rate", - "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e" + "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e", + "thumbnails" }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 79d49f13a..f6167ae74 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -93,6 +93,7 @@ PresetBundle::PresetBundle() : preset.config.optptr("printer_vendor", true); preset.config.optptr("printer_model", true); preset.config.optptr("printer_variant", true); + preset.config.optptr("thumbnails", true); if (i == 0) { preset.config.optptr("default_print_profile", true); preset.config.option("default_filament_profile", true)->values = { "" }; -- cgit v1.2.3