diff --git a/CMakeLists.txt b/CMakeLists.txt index a839fd24f..00c7e171f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,15 +45,27 @@ option(SLIC3R_ALPHA "Development/Experimental version; use separate set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") +set(IS_CROSS_COMPILE FALSE) + if (APPLE) set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_APPBUNDLE LAST) + list(FIND CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} _arch_idx) + if (CMAKE_OSX_ARCHITECTURES AND _arch_idx LESS 0) + set(IS_CROSS_COMPILE TRUE) + endif () endif () # Proposal for C++ unit tests and sandboxes option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF) option(SLIC3R_BUILD_TESTS "Build unit tests" OFF) +if (IS_CROSS_COMPILE) + message("Detected cross compilation setup. Tests and encoding checks will be forcedly disabled!") + set(SLIC3R_PERL_XS OFF CACHE BOOL "" FORCE) + set(SLIC3R_BUILD_TESTS OFF CACHE BOOL "" FORCE) +endif () + # Print out the SLIC3R_* cache options get_cmake_property(_cache_vars CACHE_VARIABLES) list (SORT _cache_vars) diff --git a/cmake/modules/OpenVDBUtils.cmake b/cmake/modules/OpenVDBUtils.cmake index f64eda6f2..f79862b83 100644 --- a/cmake/modules/OpenVDBUtils.cmake +++ b/cmake/modules/OpenVDBUtils.cmake @@ -157,7 +157,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT) endif() set(_OpenVDB_ABI) - string(REGEX REPLACE ".*abi([0-9]*).*" "\\1" _OpenVDB_ABI ${_VDB_PRINT_VERSION_STRING}) + if (_VDB_PRINT_VERSION_STRING) + string(REGEX REPLACE ".*abi([0-9]*).*" "\\1" _OpenVDB_ABI ${_VDB_PRINT_VERSION_STRING}) + endif () if(${_OpenVDB_ABI} STREQUAL ${_VDB_PRINT_VERSION_STRING}) set(_OpenVDB_ABI "") endif() diff --git a/cmake/modules/bin2h.cmake b/cmake/modules/bin2h.cmake new file mode 100644 index 000000000..b838650f3 --- /dev/null +++ b/cmake/modules/bin2h.cmake @@ -0,0 +1,89 @@ +# Source: https://gist.github.com/sivachandran/3a0de157dccef822a230#file-bin2h-cmake +# Added modifications to suit prusaslicer +include(CMakeParseArguments) + +# Function to wrap a given string into multiple lines at the given column position. +# Parameters: +# VARIABLE - The name of the CMake variable holding the string. +# AT_COLUMN - The column position at which string will be wrapped. +function(WRAP_STRING) + set(oneValueArgs VARIABLE AT_COLUMN) + cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN}) + + string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength) + math(EXPR offset "0") + + while(stringLength GREATER 0) + + if(stringLength GREATER ${WRAP_STRING_AT_COLUMN}) + math(EXPR length "${WRAP_STRING_AT_COLUMN}") + else() + math(EXPR length "${stringLength}") + endif() + + string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line) + set(lines "${lines}\n${line}") + + math(EXPR stringLength "${stringLength} - ${length}") + math(EXPR offset "${offset} + ${length}") + endwhile() + + set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE) +endfunction() + +# Function to embed contents of a file as byte array in C/C++ header file(.h). The header file +# will contain a byte array and integer variable holding the size of the array. +# Parameters +# SOURCE_FILE - The path of source file whose contents will be embedded in the header file. +# VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append +# to this name and will be used a variable name for size variable. +# HEADER_FILE - The path of header file. +# APPEND - If specified appends to the header file instead of overwriting it +# NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be +# useful if the source file is a text file and we want to use the file contents +# as string. But the size variable holds size of the byte array without this +# null byte. +# Usage: +# bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG") +function(BIN2H) + set(options APPEND NULL_TERMINATE ADD_WARNING_TEXT) + set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE) + cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN}) + + # reads source file contents as hex string + file(READ ${BIN2H_SOURCE_FILE} hexString HEX) + string(LENGTH ${hexString} hexStringLength) + + # appends null byte if asked + if(BIN2H_NULL_TERMINATE) + set(hexString "${hexString}00") + endif() + + # wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line) + wrap_string(VARIABLE hexString AT_COLUMN 32) + math(EXPR arraySize "${hexStringLength} / 2") + + # adds '0x' prefix and comma suffix before and after every byte respectively + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString}) + # removes trailing comma + string(REGEX REPLACE ", $" "" arrayValues ${arrayValues}) + + # converts the variable name into proper C identifier + string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) + # string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) + + # declares byte array and the length variables + set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };") + set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};") + set(warnTxt "") + if (BIN2H_ADD_WARNING_TEXT) + set(warnTxt "/* WARN: This file is auto-generated from ${BIN2H_SOURCE_FILE} */\n") + endif () + + set(declarations "${warnTxt}${arrayDefinition}\n\n${arraySizeDefinition}\n\n") + if(BIN2H_APPEND) + file(APPEND ${BIN2H_HEADER_FILE} "${declarations}") + else() + file(WRITE ${BIN2H_HEADER_FILE} "${declarations}") + endif() +endfunction() \ No newline at end of file diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a0bd7f24a..08bcf49c9 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -32,6 +32,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -597,6 +598,9 @@ bool CLI::setup(int argc, char **argv) } } + // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. + detect_platform(); + boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); // Path from the Slic3r binary to its resources. diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index 16bb3daab..a470d081d 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -133,16 +133,16 @@ void stl_fix_normal_directions(stl_file *stl) // Initialize list that keeps track of already fixed facets. std::vector norm_sw(stl->stats.number_of_facets, 0); // Initialize list that keeps track of reversed facets. - std::vector reversed_ids(stl->stats.number_of_facets, 0); + std::vector reversed_ids; + reversed_ids.reserve(stl->stats.number_of_facets); int facet_num = 0; - int reversed_count = 0; // If normal vector is not within tolerance and backwards: // Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances // of it being wrong randomly are low if most of the triangles are right: if (check_normal_vector(stl, 0, 0)) { reverse_facet(stl, 0); - reversed_ids[reversed_count ++] = 0; + reversed_ids.emplace_back(0); } // Say that we've fixed this facet: @@ -159,13 +159,13 @@ void stl_fix_normal_directions(stl_file *stl) if (stl->neighbors_start[facet_num].neighbor[j] != -1) { if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { // trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) - for (int id = reversed_count - 1; id >= 0; -- id) + for (int id = int(reversed_ids.size()) - 1; id >= 0; -- id) reverse_facet(stl, reversed_ids[id]); force_exit = true; break; } reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); - reversed_ids[reversed_count ++] = stl->neighbors_start[facet_num].neighbor[j]; + reversed_ids.emplace_back(stl->neighbors_start[facet_num].neighbor[j]); } } // If this edge of the facet is connected: @@ -188,6 +188,7 @@ void stl_fix_normal_directions(stl_file *stl) // Get next facet to fix from top of list. if (head->next != tail) { facet_num = head->next->facet_num; + assert(facet_num < stl->stats.number_of_facets); if (norm_sw[facet_num] != 1) { // If facet is in list mutiple times norm_sw[facet_num] = 1; // Record this one as being fixed. ++ checked; @@ -207,7 +208,7 @@ void stl_fix_normal_directions(stl_file *stl) facet_num = i; if (check_normal_vector(stl, i, 0)) { reverse_facet(stl, i); - reversed_ids[reversed_count++] = i; + reversed_ids.emplace_back(i); } norm_sw[facet_num] = 1; ++ checked; diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index fc01b7d8d..091afc6f9 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -77,22 +77,16 @@ elseif (MINGW) ) endif() -add_executable(avrdude-conf-gen conf-generate.cpp) +include(bin2h) -# Config file embedding -add_custom_command( - DEPENDS avrdude-conf-gen ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h - COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - -add_custom_target(gen_conf_h - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h +bin2h( + SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf + VARIABLE_NAME avrdude_slic3r_conf + HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h + ADD_WARNING_TEXT ) add_library(avrdude STATIC ${AVRDUDE_SOURCES}) -add_dependencies(avrdude gen_conf_h) add_executable(avrdude-slic3r main-standalone.cpp) target_link_libraries(avrdude-slic3r avrdude) diff --git a/src/avrdude/config.c b/src/avrdude/config.c index 3c95c34ff..0dec3c47b 100644 --- a/src/avrdude/config.c +++ b/src/avrdude/config.c @@ -363,6 +363,7 @@ int read_config_builtin() // Note: Can't use yy_scan_buffer, it's buggy (?), leads to fread from a null FILE* // and so unfortunatelly we have to use the copying variant here + // note: changed for avrdude_slic3r_conf_SIZE but i can't build with uper case... if you need it on your os, please use a #ifndef YY_BUFFER_STATE buffer = yy_scan_bytes((const char *)avrdude_slic3r_conf, avrdude_slic3r_conf_size); if (buffer == NULL) { avrdude_message(MSG_INFO, "%s: read_config_builtin: Failed to initialize parsing buffer\n", progname); diff --git a/src/build-utils/CMakeLists.txt b/src/build-utils/CMakeLists.txt index d47e5b97f..464fd9c8f 100644 --- a/src/build-utils/CMakeLists.txt +++ b/src/build-utils/CMakeLists.txt @@ -1,6 +1,11 @@ option(SLIC3R_ENC_CHECK "Verify encoding of source files" 1) +if (IS_CROSS_COMPILE) + # Force disable due to cross compilation. This fact is already printed on cli for users + set(SLIC3R_ENC_CHECK OFF CACHE BOOL "" FORCE) +endif () + if (SLIC3R_ENC_CHECK) add_executable(encoding-check encoding-check.cpp) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 210a7e744..33e79aa88 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -158,6 +158,8 @@ add_library(libslic3r STATIC PerimeterGenerator.hpp PlaceholderParser.cpp PlaceholderParser.hpp + Platform.cpp + Platform.hpp Point.cpp Point.hpp Polygon.cpp diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 43465f5ef..1f84d8e24 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -402,6 +402,10 @@ namespace Slic3r { bool m_check_version; XML_Parser m_xml_parser; + // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state + // after returning from XML_Parse() function, thus we keep the error state here. + bool m_parse_error { false }; + std::string m_parse_error_message; Model* m_model; float m_unit_factor; CurrentObject m_curr_object; @@ -427,7 +431,16 @@ namespace Slic3r { private: void _destroy_xml_parser(); - void _stop_xml_parser(); + void _stop_xml_parser(const std::string& msg = std::string()); + + bool parse_error() const { return m_parse_error; } + const char* parse_error_message() const { + return m_parse_error ? + // The error was signalled by the user code, not the expat parser. + (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : + // The error was signalled by the expat parser. + XML_ErrorString(XML_GetErrorCode(m_xml_parser)); + } bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); @@ -567,10 +580,14 @@ namespace Slic3r { } } - void _3MF_Importer::_stop_xml_parser() + void _3MF_Importer::_stop_xml_parser(const std::string &msg) { - if (m_xml_parser != nullptr) - XML_StopParser(m_xml_parser, false); + assert(! m_parse_error); + assert(m_parse_error_message.empty()); + assert(m_xml_parser != nullptr); + m_parse_error = true; + m_parse_error_message = msg; + XML_StopParser(m_xml_parser, false); } bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) @@ -708,12 +725,12 @@ namespace Slic3r { close_zip_reader(&archive); - for (const IdToModelObjectMap::value_type& object : m_objects) - { - ModelObject *model_object = m_model->objects[object.second]; - ObjectMetadata::VolumeMetadataList volumes; - ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; - + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= m_model->objects.size()) { + add_error("Unable to find object"); + return false; + } + ModelObject* model_object = m_model->objects[object.second]; IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); if (obj_geometry == m_geometries.end()) { @@ -743,6 +760,9 @@ namespace Slic3r { model_object->sla_drain_holes = std::move(obj_drain_holes->second); } + ObjectMetadata::VolumeMetadataList volumes; + ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) { @@ -805,12 +825,13 @@ namespace Slic3r { struct CallbackData { XML_Parser& parser; + _3MF_Importer& importer; const mz_zip_archive_file_stat& stat; - CallbackData(XML_Parser& parser, const mz_zip_archive_file_stat& stat) : parser(parser), stat(stat) {} + CallbackData(XML_Parser& parser, _3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} }; - CallbackData data(m_xml_parser, stat); + CallbackData data(m_xml_parser, *this, stat); mz_bool res = 0; @@ -818,10 +839,9 @@ namespace Slic3r { { res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { CallbackData* data = (CallbackData*)pOpaque; - if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0)) - { + if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); + ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); throw Slic3r::FileIOError(error_buf); } @@ -1419,8 +1439,11 @@ namespace Slic3r { bool _3MF_Importer::_handle_end_model() { // deletes all non-built or non-instanced objects - for (const IdToModelObjectMap::value_type& object : m_objects) - { + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= m_model->objects.size()) { + add_error("Unable to find object"); + return false; + } ModelObject *model_object = m_model->objects[object.second]; if ((model_object != nullptr) && (model_object->instances.size() == 0)) m_model->delete_object(model_object); @@ -1921,6 +1944,10 @@ namespace Slic3r { for (unsigned int v = 0; v < 3; ++v) { unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; + if (tri_id + 2 >= geometry.vertices.size()) { + add_error("Malformed triangle mesh"); + return false; + } facet.vertex[v] = Vec3f(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); } } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a74861f09..82e9fe192 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -63,23 +63,31 @@ namespace Slic3r struct AMFParserContext { AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : - m_version(0), m_parser(parser), m_model(*model), - m_object(nullptr), - m_volume(nullptr), - m_material(nullptr), - m_instance(nullptr), m_config(config) { m_path.reserve(12); } - void stop() + void stop(const std::string &msg = std::string()) { + assert(! m_error); + assert(m_error_message.empty()); + m_error = true; + m_error_message = msg; XML_StopParser(m_parser, 0); } + bool error() const { return m_error; } + const char* error_message() const { + return m_error ? + // The error was signalled by the user code, not the expat parser. + (m_error_message.empty() ? "Invalid AMF format" : m_error_message.c_str()) : + // The error was signalled by the expat parser. + XML_ErrorString(XML_GetErrorCode(m_parser)); + } + void startElement(const char *name, const char **atts); void endElement(const char *name); void endDocument(); @@ -217,33 +225,37 @@ struct AMFParserContext }; // Version of the amf file - unsigned int m_version; + unsigned int m_version { 0 }; // Current Expat XML parser instance. XML_Parser m_parser; + // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state + // after returning from XML_Parse() function, thus we keep the error state here. + bool m_error { false }; + std::string m_error_message; // Model to receive objects extracted from an AMF file. Model &m_model; // Current parsing path in the XML file. std::vector m_path; // Current object allocated for an amf/object XML subtree. - ModelObject *m_object; + ModelObject *m_object { nullptr }; // Map from obect name to object idx & instances. std::map m_object_instances_map; // Vertices parsed for the current m_object. std::vector m_object_vertices; // Current volume allocated for an amf/object/mesh/volume subtree. - ModelVolume *m_volume; + ModelVolume *m_volume { nullptr }; // Faces collected for the current m_volume. std::vector m_volume_facets; // Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system. Transform3d m_volume_transform; // Current material allocated for an amf/metadata subtree. - ModelMaterial *m_material; + ModelMaterial *m_material { nullptr }; // Current instance allocated for an amf/constellation/instance subtree. - Instance *m_instance; + Instance *m_instance { nullptr }; // Generic string buffer for vertices, face indices, metadata etc. std::string m_value[5]; // Pointer to config to update if config data are stored inside the amf file - DynamicPrintConfig *m_config; + DynamicPrintConfig *m_config { nullptr }; private: AMFParserContext& operator=(AMFParserContext&); @@ -595,9 +607,9 @@ void AMFParserContext::endElement(const char * /* name */) if (strtoul(m_value[0].c_str(), nullptr, 10) < m_object_vertices.size() && strtoul(m_value[1].c_str(), nullptr, 10) < m_object_vertices.size() && strtoul(m_value[2].c_str(), nullptr, 10) < m_object_vertices.size()) { - m_volume_facets.push_back(atoi(m_value[0].c_str())); - m_volume_facets.push_back(atoi(m_value[1].c_str())); - m_volume_facets.push_back(atoi(m_value[2].c_str())); + m_volume_facets.emplace_back(atoi(m_value[0].c_str())); + m_volume_facets.emplace_back(atoi(m_value[1].c_str())); + m_volume_facets.emplace_back(atoi(m_value[2].c_str())); } m_value[0].clear(); m_value[1].clear(); @@ -621,6 +633,10 @@ void AMFParserContext::endElement(const char * /* name */) for (unsigned int v = 0; v < 3; ++v) { unsigned int tri_id = m_volume_facets[i++] * 3; + if (tri_id < 0 || tri_id + 2 >= m_object_vertices.size()) { + this->stop("Malformed triangle mesh"); + return; + } facet.vertex[v] = Vec3f(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); } } @@ -863,10 +879,10 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) break; } int done = feof(pFile); - if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { + if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR || ctx.error()) { printf("AMF parser: Parse error at line %d:\n%s\n", (int)XML_GetCurrentLineNumber(parser), - XML_ErrorString(XML_GetErrorCode(parser))); + ctx.error_message()); break; } if (done) { @@ -917,12 +933,13 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi struct CallbackData { XML_Parser& parser; + AMFParserContext& ctx; const mz_zip_archive_file_stat& stat; - CallbackData(XML_Parser& parser, const mz_zip_archive_file_stat& stat) : parser(parser), stat(stat) {} + CallbackData(XML_Parser& parser, AMFParserContext& ctx, const mz_zip_archive_file_stat& stat) : parser(parser), ctx(ctx), stat(stat) {} }; - CallbackData data(parser, stat); + CallbackData data(parser, ctx, stat); mz_bool res = 0; @@ -930,10 +947,10 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi { res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { CallbackData* data = (CallbackData*)pOpaque; - if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0)) + if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->ctx.error()) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); + ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->ctx.error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); throw Slic3r::FileIOError(error_buf); } diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp new file mode 100644 index 000000000..ae02c42b3 --- /dev/null +++ b/src/libslic3r/Platform.cpp @@ -0,0 +1,71 @@ +#include "Platform.hpp" + +#include +#include + +namespace Slic3r { + +static auto s_platform = Platform::Uninitialized; +static auto s_platform_flavor = PlatformFlavor::Uninitialized; + +void detect_platform() +{ +#if defined(_WIN32) + BOOST_LOG_TRIVIAL(info) << "Platform: Windows"; + s_platform = Platform::Windows; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__APPLE__) + BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__linux__) + BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; + s_platform = Platform::Linux; + s_platform_flavor = PlatformFlavor::GenericLinux; + // Test for Chromium. + { + FILE *f = ::fopen("/proc/version", "rt"); + if (f) { + char buf[4096]; + // Read the 1st line. + if (::fgets(buf, 4096, f)) { + if (strstr(buf, "Chromium OS") != nullptr) { + s_platform_flavor = PlatformFlavor::LinuxOnChromium; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: LinuxOnChromium"; + } else if (strstr(buf, "microsoft") != nullptr || strstr(buf, "Microsoft") != nullptr) { + if (boost::filesystem::exists("/run/WSL") && getenv("WSL_INTEROP") != nullptr) { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL2"; + s_platform_flavor = PlatformFlavor::WSL2; + } else { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL"; + s_platform_flavor = PlatformFlavor::WSL; + } + } + } + ::fclose(f); + } + } +#elif defined(__OpenBSD__) + BOOST_LOG_TRIVIAL(info) << "Platform: OpenBSD"; + s_platform = Platform::BSDUnix; + s_platform_flavor = PlatformFlavor::OpenBSD; +#else + // This should not happen. + BOOST_LOG_TRIVIAL(info) << "Platform: Unknown"; + static_assert(false, "Unknown platform detected"); + s_platform = Platform::Unknown; + s_platform_flavor = PlatformFlavor::Unknown; +#endif +} + +Platform platform() +{ + return s_platform; +} + +PlatformFlavor platform_flavor() +{ + return s_platform_flavor; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp new file mode 100644 index 000000000..735728e89 --- /dev/null +++ b/src/libslic3r/Platform.hpp @@ -0,0 +1,41 @@ +#ifndef SLIC3R_Platform_HPP +#define SLIC3R_Platform_HPP + +namespace Slic3r { + +enum class Platform +{ + Uninitialized, + Unknown, + Windows, + OSX, + Linux, + BSDUnix, +}; + +enum class PlatformFlavor +{ + Uninitialized, + Unknown, + // For Windows and OSX, until we need to be more specific. + Generic, + // For Platform::Linux + GenericLinux, + LinuxOnChromium, + // Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel) + WSL, + // Microsoft's Windows on Linux, version 2 (virtual machine) + WSL2, + // For Platform::BSDUnix + OpenBSD, +}; + +// To be called on program start-up. +void detect_platform(); + +Platform platform(); +PlatformFlavor platform_flavor(); + +} // namespace Slic3r + +#endif // SLIC3R_Platform_HPP diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 0b7901efb..b865378a8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -108,4 +108,14 @@ #define ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING (1 && ENABLE_2_3_0_RC1) + +//==================== +// 2.3.1.alpha1 techs +//==================== +#define ENABLE_2_3_1_ALPHA1 1 + +#define ENABLE_SPLITTED_VERTEX_BUFFER (1 && ENABLE_2_3_1_ALPHA1) +#define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_SPLITTED_VERTEX_BUFFER) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index adb38700c..0d03de17b 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include "Platform.hpp" #include "Time.hpp" #ifdef WIN32 @@ -22,6 +23,11 @@ #ifdef __APPLE__ #include #endif + #ifdef __linux__ + #include + #include + #include + #endif #endif #include @@ -417,6 +423,140 @@ std::error_code rename_file(const std::string &from, const std::string &to) #endif } +#ifdef __linux__ +// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, +// for example ChromeOS Linux integration or FlashAIR WebDAV. +// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, +// and only features supported by Linux 3.10 (on our build server with CentOS 7) are kept, namely sendfile with ranges and statx() are not supported. +bool copy_file_linux(const boost::filesystem::path &from, const boost::filesystem::path &to, boost::system::error_code &ec) +{ + using namespace boost::filesystem; + + struct fd_wrapper + { + int fd { -1 }; + fd_wrapper() = default; + explicit fd_wrapper(int fd) throw() : fd(fd) {} + ~fd_wrapper() throw() { if (fd >= 0) ::close(fd); } + }; + + ec.clear(); + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (infile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + fail: + ec.assign(err, boost::system::system_category()); + return false; + } + break; + } + + struct ::stat from_stat; + if (::fstat(infile.fd, &from_stat) != 0) { + fail_errno: + err = errno; + goto fail; + } + + const mode_t from_mode = from_stat.st_mode; + if (!S_ISREG(from_mode)) { + err = ENOSYS; + goto fail; + } + + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + mode_t to_mode = from_mode | S_IWUSR; + int oflag = O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC; + + while (true) { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + goto fail; + } + break; + } + + struct ::stat to_stat; + if (::fstat(outfile.fd, &to_stat) != 0) + goto fail_errno; + + to_mode = to_stat.st_mode; + if (!S_ISREG(to_mode)) { + err = ENOSYS; + goto fail; + } + + if (from_stat.st_dev == to_stat.st_dev && from_stat.st_ino == to_stat.st_ino) { + err = EEXIST; + goto fail; + } + + //! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. + //FIXME Vojtech: This is a copy loop valid for Linux 2.6.33 and newer. + // copy_file_data_copy_file_range() supports cross-filesystem copying since 5.3, but Vojtech did not want to polute this + // function with that, we don't think the performance gain is worth it for the types of files we are copying, + // and our build server based on CentOS 7 with Linux 3.10 does not support that anyways. + { + // sendfile will not send more than this amount of data in one call + constexpr std::size_t max_send_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (off_t(offset) < from_stat.st_size) { + uintmax_t size_left = from_stat.st_size - offset; + std::size_t size_to_copy = max_send_size; + if (size_left < static_cast(max_send_size)) + size_to_copy = static_cast(size_left); + ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); + if (sz < 0) { + err = errno; + if (err == EINTR) + continue; + if (err == 0) + break; + goto fail; // err already contains the error code + } + offset += sz; + } + } + + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // Ignore that. 9p filesystem does not allow fmod(). + BOOST_LOG_TRIVIAL(info) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message() << + " This may be expected when writing to a 9p filesystem."; + } else { + // Generic linux. Write out an error to console. At least we may get some feedback. + BOOST_LOG_TRIVIAL(error) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message(); + } + } + + // Note: Use fsync/fdatasync followed by close to avoid dealing with the possibility of close failing with EINTR. + // Even if close fails, including with EINTR, most operating systems (presumably, except HP-UX) will close the + // file descriptor upon its return. This means that if an error happens later, when the OS flushes data to the + // underlying media, this error will go unnoticed and we have no way to receive it from close. Calling fsync/fdatasync + // ensures that all data have been written, and even if close fails for some unfathomable reason, we don't really + // care at that point. + err = ::fdatasync(outfile.fd); + if (err != 0) + goto fail_errno; + + return true; +} +#endif // __linux__ + CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message) { const boost::filesystem::path source(from); @@ -434,7 +574,13 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s if (ec) BOOST_LOG_TRIVIAL(debug) << "boost::filesystem::permisions before copy error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); +#ifdef __linux__ + // We want to allow copying files on Linux to succeed even if changing the file attributes fails. + // That may happen when copying on some exotic file system, for example Linux on Chrome. + copy_file_linux(source, target, ec); +#else // __linux__ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); +#endif // __linux__ if (ec) { error_message = ec.message(); return FAIL_COPY_FILE; diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 3159f9598..67b30d309 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -108,8 +108,7 @@ bool BonjourDialog::show_and_lookup() timer->SetOwner(this); timer_state = 1; timer->Start(1000); - wxTimerEvent evt_dummy; - on_timer(evt_dummy); + on_timer_process(); // The background thread needs to queue messages for this dialog // and for that it needs a valid pointer to it (mandated by the wxWidgets API). @@ -214,18 +213,27 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) } void BonjourDialog::on_timer(wxTimerEvent &) +{ + on_timer_process(); +} + +// This is here so the function can be bound to wxEVT_TIMER and also called +// explicitly (wxTimerEvent should not be created by user code). +void BonjourDialog::on_timer_process() { const auto search_str = _utf8(L("Searching for devices")); - if (timer_state > 0) { - const std::string dots(timer_state, '.'); + if (timer_state > 0) { + const std::string dots(timer_state, '.'); label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str())); - timer_state = (timer_state) % 3 + 1; - } else { + timer_state = (timer_state) % 3 + 1; + } else { label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str())); - timer->Stop(); - } + timer->Stop(); + } } + + } diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp index a9a33d522..def0838d7 100644 --- a/src/slic3r/GUI/BonjourDialog.hpp +++ b/src/slic3r/GUI/BonjourDialog.hpp @@ -43,6 +43,7 @@ private: void on_reply(BonjourReplyEvent &); void on_timer(wxTimerEvent &); + void on_timer_process(); }; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 16b40c041..42f24a601 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -173,6 +173,26 @@ void Control::msw_rescale() GetParent()->Layout(); } +void Control::sys_color_changed() +{ + m_bmp_add_tick_on .msw_rescale(); + m_bmp_add_tick_off.msw_rescale(); + m_bmp_del_tick_on .msw_rescale(); + m_bmp_del_tick_off.msw_rescale(); + m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); + + m_bmp_one_layer_lock_on .msw_rescale(); + m_bmp_one_layer_lock_off .msw_rescale(); + m_bmp_one_layer_unlock_on .msw_rescale(); + m_bmp_one_layer_unlock_off.msw_rescale(); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth(); + + m_bmp_revert.msw_rescale(); + m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); + m_bmp_cog.msw_rescale(); + m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); +} + int Control::GetActiveValue() const { return m_selection == ssLower ? @@ -310,7 +330,7 @@ double Control::get_double_value(const SelectedSlider& selection) { if (m_values.empty() || m_lower_value<0) return 0.0; - if (m_values.size() <= m_higher_value) { + if (m_values.size() <= size_t(m_higher_value)) { correct_higher_value(); return m_values.back(); } @@ -614,7 +634,7 @@ static std::string short_and_splitted_time(const std::string& time) ::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds); else if (hours > 10 && minutes > 10 && seconds > 10) ::sprintf(buffer, "%dh\n%dm\n%ds", hours, minutes, seconds); - else if (minutes < 10 && seconds > 10 || minutes > 10 && seconds < 10) + else if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10)) ::sprintf(buffer, "%dh\n%dm%ds", hours, minutes, seconds); else ::sprintf(buffer, "%dh%dm\n%ds", hours, minutes, seconds); @@ -632,15 +652,15 @@ static std::string short_and_splitted_time(const std::string& time) wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer*/) const { - const int value = tick; + const size_t value = tick; if (m_label_koef == 1.0 && m_values.empty()) - return wxString::Format("%d", value); + return wxString::Format("%lu", static_cast(value)); if (value >= m_values.size()) return "ErrVal"; if (m_draw_mode == dmSequentialGCodeView) - return wxString::Format("%d", static_cast(m_values[value])); + return wxString::Format("%lu", static_cast(m_values[value])); else { if (label_type == ltEstimatedTime) { return (value < m_layers_times.size()) ? short_and_splitted_time(get_time_dhms(m_layers_times[value])) : ""; @@ -755,7 +775,7 @@ void Control::draw_ticks_pair(wxDC& dc, wxCoord pos, wxCoord mid, int tick_len) dc.DrawLine(mid - (mid_space + tick_len), pos, mid - mid_space, pos); is_horizontal() ? dc.DrawLine(pos, mid + (mid_space + tick_len), pos, mid + mid_space) : dc.DrawLine(mid + (mid_space + tick_len), pos, mid + mid_space, pos); -}; +} void Control::draw_ticks(wxDC& dc) { @@ -766,8 +786,8 @@ void Control::draw_ticks(wxDC& dc) int height, width; get_size(&width, &height); const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; - for (auto tick : m_ticks.ticks) { - if (tick.tick >= m_values.size()) { + for (const TickCode& tick : m_ticks.ticks) { + if (size_t(tick.tick) >= m_values.size()) { // The case when OnPaint is called before m_ticks.ticks data are updated (specific for the vase mode) break; } @@ -920,7 +940,6 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do auto end_it = count == 1 ? values.end() : values.begin() + lround(values.size() / count); while (pow < 3) { - int tick = 0; for (int istep : {1, 2, 5}) { double val = (double)istep * std::pow(10,pow); auto val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); @@ -963,7 +982,7 @@ void Control::draw_ruler(wxDC& dc) dc.SetTextForeground(GREY_PEN.GetColour()); if (m_ruler.long_step < 0) - for (int tick = 1; tick < m_values.size(); tick++) { + for (size_t tick = 1; tick < m_values.size(); tick++) { wxCoord pos = get_position_from_value(tick); draw_ticks_pair(dc, pos, mid, 5); draw_tick_text(dc, wxPoint(mid, pos), tick); @@ -979,10 +998,10 @@ void Control::draw_ruler(wxDC& dc) } }; - double short_tick; - int tick = 0; - double value = 0.0; - int sequence = 0; + double short_tick = std::nan(""); + int tick = 0; + double value = 0.0; + int sequence = 0; int prev_y_pos = -1; wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; @@ -993,13 +1012,14 @@ void Control::draw_ruler(wxDC& dc) if (value > m_values.back() && sequence < m_ruler.count) { value = m_ruler.long_step; for (; tick < values_size; tick++) - if (m_values[tick] < value) - break; - // short ticks from the last tick to the end of current sequence - draw_short_ticks(dc, short_tick, tick); - sequence++; - } - short_tick = tick; + if (m_values[tick] < value) + break; + // short ticks from the last tick to the end of current sequence + assert(! std::isnan(short_tick)); + draw_short_ticks(dc, short_tick, tick); + sequence++; + } + short_tick = tick; for (; tick < values_size; tick++) { if (m_values[tick] == value) diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index af6eb8a01..532d40fdf 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -192,6 +192,7 @@ public: ~Control() {} void msw_rescale(); + void sys_color_changed(); int GetMinValue() const { return m_min_value; } int GetMaxValue() const { return m_max_value; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e5676c12d..138db1348 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -83,6 +83,18 @@ static float round_to_nearest(float value, unsigned int decimals) return res; } +#if ENABLE_SPLITTED_VERTEX_BUFFER +void GCodeViewer::VBuffer::reset() +{ + // release gpu memory + if (!vbos.empty()) { + glsafe(::glDeleteBuffers(static_cast(vbos.size()), static_cast(vbos.data()))); + vbos.clear(); + } + sizes.clear(); + count = 0; +} +#else void GCodeViewer::VBuffer::reset() { // release gpu memory @@ -93,15 +105,27 @@ void GCodeViewer::VBuffer::reset() count = 0; } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::IBuffer::reset() { +#if ENABLE_SPLITTED_VERTEX_BUFFER + // release gpu memory + if (ibo > 0) { + glsafe(::glDeleteBuffers(1, &ibo)); + ibo = 0; + } +#else // release gpu memory if (id > 0) { glsafe(::glDeleteBuffers(1, &id)); id = 0; } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER +#if ENABLE_SPLITTED_VERTEX_BUFFER + vbo = 0; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER count = 0; } @@ -123,6 +147,19 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const case EMoveType::Unretract: case EMoveType::Extrude: { // use rounding to reduce the number of generated paths +#if ENABLE_SPLITTED_VERTEX_BUFFER +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position[2] <= sub_paths.front().first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && + matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); +#else + return type == move.type && move.position[2] <= sub_paths.front().position[2] && 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; +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#else #if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position[2] <= first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && @@ -134,6 +171,7 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && extruder_temp == move.temperature; #endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#endif // ENABLE_SPLITTED_VERTEX_BUFFER } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -160,6 +198,17 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths +#if ENABLE_SPLITTED_VERTEX_BUFFER +#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE + paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, + round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, + move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); +#else + paths.push_back({ move.type, move.extrusion_role, 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, { { endpoint, endpoint } } }); +#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#else #if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE 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, @@ -169,6 +218,7 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi 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, move.layer_duration, move.time, move.temperature }); #endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE +#endif // ENABLE_SPLITTED_VERTEX_BUFFER } float GCodeViewer::Extrusions::Range::step_size(bool log) const @@ -214,6 +264,23 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value, boo return ret; } +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS +GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { + if (ibo > 0) + glsafe(::glDeleteBuffers(1, &ibo)); +} + +void GCodeViewer::SequentialRangeCap::reset() { + if (ibo > 0) + glsafe(::glDeleteBuffers(1, &ibo)); + + buffer = nullptr; + ibo = 0; + vbo = 0; + color = { 0.0f, 0.0f, 0.0f }; +} +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + void GCodeViewer::SequentialView::Marker::init() { m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); @@ -311,10 +378,97 @@ const std::vector GCodeViewer::Range_Colors {{ { 0.581f, 0.149f, 0.087f } // reddish }}; +GCodeViewer::GCodeViewer() +{ + //load Extrusion colors + { + this->Extrusion_Role_Colors = { + { 0.75f, 0.75f, 0.75f }, // erNone + { 1.00f, 0.90f, 0.30f }, // 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.25f, 0.25f }, // erTopSolidInfill + { 1.00f, 0.55f, 0.41f }, // erIroning + { 0.30f, 0.50f, 0.73f }, // erBridgeInfill + { 0.00f, 1.00f, 0.40f }, // erThinWall + { 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.70f, 0.70f, 0.70f }, // erMilling + { 0.37f, 0.82f, 0.58f }, // erCustom + { 0.00f, 0.00f, 0.00f } // erMixed + }; + + + //try to load colors from ui file + boost::property_tree::ptree tree_colors; + boost::filesystem::path path_colors = boost::filesystem::path(resources_dir()) / "ui_layout" / "colors.ini"; + try { + boost::nowide::ifstream ifs; + ifs.imbue(boost::locale::generator()("en_US.UTF-8")); + ifs.open(path_colors.string()); + boost::property_tree::read_ini(ifs, tree_colors); + + for (int i = 0; i < Extrusion_Role_Colors.size(); i++) { + std::string color_code = tree_colors.get(ExtrusionEntity::role_to_string((ExtrusionRole)i)); + if (color_code.length() > 5) { + wxColour color; + color.Set((color_code[0] == '#') ? color_code : ("#" + color_code)); + Extrusion_Role_Colors[i][0] = color.Red() / 256.f; + Extrusion_Role_Colors[i][1] = color.Green() / 256.f; + Extrusion_Role_Colors[i][2] = color.Blue() / 256.f; + } + } + } + catch (const std::ifstream::failure & err) { + trace(1, (std::string("The color file cannot be loaded. Reason: ") + err.what(), path_colors.string()).c_str()); + } + catch (const std::runtime_error & err) { + trace(1, (std::string("Failed loading the color file. Reason: ") + err.what(), path_colors.string()).c_str()); + } + } + + // initializes non OpenGL data of TBuffers + // OpenGL data are initialized into render().init_gl_data() + 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::Wipe: + 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.skip_invisible_moves = true; +} + void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { - init(); - // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) return; @@ -325,6 +479,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); + if (m_layers.empty()) return; @@ -476,9 +631,6 @@ void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* conf void GCodeViewer::reset() { - m_initialized = false; - m_gl_data_initialized = false; - m_moves_count = 0; for (TBuffer& buffer : m_buffers) { buffer.reset(); @@ -487,8 +639,9 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); m_tool_colors = std::vector(); - - m_extruders_count = 0; m_filament_colors = std::vector(); + m_extruders_count = 0; + m_extruders_count = 0; + m_filament_colors = std::vector(); m_extruder_ids = std::vector(); m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); @@ -506,10 +659,11 @@ void GCodeViewer::reset() void GCodeViewer::render() const { auto init_gl_data = [this]() { - // initializes opengl data of TBuffers - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - switch (buffer_type(i)) { + // initializes opengl data of TBuffers + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = const_cast(m_buffers[i]); + switch (buffer_type(i)) + { default: { break; } case EMoveType::Tool_change: case EMoveType::Color_change: @@ -532,18 +686,18 @@ void GCodeViewer::render() const } } - // initializes tool marker - m_sequential_view.marker.init(); + // initializes tool marker + const_cast(&m_sequential_view)->marker.init(); - // initializes point sizes - std::array point_sizes; - ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); - m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; - m_gl_data_initialized = true; + // initializes point sizes + std::array point_sizes; + ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); + *const_cast*>(&m_detected_point_sizes) = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; + *const_cast(&m_gl_data_initialized) = true; }; #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.reset_opengl(); + const_cast(&m_statistics)->reset_opengl(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // OpenGL data must be initialized after the glContext has been created. @@ -556,9 +710,10 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.render(); + SequentialView* sequential_view = const_cast(&m_sequential_view); + if (sequential_view->current.last != sequential_view->endpoints.last) { + sequential_view->marker.set_world_position(sequential_view->current_position); + sequential_view->marker.render(); } render_shells(); render_legend(); @@ -567,14 +722,25 @@ void GCodeViewer::render() const #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#if ENABLE_SPLITTED_VERTEX_BUFFER +bool GCodeViewer::can_export_toolpaths() const +{ + return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; +} +#endif // ENABLE_SPLITTED_VERTEX_BUFFER + void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) { auto is_visible = [this](unsigned int id) { for (const TBuffer& buffer : m_buffers) { if (buffer.visible) { for (const Path& path : buffer.paths) { +#if ENABLE_SPLITTED_VERTEX_BUFFER + if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) +#else if (path.first.s_id <= id && id <= path.last.s_id) - return true; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER + return true; } } } @@ -686,15 +852,24 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const wxBusyCursor busy; // the data needed is contained into the Extrude TBuffer - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (!buffer.has_data()) + const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; + if (!t_buffer.has_data()) return; +#if ENABLE_SPLITTED_VERTEX_BUFFER + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) + return; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER + // collect color information to generate materials std::vector colors; - for (const RenderPath& path : buffer.render_paths) { + for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } +#if ENABLE_SPLITTED_VERTEX_BUFFER + std::sort(colors.begin(), colors.end()); + colors.erase(std::unique(colors.begin(), colors.end()), colors.end()); +#endif // ENABLE_SPLITTED_VERTEX_BUFFER // save materials file boost::filesystem::path mat_filename(filename); @@ -712,7 +887,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const 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, "Kd %g %g %g\n", color[0], color[1], color[2]); fprintf(fp, "Ks 0 0 0\n"); } @@ -729,18 +904,105 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by " SLIC3R_BUILD_ID " " SLIC3R_BASED_ON "\n"); fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); +#if ENABLE_SPLITTED_VERTEX_BUFFER + const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); + + std::vector out_vertices; + std::vector out_normals; + + struct VerticesOffset + { + unsigned int vbo; + size_t offset; + }; + std::vector vertices_offsets; + vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 }); + + // get vertices/normals data from vertex buffers on gpu + for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) { + const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float); + VertexBuffer vertices(floats_count); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i])); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(t_buffer.vertices.sizes[i]), static_cast(vertices.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + const size_t vertices_count = floats_count / floats_per_vertex; + for (size_t j = 0; j < vertices_count; ++j) { + const size_t base = j * floats_per_vertex; + out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] }); + out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] }); + } + + if (i < t_buffer.vertices.vbos.size() - 1) + vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count }); + } + + // save vertices to file + fprintf(fp, "\n# vertices\n"); + for (const Vec3f& v : out_vertices) { + fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); + } + + // save normals to file + fprintf(fp, "\n# normals\n"); + for (const Vec3f& n : out_normals) { + fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); + } + + size_t i = 0; + for (const Color& color : colors) { + // save material triangles to file + fprintf(fp, "\nusemtl material_%zu\n", i + 1); + fprintf(fp, "# triangles material %zu\n", i + 1); + + for (const RenderPath& render_path : t_buffer.render_paths) { + if (render_path.color != color) + continue; + + const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id]; + size_t vertices_offset = 0; + for (size_t j = 0; j < vertices_offsets.size(); ++j) { + const VerticesOffset& offset = vertices_offsets[j]; + if (offset.vbo == ibuffer.vbo) { + vertices_offset = offset.offset; + break; + } + } + + // get indices data from index buffer on gpu + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo)); + for (size_t j = 0; j < render_path.sizes.size(); ++j) { + IndexBuffer indices(render_path.sizes[j]); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(render_path.offsets[j]), + static_cast(render_path.sizes[j] * sizeof(IBufferType)), static_cast(indices.data()))); + + const size_t triangles_count = render_path.sizes[j] / 3; + for (size_t k = 0; k < triangles_count; ++k) { + const size_t base = k * 3; + const size_t v1 = 1 + static_cast(indices[base + 0]) + vertices_offset; + const size_t v2 = 1 + static_cast(indices[base + 1]) + vertices_offset; + const size_t v3 = 1 + static_cast(indices[base + 2]) + vertices_offset; + if (v1 != v2) + // do not export dummy triangles + fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3); + } + } + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + ++i; + } +#else // get vertices data from vertex buffer on gpu - size_t floats_per_vertex = buffer.vertices.vertex_size_floats(); - std::vector vertices = std::vector(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())); + size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); + VertexBuffer vertices = VertexBuffer(t_buffer.vertices.count * floats_per_vertex); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, t_buffer.vertices.data_size_bytes(), vertices.data())); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // get indices data from index buffer on gpu MultiIndexBuffer indices; - for (size_t i = 0; i < buffer.indices.size(); ++i) { - indices.push_back(IndexBuffer(buffer.indices[i].count)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[i].id)); + for (size_t i = 0; i < t_buffer.indices.size(); ++i) { + indices.push_back(IndexBuffer(t_buffer.indices[i].count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t_buffer.indices[i].id)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } @@ -794,15 +1056,16 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const }; size_t out_vertices_count = 0; - unsigned int indices_per_segment = buffer.indices_per_segment(); - unsigned int start_vertex_offset = buffer.start_segment_vertex_offset(); - unsigned int end_vertex_offset = buffer.end_segment_vertex_offset(); + unsigned int indices_per_segment = t_buffer.indices_per_segment(); + unsigned int start_vertex_offset = t_buffer.start_segment_vertex_offset(); + unsigned int end_vertex_offset = t_buffer.end_segment_vertex_offset(); size_t i = 0; - for (const RenderPath& render_path : buffer.render_paths) { + for (const RenderPath& render_path : t_buffer.render_paths) { // get paths segments from buffer paths - const IndexBuffer& ibuffer = indices[render_path.index_buffer_id]; - const Path& path = buffer.paths[render_path.path_id]; + const IndexBuffer& ibuffer = indices[render_path.ibuffer_id]; + const Path& path = t_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); @@ -968,106 +1231,861 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const ++ i; } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER fclose(fp); } -void GCodeViewer::init() +#if ENABLE_SPLITTED_VERTEX_BUFFER +void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { - if (m_initialized) - return; + // max index buffer size, in bytes + static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; - //load Extrusion colors - { - this->Extrusion_Role_Colors = { - { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 0.90f, 0.30f }, // 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.25f, 0.25f }, // erTopSolidInfill - { 1.00f, 0.55f, 0.41f }, // erIroning - { 0.30f, 0.50f, 0.73f }, // erBridgeInfill - { 0.00f, 1.00f, 0.40f }, // erThinWall - { 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.70f, 0.70f, 0.70f }, // erMilling - { 0.37f, 0.82f, 0.58f }, // erCustom - { 0.00f, 0.00f, 0.00f } // erMixed + auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { + int64_t vertices_size = 0; + for (const MultiVertexBuffer& buffers : vertices) { + for (const VertexBuffer& buffer : buffers) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float); + } + } + int64_t indices_size = 0; + for (const MultiIndexBuffer& buffers : indices) { + for (const IndexBuffer& buffer : buffers) { + indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType); + } + } + log_memory_used(label, vertices_size + indices_size); + }; + + // format data into the buffers to be rendered as points + auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { + vertices.push_back(curr.position[0]); + vertices.push_back(curr.position[1]); + vertices.push_back(curr.position[2]); + }; + auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { + buffer.add_path(curr, ibuffer_id, indices.size(), move_id); + indices.push_back(static_cast(indices.size())); + }; + + // format data into the buffers to be rendered as lines + auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { + // 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]; + + auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + // add position + vertices.push_back(vertex.position[0]); + vertices.push_back(vertex.position[1]); + vertices.push_back(vertex.position[2]); + // add normal x component + vertices.push_back(normal_x); }; + // add previous vertex + add_vertex(prev); + // add current vertex + add_vertex(curr); + }; + auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + // add starting index + indices.push_back(static_cast(indices.size())); + buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); + buffer.paths.back().sub_paths.front().first.position = prev.position; + } - //try to load colors from ui file - boost::property_tree::ptree tree_colors; - boost::filesystem::path path_colors = boost::filesystem::path(resources_dir()) / "ui_layout" / "colors.ini"; - try { - boost::nowide::ifstream ifs; - ifs.imbue(boost::locale::generator()("en_US.UTF-8")); - ifs.open(path_colors.string()); - boost::property_tree::read_ini(ifs, tree_colors); + Path& last_path = buffer.paths.back(); + if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { + // add previous index + indices.push_back(static_cast(indices.size())); + } - for (int i = 0; i < Extrusion_Role_Colors.size(); i++) { - std::string color_code = tree_colors.get(ExtrusionEntity::role_to_string((ExtrusionRole)i)); - if (color_code.length() > 5) { - wxColour color; - color.Set((color_code[0] == '#') ? color_code : ("#" + color_code)); - Extrusion_Role_Colors[i][0] = color.Red() / 256.f; - Extrusion_Role_Colors[i][1] = color.Green() / 256.f; - Extrusion_Role_Colors[i][2] = color.Blue() / 256.f; + // add current index + indices.push_back(static_cast(indices.size())); + last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; + }; + + // format data into the buffers to be rendered as solid + auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { + auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { + // append position + vertices.push_back(position[0]); + vertices.push_back(position[1]); + vertices.push_back(position[2]); + // append normal + vertices.push_back(normal[0]); + vertices.push_back(normal[1]); + vertices.push_back(normal[2]); + }; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); + buffer.paths.back().sub_paths.back().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f left = -right; + Vec3f up = right.cross(dir); + Vec3f down = -up; + 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; + Vec3f d_up = half_height * up; + Vec3f d_down = -half_height * up; + Vec3f d_right = half_width * right; + Vec3f d_left = -half_width * right; + + // vertices 1st endpoint + if (last_path.vertices_count() == 1 || vertices.empty()) { + // 1st segment or restart into a new vertex buffer + // =============================================== + store_vertex(vertices, prev_pos + d_up, up); + store_vertex(vertices, prev_pos + d_right, right); + store_vertex(vertices, prev_pos + d_down, down); + store_vertex(vertices, prev_pos + d_left, left); + } + else { + // any other segment + // ================= + store_vertex(vertices, prev_pos + d_right, right); + store_vertex(vertices, prev_pos + d_left, left); + } + + // vertices 2nd endpoint + store_vertex(vertices, curr_pos + d_up, up); + store_vertex(vertices, curr_pos + d_right, right); + store_vertex(vertices, curr_pos + d_down, down); + store_vertex(vertices, curr_pos + d_left, left); + + last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; + }; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + auto add_indices_as_solid = [&](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, const GCodeProcessor::MoveVertex* next, + TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { +#else + auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + static Vec3f prev_dir; + static Vec3f prev_up; + static float sq_prev_length; + auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) { + indices.push_back(i1); + indices.push_back(i2); + indices.push_back(i3); + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) { + store_triangle(indices, id, id, id); + store_triangle(indices, id, id, id); + }; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { + std::array ret = { + static_cast(static_cast(vbuffer_size) + v_offsets[0]), + static_cast(static_cast(vbuffer_size) + v_offsets[1]), + static_cast(static_cast(vbuffer_size) + v_offsets[2]), + static_cast(static_cast(vbuffer_size) + v_offsets[3]), + static_cast(static_cast(vbuffer_size) + v_offsets[4]), + static_cast(static_cast(vbuffer_size) + v_offsets[5]), + static_cast(static_cast(vbuffer_size) + v_offsets[6]), + static_cast(static_cast(vbuffer_size) + v_offsets[7]) + }; + return ret; + }; + auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]); + store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]); + }; + auto append_stem_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]); + store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]); + store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]); + store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]); + store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]); + store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]); + store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]); + store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]); + }; + auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array& v_offsets) { + store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); + store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); + }; +#else + auto append_stem_triangles = [&](IndexBuffer& indices, size_t vbuffer_size, const std::array& v_offsets) { + std::array v_ids; + for (size_t i = 0; i < v_ids.size(); ++i) { + v_ids[i] = static_cast(static_cast(vbuffer_size) + v_offsets[i]); + } + + // triangles starting cap + store_triangle(indices, v_ids[0], v_ids[2], v_ids[1]); + store_triangle(indices, v_ids[0], v_ids[3], v_ids[2]); + + // triangles sides + store_triangle(indices, v_ids[0], v_ids[1], v_ids[4]); + store_triangle(indices, v_ids[1], v_ids[5], v_ids[4]); + store_triangle(indices, v_ids[1], v_ids[2], v_ids[5]); + store_triangle(indices, v_ids[2], v_ids[6], v_ids[5]); + store_triangle(indices, v_ids[2], v_ids[3], v_ids[6]); + store_triangle(indices, v_ids[3], v_ids[7], v_ids[6]); + store_triangle(indices, v_ids[3], v_ids[0], v_ids[7]); + store_triangle(indices, v_ids[0], v_ids[4], v_ids[7]); + + // triangles ending cap + store_triangle(indices, v_ids[4], v_ids[6], v_ids[7]); + store_triangle(indices, v_ids[4], v_ids[5], v_ids[6]); + }; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); + buffer.paths.back().sub_paths.back().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + float sq_length = (curr.position - prev.position).squaredNorm(); + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); + const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + if (last_path.vertices_count() == 1 || vbuffer_size == 0) { + // 1st segment or restart into a new vertex buffer + // =============================================== +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (last_path.vertices_count() == 1) + // starting cap triangles + append_starting_cap_triangles(indices, first_seg_v_offsets); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + // dummy triangles outer corner cap + append_dummy_cap(indices, vbuffer_size); + + // stem triangles +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + append_stem_triangles(indices, first_seg_v_offsets); +#else + append_stem_triangles(indices, vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + vbuffer_size += 8; + } + else { + // any other segment + // ================= + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + float half_width = 0.5f * last_path.width; + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + float sq_displacement = sqr(displacement); + bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; + + 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 = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + if (!is_sharp && can_displace) { + if (is_right_turn) + left_displaced = true; + else + right_displaced = true; + } + + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(indices, vbuffer_size); + else { + store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1); + store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1); + } + } + else { + if (right_displaced) + // dummy triangles + append_dummy_cap(indices, vbuffer_size); + else { + store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0); + store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0); + } + } + + // stem triangles +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + append_stem_triangles(indices, non_first_seg_v_offsets); +#else + append_stem_triangles(indices, vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + vbuffer_size += 6; + } + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) + // ending cap triangles + append_ending_cap_triangles(indices, non_first_seg_v_offsets); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + sq_prev_length = sq_length; + }; + +#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 + + m_moves_count = gcode_result.moves.size(); + if (m_moves_count == 0) + return; + + unsigned int progress_count = 0; + static const unsigned int progress_threshold = 1000; + wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? + new wxProgressDialog(_L("Generating toolpaths"), "...", + 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; + + wxBusyCursor busy; + + // extract approximate paths bounding box from result + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + if (wxGetApp().is_gcode_viewer()) + // for the gcode viewer we need to take in account all moves to correctly size the printbed + m_paths_bounding_box.merge(move.position.cast()); + else { + if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) + m_paths_bounding_box.merge(move.position.cast()); + } + } + + // set approximate max bounding box (take in account also the 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()); + + std::vector vertices(m_buffers.size()); + std::vector indices(m_buffers.size()); + std::vector options_zs; + + // toolpaths data -> extract vertices from result + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + + // update progress dialog + ++progress_count; + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), + _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog->Fit(); + progress_count = 0; + } + + unsigned char id = buffer_id(curr.type); + TBuffer& t_buffer = m_buffers[id]; + MultiVertexBuffer& v_multibuffer = vertices[id]; + + // ensure there is at least one vertex buffer + if (v_multibuffer.empty()) + v_multibuffer.push_back(VertexBuffer()); + + // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer + // add another vertex buffer + if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) { + v_multibuffer.push_back(VertexBuffer()); + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + Path& last_path = t_buffer.paths.back(); + if (prev.type == curr.type && last_path.matches(curr)) + last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, i - 1); + } + } + + VertexBuffer& v_buffer = v_multibuffer.back(); + + switch (t_buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; } + case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } + case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, i); break; } + } + + // collect options zs for later use + if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { + const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); + if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) + options_zs.emplace_back(curr.position[2]); + } + } + + // smooth toolpaths corners for the given TBuffer using triangles + auto smooth_triangle_toolpaths_corners = [&gcode_result](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) { + auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) { + return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); + }; + auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { + vertices[offset + 0] = position[0]; + vertices[offset + 1] = position[1]; + vertices[offset + 2] = position[2]; + }; + auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, + size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { + if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer + VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + // offset into the vertex buffer of the next segment 1st vertex + size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + // offset into the vertex buffer of the right vertex of the previous segment + size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; + // new position of the right vertices + Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; + // update previous segment + update_position_at(vbuffer, prev_right_offset, shared_vertex); + // offset into the vertex buffer of the right vertex of the next segment + size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; + // update next segment + update_position_at(vbuffer, next_right_offset, shared_vertex); + } + else { // previous and next segment are contained into different vertex buffers + VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; + // offset into the previous vertex buffer of the right vertex of the previous segment + size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; + // new position of the right vertices + Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; + // update previous segment + update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); + // offset into the next vertex buffer of the right vertex of the next segment + size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; + // update next segment + update_position_at(next_vbuffer, next_right_offset, shared_vertex); + } + }; + auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, + size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { + if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer + VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + // offset into the vertex buffer of the next segment 1st vertex + size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + // offset into the vertex buffer of the left vertex of the previous segment + size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; + // new position of the left vertices + Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; + // update previous segment + update_position_at(vbuffer, prev_left_offset, shared_vertex); + // offset into the vertex buffer of the left vertex of the next segment + size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; + // update next segment + update_position_at(vbuffer, next_left_offset, shared_vertex); + } + else { // previous and next segment are contained into different vertex buffers + VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; + VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; + // offset into the previous vertex buffer of the left vertex of the previous segment + size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; + // new position of the left vertices + Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; + // update previous segment + update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); + // offset into the next vertex buffer of the left vertex of the next segment + size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; + // update next segment + update_position_at(next_vbuffer, next_left_offset, shared_vertex); + } + }; + + size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); + for (const Path& path : t_buffer.paths) { + // the two segments of the path sharing the current vertex may belong + // to two different vertex buffers + size_t prev_sub_path_id = 0; + size_t next_sub_path_id = 0; + size_t path_vertices_count = path.vertices_count(); + float half_width = 0.5f * path.width; + for (size_t j = 1; j < path_vertices_count - 1; ++j) { + size_t curr_s_id = path.sub_paths.front().first.s_id + j; + const Vec3f& prev = gcode_result.moves[curr_s_id - 1].position; + const Vec3f& curr = gcode_result.moves[curr_s_id].position; + const Vec3f& next = gcode_result.moves[curr_s_id + 1].position; + + // select the subpaths which contains the previous/next segments + if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id)) + ++prev_sub_path_id; + if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1)) + ++next_sub_path_id; + const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; + const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; + + Vec3f prev_dir = (curr - prev).normalized(); + Vec3f prev_right = Vec3f(prev_dir[1], -prev_dir[0], 0.0f).normalized(); + Vec3f prev_up = prev_right.cross(prev_dir); + + Vec3f next_dir = (next - curr).normalized(); + + bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; + float cos_dir = prev_dir.dot(next_dir); + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + float displacement = 0.0f; + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + next_dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); + } + + float sq_prev_length = (curr - prev).squaredNorm(); + float sq_next_length = (next - curr).squaredNorm(); + float sq_displacement = sqr(displacement); + bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; + + if (can_displace) { + // displacement to apply to the vertices to match + Vec3f displacement_vec = displacement * prev_dir; + // matches inner corner vertices + if (is_right_turn) + match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); + else + match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec); + + if (!is_sharp) { + // matches outer corner vertices + if (is_right_turn) + match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); + else + match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec); + } } } } - catch (const std::ifstream::failure & err) { - trace(1, (std::string("The color file cannot be loaded. Reason: ") + err.what(), path_colors.string()).c_str()); - } - catch (const std::runtime_error & err) { - trace(1, (std::string("Failed loading the color file. Reason: ") + err.what(), path_colors.string()).c_str()); - } - } + }; - // initializes non opengl data of TBuffers +#if ENABLE_GCODE_VIEWER_STATISTICS + auto load_vertices_time = std::chrono::high_resolution_clock::now(); + m_statistics.load_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // smooth toolpaths corners for TBuffers using triangles for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - switch (buffer_type(i)) + const TBuffer& t_buffer = m_buffers[i]; + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + smooth_triangle_toolpaths_corners(t_buffer, vertices[i]); + } + } + + for (MultiVertexBuffer& v_multibuffer : vertices) { + for (VertexBuffer& v_buffer : v_multibuffer) { + v_buffer.shrink_to_fit(); + } + } + + // move the wipe toolpaths half height up to render them on proper position + MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; + for (VertexBuffer& v_buffer : wipe_vertices) { + for (size_t i = 2; i < v_buffer.size(); i += 3) { + v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height; + } + } + + // send vertices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& t_buffer = m_buffers[i]; + + const MultiVertexBuffer& v_multibuffer = vertices[i]; + for (const VertexBuffer& v_buffer : v_multibuffer) { + size_t size_elements = v_buffer.size(); + size_t size_bytes = size_elements * sizeof(float); + size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); + t_buffer.vertices.count += vertices_count; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.total_vertices_gpu_size += static_cast(size_bytes); + m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.vbuffers_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + GLuint id = 0; + glsafe(::glGenBuffers(1, &id)); + t_buffer.vertices.vbos.push_back(static_cast(id)); + t_buffer.vertices.sizes.push_back(size_bytes); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + auto smooth_vertices_time = std::chrono::high_resolution_clock::now(); + m_statistics.smooth_vertices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - load_vertices_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices); + + // dismiss vertices data, no more needed + std::vector().swap(vertices); + + // toolpaths data -> extract indices from result + // paths may have been filled while extracting vertices, + // so reset them, they will be filled again while extracting indices + for (TBuffer& buffer : m_buffers) { + buffer.paths.clear(); + } + + // variable used to keep track of the current vertex buffers index and size + using CurrVertexBuffer = std::pair; + std::vector curr_vertex_buffers(m_buffers.size(), { 0, 0 }); + + // variable used to keep track of the vertex buffers ids + using VboIndexList = std::vector; + std::vector vbo_indices(m_buffers.size()); + + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + const GCodeProcessor::MoveVertex* next = nullptr; + if (i < m_moves_count - 1) + next = &gcode_result.moves[i + 1]; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + ++progress_count; + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), + _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog->Fit(); + progress_count = 0; + } + + unsigned char id = buffer_id(curr.type); + TBuffer& t_buffer = m_buffers[id]; + MultiIndexBuffer& i_multibuffer = indices[id]; + CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; + VboIndexList& vbo_index_list = vbo_indices[id]; + + // ensure there is at least one index buffer + if (i_multibuffer.empty()) { + i_multibuffer.push_back(IndexBuffer()); + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + } + + // if adding the indices for the current segment exceeds the threshold size of the current index buffer + // create another index buffer +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.max_indices_per_segment_size_bytes()) { +#else + if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.indices_per_segment_size_bytes()) { +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + i_multibuffer.push_back(IndexBuffer()); + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { + Path& last_path = t_buffer.paths.back(); + last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, i - 1); + } + } + + // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer + // create another index buffer + if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) { + i_multibuffer.push_back(IndexBuffer()); + + ++curr_vertex_buffer.first; + curr_vertex_buffer.second = 0; + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); + + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { + Path& last_path = t_buffer.paths.back(); + last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, i - 1); + } + } + + IndexBuffer& i_buffer = i_multibuffer.back(); + + switch (t_buffer.render_primitive_type) { - 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; + case TBuffer::ERenderPrimitiveType::Point: { + add_indices_as_point(curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, i); + curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); break; } - case EMoveType::Wipe: - case EMoveType::Extrude: - { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; - buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + case TBuffer::ERenderPrimitiveType::Line: { + add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, i); + curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); break; } - case EMoveType::Travel: - { - buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; - buffer.vertices.format = VBuffer::EFormat::PositionNormal1; + case TBuffer::ERenderPrimitiveType::Triangle: { +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); +#else + add_indices_as_solid(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } } } - set_toolpath_move_type_visible(EMoveType::Extrude, true); -// m_sequential_view.skip_invisible_moves = true; + for (MultiIndexBuffer& i_multibuffer : indices) { + for (IndexBuffer& i_buffer : i_multibuffer) { + i_buffer.shrink_to_fit(); + } + } - m_initialized = true; + // toolpaths data -> send indices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& t_buffer = m_buffers[i]; + const MultiIndexBuffer& i_multibuffer = indices[i]; + for (const IndexBuffer& i_buffer : i_multibuffer) { + size_t size_elements = i_buffer.size(); + size_t size_bytes = size_elements * sizeof(IBufferType); + + // stores index buffer informations into TBuffer + t_buffer.indices.push_back(IBuffer()); + IBuffer& ibuf = t_buffer.indices.back(); + ibuf.count = size_elements; + ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.total_indices_gpu_size += static_cast(size_bytes); + m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.ibuffers_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + glsafe(::glGenBuffers(1, &ibuf.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + } + + if (progress_dialog != nullptr) { + progress_dialog->Update(100, ""); + progress_dialog->Fit(); + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const TBuffer& buffer : m_buffers) { + m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } + + auto update_segments_count = [&](EMoveType type, int64_t& count) { + unsigned int id = buffer_id(type); + const MultiIndexBuffer& buffers = indices[id]; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + int64_t indices_count = 0; + for (const IndexBuffer& buffer : buffers) { + indices_count += buffer.size(); + } + const TBuffer& t_buffer = m_buffers[id]; + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) + indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles + + count += indices_count / t_buffer.indices_per_segment(); +#else + for (const IndexBuffer& buffer : buffers) { + count += buffer.size() / m_buffers[id].indices_per_segment(); + } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + }; + + update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); + update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count); + update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count); + + m_statistics.load_indices = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices); + + // dismiss indices data, no more needed + std::vector().swap(indices); + + // layers zs / roles / extruder ids -> extract from result + size_t last_travel_s_id = 0; + for (size_t i = 0; i < m_moves_count; ++i) { + const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; + if (move.type == EMoveType::Extrude) { + // layers zs + const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); + double z = static_cast(move.position[2]); + if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) + m_layers.append(z, { last_travel_s_id, i }); + else + m_layers.get_endpoints().back().last = i; + // extruder ids + m_extruder_ids.emplace_back(move.extruder_id); + // roles + if (i > 0) + m_roles.emplace_back(move.extrusion_role); + } + else if (move.type == EMoveType::Travel) { + if (i - last_travel_s_id > 1 && !m_layers.empty()) + m_layers.get_endpoints().back().last = i; + + last_travel_s_id = i; + } + } + + // 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()); + m_roles.shrink_to_fit(); + + // 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()); + m_extruder_ids.shrink_to_fit(); + + // set layers z range + if (!m_layers.empty()) + m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; + + // change color of paths whose layer contains option points + if (!options_zs.empty()) { + TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; + for (Path& path : extrude_buffer.paths) { + float z = path.sub_paths.front().first.position[2]; + if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) + path.cp_color_id = 255 - path.cp_color_id; + } + } + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (progress_dialog != nullptr) + progress_dialog->Destroy(); } - +#else void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1104,7 +2122,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) 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()); - auto log_memory_usage = [this](const std::string& label, const std::vector>& vertices, const std::vector& indices) { + auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { int64_t vertices_size = 0; for (size_t i = 0; i < vertices.size(); ++i) { vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); @@ -1119,30 +2137,30 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) }; // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, std::vector& buffer_vertices) { - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } + auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { + vertices.push_back(curr.position[0]); + vertices.push_back(curr.position[1]); + vertices.push_back(curr.position[2]); }; auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { - buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id); - buffer_indices.push_back(static_cast(buffer_indices.size())); + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { + buffer.add_path(curr, ibuffer_id, indices.size(), move_id); + indices.push_back(static_cast(indices.size())); }; // format data into the buffers to be rendered as lines auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, - TBuffer& buffer, std::vector& buffer_vertices) { + VertexBuffer& vertices) { // 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]; - auto add_vertex = [&buffer_vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { // add position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(vertex.position[j]); - } + vertices.push_back(vertex.position[0]); + vertices.push_back(vertex.position[1]); + vertices.push_back(vertex.position[2]); // add normal x component - buffer_vertices.push_back(normal_x); + vertices.push_back(normal_x); }; // add previous vertex @@ -1151,48 +2169,45 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) add_vertex(curr); }; auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int index_buffer_id, IndexBuffer& 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]; - + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { // add starting index - buffer_indices.push_back(static_cast(buffer_indices.size())); - buffer.add_path(curr, index_buffer_id, buffer_indices.size() - 1, move_id - 1); + indices.push_back(static_cast(indices.size())); + buffer.add_path(curr, ibuffer_id, indices.size() - 1, 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 index - buffer_indices.push_back(static_cast(buffer_indices.size())); + indices.push_back(static_cast(indices.size())); } // add current index - buffer_indices.push_back(static_cast(buffer_indices.size())); - last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; + indices.push_back(static_cast(indices.size())); + last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as solid auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, size_t move_id) { + VertexBuffer& vertices, size_t move_id) { static Vec3f prev_dir; static Vec3f prev_up; static float prev_length; - auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { + auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(position[j]); - } + vertices.push_back(position[0]); + vertices.push_back(position[1]); + vertices.push_back(position[2]); // append normal - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(normal[j]); - } + vertices.push_back(normal[0]); + vertices.push_back(normal[1]); + vertices.push_back(normal[2]); }; - auto extract_position_at = [](const std::vector& vertices, size_t id) { + auto extract_position_at = [](const VertexBuffer& vertices, size_t id) { return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); }; - auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { + auto update_position_at = [](VertexBuffer& vertices, size_t id, const Vec3f& position) { vertices[id + 0] = position[0]; vertices[id + 1] = position[1]; vertices[id + 2] = position[2]; @@ -1203,7 +2218,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer.paths.back().first.position = prev.position; } - unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + unsigned int starting_vertices_size = static_cast(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(); @@ -1224,16 +2239,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // 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 * down, down); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); + store_vertex(vertices, prev_pos + half_height * up, up); + store_vertex(vertices, prev_pos + half_width * right, right); + store_vertex(vertices, prev_pos + half_height * down, down); + store_vertex(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 * down, down); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); + store_vertex(vertices, curr_pos + half_height * up, up); + store_vertex(vertices, curr_pos + half_width * right, right); + store_vertex(vertices, curr_pos + half_height * down, down); + store_vertex(vertices, curr_pos + half_width * left, left); } else { // any other segment @@ -1250,8 +2265,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) 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); + Vec3f prev_right_pos = extract_position_at(vertices, prev_right_id); + Vec3f prev_left_pos = extract_position_at(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 @@ -1264,12 +2279,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (can_displace) { if (is_right_turn) { prev_right_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + update_position_at(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); + update_position_at(vertices, prev_left_id, prev_left_pos); left_displaced = true; } } @@ -1279,39 +2294,39 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (can_displace) { if (is_right_turn) { prev_left_pos += displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + update_position_at(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); + update_position_at(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); + store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(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); + store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(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); + store_vertex(vertices, prev_pos + half_width * right, right); + store_vertex(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 * down, down); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); + store_vertex(vertices, curr_pos + half_height * up, up); + store_vertex(vertices, curr_pos + half_width * right, right); + store_vertex(vertices, curr_pos + half_height * down, down); + store_vertex(vertices, curr_pos + half_width * left, left); } last_path.last = { 0, 0, move_id, curr.position }; @@ -1319,29 +2334,26 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) prev_up = up; prev_length = length; }; - auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - size_t& buffer_vertices_size, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + size_t& buffer_vertices_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { static Vec3f prev_dir; static Vec3f prev_up; static float prev_length; - auto store_triangle = [](IndexBuffer& 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 store_triangle = [](IndexBuffer& indices, unsigned int i1, unsigned int i2, unsigned int i3) { + indices.push_back(i1); + indices.push_back(i2); + indices.push_back(i3); }; - auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { - store_triangle(buffer_indices, id, id, id); - store_triangle(buffer_indices, id, id, id); + auto append_dummy_cap = [store_triangle](IndexBuffer& indices, unsigned int id) { + store_triangle(indices, id, id, id); + store_triangle(indices, id, id, id); }; if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id - 1); + buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); buffer.paths.back().first.position = prev.position; } - unsigned int starting_vertices_size = static_cast(buffer_vertices_size); - 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 up = right.cross(dir); @@ -1357,28 +2369,29 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) float length = (curr_pos - prev_pos).norm(); if (last_path.vertices_count() == 1) { // 1st segment - buffer_vertices_size += 8; // 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); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 2, buffer_vertices_size + 1); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); // dummy triangles outer corner cap - append_dummy_cap(buffer_indices, starting_vertices_size); + append_dummy_cap(indices, buffer_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); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 1, buffer_vertices_size + 4); + store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); + store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 2, buffer_vertices_size + 5); + store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 6, buffer_vertices_size + 5); + store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 6); + store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 7, buffer_vertices_size + 6); + store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 0, buffer_vertices_size + 7); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 4, buffer_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); + store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 6, buffer_vertices_size + 7); + store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 5, buffer_vertices_size + 6); + + buffer_vertices_size += 8; } else { // any other segment @@ -1409,48 +2422,48 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - buffer_vertices_size += 6; - // 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); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 2, buffer_vertices_size + 0); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 2); // triangles outer corner cap if (is_right_turn) { if (left_displaced) // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); + append_dummy_cap(indices, buffer_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); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 1); + store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 2, buffer_vertices_size - 1); } } else { if (right_displaced) // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); + append_dummy_cap(indices, buffer_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); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 3, buffer_vertices_size + 0); + store_triangle(indices, buffer_vertices_size - 3, buffer_vertices_size - 2, buffer_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); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 0, buffer_vertices_size + 2); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); + store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size - 2, buffer_vertices_size + 3); + store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 4, buffer_vertices_size + 3); + store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 1, buffer_vertices_size + 4); + store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); + store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 4, buffer_vertices_size + 5); + store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 2, buffer_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); + store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 4, buffer_vertices_size + 5); + store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 4); + + buffer_vertices_size += 6; } - last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; + last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; prev_dir = dir; prev_up = up; prev_length = length; @@ -1460,7 +2473,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. // the data are deleted as soon as they are sent to the gpu. - std::vector> vertices(m_buffers.size()); + std::vector vertices(m_buffers.size()); std::vector indices(m_buffers.size()); std::vector options_zs; @@ -1483,7 +2496,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned char id = buffer_id(curr.type); TBuffer& buffer = m_buffers[id]; - std::vector& buffer_vertices = vertices[id]; + VertexBuffer& buffer_vertices = vertices[id]; switch (buffer.render_primitive_type) { @@ -1492,7 +2505,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) break; } case TBuffer::ERenderPrimitiveType::Line: { - add_vertices_as_line(prev, curr, buffer, buffer_vertices); + add_vertices_as_line(prev, curr, buffer_vertices); break; } case TBuffer::ERenderPrimitiveType::Triangle: { @@ -1501,17 +2514,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - EMoveType type = buffer_type(id); - if (type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) { + if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - float z = static_cast(curr.position[2]); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) + if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) options_zs.emplace_back(curr.position[2]); } } // move the wipe toolpaths half height up to render them on proper position - std::vector& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; + VertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; for (size_t i = 2; i < wipe_vertices.size(); i += 3) { wipe_vertices[i] += 0.5f * GCodeProcessor::Wipe_Height; } @@ -1522,12 +2533,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; - const std::vector& buffer_vertices = vertices[i]; + const VertexBuffer& buffer_vertices = vertices[i]; buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.total_vertices_gpu_size += buffer_vertices.size() * sizeof(float); m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(buffer_vertices.size() * sizeof(float))); - m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS if (buffer.vertices.count > 0) { @@ -1542,7 +2552,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // dismiss vertices data, no more needed - std::vector>().swap(vertices); + std::vector().swap(vertices); // toolpaths data -> extract indices from result // paths may have been filled while extracting vertices, @@ -1638,7 +2648,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.total_indices_gpu_size += ibuffer.count * sizeof(unsigned int); m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(ibuffer.count * sizeof(unsigned int))); - m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(ibuffer.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS if (ibuffer.count > 0) { @@ -1660,17 +2669,17 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { - m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); + m_statistics.travel_segments_count += travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); } unsigned int wipe_buffer_id = buffer_id(EMoveType::Wipe); const MultiIndexBuffer& wipe_buffer_indices = indices[wipe_buffer_id]; for (size_t i = 0; i < wipe_buffer_indices.size(); ++i) { - m_statistics.wipe_segments_count = wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment(); + m_statistics.wipe_segments_count += wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment(); } unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { - m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + m_statistics.extrude_segments_count += extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1736,6 +2745,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (progress_dialog != nullptr) progress_dialog->Destroy(); } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::load_shells(const Print& print, bool initialized) { @@ -1795,6 +2805,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } +#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1822,6 +2833,465 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool color = { 0.5f, 0.5f, 0.5f }; // // complementary color // color = m_tool_colors[255 - path.cp_color_id]; +// color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] }; + } + else + 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 */); + }; + + auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { + auto in_layers_range = [this, min_id, max_id](size_t id) { + return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; + }; + + return in_layers_range(path.sub_paths.front().first.s_id) || in_layers_range(path.sub_paths.back().last.s_id); + }; + + auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { + const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; + if (path_id >= buffer.paths.size()) + return false; + + Path path = buffer.paths[path_id]; + size_t first = path_id; + size_t last = path_id; + + // check adjacent paths + while (first > 0 && path.sub_paths.front().first.position.isApprox(buffer.paths[first - 1].sub_paths.back().last.position)) { + --first; + path.sub_paths.front().first = buffer.paths[first].sub_paths.front().first; + } + while (last < buffer.paths.size() - 1 && path.sub_paths.back().last.position.isApprox(buffer.paths[last + 1].sub_paths.front().first.position)) { + ++last; + path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; + } + + size_t min_s_id = m_layers.get_endpoints_at(min_id).first; + size_t max_s_id = m_layers.get_endpoints_at(max_id).last; + + return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || + (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + Statistics* statistics = const_cast(&m_statistics); + statistics->render_paths_size = 0; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; + + SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; + SequentialView::Endpoints top_layer_endpoints = global_endpoints; + SequentialView* sequential_view = const_cast(&m_sequential_view); + if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; + if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; + + // first pass: collect visible paths and update sequential view data +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + std::vector> paths; +#else + std::vector> paths; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer& buffer = const_cast(m_buffers[b]); + // 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_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) + continue; + } + else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) + continue; + + if (path.type == EMoveType::Extrude && !is_visible(path)) + continue; + + // store valid path + for (size_t j = 0; j < path.sub_paths.size(); ++j) { +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); +#else + paths.push_back({ &buffer, path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + } + + global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); + global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); + + if (top_layer_only) { + if (path.type == EMoveType::Travel) { + if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { + top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); + } + } + else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { + top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); + } + } + } + } + + // update current sequential position + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; + sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_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)) { + int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); + if (sub_path_id != -1) { + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + unsigned int offset = static_cast(m_sequential_view.current.last - sub_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(); +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + offset = indices_count * (offset - 1) + (indices_count - 2); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap +#else + offset = indices_count * (offset - 1) + (indices_count - 6); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + } + } + offset += static_cast(sub_path.first.i_id); + + // gets the vertex index from the index buffer on gpu + const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; + unsigned int index = 0; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // gets the position from the vertices buffer on gpu + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(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 + RenderPath* render_path = nullptr; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { + TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); + const Path& path = buffer.paths[path_id]; +#else + for (const auto& [buffer, ibuffer_id, path_id, sub_path_id] : paths) { + const Path& path = buffer->paths[path_id]; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) + continue; + + Color color; + switch (path.type) + { + case EMoveType::Tool_change: { color = Options_Colors[static_cast(EOptionsColors::ToolChanges)]; break; } + case EMoveType::Color_change: { color = Options_Colors[static_cast(EOptionsColors::ColorChanges)]; break; } + case EMoveType::Pause_Print: { color = Options_Colors[static_cast(EOptionsColors::PausePrints)]; break; } + case EMoveType::Custom_GCode: { color = Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; break; } + case EMoveType::Retract: { color = Options_Colors[static_cast(EOptionsColors::Retractions)]; break; } + case EMoveType::Unretract: { color = Options_Colors[static_cast(EOptionsColors::Unretractions)]; break; } + case EMoveType::Extrude: { + if (!top_layer_only || + m_sequential_view.current.last == global_endpoints.last || + is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) + color = extrusion_color(path); + else + color = { 0.25f, 0.25f, 0.25f }; + + break; + } + case EMoveType::Travel: { + if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) + color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); + else + color = { 0.25f, 0.25f, 0.25f }; + + break; + } + case EMoveType::Wipe: { color = Wipe_Color; break; } + default: { color = { 0.0f, 0.0f, 0.0f }; break; } + } + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; + if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) + render_path = const_cast(&(*buffer.render_paths.emplace(key).first)); + + unsigned int delta_1st = 0; + if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) + delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); +#else + RenderPath key{ color, static_cast(ibuffer_id), path_id }; + if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) + render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + unsigned int size_in_indices = 0; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + switch (buffer.render_primitive_type) +#else + switch (buffer->render_primitive_type) +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + { + case TBuffer::ERenderPrimitiveType::Point: { +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + size_in_indices = buffer.indices_per_segment(); +#else + size_in_indices = buffer->indices_per_segment(); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + break; + } + case TBuffer::ERenderPrimitiveType::Line: + case TBuffer::ERenderPrimitiveType::Triangle: { + unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + size_in_indices = buffer.indices_per_segment() * segments_count; +#else + size_in_indices = buffer->indices_per_segment() * segments_count; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + break; + } + } + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (size_in_indices == 0) + continue; + + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + if (sub_path_id == 0 && delta_1st == 0) + size_in_indices += 6; // add 2 triangles for starting cap + if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last) + size_in_indices += 6; // add 2 triangles for ending cap + if (delta_1st > 0) + size_in_indices -= 6; // remove 2 triangles for corner cap + } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + render_path->sizes.push_back(size_in_indices); + +#if !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + unsigned int delta_1st = 0; + if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) + delta_1st = m_sequential_view.current.first - sub_path.first.s_id; +#endif // !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + delta_1st *= buffer.indices_per_segment(); + if (delta_1st > 0) { + delta_1st += 6; // skip 2 triangles for corner cap + if (sub_path_id == 0) + delta_1st += 6; // skip 2 triangles for starting cap + } + } +#else + if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) + delta_1st *= buffer->indices_per_segment(); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); + +#if 0 + // check sizes and offsets against index buffer size on gpu + GLint buffer_size; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo)); + glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size) + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data"; +#endif + } + + // set sequential data to their final value + sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + // updates sequential range caps + std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); + (*sequential_range_caps)[0].reset(); + (*sequential_range_caps)[1].reset(); + + if (m_sequential_view.current.first != m_sequential_view.current.last) { + for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { + TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); + if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) + continue; + + const Path& path = buffer.paths[path_id]; + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first) + continue; + + // update cap for first endpoint of current range + if (m_sequential_view.current.first > sub_path.first.s_id) { + SequentialRangeCap& cap = (*sequential_range_caps)[0]; + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + cap.buffer = &buffer; + cap.vbo = i_buffer.vbo; + + // calculate offset into the index buffer + unsigned int offset = sub_path.first.i_id; + offset += 6; // add 2 triangles for corner cap + offset += static_cast(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment(); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + + // extract indices from index buffer + std::array indices{ 0, 0, 0, 0, 0, 0 }; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 0) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 7) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 1) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 13) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[4]))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + indices[3] = indices[0]; + indices[5] = indices[1]; + + // send indices to gpu + glsafe(::glGenBuffers(1, &cap.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // extract color from render path + size_t offset_bytes = offset * sizeof(IBufferType); + for (const RenderPath& render_path : buffer.render_paths) { + if (render_path.ibuffer_id == ibuffer_id) { + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + if (render_path.contains(offset_bytes)) { + cap.color = render_path.color; + break; + } + } + } + } + } + + // update cap for last endpoint of current range + if (m_sequential_view.current.last < sub_path.last.s_id) { + SequentialRangeCap& cap = (*sequential_range_caps)[1]; + const IBuffer& i_buffer = buffer.indices[ibuffer_id]; + cap.buffer = &buffer; + cap.vbo = i_buffer.vbo; + + // calculate offset into the index buffer + unsigned int offset = sub_path.first.i_id; + offset += 6; // add 2 triangles for corner cap + offset += static_cast(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment(); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + + // extract indices from index buffer + std::array indices{ 0, 0, 0, 0, 0, 0 }; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 2) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[0]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 4) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[1]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 10) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[2]))); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast((offset + 16) * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&indices[5]))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + indices[3] = indices[0]; + indices[4] = indices[2]; + + // send indices to gpu + glsafe(::glGenBuffers(1, &cap.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // extract color from render path + size_t offset_bytes = offset * sizeof(IBufferType); + for (const RenderPath& render_path : buffer.render_paths) { + if (render_path.ibuffer_id == ibuffer_id) { + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + if (render_path.contains(offset_bytes)) { + cap.color = render_path.color; + break; + } + } + } + } + } + + if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable()) + break; + } + } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + + wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); + +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const TBuffer& buffer : m_buffers) { + statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } + statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS +} +#else +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(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: { + if (path.cp_color_id >= static_cast(m_tool_colors.size())) { + color = { 0.5f, 0.5f, 0.5f }; +// // complementary color +// color = m_tool_colors[255 - path.cp_color_id]; // color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] }; } else @@ -1884,19 +3354,22 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool }; #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.render_paths_size = 0; + Statistics* statistics = const_cast(&m_statistics); + statistics->render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; SequentialView::Endpoints top_layer_endpoints = global_endpoints; - if (top_layer_only || !keep_sequential_current_first) m_sequential_view.current.first = 0; - if (!keep_sequential_current_last) m_sequential_view.current.last = m_moves_count; + SequentialView* sequential_view = const_cast(&m_sequential_view); + if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; + if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; // first pass: collect visible paths and update sequential view data std::vector> paths; - for (TBuffer& buffer : m_buffers) { + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer& buffer = const_cast(m_buffers[b]); // reset render paths buffer.render_paths.clear(); @@ -1941,8 +3414,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // update current sequential position - m_sequential_view.current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; + sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; // get the world position from gpu bool found = false; @@ -1969,7 +3442,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // gets the position from the vertices buffer on gpu glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(m_sequential_view.current_position.data()))); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); found = true; break; @@ -1981,7 +3454,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // second pass: filter paths by sequential data and collect them by color RenderPath *render_path = nullptr; - for (const auto& [buffer, index_buffer_id, path_id] : paths) { + for (const auto& [buffer, ibuffer_id, path_id] : paths) { const Path& path = buffer->paths[path_id]; if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; @@ -2011,7 +3484,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } - RenderPath key{ color, static_cast(index_buffer_id), path_id }; + RenderPath key{ color, static_cast(ibuffer_id), path_id }; if (render_path == nullptr || ! RenderPathPropertyEqual()(*render_path, key)) render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; @@ -2035,23 +3508,25 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // set sequential data to their final value - m_sequential_view.endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - m_sequential_view.current.first = !top_layer_only && 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; + sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; + sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); #if ENABLE_GCODE_VIEWER_STATISTICS for (const TBuffer& buffer : m_buffers) { - m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); + statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_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); + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } } - m_statistics.refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); + statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER +#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_toolpaths() const { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS @@ -2072,7 +3547,184 @@ void GCodeViewer::render_toolpaths() const }; auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] - (const TBuffer& buffer, unsigned int index_buffer_id, EOptionsColors color_id, GLShaderProgram& shader) { + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS + shader.set_uniform("use_fixed_screen_size", 1); +#else + shader.set_uniform("use_fixed_screen_size", 0); +#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS + 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) { + if (path.ibuffer_id == ibuffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++const_cast(&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, unsigned int ibuffer_id, GLShaderProgram& shader) { + shader.set_uniform("light_intensity", light_intensity); + for (const RenderPath& path : buffer.render_paths) { + if (path.ibuffer_id == ibuffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++const_cast(&m_statistics)->gl_multi_lines_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + } + }; + + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { + for (const RenderPath& path : buffer.render_paths) { + if (path.ibuffer_id == ibuffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++const_cast(&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(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 || !buffer.has_data()) + continue; + + GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); + if (shader != nullptr) { + shader->start_using(); + + for (size_t j = 0; j < buffer.indices.size(); ++j) { + const IBuffer& i_buffer = buffer.indices[j]; + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + 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, i_buffer.ibo)); + + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: { + render_as_points(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Line: { + render_as_lines(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: { + render_as_triangles(buffer, static_cast(j), *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(); + } + } + +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + auto render_sequential_range_cap = [this, set_uniform_color](const SequentialRangeCap& cap) { + GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); + if (shader != nullptr) { + shader->start_using(); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); + glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_size())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; + if (has_normals) { + glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_size())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } + + set_uniform_color(cap.color, *shader); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++const_cast(&m_statistics)->gl_triangles_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + shader->stop_using(); + } + }; + + for (unsigned int i = 0; i < 2; ++i) { + if (m_sequential_range_caps[i].is_renderable()) + render_sequential_range_cap(m_sequential_range_caps[i]); + } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS +} +#else +void GCodeViewer::render_toolpaths() const +{ +#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS + float point_size = 20.0f; +#else + float point_size = 0.8f; +#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS + std::array 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& viewport = camera.get_viewport(); + float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : + static_cast(viewport[3]) * 0.0005; + + auto set_uniform_color = [](const std::array& color, GLShaderProgram& shader) { + std::array 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, unsigned int ibuffer_id, EOptionsColors color_id, GLShaderProgram& shader) { set_uniform_color(Options_Colors[static_cast(color_id)], shader); #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS shader.set_uniform("use_fixed_screen_size", 1); @@ -2089,10 +3741,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { - if (path.index_buffer_id == index_buffer_id) { + if (path.ibuffer_id == ibuffer_id) { 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; + ++const_cast(&m_statistics)->gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } @@ -2101,26 +3753,26 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { + auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { - if (path.index_buffer_id == index_buffer_id) { + if (path.ibuffer_id == ibuffer_id) { 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; + ++const_cast(&m_statistics)->gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } }; - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { - if (path.index_buffer_id == index_buffer_id) { + if (path.ibuffer_id == ibuffer_id) { 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; + ++const_cast(&m_statistics)->gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } @@ -2160,7 +3812,7 @@ void GCodeViewer::render_toolpaths() const { case TBuffer::ERenderPrimitiveType::Point: { - EOptionsColors color; + EOptionsColors color = EOptionsColors(0); switch (buffer_type(i)) { case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } @@ -2169,6 +3821,7 @@ void GCodeViewer::render_toolpaths() const case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + default: { assert(false); break; } } render_as_points(buffer, static_cast(j), color, *shader); break; @@ -2198,6 +3851,7 @@ void GCodeViewer::render_toolpaths() const } } } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_shells() const { @@ -2505,7 +4159,8 @@ void GCodeViewer::render_legend() const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(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); + Extrusions* extrusions = const_cast(&m_extrusions); + extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->update_preview_moves_slider(); @@ -2897,6 +4552,7 @@ void GCodeViewer::render_legend() const imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); break; } + default : { assert(false); break; } } ImGui::SameLine(); imgui.text(short_time(get_time_dhms(time_mode.time))); @@ -2912,7 +4568,7 @@ void GCodeViewer::render_legend() const } if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { if (imgui.button(label)) { - m_time_estimate_mode = mode; + *const_cast(&m_time_estimate_mode) = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } @@ -2928,6 +4584,7 @@ void GCodeViewer::render_legend() const show_mode_button(_L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); break; } + default : { assert(false); break; } } } @@ -2991,18 +4648,20 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); add_time(std::string("Load:"), m_statistics.load_time); + add_time(std::string(" Load vertices:"), m_statistics.load_vertices); + add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices); + add_time(std::string(" Load indices:"), m_statistics.load_indices); add_time(std::string("Refresh:"), m_statistics.refresh_time); add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } if (ImGui::CollapsingHeader("OpenGL calls")) { add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } if (ImGui::CollapsingHeader("CPU memory")) { @@ -3011,8 +4670,6 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); add_memory(std::string("Paths:"), m_statistics.paths_size); add_memory(std::string("Render paths:"), m_statistics.render_paths_size); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } if (ImGui::CollapsingHeader("GPU memory")) { @@ -3021,8 +4678,6 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } if (ImGui::CollapsingHeader("Other")) { @@ -3031,12 +4686,7 @@ void GCodeViewer::render_statistics() const add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); ImGui::Separator(); add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); - add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); - ImGui::Separator(); - add_counter(std::string("Max vertices in VBuffer:"), m_statistics.max_vertices_in_vertex_buffer); - add_counter(std::string("Max indices in IBuffer:"), m_statistics.max_indices_in_index_buffer); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); } imgui.end(); @@ -3058,9 +4708,15 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); +#if ENABLE_SPLITTED_VERTEX_BUFFER + BOOST_LOG_TRIVIAL(trace) << label + << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" + << log_memory_info(); +#else BOOST_LOG_TRIVIAL(trace) << label << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << log_memory_info(); +#endif // ENABLE_SPLITTED_VERTEX_BUFFER } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 316d3f01e..a5be8e77c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -19,8 +19,17 @@ namespace GUI { class GCodeViewer { +#if ENABLE_SPLITTED_VERTEX_BUFFER + using IBufferType = unsigned short; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER using Color = std::array; + using VertexBuffer = std::vector; +#if ENABLE_SPLITTED_VERTEX_BUFFER + using MultiVertexBuffer = std::vector; + using IndexBuffer = std::vector; +#else using IndexBuffer = std::vector; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER using MultiIndexBuffer = std::vector; std::vector Extrusion_Role_Colors; @@ -39,7 +48,7 @@ class GCodeViewer CustomGCodes }; - // vbo buffer containing vertices data used to rendder a specific toolpath type + // vbo buffer containing vertices data used to render a specific toolpath type struct VBuffer { enum class EFormat : unsigned char @@ -53,38 +62,48 @@ class GCodeViewer }; EFormat format{ EFormat::Position }; +#if ENABLE_SPLITTED_VERTEX_BUFFER + // vbos id + std::vector vbos; + // sizes of the buffers, in bytes, used in export to obj + std::vector sizes; +#else // vbo id unsigned int id{ 0 }; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER // 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(); } +#if ENABLE_SPLITTED_VERTEX_BUFFER + // We set 65536 as max count of vertices inside a vertex buffer to allow + // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory + size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER 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 - { + size_t position_size_floats() const { switch (format) { case EFormat::Position: case EFormat::PositionNormal3: { return 3; } case EFormat::PositionNormal1: { return 4; } - default: { return 0; } + default: { return 0; } } } size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } - size_t normal_offset_floats() const - { + size_t normal_offset_floats() const { switch (format) { case EFormat::Position: case EFormat::PositionNormal1: { return 0; } case EFormat::PositionNormal3: { return 3; } - default: { return 0; } + default: { return 0; } } } size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); } @@ -102,11 +121,18 @@ class GCodeViewer void reset(); }; - // ibo buffer containing indices data (lines/triangles) used to render a specific toolpath type + // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type struct IBuffer { +#if ENABLE_SPLITTED_VERTEX_BUFFER + // id of the associated vertex buffer + unsigned int vbo{ 0 }; + // ibo id + unsigned int ibo{ 0 }; +#else // ibo id unsigned int id{ 0 }; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER // count of indices, updated after data are sent to gpu size_t count{ 0 }; @@ -118,19 +144,36 @@ class GCodeViewer { struct Endpoint { - // index of the index buffer + // index of the buffer in the multibuffer vector + // the buffer type may change: + // it is the vertex buffer while extracting vertices data, + // the index buffer while extracting indices data unsigned int b_id{ 0 }; - // index into the index buffer + // index into the buffer size_t i_id{ 0 }; - // sequential id (index into the vertex buffer) + // move id size_t s_id{ 0 }; Vec3f position{ Vec3f::Zero() }; }; +#if ENABLE_SPLITTED_VERTEX_BUFFER + struct Sub_Path + { + Endpoint first; + Endpoint last; + + bool contains(size_t s_id) const { + return first.s_id <= s_id && s_id <= last.s_id; + } + }; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER + EMoveType type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; +#if !ENABLE_SPLITTED_VERTEX_BUFFER Endpoint first; Endpoint last; +#endif // !ENABLE_SPLITTED_VERTEX_BUFFER float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; @@ -139,46 +182,98 @@ class GCodeViewer float volumetric_rate{ 0.0f }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; +#if ENABLE_SPLITTED_VERTEX_BUFFER + std::vector sub_paths; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER float layer_time{ 0.0f }; float elapsed_time{ 0.0f }; float extruder_temp{ 0.0f }; bool matches(const GCodeProcessor::MoveVertex& move) const; +#if ENABLE_SPLITTED_VERTEX_BUFFER + size_t vertices_count() const { + return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; + } + bool contains(size_t s_id) const { + return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id; + } + int get_id_of_sub_path_containing(size_t s_id) const { + if (sub_paths.empty()) + return -1; + else { + for (int i = 0; i < static_cast(sub_paths.size()); ++i) { + if (sub_paths[i].contains(s_id)) + return i; + } + return -1; + } + } + void add_sub_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { + Endpoint endpoint = { b_id, i_id, s_id, move.position }; + sub_paths.push_back({ endpoint , endpoint }); + } +#else size_t vertices_count() const { return last.s_id - first.s_id + 1; } bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; - // Used to batch the indices needed to render paths + // Used to batch the indices needed to render the paths struct RenderPath { +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + // Index of the parent tbuffer + unsigned char tbuffer_id; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // Render path property Color color; - unsigned int index_buffer_id; + // Index of the buffer in TBuffer::indices + unsigned int ibuffer_id; // Render path content + // Index of the path in TBuffer::paths unsigned int path_id; std::vector sizes; - std::vector offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements()) - }; - struct RenderPathPropertyHash { - size_t operator() (const RenderPath &p) const { - // Conver the RGB value to an integer hash. -// return (size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) * 7919) ^ size_t(p.index_buffer_id); - return size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) ^ size_t(p.index_buffer_id); + std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + bool contains(size_t offset) const { + for (size_t i = 0; i < offsets.size(); ++i) { + if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) + return true; + } + return false; } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS }; +// // for unordered_set implementation of render_paths +// struct RenderPathPropertyHash { +// size_t operator() (const RenderPath &p) const { +// // Convert the RGB value to an integer hash. +//// return (size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) * 7919) ^ size_t(p.ibuffer_id); +// return size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) ^ size_t(p.ibuffer_id); +// } +// }; struct RenderPathPropertyLower { bool operator() (const RenderPath &l, const RenderPath &r) const { - for (int i = 0; i < 3; ++ i) +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + if (l.tbuffer_id < r.tbuffer_id) + return true; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + for (int i = 0; i < 3; ++i) { if (l.color[i] < r.color[i]) return true; else if (l.color[i] > r.color[i]) return false; - return l.index_buffer_id < r.index_buffer_id; + } + return l.ibuffer_id < r.ibuffer_id; } }; struct RenderPathPropertyEqual { bool operator() (const RenderPath &l, const RenderPath &r) const { - return l.color == r.color && l.index_buffer_id == r.index_buffer_id; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; +#else + return l.color == r.color && l.ibuffer_id == r.ibuffer_id; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } }; @@ -198,45 +293,79 @@ class GCodeViewer std::string shader; std::vector paths; - // std::set seems to perform singificantly better, at least on Windows. + // std::set seems to perform significantly better, at least on Windows. // std::unordered_set render_paths; std::set render_paths; bool visible{ false }; void reset(); + // b_id index of buffer contained in this->indices // i_id index of first index contained in this->indices[b_id] // s_id index of first vertex contained in this->vertices void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); + +#if ENABLE_SPLITTED_VERTEX_BUFFER + unsigned int max_vertices_per_segment() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Point: { return 1; } + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 8; } + default: { return 0; } + } + } + + size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } + size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER unsigned int indices_per_segment() const { switch (render_primitive_type) { case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles +#else case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS default: { return 0; } } } - unsigned int start_segment_vertex_offset() const { +#if ENABLE_SPLITTED_VERTEX_BUFFER + size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + unsigned int max_indices_per_segment() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: - case ERenderPrimitiveType::Line: - case ERenderPrimitiveType::Triangle: - default: { return 0; } + case ERenderPrimitiveType::Point: { return 1; } + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles + default: { return 0; } } } + size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + +#if ENABLE_SPLITTED_VERTEX_BUFFER + bool has_data() const { + return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; + } +#else + unsigned int start_segment_vertex_offset() const { return 0; } unsigned int end_segment_vertex_offset() const { switch (render_primitive_type) { - case ERenderPrimitiveType::Point: { return 0; } - case ERenderPrimitiveType::Line: { return 1; } - case ERenderPrimitiveType::Triangle: { return 36; } // 1 vertex of 13th triangle - default: { return 0; } + case ERenderPrimitiveType::Point: { return 0; } + case ERenderPrimitiveType::Line: { return 1; } + case ERenderPrimitiveType::Triangle: { return 36; } // 1st vertex of 13th triangle + default: { return 0; } } } bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; // helper to render shells @@ -322,6 +451,12 @@ class GCodeViewer { size_t first{ 0 }; size_t last{ 0 }; + +#if ENABLE_SPLITTED_VERTEX_BUFFER + bool operator == (const Endpoints& other) const { + return first == other.first && last == other.last; + } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; private: @@ -329,14 +464,12 @@ class GCodeViewer std::vector m_endpoints; public: - void append(double z, Endpoints endpoints) - { + void append(double z, Endpoints endpoints) { m_zs.emplace_back(z); m_endpoints.emplace_back(endpoints); } - void reset() - { + void reset() { m_zs = std::vector(); m_endpoints = std::vector(); } @@ -348,20 +481,54 @@ class GCodeViewer std::vector& get_endpoints() { return m_endpoints; } double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); } + +#if ENABLE_SPLITTED_VERTEX_BUFFER + bool operator != (const Layers& other) const { + if (m_zs != other.m_zs) + return true; + if (!(m_endpoints == other.m_endpoints)) + return true; + + return false; + } +#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + // used to render the toolpath caps of the current sequential range + // (i.e. when sliding on the horizontal slider) + struct SequentialRangeCap + { + TBuffer* buffer{ nullptr }; + unsigned int ibo{ 0 }; + unsigned int vbo{ 0 }; + Color color; + + ~SequentialRangeCap(); + bool is_renderable() const { return buffer != nullptr; } + void reset(); + size_t indices_count() const { return 6; } + }; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { // time int64_t results_time{ 0 }; int64_t load_time{ 0 }; + int64_t load_vertices{ 0 }; + int64_t smooth_vertices{ 0 }; + int64_t load_indices{ 0 }; int64_t refresh_time{ 0 }; int64_t refresh_paths_time{ 0 }; // opengl calls int64_t gl_multi_points_calls_count{ 0 }; int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 }; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + int64_t gl_triangles_calls_count{ 0 }; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // memory int64_t results_size{ 0 }; int64_t total_vertices_gpu_size{ 0 }; @@ -376,8 +543,6 @@ class GCodeViewer int64_t extrude_segments_count{ 0 }; int64_t vbuffers_count{ 0 }; int64_t ibuffers_count{ 0 }; - int64_t max_vertices_in_vertex_buffer{ 0 }; - int64_t max_indices_in_index_buffer{ 0 }; void reset_all() { reset_times(); @@ -389,6 +554,9 @@ class GCodeViewer void reset_times() { results_time = 0; load_time = 0; + load_vertices = 0; + smooth_vertices = 0; + load_indices = 0; refresh_time = 0; refresh_paths_time = 0; } @@ -397,6 +565,9 @@ class GCodeViewer gl_multi_points_calls_count = 0; gl_multi_lines_calls_count = 0; gl_multi_triangles_calls_count = 0; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + gl_triangles_calls_count = 0; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } void reset_sizes() { @@ -415,8 +586,6 @@ class GCodeViewer extrude_segments_count = 0; vbuffers_count = 0; ibuffers_count = 0; - max_vertices_in_vertex_buffer = 0; - max_indices_in_index_buffer = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -480,11 +649,10 @@ public: }; private: - bool m_initialized{ false }; - mutable bool m_gl_data_initialized{ false }; + bool m_gl_data_initialized{ false }; unsigned int m_last_result_id{ 0 }; size_t m_moves_count{ 0 }; - mutable std::vector m_buffers{ static_cast(EMoveType::Extrude) }; + std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; // bounding box of toolpaths + marker tools @@ -496,21 +664,24 @@ private: std::vector m_roles; size_t m_extruders_count; std::vector m_extruder_ids; - mutable Extrusions m_extrusions; - mutable SequentialView m_sequential_view; + Extrusions m_extrusions; + 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 }; + PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS - mutable Statistics m_statistics; + Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS - mutable std::array m_detected_point_sizes = { 0.0f, 0.0f }; + std::array m_detected_point_sizes = { 0.0f, 0.0f }; GCodeProcessor::Result::SettingsIds m_settings_ids; +#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS + std::array m_sequential_range_caps; +#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS public: - GCodeViewer() = default; + GCodeViewer(); ~GCodeViewer() { reset(); } // extract rendering data from the given parameters @@ -526,10 +697,13 @@ public: void render() const; bool has_data() const { return !m_roles.empty(); } +#if ENABLE_SPLITTED_VERTEX_BUFFER + bool can_export_toolpaths() const; +#endif // ENABLE_SPLITTED_VERTEX_BUFFER 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& get_layers_zs() const { return m_layers.get_zs(); }; + const std::vector& get_layers_zs() const { return m_layers.get_zs(); } const SequentialView& get_sequential_view() const { return m_sequential_view; } void update_sequential_view_current(unsigned int first, unsigned int last); @@ -556,7 +730,6 @@ public: void export_toolpaths_to_obj(const char* filename) const; private: - void init(); 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; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f5fc53b27..2f80db816 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3883,7 +3883,11 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() bool GLCanvas3D::has_toolpaths_to_export() const { +#if ENABLE_SPLITTED_VERTEX_BUFFER + return m_gcode_viewer.can_export_toolpaths(); +#else return m_gcode_viewer.has_data(); +#endif // ENABLE_SPLITTED_VERTEX_BUFFER } void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const @@ -5862,7 +5866,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c int get_color_idx_for_tool_change(std::vector::const_iterator it, const int extruder) const { const int current_extruder = it->extruder == 0 ? extruder : it->extruder; - if (number_tools() == extruders_cnt + 1) // there is no one "M600" + if (number_tools() == size_t(extruders_cnt + 1)) // there is no one "M600" return std::min(extruders_cnt - 1, std::max(current_extruder - 1, 0)); auto it_n = it; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4b11c2c1a..f69a32cad 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -341,7 +341,7 @@ private: size_t cur_len = 0; wxString longest_sub_string; - auto get_longest_sub_string = [longest_sub_string, input](wxString &longest_sub_str, int cur_len, size_t i) { + auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { if (cur_len > longest_sub_str.Len()) longest_sub_str = input.SubString(i - cur_len + 1, i); }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2898cc8bd..d5965c59d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -451,6 +451,12 @@ void Preview::msw_rescale() refresh_print(); } +void Preview::sys_color_changed() +{ + if (m_layers_slider != nullptr) + m_layers_slider->sys_color_changed(); +} + void Preview::jump_layers_slider(wxKeyEvent& evt) { if (m_layers_slider) m_layers_slider->OnChar(evt); @@ -550,13 +556,6 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) 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) @@ -567,6 +566,13 @@ void Preview::on_combochecklist_options(wxCommandEvent& evt) #if ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE m_canvas->refresh_gcode_preview_render_paths(); #else + 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); + }; + bool skip_refresh = xored(curr_flags, new_flags, static_cast(OptionType::Shells)) || xored(curr_flags, new_flags, static_cast(OptionType::ToolMarker)); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index eeae2d243..8c72d7a3f 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -182,6 +182,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSl ForceState get_force_state() { return current_force_state; } void msw_rescale(); + void sys_color_changed(); void jump_layers_slider(wxKeyEvent& evt); void move_layers_slider(wxKeyEvent& evt); void edit_layers_slider(wxKeyEvent& evt); diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 3235d6e9e..dbb200f83 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -108,6 +108,7 @@ public: // recalc_font(); +#ifndef __WXOSX__ #if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; @@ -128,6 +129,7 @@ public: rescale(evt.rect); }); #endif // wxVERSION_EQUAL_OR_GREATER_THAN +#endif // no __WXOSX__ this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 02455f01b..3aa4c9fb8 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -41,7 +41,7 @@ namespace instance_check_internal //if (argc < 2) // return ret; std::vector arguments { argv[0] }; - for (size_t i = 1; i < argc; ++i) { + for (int i = 1; i < argc; ++i) { const std::string token = argv[i]; // Processing of boolean command line arguments shall match DynamicConfig::read_cli(). if (token == "--single-instance") @@ -180,7 +180,7 @@ namespace instance_check_internal if ( !checker->IsAnotherRunning() ) */ { DBusMessage* msg; - DBusMessageIter args; + // DBusMessageIter args; DBusConnection* conn; DBusError err; dbus_uint32_t serial = 0; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b2c5fec0b..4cef5c87a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -394,7 +394,7 @@ void NotificationManager::PopNotification::count_spaces() } void NotificationManager::PopNotification::init() { - std::string text = m_text1 + " " + m_hypertext; + std::string text = m_text1; int last_end = 0; m_lines_count = 0; @@ -443,6 +443,15 @@ void NotificationManager::PopNotification::init() } m_lines_count++; } + // hypertext calculation + if (!m_hypertext.empty()) { + int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; + if (ImGui::CalcTextSize((text.substr(prev_end, last_end - prev_end) + m_hypertext).c_str()).x > m_window_width - m_window_width_offset) { + m_endlines.push_back(last_end); + m_lines_count++; + } + } + if (m_lines_count >= 3) m_multiline = true; m_initialized = true; @@ -469,8 +478,9 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons int last_end = 0; float starting_y = m_line_height/2;//10; float shift_y = m_line_height;// -m_line_height / 20; + std::string line; for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); + line = m_text1.substr(last_end , m_endlines[i] - last_end); if(i < m_lines_count - 1) last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); ImGui::SetCursorPosX(x_offset); @@ -478,9 +488,8 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons imgui.text(line.c_str()); } //hyperlink text - if (!m_hypertext.empty()) - { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); + if (!m_hypertext.empty()) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); } } @@ -840,15 +849,17 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& float starting_y = m_line_height / 2;//10; float shift_y = m_line_height;// -m_line_height / 20; for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); - //hyperlink text - if ( i == 0 ) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(0, last_end).c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + if (m_text1.size() >= m_endlines[i]) { + std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); + if (i < m_lines_count - 1) + last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + //hyperlink text + if ( i == 0 ) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + } } } diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index ccc9413ef..931876c23 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -413,6 +413,9 @@ void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line, void OG_CustomCtrl::msw_rescale() { +#ifdef __WXOSX__ + return; +#endif m_font = wxGetApp().normal_font(); m_em_unit = em_unit(m_parent); m_v_gap = lround(1.0 * m_em_unit); @@ -435,7 +438,6 @@ void OG_CustomCtrl::msw_rescale() void OG_CustomCtrl::sys_color_changed() { - msw_rescale(); } OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height, diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index a7c436106..b6b1757e3 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -5,6 +5,8 @@ #include "I18N.hpp" #include "3DScene.hpp" +#include "libslic3r/Platform.hpp" + #include #include @@ -333,7 +335,13 @@ void OpenGLManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; bool enable_multisample = wxVersion >= 30003; - s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; + s_multisample = + enable_multisample && + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + platform_flavor() != PlatformFlavor::LinuxOnChromium && + wxGLCanvas::IsDisplaySupported(attribList) + ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 309f7163f..5988f3cab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -88,6 +88,7 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" #include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Platform.hpp" using boost::optional; namespace fs = boost::filesystem; @@ -2576,7 +2577,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); #ifndef AUTOPLACEMENT_ON_LOAD - bool need_arrange = false; + // bool need_arrange = false; #endif /* AUTOPLACEMENT_ON_LOAD */ bool scaled_down = false; std::vector obj_idxs; @@ -2596,7 +2597,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode new_instances.emplace_back(object->add_instance()); #else /* AUTOPLACEMENT_ON_LOAD */ // if object has no defined position(s) we need to rearrange everything after loading - need_arrange = true; + // need_arrange = true; // add a default instance and center object around origin object->center_around_origin(); // also aligns object to Z = 0 ModelInstance* instance = object->add_instance(); @@ -3758,9 +3759,8 @@ bool Plater::priv::warnings_dialog() if (current_warnings.empty()) return true; std::string text = _u8L("There are active warnings concerning sliced models:") + "\n"; - bool empt = true; for (auto const& it : current_warnings) { - int next_n = it.first.message.find_first_of('\n', 0); + size_t next_n = it.first.message.find_first_of('\n', 0); text += "\n"; if (next_n != std::string::npos) text += it.first.message.substr(0, next_n); @@ -3847,7 +3847,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); - notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, + // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it. + platform_flavor() != PlatformFlavor::LinuxOnChromium); wxGetApp().removable_drive_manager()->set_exporting_finished(true); } else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) { notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); @@ -5183,6 +5185,10 @@ bool Plater::load_files(const wxArrayString& filenames) load_files(in_paths, false, true); break; } + case LoadType::Unknown : { + assert(false); + break; + } } return true; @@ -6077,7 +6083,6 @@ void Plater::force_print_bed_update() void Plater::on_activate() { #if defined(__linux__) || defined(_WIN32) - wxWindow *focus_window = wxWindow::FindFocus(); // Activating the main frame, and no window has keyboard focus. // Set the keyboard focus to the visible Canvas3D. if (this->p->view3D->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas()) @@ -6359,6 +6364,7 @@ void Plater::msw_rescale() void Plater::sys_color_changed() { + p->preview->sys_color_changed(); p->sidebar->sys_color_changed(); // msw_rescale_menu updates just icons, so use it diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 1a1f015cb..9cc2887b6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -759,7 +759,7 @@ void PlaterPresetComboBox::update() this->Clear(); invalidate_selection(); - const Preset* selected_filament_preset; + const Preset* selected_filament_preset = nullptr; std::string extruder_color; if (m_type == Preset::TYPE_FILAMENT) { diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index b11cc8dd5..59a202beb 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -1,4 +1,5 @@ #include "RemovableDriveManager.hpp" +#include "libslic3r/Platform.hpp" #include #include @@ -185,8 +186,13 @@ namespace search_for_drives_internal { //confirms if the file is removable drive and adds it to vector - //if not same file system - could be removable drive - if (! compare_filesystem_id(path, parent_path)) { + if ( +#ifdef __linux__ + // Chromium mounts removable drives in a way that produces the same device ID. + platform_flavor() == PlatformFlavor::LinuxOnChromium || +#endif + // If not same file system - could be removable drive. + ! compare_filesystem_id(path, parent_path)) { //free space boost::system::error_code ec; boost::filesystem::space_info si = boost::filesystem::space(path, ec); @@ -229,22 +235,28 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons #else - //search /media/* folder - search_for_drives_internal::search_path("/media/*", "/media", current_drives); + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // ChromeOS specific: search /mnt/chromeos/removable/* folder + search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); + } else { + //search /media/* folder + search_for_drives_internal::search_path("/media/*", "/media", current_drives); - //search_path("/Volumes/*", "/Volumes"); - std::string path(std::getenv("USER")); - std::string pp(path); + //search_path("/Volumes/*", "/Volumes"); + std::string path(std::getenv("USER")); + std::string pp(path); - //search /media/USERNAME/* folder - pp = "/media/"+pp; - path = "/media/" + path + "/*"; - search_for_drives_internal::search_path(path, pp, current_drives); + //search /media/USERNAME/* folder + pp = "/media/"+pp; + path = "/media/" + path + "/*"; + search_for_drives_internal::search_path(path, pp, current_drives); + + //search /run/media/USERNAME/* folder + path = "/run" + path; + pp = "/run"+pp; + search_for_drives_internal::search_path(path, pp, current_drives); + } - //search /run/media/USERNAME/* folder - path = "/run" + path; - pp = "/run"+pp; - search_for_drives_internal::search_path(path, pp, current_drives); #endif return current_drives; @@ -441,7 +453,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() RemovableDriveManager::RemovableDrivesStatus out; { tbb::mutex::scoped_lock lock(m_drives_mutex); - out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end(); + out.has_eject = + // Cannot control eject on Chromium. + platform_flavor() != PlatformFlavor::LinuxOnChromium && + this->find_last_save_path_drive_data() != m_current_drives.end(); out.has_removable_drives = ! m_current_drives.empty(); } if (! out.has_eject) diff --git a/src/slic3r/GUI/RemovableDriveManagerMM.mm b/src/slic3r/GUI/RemovableDriveManagerMM.mm index 0b8646af1..1c4dc5145 100644 --- a/src/slic3r/GUI/RemovableDriveManagerMM.mm +++ b/src/slic3r/GUI/RemovableDriveManagerMM.mm @@ -81,7 +81,9 @@ static void unmount_callback(DADiskRef disk, DADissenterRef dissenter, void *con NSLog(@"-%@",(CFStringRef)deviceModelKey); */ if (mediaEjectableKey != nullptr) { - BOOL op = ejectable && (CFEqual(deviceProtocolName, CFSTR("USB")) || CFEqual(deviceModelKey, CFSTR("SD Card Reader")) || CFEqual(deviceProtocolName, CFSTR("Secure Digital"))); + BOOL op = ejectable && + ( (deviceProtocolName != nullptr && (CFEqual(deviceProtocolName, CFSTR("USB")) || CFEqual(deviceProtocolName, CFSTR("Secure Digital")))) || + (deviceModelKey != nullptr && CFEqual(deviceModelKey, CFSTR("SD Card Reader"))) ); //!CFEqual(deviceModelKey, CFSTR("Disk Image")); if (op) [result addObject:volURL.path]; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 40605c0e6..a3eadca15 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -121,18 +121,13 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty } } -// Wrap a string with ColorMarkerStart and ColorMarkerEnd symbols -static wxString wrap_string(const wxString& str) -{ - return wxString::Format("%c%s%c", ImGui::ColorMarkerStart, str, ImGui::ColorMarkerEnd); -} - // Mark a string using ColorMarkerStart and ColorMarkerEnd symbols -static std::wstring mark_string(const std::wstring &str, const std::vector &matches) +static std::wstring mark_string(const std::wstring &str, const std::vector &matches, Preset::Type type, PrinterTechnology pt) { std::wstring out; + out += marker_by_type(type, pt); if (matches.empty()) - out = str; + out += str; else { out.reserve(str.size() * 2); if (matches.front() > 0) @@ -205,10 +200,11 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) bool full_list = search.empty(); std::wstring sep = L" : "; - auto get_label = [this, &sep](const Option& opt) + auto get_label = [this, &sep](const Option& opt, bool marked = true) { std::wstring out; - out += marker_by_type(opt.type, printer_technology); + if (marked) + out += marker_by_type(opt.type, printer_technology); const std::wstring* prev = nullptr; for (const std::wstring* const s : { view_params.category ? &opt.category_local : nullptr, @@ -222,10 +218,11 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) return out; }; - auto get_label_english = [this, &sep](const Option& opt) + auto get_label_english = [this, &sep](const Option& opt, bool marked = true) { std::wstring out; - out += marker_by_type(opt.type, printer_technology); + if (marked) + out += marker_by_type(opt.type, printer_technology); const std::wstring* prev = nullptr; for (const std::wstring* const s : { view_params.category ? &opt.category : nullptr, @@ -258,8 +255,8 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) std::wstring wsearch = boost::nowide::widen(search); boost::trim_left(wsearch); - std::wstring label = get_label(opt); - std::wstring label_english = get_label_english(opt); + std::wstring label = get_label(opt, false); + std::wstring label_english = get_label_english(opt, false); int score = std::numeric_limits::min(); int score2; matches.clear(); @@ -280,8 +277,8 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) matches = std::move(matches2); score = score2; } - if (score > std::numeric_limits::min()) { - label = mark_string(label, matches); + if (score > 90/*std::numeric_limits::min()*/) { + label = mark_string(label, matches, opt.type, printer_technology); label += L" [" + std::to_wstring(score) + L"]";// add score value std::string label_u8 = into_u8(label); std::string label_plain = label_u8; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8887e3c31..91ad731a8 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2092,7 +2092,7 @@ static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d & static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) { - for (size_t idx_object = 0; idx_object < model.objects.size(); ++idx_object) { + for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { int idx_volume_first = -1; for (int i = 0; i < (int)volumes.size(); ++i) { if (volumes[i]->object_idx() == idx_object) { diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index b3c0ca335..c11e0279e 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1006,7 +1006,7 @@ wxString UnsavedChangesDialog::get_short_string(wxString full_string) { int max_len = 30; if (full_string.IsEmpty() || full_string.StartsWith("#") || - (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < max_len)) + (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < size_t(max_len))) return full_string; m_has_long_strings = true; diff --git a/src/slic3r/GUI/fts_fuzzy_match.h b/src/slic3r/GUI/fts_fuzzy_match.h index 4dd206aac..379fd9c74 100644 --- a/src/slic3r/GUI/fts_fuzzy_match.h +++ b/src/slic3r/GUI/fts_fuzzy_match.h @@ -62,13 +62,13 @@ namespace fts { } // Public interface - static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore) { + [[maybe_unused]] static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore) { pos_type matches[max_matches + 1]; // with the room for the stopper matches[0] = stopper; return fuzzy_match(pattern, str, outScore, matches); } - static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches) { + [[maybe_unused]] static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches) { int recursionCount = 0; static constexpr int recursionLimit = 10; return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, 0, recursionCount, recursionLimit); @@ -113,15 +113,16 @@ namespace fts { bool first_match = true; while (*pattern != '\0' && *str != '\0') { - int num_matched = std::tolower(*pattern) == std::tolower(*str) ? 1 : 0; - bool folded_match = false; - if (! num_matched) { + int num_matched = std::towlower(*pattern) == std::towlower(*str) ? 1 : 0; + // bool folded_match = false; + + if (! num_matched) { wchar_t tmp[4]; wchar_t *end = Slic3r::fold_to_ascii(*str, tmp); wchar_t *c = tmp; for (const wchar_t* d = pattern; c != end && *d != 0 && std::towlower(*c) == std::towlower(*d); ++c, ++d); if (c == end) { - folded_match = true; + // folded_match = true; num_matched = end - tmp; } } @@ -168,11 +169,11 @@ namespace fts { // Calculate score if (matched) { static constexpr int sequential_bonus = 15; // bonus for adjacent matches - static constexpr int separator_bonus = 30; // bonus if match occurs after a separator - static constexpr int camel_bonus = 30; // bonus if match is uppercase and prev is lower + static constexpr int separator_bonus = 10/*30*/; // bonus if match occurs after a separator + // static constexpr int camel_bonus = 30; // bonus if match is uppercase and prev is lower static constexpr int first_letter_bonus = 15; // bonus if the first letter is matched - static constexpr int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match + static constexpr int leading_letter_penalty = -1/*-5*/; // penalty applied for every letter in str before the first match static constexpr int max_leading_letter_penalty = -15; // maximum penalty for leading letters static constexpr int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 2082dfb88..cbb55077b 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -75,6 +75,7 @@ #undef times #undef accept #undef wait + #undef abort // Breaks compilation with Eigen matrices embedded into Slic3r::Point. #undef malloc