diff --git a/cmake/modules/FindTBB.cmake.in b/cmake/modules/FindTBB.cmake.in index a7eafa545f..49e405c184 100644 --- a/cmake/modules/FindTBB.cmake.in +++ b/cmake/modules/FindTBB.cmake.in @@ -293,7 +293,7 @@ if(NOT TBB_FOUND) # Create targets ################################## - if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND AND NOT TARGET TBB::tbb) add_library(TBB::tbb UNKNOWN IMPORTED) set_target_properties(TBB::tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}" diff --git a/resources/icons/legend_cog.svg b/resources/icons/legend_cog.svg new file mode 100644 index 0000000000..9a55fb7f5c --- /dev/null +++ b/resources/icons/legend_cog.svg @@ -0,0 +1,50 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs index d1316ca2fe..bef0751580 100644 --- a/resources/shaders/printbed.fs +++ b/resources/shaders/printbed.fs @@ -1,6 +1,6 @@ #version 110 -const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); +const vec3 back_color_dark = vec3(0.235, 0.235, 0.235); const vec3 back_color_light = vec3(0.365, 0.365, 0.365); uniform sampler2D texture; diff --git a/resources/shaders/printbed.vs b/resources/shaders/printbed.vs index 7633017f12..3b3f8875d2 100644 --- a/resources/shaders/printbed.vs +++ b/resources/shaders/printbed.vs @@ -1,14 +1,9 @@ #version 110 -attribute vec3 v_position; -attribute vec2 v_tex_coords; - varying vec2 tex_coords; void main() { - gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position.x, v_position.y, v_position.z, 1.0); - // the following line leads to crash on some Intel graphics card - //gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0); - tex_coords = v_tex_coords; + gl_Position = ftransform(); + tex_coords = gl_MultiTexCoord0.xy; } diff --git a/resources/shaders/toolpaths_cog.fs b/resources/shaders/toolpaths_cog.fs new file mode 100644 index 0000000000..f88d79b969 --- /dev/null +++ b/resources/shaders/toolpaths_cog.fs @@ -0,0 +1,18 @@ +#version 110 + +const vec4 BLACK = vec4(vec3(0.1), 1.0); +const vec4 WHITE = vec4(vec3(1.0), 1.0); + +const float emission_factor = 0.25; + +// x = tainted, y = specular; +varying vec2 intensity; +varying vec3 world_position; +uniform vec3 world_center; + +void main() +{ + vec3 delta = world_position - world_center; + vec4 color = delta.x * delta.y * delta.z > 0.0 ? BLACK : WHITE; + gl_FragColor = vec4(vec3(intensity.y) + color.rgb * (intensity.x + emission_factor), 1.0); +} diff --git a/resources/shaders/toolpaths_cog.vs b/resources/shaders/toolpaths_cog.vs new file mode 100644 index 0000000000..c7b1abfdbd --- /dev/null +++ b/resources/shaders/toolpaths_cog.vs @@ -0,0 +1,40 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +// x = tainted, y = specular; +varying vec2 intensity; +varying vec3 world_position; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + world_position = gl_Vertex.xyz; + gl_Position = ftransform(); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e89e82f67..ab1c7b9645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(Shiny) add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) +add_subdirectory(qoi) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2648fba9e2..4483d60102 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -498,8 +498,6 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - SL1Archive sla_archive(sla_print.printer_config()); - sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -539,7 +537,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_archive.export_print(outfile_final, sla_print); + sla_print.export_print(outfile_final); } if (outfile != outfile_final) { if (Slic3r::rename_file(outfile, outfile_final)) { @@ -838,6 +836,7 @@ extern "C" { "leak:libnvidia-glcore.so\n" // For NVidia driver. "leak:libnvidia-tls.so\n" // For NVidia driver. "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. + "leak:swrast_dri.so\n" // For Mesa 3D software driver. ; } } diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index db0e54e60d..f2c3ef0837 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -165,8 +165,9 @@ namespace ImGui const wchar_t LegendColorChanges = 0x2612; const wchar_t LegendPausePrints = 0x2613; const wchar_t LegendCustomGCodes = 0x2614; - const wchar_t LegendShells = 0x2615; - const wchar_t LegendToolMarker = 0x2616; + const wchar_t LegendCOG = 0x2615; + const wchar_t LegendShells = 0x2616; + const wchar_t LegendToolMarker = 0x2617; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index cb66a4adb5..7809b30445 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -98,8 +98,12 @@ set(SLIC3R_SOURCES Format/STL.hpp Format/SL1.hpp Format/SL1.cpp + Format/SL1_SVG.hpp + Format/SL1_SVG.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp + GCode/Thumbnails.cpp + GCode/Thumbnails.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/FindReplace.cpp @@ -352,6 +356,9 @@ encoding_check(libslic3r) target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0) target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS}) + +find_package(JPEG REQUIRED) + target_link_libraries(libslic3r libnest2d admesh @@ -370,6 +377,8 @@ target_link_libraries(libslic3r ${CMAKE_DL_LIBS} PNG::PNG ZLIB::ZLIB + JPEG::JPEG + qoi ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index f7a5c50071..d2ff650974 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -1,4 +1,5 @@ #include "Extruder.hpp" +#include "GCodeWriter.hpp" #include "PrintConfig.hpp" namespace Slic3r { @@ -7,24 +8,24 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config) : m_id(id), m_config(config) { - reset(); - // cache values that are going to be called often m_e_per_mm3 = this->extrusion_multiplier(); if (! m_config->use_volumetric_e) m_e_per_mm3 /= this->filament_crossection(); } -double Extruder::extrude(double dE) +std::pair Extruder::extrude(double dE) { // in case of relative E distances we always reset to 0 before any output if (m_config->use_relative_e_distances) m_E = 0.; + // Quantize extruder delta to G-code resolution. + dE = GCodeFormatter::quantize_e(dE); m_E += dE; m_absolute_E += dE; if (dE < 0.) m_retracted -= dE; - return dE; + return std::make_pair(dE, m_E); } /* This method makes sure the extruder is retracted by the specified amount @@ -34,28 +35,33 @@ double Extruder::extrude(double dE) The restart_extra argument sets the extra length to be used for unretraction. If we're actually performing a retraction, any restart_extra value supplied will overwrite the previous one if any. */ -double Extruder::retract(double length, double restart_extra) +std::pair Extruder::retract(double retract_length, double restart_extra) { // in case of relative E distances we always reset to 0 before any output if (m_config->use_relative_e_distances) m_E = 0.; - double to_retract = std::max(0., length - m_retracted); + // Quantize extruder delta to G-code resolution. + double to_retract = this->retract_to_go(retract_length); if (to_retract > 0.) { m_E -= to_retract; m_absolute_E -= to_retract; m_retracted += to_retract; - m_restart_extra = restart_extra; + m_restart_extra = restart_extra; } - return to_retract; + return std::make_pair(to_retract, m_E); } -double Extruder::unretract() +double Extruder::retract_to_go(double retract_length) const { - double dE = m_retracted + m_restart_extra; - this->extrude(dE); + return std::max(0., GCodeFormatter::quantize_e(retract_length - m_retracted)); +} + +std::pair Extruder::unretract() +{ + auto [dE, emitE] = this->extrude(m_retracted + m_restart_extra); m_retracted = 0.; m_restart_extra = 0.; - return dE; + return std::make_pair(dE, emitE); } // Used filament volume in mm^3. diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index e9c6927f85..7491b1c8f5 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -12,22 +12,24 @@ class Extruder { public: Extruder(unsigned int id, GCodeConfig *config); - virtual ~Extruder() {} - - void reset() { - m_E = 0; - m_absolute_E = 0; - m_retracted = 0; - m_restart_extra = 0; - } + ~Extruder() = default; unsigned int id() const { return m_id; } - double extrude(double dE); - double retract(double length, double restart_extra); - double unretract(); - double E() const { return m_E; } - void reset_E() { m_E = 0.; } + // Following three methods emit: + // first - extrusion delta + // second - number to emit to G-code: This may be delta for relative mode or a distance from last reset_E() for absolute mode. + // They also quantize the E axis to G-code resolution. + std::pair extrude(double dE); + std::pair retract(double retract_length, double restart_extra); + std::pair unretract(); + // How much to retract yet before retract_length is reached? + // The value is quantized to G-code resolution. + double retract_to_go(double retract_length) const; + + // Reset the current state of the E axis (this is only needed for relative extruder addressing mode anyways). + // Returns true if the extruder was non-zero before reset. + bool reset_E() { bool modified = m_E != 0; m_E = 0.; return modified; } double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; } double e_per_mm3() const { return m_e_per_mm3; } // Used filament volume in mm^3. @@ -57,14 +59,16 @@ private: GCodeConfig *m_config; // Print-wide global ID of this extruder. unsigned int m_id; - // Current state of the extruder axis, may be resetted if use_relative_e_distances. - double m_E; + // Current state of the extruder axis. + // For absolute extruder addressing, it is the current state since the last reset (G92 E0) issued at the end of the last retraction. + // For relative extruder addressing, it is the E axis difference emitted into the G-code the last time. + double m_E { 0 }; // Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics. - double m_absolute_E; + double m_absolute_E { 0 }; // Current positive amount of retraction. - double m_retracted; + double m_retracted { 0 }; // When retracted, this value stores the extra amount of priming on deretraction. - double m_restart_extra; + double m_restart_extra { 0 }; double m_e_per_mm3; }; @@ -76,4 +80,4 @@ inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id() } -#endif +#endif // slic3r_Extruder_hpp_ diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index df6925a421..3359983a55 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -125,18 +125,21 @@ static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; static constexpr const char* OBJECT_TYPE = "object"; static constexpr const char* VOLUME_TYPE = "volume"; -static constexpr const char* NAME_KEY = "name"; -static constexpr const char* MODIFIER_KEY = "modifier"; +static constexpr const char* NAME_KEY = "name"; +static constexpr const char* MODIFIER_KEY = "modifier"; static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; -static constexpr const char* MATRIX_KEY = "matrix"; -static constexpr const char* SOURCE_FILE_KEY = "source_file"; -static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; -static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; -static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; -static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; -static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; -static constexpr const char* SOURCE_IN_INCHES = "source_in_inches"; -static constexpr const char* SOURCE_IN_METERS = "source_in_meters"; +static constexpr const char* MATRIX_KEY = "matrix"; +static constexpr const char* SOURCE_FILE_KEY = "source_file"; +static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; +static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; +static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; +#if ENABLE_RELOAD_FROM_DISK_REWORK +static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; @@ -843,6 +846,20 @@ namespace Slic3r { return false; } +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { + ModelVolume* v = o->volumes[vol_id]; + if (v->source.input_file.empty()) + v->source.input_file = v->name.empty() ? filename : v->name; + if (v->source.volume_idx == -1) + v->source.volume_idx = vol_id; + if (v->source.object_idx == -1) + v->source.object_idx = obj_id; + } + } +#else int object_idx = 0; for (ModelObject* o : model.objects) { int volume_idx = 0; @@ -858,6 +875,7 @@ namespace Slic3r { } ++object_idx; } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // // fixes the min z of the model if negative // model.adjust_min_z(); @@ -2120,15 +2138,19 @@ namespace Slic3r { else if (metadata.key == SOURCE_VOLUME_ID_KEY) volume->source.volume_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_X_KEY) - volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Y_KEY) - volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Z_KEY) - volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_IN_INCHES) + volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_IN_INCHES_KEY) volume->source.is_converted_from_inches = metadata.value == "1"; - else if (metadata.key == SOURCE_IN_METERS) + else if (metadata.key == SOURCE_IN_METERS_KEY) volume->source.is_converted_from_meters = metadata.value == "1"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) + volume->source.is_from_builtin_objects = metadata.value == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } @@ -3063,36 +3085,40 @@ namespace Slic3r { // stores volume's type (overrides the modifier field above) add_metadata(stream, 3, MetadataType::volume, VOLUME_TYPE_KEY, ModelVolume::type_to_string(volume->type())); - // stores volume's local matrix - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix(r, c); - if (r != 3 || c != 3) - stream << " "; - } - } - stream << "\"/>\n"; + // stores volume's local matrix + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; + const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + stream << matrix(r, c); + if (r != 3 || c != 3) + stream << " "; + } + } + stream << "\"/>\n"; - // stores volume's source data - { - std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); - std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; - if (! volume->source.input_file.empty()) { - stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; - stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; - stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; - } - assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); - if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - } + // stores volume's source data + { + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; + if (! volume->source.input_file.empty()) { + stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; + stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; + stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; + } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); + if (volume->source.is_converted_from_inches) + stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + else if (volume->source.is_converted_from_meters) + stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + } // stores volume's config data for (const std::string& key : volume->config.keys()) { @@ -3201,6 +3227,36 @@ static void handle_legacy_project_loaded(unsigned int version_project_file, Dyna } } +bool is_project_3mf(const std::string& filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return false; + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + // loop the entries to search for config + mz_zip_archive_file_stat stat; + bool config_found = false; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + config_found = true; + break; + } + } + } + + close_zip_reader(&archive); + + return config_found; +} + bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { if (path == nullptr || model == nullptr) diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b91e90da74..5c2a2e672a 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -29,6 +29,9 @@ namespace Slic3r { class DynamicPrintConfig; struct ThumbnailData; + // Returns true if the 3mf file with the given filename is a PrusaSlicer project file (i.e. if it contains a config). + extern bool is_project_3mf(const std::string& filename); + // Load the content of a 3mf file into the given model and preset bundle. extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a8b66b15b1..f2a902758e 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -657,11 +657,16 @@ void AMFParserContext::endElement(const char * /* name */) if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform) m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); - if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) { +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (m_volume->source.input_file.empty()) { +#else + if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) { +#endif // ENABLE_RELOAD_FROM_DISK_REWORK m_volume->source.object_idx = (int)m_model.objects.size() - 1; m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->center_geometry_after_creation(); - } else + } + else // pass false if the mesh offset has been already taken from the data m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); @@ -792,46 +797,44 @@ void AMFParserContext::endElement(const char * /* name */) // Is this volume a modifier volume? // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); - } else if (strcmp(opt_key, "volume_type") == 0) { + } + else if (strcmp(opt_key, "volume_type") == 0) m_volume->set_type(ModelVolume::type_from_string(m_value[1])); - } - else if (strcmp(opt_key, "matrix") == 0) { + else if (strcmp(opt_key, "matrix") == 0) m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]); - } - else if (strcmp(opt_key, "source_file") == 0) { + else if (strcmp(opt_key, "source_file") == 0) m_volume->source.input_file = m_value[1]; - } - else if (strcmp(opt_key, "source_object_id") == 0) { + else if (strcmp(opt_key, "source_object_id") == 0) m_volume->source.object_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_volume_id") == 0) { + else if (strcmp(opt_key, "source_volume_id") == 0) m_volume->source.volume_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_x") == 0) { - m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_y") == 0) { - m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_z") == 0) { - m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_in_inches") == 0) { + else if (strcmp(opt_key, "source_offset_x") == 0) + m_volume->source.mesh_offset.x() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_y") == 0) + m_volume->source.mesh_offset.y() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_z") == 0) + m_volume->source.mesh_offset.z() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_in_inches") == 0) m_volume->source.is_converted_from_inches = m_value[1] == "1"; - } - else if (strcmp(opt_key, "source_in_meters") == 0) { + else if (strcmp(opt_key, "source_in_meters") == 0) m_volume->source.is_converted_from_meters = m_value[1] == "1"; - } +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (strcmp(opt_key, "source_is_builtin_volume") == 0) + m_volume->source.is_from_builtin_objects = m_value[1] == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } - } else if (m_path.size() == 3) { + } + else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_material) m_material->attributes[m_value[0]] = m_value[1]; - } else if (m_path[1] == NODE_TYPE_OBJECT) { + } + else if (m_path[1] == NODE_TYPE_OBJECT) { if (m_object && m_value[0] == "name") m_object->name = std::move(m_value[1]); } - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } @@ -919,7 +922,11 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut unsigned int counter = 0; for (ModelVolume* v : o->volumes) { ++counter; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; if (v->name.empty()) { v->name = o->name; @@ -1068,7 +1075,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti for (ModelObject *o : model->objects) for (ModelVolume *v : o->volumes) - if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART)) +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; return true; @@ -1237,18 +1248,15 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " "; const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix(); stream << std::setprecision(std::numeric_limits::max_digits10); - for (int r = 0; r < 4; ++r) - { - for (int c = 0; c < 4; ++c) - { + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { stream << matrix(r, c); - if ((r != 3) || (c != 3)) + if (r != 3 || c != 3) stream << " "; } } stream << "\n"; - if (!volume->source.input_file.empty()) - { + if (!volume->source.input_file.empty()) { std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); stream << " " << input_file << "\n"; stream << " " << volume->source.object_idx << "\n"; @@ -1262,12 +1270,16 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " 1\n"; else if (volume->source.is_converted_from_meters) stream << " 1\n"; - stream << std::setprecision(std::numeric_limits::max_digits10); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << " 1\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + stream << std::setprecision(std::numeric_limits::max_digits10); const indexed_triangle_set &its = volume->mesh().its; for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << its.indices[i][j] + vertices_offset << "\n"; + stream << " " << its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 6ed8c5ebed..6f1e95528a 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -446,8 +446,8 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) std::unique_ptr SL1Archive::create_raster() const { - sla::RasterBase::Resolution res; - sla::RasterBase::PixelDim pxdim; + sla::Resolution res; + sla::PixelDim pxdim; std::array mirror; double w = m_cfg.display_width.getFloat(); @@ -468,8 +468,8 @@ std::unique_ptr SL1Archive::create_raster() const std::swap(pw, ph); } - res = sla::RasterBase::Resolution{pw, ph}; - pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + res = sla::Resolution{pw, ph}; + pxdim = sla::PixelDim{w / pw, h / ph}; sla::RasterBase::Trafo tr{orientation, mirror}; double gamma = m_cfg.gamma_correction.getFloat(); diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 46a82e1b85..e6c0ff0896 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -15,27 +15,16 @@ protected: std::unique_ptr create_raster() const override; sla::RasterEncoder get_encoder() const override; + SLAPrinterConfig & cfg() { return m_cfg; } + const SLAPrinterConfig & cfg() const { return m_cfg; } + public: SL1Archive() = default; explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); - void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") - { - Zipper zipper(fname); - export_print(zipper, print, projectname); - } - - void apply(const SLAPrinterConfig &cfg) override - { - auto diff = m_cfg.diff(cfg); - if (!diff.empty()) { - m_cfg.apply_only(cfg, diff); - m_layers = {}; - } - } + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override; }; ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp new file mode 100644 index 0000000000..0ea2306119 --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -0,0 +1,221 @@ +#include "SL1_SVG.hpp" +#include "SLA/RasterBase.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/ClipperUtils.hpp" + +#include +#include +#include + +namespace Slic3r { + +namespace { + +size_t constexpr coord_t_bufsize = 40; + +char const* decimal_from(coord_t snumber, char* buffer) +{ + std::make_unsigned_t number = 0; + + char* ret = buffer; + + if( snumber < 0 ) { + *buffer++ = '-'; + number = -snumber; + } else + number = snumber; + + if( number == 0 ) { + *buffer++ = '0'; + } else { + char* p_first = buffer; + while( number != 0 ) { + *buffer++ = '0' + number % 10; + number /= 10; + } + std::reverse( p_first, buffer ); + } + + *buffer = '\0'; + + return ret; +} + +inline std::string coord2str(coord_t crd) +{ + char buf[coord_t_bufsize]; + return decimal_from(crd, buf); +} + +void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb) +{ + if (tr.flipXY) { + for (auto &p : ep.contour.points) std::swap(p.x(), p.y()); + for (auto &h : ep.holes) + for (auto &p : h.points) std::swap(p.x(), p.y()); + } + + if (tr.mirror_x){ + for (auto &p : ep.contour.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + } + + if (tr.mirror_y){ + for (auto &p : ep.contour.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + } +} + +void append_svg(std::string &buf, const Polygon &poly) +{ + if (poly.points.empty()) + return; + + auto c = poly.points.front(); + + char intbuf[coord_t_bufsize]; + + buf += std::string("(m_bb.size().x())); + std::string hf = float_to_string_decimal_point(unscaled(m_bb.size().y())); + std::string w = coord2str(coord_t(m_res.width_px)); + std::string h = coord2str(coord_t(m_res.height_px)); + + // Notice the header also defines the fill-rule as nonzero which should + // generate correct results for our ExPolygons. + + // Add svg header. + m_svg = + "\n" + "\n" + "\n"; + + // Add black background; + m_svg += "\n"; + } + + void draw(const ExPolygon& poly) override + { + auto cpoly = poly; + + double tol = std::min(m_bb.size().x() / double(m_res.width_px), + m_bb.size().y() / double(m_res.height_px)); + + ExPolygons cpolys = poly.simplify(tol); + + for (auto &cpoly : cpolys) { + transform(cpoly, m_trafo, m_bb); + + for (auto &p : cpoly.contour.points) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + for (auto &h : cpoly.holes) + for (auto &p : h) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + append_svg(m_svg, cpoly.contour); + for (auto &h : cpoly.holes) + append_svg(m_svg, h); + } + } + + Trafo trafo() const override { return m_trafo; } + + sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override + { + std::vector data; + constexpr const char finish[] = "\n"; + + data.reserve(m_svg.size() + std::size(finish)); + + std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data)); + std::copy(finish, finish + std::size(finish) - 1, std::back_inserter(data)); + + return sla::EncodedRaster{std::move(data), "svg"}; + } +}; + +std::unique_ptr SL1_SVGArchive::create_raster() const +{ + auto w = cfg().display_width.getFloat(); + auto h = cfg().display_height.getFloat(); + +// auto res_x = size_t(cfg().display_pixels_x.getInt()); +// auto res_y = size_t(cfg().display_pixels_y.getInt()); + float precision_nm = scaled(cfg().sla_output_precision.getFloat()); + size_t res_x = std::round(scaled(w) / precision_nm); + size_t res_y = std::round(scaled(h) / precision_nm); + + std::array mirror; + + mirror[X] = cfg().display_mirror_x.getBool(); + mirror[Y] = cfg().display_mirror_y.getBool(); + + auto ro = cfg().display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + std::swap(res_x, res_y); + } + + BoundingBox svgarea{{0, 0}, {scaled(w), scaled(h)}}; + + sla::RasterBase::Trafo tr{orientation, mirror}; + + // Gamma does not really make sense in an svg, right? + // double gamma = cfg().gamma_correction.getFloat(); + return std::make_unique(svgarea, sla::Resolution{res_x, res_y}, tr); +} + +sla::RasterEncoder SL1_SVGArchive::get_encoder() const +{ + return nullptr; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp new file mode 100644 index 0000000000..a3afbcdfff --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -0,0 +1,22 @@ +#ifndef SL1_SVG_HPP +#define SL1_SVG_HPP + +#include "SL1.hpp" + +namespace Slic3r { + +class SL1_SVGArchive: public SL1Archive { +protected: + + // Override the factory methods to produce svg instead of a real raster. + std::unique_ptr create_raster() const override; + sla::RasterEncoder get_encoder() const override; + +public: + + using SL1Archive::SL1Archive; +}; + +} // namespace Slic3r + +#endif // SL1_SVG_HPP diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5fac4b8229..fb6dee37ba 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "EdgeGrid.hpp" #include "Geometry/ConvexHull.hpp" #include "GCode/PrintExtents.hpp" +#include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" #include "Print.hpp" @@ -26,7 +27,6 @@ #include #include #include -#include #include #include @@ -54,8 +54,6 @@ #include -#include "miniz_extension.hpp" - using namespace std::literals::string_view_literals; #if 0 @@ -156,63 +154,52 @@ namespace Slic3r { std::string Wipe::wipe(GCode& gcodegen, bool toolchange) { - std::string gcode; + std::string gcode; + const Extruder &extruder = *gcodegen.writer().extruder(); - /* Reduce feedrate a bit; travel speed is often too high to move on existing material. - Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ - double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; - - // get the retraction length - double length = toolchange - ? gcodegen.writer().extruder()->retract_length_toolchange() - : gcodegen.writer().extruder()->retract_length(); - // Shorten the retraction length by the amount already retracted before wipe. - length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); - - if (length > 0) { - /* Calculate how long we need to travel in order to consume the required - amount of retraction. In other words, how far do we move in XY at wipe_speed - for the time needed to consume retract_length at retract_speed? */ - double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed); - - /* Take the stored wipe path and replace first point with the current actual position - (they might be different, for example, in case of loop clipping). */ - Polyline wipe_path; - wipe_path.append(gcodegen.last_pos()); - wipe_path.append( - this->path.points.begin() + 1, - this->path.points.end() - ); - - wipe_path.clip_end(wipe_path.length() - wipe_dist); - - // subdivide the retraction in segments - if (!wipe_path.empty()) { - // add tag for processor + // Remaining quantized retraction length. + if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length()); + retract_length > 0 && this->path.size() >= 2) { + // Reduce feedrate a bit; travel speed is often too high to move on existing material. + // Too fast = ripping of existing material; too slow = short wipe path, thus more blob. + const double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; + // Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + // due to rounding (TODO: test and/or better math for this). + const double xy_to_e = 0.95 * extruder.retract_speed() / wipe_speed; + // Start with the current position, which may be different from the wipe path start in case of loop clipping. + Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos()); + auto it = this->path.points.begin(); + Vec2d p = gcodegen.point_to_gcode_quantized(*(++ it)); + if (p != prev) { gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; - for (const Line& line : wipe_path.lines()) { - double segment_length = line.length(); - /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one - due to rounding (TODO: test and/or better math for this) */ - double dE = length * (segment_length / wipe_dist) * 0.95; + auto end = this->path.points.end(); + bool done = false; + for (; it != end && ! done; ++ it) { + p = gcodegen.point_to_gcode_quantized(*it); + double segment_length = (p - prev).norm(); + double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length); + if (dE > retract_length - EPSILON) { + if (dE > retract_length + EPSILON) + // Shorten the segment. + p = prev + (p - prev) * (retract_length / dE); + dE = retract_length; + done = true; + } //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. // Is it here for the cooling markers? Or should it be outside of the cycle? - gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); - gcode += gcodegen.writer().extrude_to_xy( - gcodegen.point_to_gcode(line.b), - -dE, - "wipe and retract" - ); + gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); + gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract"); + prev = p; + retract_length -= dE; } // add tag for processor gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; - gcodegen.set_last_pos(wipe_path.points.back()); + gcodegen.set_last_pos(gcodegen.gcode_to_point(prev)); } - - // prevent wiping again on same path - this->reset_path(); } + // Prevent wiping again on the same path. + this->reset_path(); return gcode; } @@ -937,49 +924,6 @@ namespace DoExport { } } - template - static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) - { - // Write thumbnails using base64 encoding - if (thumbnail_cb != nullptr) - { - const size_t max_row_length = 78; - ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - { - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) - { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); - encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); - - output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); - - unsigned int row_count = 0; - while (encoded.size() > max_row_length) - { - output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); - encoded = encoded.substr(max_row_length); - ++row_count; - } - - if (encoded.size() > 0) - output((boost::format("; %s\n") % encoded).str().c_str()); - - output("; thumbnail end\n;\n"); - - mz_free(png_data); - } - } - throw_if_canceled(); - } - } - } - // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. static std::string update_print_stats_and_format_filament_stats( const bool has_wipe_tower, @@ -1163,9 +1107,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write information on the generator. file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, - [&file](const char* sz) { file.write(sz); }, - [&print]() { print.throw_if_canceled(); }); + // Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". + // If "thumbnails_format" is not defined, export to PNG. + if (const auto [thumbnails, thumbnails_format] = std::make_pair( + print.full_print_config().option("thumbnails"), + print.full_print_config().option>("thumbnails_format")); + thumbnails) + GCodeThumbnails::export_thumbnails_to_file( + thumbnail_cb, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG, + [&file](const char* sz) { file.write(sz); }, + [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) { @@ -3047,13 +2998,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; - for (const Line &line : path.polyline.lines()) { - const double line_length = line.length() * SCALING_FACTOR; + Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front()); + auto it = path.polyline.points.begin(); + auto end = path.polyline.points.end(); + for (++ it; it != end; ++ it) { + Vec2d p = this->point_to_gcode_quantized(*it); + const double line_length = (p - prev).norm(); path_length += line_length; - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - e_per_mm * line_length, - comment); + gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); + prev = p; } } if (m_enable_cooling_markers) @@ -3276,7 +3229,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) Vec2d GCode::point_to_gcode(const Point &point) const { Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return unscale(point) + m_origin - extruder_offset; + return unscaled(point) + m_origin - extruder_offset; +} + +Vec2d GCode::point_to_gcode_quantized(const Point &point) const +{ + Vec2d p = this->point_to_gcode(point); + return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; } // convert a model-space scaled point into G-code coordinates diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index f46558c353..c0e636c797 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -55,9 +55,9 @@ public: Polyline path; Wipe() : enable(false) {} - bool has_path() const { return !this->path.points.empty(); } - void reset_path() { this->path = Polyline(); } - std::string wipe(GCode &gcodegen, bool toolchange = false); + bool has_path() const { return ! this->path.empty(); } + void reset_path() { this->path.clear(); } + std::string wipe(GCode &gcodegen, bool toolchange); }; class WipeTowerIntegration { @@ -151,7 +151,10 @@ public: void set_origin(const Vec2d &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } const Point& last_pos() const { return m_last_pos; } + // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. Vec2d point_to_gcode(const Point &point) const; + // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. + Vec2d point_to_gcode_quantized(const Point &point) const; Point gcode_to_point(const Vec2d &point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 995964eb5f..50c0ce4a54 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1196,6 +1196,7 @@ void GCodeProcessor::reset() m_line_id = 0; m_last_line_id = 0; m_feedrate = 0.0f; + m_feed_multiply.reset(); m_width = 0.0f; m_height = 0.0f; m_forced_width = 0.0f; @@ -1698,6 +1699,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool break; case '2': switch (cmd[3]) { + case '0': { process_M220(line); break; } // Set Feedrate Percentage case '1': { process_M221(line); break; } // Set extrude factor override percentage default: break; } @@ -1955,7 +1957,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) m_result.spiral_vase_layers.back().second.second = move_id; else - m_result.spiral_vase_layers.push_back({ m_end_position[Z], { move_id, move_id } }); + m_result.spiral_vase_layers.push_back({ static_cast(m_end_position[Z]), { move_id, move_id } }); } #endif // ENABLE_SPIRAL_VASE_LAYERS return; @@ -2498,14 +2500,14 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // updates feedrate from line, if present if (line.has_f()) - m_feedrate = line.f() * MMMIN_TO_MMSEC; + m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; // calculates movement deltas float max_abs_delta = 0.0f; AxisCoords delta_pos; for (unsigned char a = X; a <= E; ++a) { delta_pos[a] = m_end_position[a] - m_start_position[a]; - max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); + max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); } // no displacement, return @@ -2615,7 +2617,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (curr.abs_axis_feedrate[a] != 0.0f) { float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); if (axis_max_feedrate != 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); + min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -2863,7 +2865,7 @@ void GCodeProcessor::process_G61(const GCodeReader::GCodeLine& line) modified = true; } if (line.has_f()) - m_feedrate = line.f(); + m_feedrate = m_feed_multiply.current * line.f(); if (!modified) m_end_position = m_saved_position; @@ -3136,6 +3138,20 @@ void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) } } +void GCodeProcessor::process_M220(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfMarlinLegacy && m_flavor != gcfMarlinFirmware) + return; + + if (line.has('B')) + m_feed_multiply.saved = m_feed_multiply.current; + float value; + if (line.has_value('S', value)) + m_feed_multiply.current = value * 0.01f; + if (line.has('R')) + m_feed_multiply.current = m_feed_multiply.saved; +} + void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) { float value_s; @@ -3279,7 +3295,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #else Vec3f(m_end_position[X], m_end_position[Y], m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[m_extruder_id], #endif // ENABLE_Z_OFFSET_CORRECTION - m_end_position[E] - m_start_position[E], + static_cast(m_end_position[E] - m_start_position[E]), m_feedrate, m_width, m_height, diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 153f4a9c5c..25375f61b6 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -178,7 +178,7 @@ namespace Slic3r { #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING private: - using AxisCoords = std::array; + using AxisCoords = std::array; using ExtruderColors = std::vector; using ExtruderTemps = std::vector; @@ -525,6 +525,17 @@ namespace Slic3r { unsigned int m_line_id; unsigned int m_last_line_id; float m_feedrate; // mm/s + struct FeedMultiply + { + float current; // percentage + float saved; // percentage + + void reset() { + current = 1.0f; + saved = 1.0f; + } + }; + FeedMultiply m_feed_multiply; float m_width; // mm float m_height; // mm float m_forced_width; // mm @@ -719,6 +730,9 @@ namespace Slic3r { // Advanced settings void process_M205(const GCodeReader::GCodeLine& line); + // Set Feedrate Percentage + void process_M220(const GCodeReader::GCodeLine& line); + // Set extrude factor override percentage void process_M221(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp new file mode 100644 index 0000000000..8d70539b7c --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -0,0 +1,119 @@ +#include "Thumbnails.hpp" +#include "../miniz_extension.hpp" + +#include +#include +#include + +namespace Slic3r::GCodeThumbnails { + +using namespace std::literals; + +struct CompressedPNG : CompressedImageBuffer +{ + ~CompressedPNG() override { if (data) mz_free(data); } + std::string_view tag() const override { return "thumbnail"sv; } +}; + +struct CompressedJPG : CompressedImageBuffer +{ + ~CompressedJPG() override { free(data); } + std::string_view tag() const override { return "thumbnail_JPG"sv; } +}; + +struct CompressedQOI : CompressedImageBuffer +{ + ~CompressedQOI() override { free(data); } + std::string_view tag() const override { return "thumbnail_QOI"sv; } +}; + +std::unique_ptr compress_thumbnail_png(const ThumbnailData &data) +{ + auto out = std::make_unique(); + out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1); + return out; +} + +std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) +{ + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size()); + const unsigned int row_size = data.width * 4; + for (unsigned int y = 0; y < data.height; ++y) { + ::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + } + + // Store pointers to scanlines start for later use + std::vector rows_ptrs; + rows_ptrs.reserve(data.height); + for (unsigned int y = 0; y < data.height; ++y) { + rows_ptrs.emplace_back(&rgba_pixels[y * row_size]); + } + + std::vector compressed_data(data.pixels.size()); + unsigned char* compressed_data_ptr = compressed_data.data(); + unsigned long compressed_data_size = data.pixels.size(); + + jpeg_error_mgr err; + jpeg_compress_struct info; + info.err = jpeg_std_error(&err); + jpeg_create_compress(&info); + jpeg_mem_dest(&info, &compressed_data_ptr, &compressed_data_size); + + info.image_width = data.width; + info.image_height = data.height; + info.input_components = 4; + info.in_color_space = JCS_EXT_RGBA; + + jpeg_set_defaults(&info); + jpeg_set_quality(&info, 85, TRUE); + jpeg_start_compress(&info, TRUE); + + jpeg_write_scanlines(&info, rows_ptrs.data(), data.height); + jpeg_finish_compress(&info); + jpeg_destroy_compress(&info); + + // FIXME -> Add error checking + + auto out = std::make_unique(); + out->data = malloc(compressed_data_size); + out->size = size_t(compressed_data_size); + ::memcpy(out->data, (const void*)compressed_data.data(), out->size); + return out; +} + +std::unique_ptr compress_thumbnail_qoi(const ThumbnailData &data) +{ + qoi_desc desc; + desc.width = data.width; + desc.height = data.height; + desc.channels = 4; + desc.colorspace = QOI_SRGB; + + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size() * 4); + size_t row_size = data.width * 4; + for (size_t y = 0; y < data.height; ++ y) + memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + + auto out = std::make_unique(); + int size; + out->data = qoi_encode((const void*)rgba_pixels.data(), &desc, &size); + out->size = size; + return out; +} + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format) +{ + switch (format) { + case GCodeThumbnailsFormat::PNG: + default: + return compress_thumbnail_png(data); + case GCodeThumbnailsFormat::JPG: + return compress_thumbnail_jpg(data); + case GCodeThumbnailsFormat::QOI: + return compress_thumbnail_qoi(data); + } +} + +} // namespace Slic3r::GCodeThumbnails diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp new file mode 100644 index 0000000000..30bb6b653b --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -0,0 +1,60 @@ +#ifndef slic3r_GCodeThumbnails_hpp_ +#define slic3r_GCodeThumbnails_hpp_ + +#include "../Point.hpp" +#include "../PrintConfig.hpp" +#include "ThumbnailData.hpp" + +#include +#include +#include + +#include + +namespace Slic3r::GCodeThumbnails { + +struct CompressedImageBuffer +{ + void *data { nullptr }; + size_t size { 0 }; + virtual ~CompressedImageBuffer() {} + virtual std::string_view tag() const = 0; +}; + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); + +template +inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) +{ + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) { + static constexpr const size_t max_row_length = 78; + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) { + auto compressed = compress_thumbnail(data, format); + if (compressed->data && compressed->size) { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); + encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size)); + + output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str()); + + while (encoded.size() > max_row_length) { + output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); + encoded = encoded.substr(max_row_length); + } + + if (encoded.size() > 0) + output((boost::format("; %s\n") % encoded).str().c_str()); + + output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str()); + } + throw_if_canceled(); + } + } +} + +} // namespace Slic3r::GCodeThumbnails + +#endif // slic3r_GCodeThumbnails_hpp_ diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 9dc9f3f96e..870096bb9b 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -74,7 +74,7 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object { double max_layer_height = std::numeric_limits::max(); for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) { - double mlh = config.max_layer_height.values[i]; + double mlh = config.max_layer_height.get_at(i); if (mlh == 0.) mlh = 0.75 * config.nozzle_diameter.values[i]; max_layer_height = std::min(max_layer_height, mlh); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 233976b195..c5279c0f56 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -79,7 +79,7 @@ std::string GCodeWriter::postamble() const std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const { if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) - return ""; + return {}; std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) { @@ -192,32 +192,18 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration) std::string GCodeWriter::reset_e(bool force) { - if (FLAVOR_IS(gcfMach3) - || FLAVOR_IS(gcfMakerWare) - || FLAVOR_IS(gcfSailfish)) - return ""; - - if (m_extruder != nullptr) { - if (m_extruder->E() == 0. && ! force) - return ""; - m_extruder->reset_E(); - } - - if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) { - std::ostringstream gcode; - gcode << "G92 " << m_extrusion_axis << "0"; - if (this->config.gcode_comments) gcode << " ; reset extrusion distance"; - gcode << "\n"; - return gcode.str(); - } else { - return ""; - } + return + FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish) || this->config.use_relative_e_distances || + (m_extruder != nullptr && ! m_extruder->reset_E() && ! force) || + m_extrusion_axis.empty() ? + std::string{} : + std::string("G92 ") + m_extrusion_axis + (this->config.gcode_comments ? "0 ; reset extrusion distance\n" : "0\n"); } std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) - return ""; + return {}; unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5); if (!allow_100) percent = std::min(percent, (unsigned int)99); @@ -269,8 +255,8 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) { - m_pos(0) = point(0); - m_pos(1) = point(1); + m_pos.x() = point.x(); + m_pos.y() = point.y(); GCodeG1Formatter w; w.emit_xy(point); @@ -290,9 +276,9 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co don't perform the Z move but we only move in the XY plane and adjust the nominal Z by reducing the lift amount that will be used for unlift. */ - if (!this->will_move_z(point(2))) { - double nominal_z = m_pos(2) - m_lifted; - m_lifted -= (point(2) - nominal_z); + if (!this->will_move_z(point.z())) { + double nominal_z = m_pos.z() - m_lifted; + m_lifted -= (point.z() - nominal_z); // In case that retract_lift == layer_height we could end up with almost zero in_m_lifted // and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154 if (std::abs(m_lifted) < EPSILON) @@ -318,11 +304,11 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) we don't perform the move but we only adjust the nominal Z by reducing the lift amount that will be used for unlift. */ if (!this->will_move_z(z)) { - double nominal_z = m_pos(2) - m_lifted; + double nominal_z = m_pos.z() - m_lifted; m_lifted -= (z - nominal_z); if (std::abs(m_lifted) < EPSILON) m_lifted = 0.; - return ""; + return {}; } /* In all the other cases, we perform an actual Z move and cancel @@ -333,7 +319,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) { - m_pos(2) = z; + m_pos.z() = z; double speed = this->config.travel_speed_z.value; if (speed == 0.) @@ -351,8 +337,8 @@ bool GCodeWriter::will_move_z(double z) const /* If target Z is lower than current Z but higher than nominal Z we don't perform an actual Z move. */ if (m_lifted > 0) { - double nominal_z = m_pos(2) - m_lifted; - if (z >= nominal_z && z <= m_pos(2)) + double nominal_z = m_pos.z() - m_lifted; + if (z >= nominal_z && z <= m_pos.z()) return false; } return true; @@ -360,17 +346,17 @@ bool GCodeWriter::will_move_z(double z) const std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment) { - m_pos(0) = point(0); - m_pos(1) = point(1); - m_extruder->extrude(dE); + m_pos.x() = point.x(); + m_pos.y() = point.y(); GCodeG1Formatter w; w.emit_xy(point); - w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); w.emit_comment(this->config.gcode_comments, comment); return w.string(); } +#if 0 std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) { m_pos = point; @@ -383,6 +369,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std w.emit_comment(this->config.gcode_comments, comment); return w.string(); } +#endif std::string GCodeWriter::retract(bool before_wipe) { @@ -422,14 +409,13 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std restart_extra = restart_extra * area; } - std::string gcode; - if (double dE = m_extruder->retract(length, restart_extra); dE != 0) { + if (auto [dE, emitE] = m_extruder->retract(length, restart_extra); dE != 0) { if (this->config.use_firmware_retraction) { gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n"; } else if (! m_extrusion_axis.empty()) { GCodeG1Formatter w; - w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_e(m_extrusion_axis, emitE); w.emit_f(m_extruder->retract_speed() * 60.); w.emit_comment(this->config.gcode_comments, comment); gcode = w.string(); @@ -449,14 +435,14 @@ std::string GCodeWriter::unretract() if (FLAVOR_IS(gcfMakerWare)) gcode = "M101 ; extruder on\n"; - if (double dE = m_extruder->unretract(); dE != 0) { + if (auto [dE, emitE] = m_extruder->unretract(); dE != 0) { if (this->config.use_firmware_retraction) { gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n"; gcode += this->reset_e(); } else if (! m_extrusion_axis.empty()) { // use G1 instead of G0 because G0 will blend the restart with the previous travel move GCodeG1Formatter w; - w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_e(m_extrusion_axis, emitE); w.emit_f(m_extruder->deretract_speed() * 60.); w.emit_comment(this->config.gcode_comments, " ; unretract"); gcode += w.string(); @@ -476,21 +462,21 @@ std::string GCodeWriter::lift() { double above = this->config.retract_lift_above.get_at(m_extruder->id()); double below = this->config.retract_lift_below.get_at(m_extruder->id()); - if (m_pos(2) >= above && (below == 0 || m_pos(2) <= below)) + if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below)) target_lift = this->config.retract_lift.get_at(m_extruder->id()); } if (m_lifted == 0 && target_lift > 0) { m_lifted = target_lift; - return this->_travel_to_z(m_pos(2) + target_lift, "lift Z"); + return this->_travel_to_z(m_pos.z() + target_lift, "lift Z"); } - return ""; + return {}; } std::string GCodeWriter::unlift() { std::string gcode; if (m_lifted > 0) { - gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z"); + gcode += this->_travel_to_z(m_pos.z() - m_lifted, "restore layer Z"); m_lifted = 0; } return gcode; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index e8a54737e0..6c36c0d3ac 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -61,7 +61,7 @@ public: std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string()); - std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); +// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); @@ -121,6 +121,14 @@ public: // static constexpr const int E_EXPORT_DIGITS = 9; #endif + static constexpr const std::array pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.}; + static constexpr const std::array pow_10_inv{1./1., 1./10., 1./100., 1./1000., 1./10000., 1./100000., 1./1000000., 1./10000000., 1./100000000., 1./1000000000.}; + + // Quantize doubles to a resolution of the G-code. + static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; } + static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); } + static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); } + void emit_axis(const char axis, const double v, size_t digits); void emit_xy(const Vec2d &point) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e65b604df1..ca1363da24 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -484,7 +484,7 @@ static std::vector s_Preset_printer_options { "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", - "machine_limits_usage", "thumbnails" + "machine_limits_usage", "thumbnails", "thumbnails_format" }; static std::vector s_Preset_sla_print_options { @@ -573,7 +573,7 @@ static std::vector s_Preset_sla_printer_options { "elefant_foot_min_width", "gamma_correction", "min_exposure_time", "max_exposure_time", - "min_initial_exposure_time", "max_initial_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", "sla_output_precision", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dc47b382da..9f848b49b6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -130,7 +130,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "start_gcode", "start_filament_gcode", "toolchange_gcode", - "threads", + "thumbnails", + "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "use_volumetric_e", diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b6e4802bbb..c5e312a2bf 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -346,10 +346,11 @@ private: friend class Print; PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); - ~PrintObject() { + ~PrintObject() override { if (m_shared_regions && --m_shared_regions->m_ref_cnt == 0) delete m_shared_regions; clear_layers(); + clear_support_layers(); } void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1aaf8c1dde..5a674da846 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -43,7 +43,7 @@ static t_config_enum_values s_keys_map_PrinterTechnology { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) -static t_config_enum_values s_keys_map_GCodeFlavor { +static const t_config_enum_values s_keys_map_GCodeFlavor { { "reprap", gcfRepRapSprinter }, { "reprapfirmware", gcfRepRapFirmware }, { "repetier", gcfRepetier }, @@ -59,14 +59,14 @@ static t_config_enum_values s_keys_map_GCodeFlavor { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) -static t_config_enum_values s_keys_map_MachineLimitsUsage { +static const t_config_enum_values s_keys_map_MachineLimitsUsage { { "emit_to_gcode", int(MachineLimitsUsage::EmitToGCode) }, { "time_estimate_only", int(MachineLimitsUsage::TimeEstimateOnly) }, { "ignore", int(MachineLimitsUsage::Ignore) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) -static t_config_enum_values s_keys_map_PrintHostType { +static const t_config_enum_values s_keys_map_PrintHostType { { "prusalink", htPrusaLink }, { "octoprint", htOctoPrint }, { "duet", htDuet }, @@ -77,20 +77,20 @@ static t_config_enum_values s_keys_map_PrintHostType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) -static t_config_enum_values s_keys_map_AuthorizationType { +static const t_config_enum_values s_keys_map_AuthorizationType { { "key", atKeyPassword }, { "user", atUserPassword } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) -static t_config_enum_values s_keys_map_FuzzySkinType { +static const t_config_enum_values s_keys_map_FuzzySkinType { { "none", int(FuzzySkinType::None) }, { "external", int(FuzzySkinType::External) }, { "all", int(FuzzySkinType::All) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) -static t_config_enum_values s_keys_map_InfillPattern { +static const t_config_enum_values s_keys_map_InfillPattern { { "rectilinear", ipRectilinear }, { "monotonic", ipMonotonic }, { "alignedrectilinear", ipAlignedRectilinear }, @@ -114,41 +114,41 @@ static t_config_enum_values s_keys_map_InfillPattern { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) -static t_config_enum_values s_keys_map_IroningType { +static const t_config_enum_values s_keys_map_IroningType { { "top", int(IroningType::TopSurfaces) }, { "topmost", int(IroningType::TopmostOnly) }, { "solid", int(IroningType::AllSolid) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) -static t_config_enum_values s_keys_map_SlicingMode { +static const t_config_enum_values s_keys_map_SlicingMode { { "regular", int(SlicingMode::Regular) }, { "even_odd", int(SlicingMode::EvenOdd) }, { "close_holes", int(SlicingMode::CloseHoles) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) -static t_config_enum_values s_keys_map_SupportMaterialPattern { +static const t_config_enum_values s_keys_map_SupportMaterialPattern { { "rectilinear", smpRectilinear }, { "rectilinear-grid", smpRectilinearGrid }, { "honeycomb", smpHoneycomb } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) -static t_config_enum_values s_keys_map_SupportMaterialStyle { +static const t_config_enum_values s_keys_map_SupportMaterialStyle { { "grid", smsGrid }, { "snug", smsSnug } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) -static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { +static const t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { { "auto", smipAuto }, { "rectilinear", smipRectilinear }, { "concentric", smipConcentric } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) -static t_config_enum_values s_keys_map_SeamPosition { +static const t_config_enum_values s_keys_map_SeamPosition { { "random", spRandom }, { "nearest", spNearest }, { "aligned", spAligned }, @@ -190,6 +190,13 @@ static const t_config_enum_values s_keys_map_DraftShield = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) +static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { + { "PNG", int(GCodeThumbnailsFormat::PNG) }, + { "JPG", int(GCodeThumbnailsFormat::JPG) }, + { "QOI", int(GCodeThumbnailsFormat::QOI) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat) + static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { { "disable", ForwardCompatibilitySubstitutionRule::Disable }, { "enable", ForwardCompatibilitySubstitutionRule::Enable }, @@ -259,6 +266,16 @@ void PrintConfigDef::init_common_params() def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints()); + def = this->add("thumbnails_format", coEnum); + def->label = L("Format of G-code thumbnails"); + def->tooltip = L("Format of G-code thumbnails: PNG for best quality, JPG for smallest size, QOI for low memory firmware"); + def->mode = comExpert; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("PNG"); + def->enum_values.push_back("JPG"); + def->enum_values.push_back("QOI"); + def->set_default_value(new ConfigOptionEnum(GCodeThumbnailsFormat::PNG)); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); @@ -3783,6 +3800,19 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Fast")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(slamsFast)); + + def = this->add("sla_archive_format", coString); + def->label = L("Format of the output SLA archive"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("SL1")); + + def = this->add("sla_output_precision", coFloat); + def->label = L("SLA output precision"); + def->tooltip = L("Minimum resolution in nanometers"); + def->sidetext = L("mm"); + def->min = SCALING_FACTOR; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.001)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2cc758e7be..b97168e84e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -131,6 +131,10 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; +enum class GCodeThumbnailsFormat { + PNG, JPG, QOI +}; + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -152,6 +156,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -757,6 +762,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInts, temperature)) ((ConfigOptionInt, threads)) + ((ConfigOptionPoints, thumbnails)) + ((ConfigOptionEnum, thumbnails_format)) ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) ((ConfigOptionFloat, wipe_tower_x)) @@ -975,6 +982,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, max_exposure_time)) ((ConfigOptionFloat, min_initial_exposure_time)) ((ConfigOptionFloat, max_initial_exposure_time)) + ((ConfigOptionString, sla_archive_format)) + ((ConfigOptionFloat, sla_output_precision)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index bc68cd3778..7c8e71c2ae 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -41,7 +41,7 @@ public: using TValue = typename TColor::value_type; using TPixel = typename PixelRenderer::pixel_type; using TRawBuffer = agg::rendering_buffer; - + protected: Resolution m_resolution; @@ -153,8 +153,8 @@ public: } Trafo trafo() const override { return m_trafo; } - Resolution resolution() const override { return m_resolution; } - PixelDim pixel_dimensions() const override + Resolution resolution() const { return m_resolution; } + PixelDim pixel_dimensions() const { return {SCALING_FACTOR / m_pxdim_scaled.w_mm, SCALING_FACTOR / m_pxdim_scaled.h_mm}; @@ -186,11 +186,15 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA { using typename Base::TValue; public: template - RasterGrayscaleAA(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - GammaFn && fn) - : Base(res, pd, trafo, Colors::White, Colors::Black, + RasterGrayscaleAA(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + GammaFn &&fn) + : Base(res, + pd, + trafo, + Colors::White, + Colors::Black, std::forward(fn)) {} @@ -208,10 +212,10 @@ public: class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { public: - RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - double gamma = 1.) + RasterGrayscaleAAGammaPower(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + double gamma = 1.) : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) {} }; diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index cc9aca0274..0b6c45eff3 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -68,10 +68,10 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, } std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma, - const RasterBase::Trafo & tr) + const Resolution &res, + const PixelDim &pxdim, + double gamma, + const RasterBase::Trafo &tr) { std::unique_ptr rst; diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 6439830fe8..657fc865c2 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -31,6 +31,27 @@ public: const char * extension() const { return m_ext.c_str(); } }; +/// Type that represents a resolution in pixels. +struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } +}; + +/// Types that represents the dimension of a pixel in millimeters. +struct PixelDim { + double w_mm = 1.; + double h_mm = 1.; + + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} +}; + using RasterEncoder = std::function; @@ -63,35 +84,14 @@ public: Point get_center() const { return {center_x, center_y}; } }; - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px = 0; - size_t height_px = 0; - - Resolution() = default; - Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} - size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm = 1.; - double h_mm = 1.; - - PixelDim() = default; - PixelDim(double px_width_mm, double px_height_mm) - : w_mm(px_width_mm), h_mm(px_height_mm) - {} - }; - virtual ~RasterBase() = default; /// Draw a polygon with holes. virtual void draw(const ExPolygon& poly) = 0; /// Get the resolution of the raster. - virtual Resolution resolution() const = 0; - virtual PixelDim pixel_dimensions() const = 0; +// virtual Resolution resolution() const = 0; +// virtual PixelDim pixel_dimensions() const = 0; virtual Trafo trafo() const = 0; virtual EncodedRaster encode(RasterEncoder encoder) const = 0; @@ -109,10 +109,10 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); // If gamma is zero, thresholding will be performed which disables AA. std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma = 1.0, - const RasterBase::Trafo & tr = {}); + const Resolution &res, + const PixelDim &pxdim, + double gamma = 1.0, + const RasterBase::Trafo &tr = {}); }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 55acd38465..7b78dfea2f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,6 +1,9 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "Format/SL1.hpp" +#include "Format/SL1_SVG.hpp" + #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -240,8 +243,13 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); - - if (m_printer) m_printer->apply(m_printer_config); + + if (!m_archiver || !printer_diff.empty()) { + if (m_printer_config.sla_archive_format.value == "SL1") + m_archiver = std::make_unique(m_printer_config); + else if (m_printer_config.sla_archive_format.value == "SL2") + m_archiver = std::make_unique(m_printer_config); + } struct ModelObjectStatus { enum Status { @@ -670,12 +678,6 @@ std::string SLAPrint::validate(std::string*) const return ""; } -void SLAPrint::set_printer(SLAArchive *arch) -{ - invalidate_step(slapsRasterize); - m_printer = arch; -} - bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -835,7 +837,9 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_ignore = { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0622bec4e5..b2df0c4e92 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -399,8 +399,6 @@ protected: public: virtual ~SLAArchive() = default; - virtual void apply(const SLAPrinterConfig &cfg) = 0; - // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); template void draw_layers( @@ -422,6 +420,14 @@ public: }, execution::max_concurrency(ep)); } + + // Export the print into an archive using the provided zipper. + // TODO: Use an archive writer interface instead of Zipper. + // This is quite limiting as the Zipper is a complete class, not an interface. + // The output can only be a zip archive. + virtual void export_print(Zipper &zipper, + const SLAPrint &print, + const std::string &projectname = "") = 0; }; /** @@ -527,8 +533,17 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - - void set_printer(SLAArchive *archiver); + + void export_print(Zipper &zipper, const std::string &projectname = "") + { + m_archiver->export_print(zipper, *this, projectname); + } + + void export_print(const std::string &fname, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, projectname); + } private: @@ -550,7 +565,7 @@ private: std::vector m_printer_input; // The archive object which collects the raster images after slicing - SLAArchive *m_printer = nullptr; + std::unique_ptr m_archiver; // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fa7348781d..435e8c8e39 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1044,7 +1044,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled() || !m_print->m_printer) return; + if(canceled() || !m_print->m_archiver) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -1089,7 +1089,7 @@ void SLAPrint::Steps::rasterize() if(canceled()) return; // Print all the layers in parallel - m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn, + m_print->m_archiver->draw_layers(m_print->m_printer_input.size(), lvlfn, [this]() { return canceled(); }, ex_tbb); } diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 67bd2639b4..195fc9e172 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3754,6 +3754,7 @@ void modulate_extrusion_by_overlapping_layers( assert(path != nullptr); polylines.emplace_back(Polyline(std::move(path->polyline))); path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); + delete path; } } // Destroy the original extrusion paths, their polylines were moved to path_fragments already. diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 5668c8b8b4..345eba3ee8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -68,8 +68,16 @@ #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of old OpenGL render calls #define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable replace GLIndexedVertexArray with GLModel +#define ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL (1 && ENABLE_GLBEGIN_GLEND_REMOVAL) // Enable show non-manifold edges #define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) +// Enable rework of Reload from disk command +#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing toolpaths center of gravity +#define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1) +// Enable recalculating toolpaths when switching to/from volumetric rate visualization +#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) //==================== diff --git a/src/qoi/CMakeLists.txt b/src/qoi/CMakeLists.txt new file mode 100644 index 0000000000..af8bf2e051 --- /dev/null +++ b/src/qoi/CMakeLists.txt @@ -0,0 +1,9 @@ +# PrusaSlicer specific CMake + +cmake_minimum_required(VERSION 2.8.12) +project(qoi) + +add_library(qoi STATIC + qoi.h + qoilib.c +) diff --git a/src/qoi/README.md b/src/qoi/README.md new file mode 100644 index 0000000000..4006c38507 --- /dev/null +++ b/src/qoi/README.md @@ -0,0 +1,108 @@ +Bundled with PrusaSlicer: commit 6c0831f91ffde5dfe2ceef32cbaff91d62b0e0ee +Original README follows: + + +![QOI Logo](https://qoiformat.org/qoi-logo.svg) + +# QOI - The “Quite OK Image Format” for fast, lossless image compression + +Single-file MIT licensed library for C/C++ + +See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for +the documentation and format specification. + +More info at https://qoiformat.org + + +## Why? + +Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding, +3x-4x faster decoding and 20% better compression. It's also stupidly simple and +fits in about 300 lines of C. + + +## Example Usage + +- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c) +converts between png <> qoi + - [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c) +a simple wrapper to benchmark stbi, libpng and qoi + + +## Limitations + +The QOI file format allows for huge images with up to 18 exa-pixels. A streaming +en-/decoder can handle these with minimal RAM requirements, assuming there is +enough storage space. + +This particular implementation of QOI however is limited to images with a +maximum size of 400 million pixels. It will safely refuse to en-/decode anything +larger than that. This is not a streaming en-/decoder. It loads the whole image +file into RAM before doing any work and is not extensively optimized for +performance (but it's still very fast). + +If this is a limitation for your use case, please look into any of the other +implementations listed below. + + +## Tools + +- https://github.com/floooh/qoiview - native QOI viewer +- https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.0 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP +- https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET +- https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer +- https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats) +- https://apps.apple.com/br/app/qoiconverterx/id1602159820 QOI <=> PNG converter available on the Mac App Store +- https://github.com/kaetemi/qoi-max - QOI Bitmap I/O Plugin for 3ds Max +- https://raylibtech.itch.io/rtexviewer - texture viewer, supports QOI + + +## Implementations & Bindings of QOI + +- https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift) +- https://github.com/kodonnell/qoi (Python) +- https://github.com/Cr4xy/lua-qoi (Lua) +- https://github.com/superzazu/SDL_QOI (C, SDL2 bindings) +- https://github.com/saharNooby/qoi-java (Java) +- https://github.com/MasterQ32/zig-qoi (Zig) +- https://github.com/rbino/qoix (Elixir) +- https://github.com/NUlliiON/QoiSharp (C#) +- https://github.com/aldanor/qoi-rust (Rust) +- https://github.com/zakarumych/rapid-qoi (Rust) +- https://github.com/takeyourhatoff/qoi (Go) +- https://github.com/DosWorld/pasqoi (Pascal) +- https://github.com/elihwyma/Swift-QOI (Swift) +- https://github.com/xfmoulet/qoi (Go) +- https://erratique.ch/software/qoic (OCaml) +- https://github.com/arian/go-qoi (Go) +- https://github.com/kchapelier/qoijs (JavaScript) +- https://github.com/KristofferC/QOI.jl (Julia) +- https://github.com/shadowMitia/libqoi/ (C++) +- https://github.com/MKCG/php-qoi (PHP) +- https://github.com/LightHouseSoftware/qoiformats (D) +- https://github.com/mhoward540/qoi-nim (Nim) + + +## QOI Support in Other Software + +- [SerenityOS](https://github.com/SerenityOS/serenity) supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/QOILoader.h) +- [Raylib](https://github.com/raysan5/raylib) supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c) +- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec +- [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively +- [SAIL](https://github.com/HappySeaFox/sail) image decoding library, supports decoding and encoding QOI images +- [Orx](https://github.com/orx/orx) 2D game engine, supports QOI natively + + +## Packages + +[AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages. + + +## Implementations not yet conforming to the final specification + +These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version. + +- https://github.com/ChevyRay/qoi_rs (Rust) +- https://github.com/panzi/jsqoi (TypeScript) +- https://github.com/0xd34df00d/hsqoi (Haskell) + diff --git a/src/qoi/qoi.h b/src/qoi/qoi.h new file mode 100644 index 0000000000..988f9edcb4 --- /dev/null +++ b/src/qoi/qoi.h @@ -0,0 +1,671 @@ +/* + +QOI - The "Quite OK Image" format for fast, lossless image compression + +Dominic Szablewski - https://phoboslab.org + + +-- LICENSE: The MIT License(MIT) + +Copyright(c) 2021 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif /* QOI_H */ + + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (channels == 4) { + px = *(qoi_rgba_t *)(pixels + px_pos); + } + else { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } + else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } + else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if ( + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); + } + else if ( + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 + ) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } + else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } + else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + if (channels == 4) { + *(qoi_rgba_t*)(pixels + px_pos) = px; + } + else { + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOI_FREE(encoded); + return size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + pixels = qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/src/qoi/qoilib.c b/src/qoi/qoilib.c new file mode 100644 index 0000000000..e3aa809c74 --- /dev/null +++ b/src/qoi/qoilib.c @@ -0,0 +1,6 @@ +// PrusaSlicer specific: +// Include and compile QOI library. + +#define QOI_IMPLEMENTATION +#define QOI_NO_STDIO +#include "qoi.h" diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index c4f1a4407c..8488c1148f 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -27,6 +27,7 @@ static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0 namespace Slic3r { namespace GUI { +#if !ENABLE_GLBEGIN_GLEND_REMOVAL bool GeometryBuffer::set_from_triangles(const std::vector &triangles, float z) { if (triangles.empty()) { @@ -95,6 +96,7 @@ const float* GeometryBuffer::get_vertices_data() const { return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; @@ -198,6 +200,13 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c m_model_filename = model_filename; m_extended_bounding_box = this->calc_extended_bounding_box(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_contour = ExPolygon(Polygon::new_scale(bed_shape)); + m_polygon = offset(m_contour.contour, (float)m_contour.contour.bounding_box().radius() * 1.7f, jtRound, scale_(0.5)).front(); + + m_triangles.reset(); + m_gridlines.reset(); +#else ExPolygon poly{ Polygon::new_scale(bed_shape) }; calc_triangles(poly); @@ -205,9 +214,10 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c const BoundingBox& bed_bbox = poly.contour.bounding_box(); calc_gridlines(poly, bed_bbox); - m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0]; + m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5)).front(); this->release_VBOs(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_texture.reset(); m_model.reset(); @@ -288,6 +298,107 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const return out; } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void Bed3D::init_triangles() +{ + if (m_triangles.is_initialized()) + return; + + if (m_contour.empty()) + return; + + const std::vector triangles = triangulate_expolygon_2f(m_contour, NORMALS_UP); + if (triangles.empty() || triangles.size() % 3 != 0) + return; + + GLModel::Geometry init_data; + const GLModel::Geometry::EIndexType index_type = (triangles.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2, index_type }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + + Vec2f min = triangles.front(); + Vec2f max = min; + for (const Vec2f& v : triangles) { + min = min.cwiseMin(v).eval(); + max = max.cwiseMax(v).eval(); + } + + const Vec2f size = max - min; + if (size.x() <= 0.0f || size.y() <= 0.0f) + return; + + Vec2f inv_size = size.cwiseInverse(); + inv_size.y() *= -1.0f; + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Vec2f& v : triangles) { + const Vec3f p = { v.x(), v.y(), GROUND_Z }; + init_data.add_vertex(p, (Vec2f)v.cwiseProduct(inv_size).eval()); + ++vertices_counter; + if (vertices_counter % 3 == 0) { + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_triangle((unsigned short)vertices_counter - 3, (unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1); + else + init_data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + + m_triangles.init_from(std::move(init_data)); +} + +void Bed3D::init_gridlines() +{ + if (m_gridlines.is_initialized()) + return; + + if (m_contour.empty()) + return; + + const BoundingBox& bed_bbox = m_contour.contour.bounding_box(); + const coord_t step = scale_(10.0); + + Polylines axes_lines; + for (coord_t x = bed_bbox.min.x(); x <= bed_bbox.max.x(); x += step) { + Polyline line; + line.append(Point(x, bed_bbox.min.y())); + line.append(Point(x, bed_bbox.max.y())); + axes_lines.push_back(line); + } + for (coord_t y = bed_bbox.min.y(); y <= bed_bbox.max.y(); y += step) { + Polyline line; + line.append(Point(bed_bbox.min.x(), y)); + line.append(Point(bed_bbox.max.x(), y)); + axes_lines.push_back(line); + } + + // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped + Lines gridlines = to_lines(intersection_pl(axes_lines, offset(m_contour, float(SCALED_EPSILON)))); + + // append bed contours + Lines contour_lines = to_lines(m_contour); + std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); + + GLModel::Geometry init_data; + const GLModel::Geometry::EIndexType index_type = (2 * gridlines.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, index_type }; + init_data.reserve_vertices(2 * gridlines.size()); + init_data.reserve_indices(2 * gridlines.size()); + + for (const Line& l : gridlines) { + init_data.add_vertex(Vec3f(unscale(l.a.x()), unscale(l.a.y()), GROUND_Z)); + init_data.add_vertex(Vec3f(unscale(l.b.x()), unscale(l.b.y()), GROUND_Z)); + const unsigned int vertices_counter = (unsigned int)init_data.vertices_count(); + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_line((unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1); + else + init_data.add_uint_line(vertices_counter - 2, vertices_counter - 1); + } + + m_gridlines.init_from(std::move(init_data)); +} +#else void Bed3D::calc_triangles(const ExPolygon& poly) { if (! m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) @@ -320,6 +431,7 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) BOOST_LOG_TRIVIAL(error) << "Unable to create bed grid lines\n"; } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // Try to match the print bed shape with the shape of an active profile. If such a match exists, // return the print bed model. @@ -421,6 +533,44 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) canvas.request_extra_frame(); } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + init_triangles(); + + GLShaderProgram* shader = wxGetApp().get_shader("printbed"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("transparent_background", bottom); + shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); + + glsafe(::glEnable(GL_DEPTH_TEST)); + if (bottom) + glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + if (bottom) + glsafe(::glFrontFace(GL_CW)); + + // show the temporary texture while no compressed data is available + GLuint tex_id = (GLuint)m_temp_texture.get_id(); + if (tex_id == 0) + tex_id = (GLuint)m_texture.get_id(); + + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); + m_triangles.render(); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + if (bottom) + glsafe(::glFrontFace(GL_CCW)); + + glsafe(::glDisable(GL_BLEND)); + if (bottom) + glsafe(::glDepthMask(GL_TRUE)); + + shader->stop_using(); + } +#else if (m_triangles.get_vertices_count() > 0) { GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { @@ -488,6 +638,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) shader->stop_using(); } } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } void Bed3D::render_model() @@ -541,6 +692,40 @@ void Bed3D::render_default(bool bottom, bool picking) { m_texture.reset(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + init_gridlines(); + init_triangles(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + const bool has_model = !m_model.get_filename().empty(); + + if (!has_model && !bottom) { + // draw background + glsafe(::glDepthMask(GL_FALSE)); + m_triangles.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR); + m_triangles.render(); + glsafe(::glDepthMask(GL_TRUE)); + } + + if (!picking) { + // draw grid + glsafe(::glLineWidth(1.5f * m_scale_factor)); + m_gridlines.set_color(has_model && !bottom ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR); + m_gridlines.render(); + } + + glsafe(::glDisable(GL_BLEND)); + + shader->stop_using(); + } +#else const unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { const bool has_model = !m_model.get_filename().empty(); @@ -573,8 +758,10 @@ void Bed3D::render_default(bool bottom, bool picking) glsafe(::glDisable(GL_BLEND)); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if !ENABLE_GLBEGIN_GLEND_REMOVAL void Bed3D::release_VBOs() { if (m_vbo_id > 0) { @@ -582,6 +769,7 @@ void Bed3D::release_VBOs() m_vbo_id = 0; } } +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL } // GUI } // Slic3r diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 82c6b817be..350ae48f6a 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -5,7 +5,10 @@ #include "3DScene.hpp" #include "GLModel.hpp" -#include +#include "libslic3r/BuildVolume.hpp" +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#include "libslic3r/ExPolygon.hpp" +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL #include #include @@ -15,6 +18,7 @@ namespace GUI { class GLCanvas3D; +#if !ENABLE_GLBEGIN_GLEND_REMOVAL class GeometryBuffer { struct Vertex @@ -36,6 +40,7 @@ public: size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL class Bed3D { @@ -79,23 +84,38 @@ private: std::string m_model_filename; // Print volume bounding box exteded with axes and model. BoundingBoxf3 m_extended_bounding_box; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // Print bed polygon + ExPolygon m_contour; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // Slightly expanded print bed polygon, for collision detection. Polygon m_polygon; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_triangles; + GLModel m_gridlines; +#else GeometryBuffer m_triangles; GeometryBuffer m_gridlines; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed GLTexture m_temp_texture; GLModel m_model; Vec3d m_model_offset{ Vec3d::Zero() }; +#if !ENABLE_GLBEGIN_GLEND_REMOVAL unsigned int m_vbo_id{ 0 }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL Axes m_axes; float m_scale_factor{ 1.0f }; public: Bed3D() = default; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + ~Bed3D() = default; +#else ~Bed3D() { release_VBOs(); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL // Update print bed model from configuration. // Return true if the bed shape changed, so the calee will update the UI. @@ -125,8 +145,13 @@ public: private: // Calculate an extended bounding box from axes and current model for visualization purposes. BoundingBoxf3 calc_extended_bounding_box() const; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + void init_triangles(); + void init_gridlines(); +#else void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking); @@ -136,7 +161,9 @@ private: void render_model(); void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking); void render_default(bool bottom, bool picking); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL void release_VBOs(); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 4e929d060c..339754790f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -326,8 +326,8 @@ void GLVolume::SinkingContours::update() for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { #if ENABLE_GLBEGIN_GLEND_REMOVAL const std::vector triangulation = triangulate_expolygon_3d(expoly); - init_data.vertices.reserve(init_data.vertices.size() + triangulation.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(init_data.indices.size() + triangulation.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(init_data.vertices_count() + triangulation.size()); + init_data.reserve_indices(init_data.indices_count() + triangulation.size()); for (const Vec3d& v : triangulation) { init_data.add_vertex((Vec3f)(v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting ++vertices_counter; @@ -400,9 +400,10 @@ void GLVolume::NonManifoldEdges::update() if (!edges.empty()) { GUI::GLModel::Geometry init_data; #if ENABLE_GLBEGIN_GLEND_REMOVAL - init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::EIndexType::UINT }; - init_data.vertices.reserve(2 * edges.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * edges.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format)); + const GUI::GLModel::Geometry::EIndexType index_type = (2 * edges.size() < 65536) ? GUI::GLModel::Geometry::EIndexType::USHORT : GUI::GLModel::Geometry::EIndexType::UINT; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, index_type }; + init_data.reserve_vertices(2 * edges.size()); + init_data.reserve_indices(2 * edges.size()); // vertices + indices unsigned int vertices_count = 0; @@ -410,7 +411,10 @@ void GLVolume::NonManifoldEdges::update() init_data.add_vertex((Vec3f)mesh.its.vertices[edge.first].cast()); init_data.add_vertex((Vec3f)mesh.its.vertices[edge.second].cast()); vertices_count += 2; - init_data.add_uint_line(vertices_count - 2, vertices_count - 1); + if (index_type == GUI::GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_line((unsigned short)vertices_count - 2, (unsigned short)vertices_count - 1); + else + init_data.add_uint_line(vertices_count - 2, vertices_count - 1); } m_model.init_from(std::move(init_data)); #else diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 5f7b4e8d3f..5d3d47c202 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -190,7 +190,7 @@ void BackgroundSlicingProcess::process_sla() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); Zipper zipper(export_path); - m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad + m_sla_print->export_print(zipper); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); @@ -741,7 +741,7 @@ void BackgroundSlicingProcess::prepare_upload() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); // true, false, true, true); // renders also supports and pad Zipper zipper{source_path.string()}; - m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_print(zipper, m_upload_job.upload_data.upload_path.string()); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 5fba237e35..00a3ab6d05 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -11,7 +11,6 @@ #include "libslic3r/PrintBase.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -84,7 +83,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } + void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } @@ -218,9 +217,9 @@ private: // Data structure, to which the G-code export writes its annotations. GCodeProcessorResult *m_gcode_result = nullptr; // Callback function, used to write thumbnails into gcode. - ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; - SL1Archive m_sla_archive; - // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. + ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, + // differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, // but once set, it cannot be re-set. diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 367846f0c8..d54c28293f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -103,7 +103,11 @@ void GCodeViewer::IBuffer::reset() count = 0; } +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC +bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const +#else bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC { auto matches_percent = [](float value1, float value2, float max_percent) { return std::abs(value2 - value1) / value1 <= max_percent; @@ -120,10 +124,22 @@ bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) co case EMoveType::Seam: case EMoveType::Extrude: { // use rounding to reduce the number of generated paths +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (account_for_volumetric_rate) + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_bin(move.height) && width == round_to_bin(move.width) && + matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f); + else + return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_bin(move.height) && width == round_to_bin(move.width); +#else return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && height == round_to_bin(move.height) && width == round_to_bin(move.width) && matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -160,6 +176,66 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } +#if ENABLE_SHOW_TOOLPATHS_COG +void GCodeViewer::COG::render() +{ + if (!m_visible) + return; + + init(); + + GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog"); + if (shader == nullptr) + return; + + shader->start_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + glsafe(::glPushMatrix()); + const Vec3d position = cog(); + glsafe(::glTranslated(position.x(), position.y(), position.z())); + if (m_fixed_size) { + const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom(); + glsafe(::glScaled(inv_zoom, inv_zoom, inv_zoom)); + } + m_model.render(); + + glsafe(::glPopMatrix()); + + shader->stop_using(); + + ////Show ImGui window + //static float last_window_width = 0.0f; + //static size_t last_text_length = 0; + + //ImGuiWrapper& imgui = *wxGetApp().imgui(); + //const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + //imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f); + //ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + //ImGui::SetNextWindowBgAlpha(0.25f); + //imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + //imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":"); + //ImGui::SameLine(); + //char buf[1024]; + //const Vec3d position = cog(); + //sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z()); + //imgui.text(std::string(buf)); + + //// force extra frame to automatically update window size + //const float width = ImGui::GetWindowWidth(); + //const size_t length = strlen(buf); + //if (width != last_window_width || length != last_text_length) { + // last_window_width = width; + // last_text_length = length; + // imgui.set_requires_extra_frame(); + //} + + //imgui.end(); + //ImGui::PopStyleVar(); +} +#endif // ENABLE_SHOW_TOOLPATHS_COG + #if ENABLE_PREVIEW_LAYER_TIME float GCodeViewer::Extrusions::Range::step_size(EType type) const { @@ -638,10 +714,19 @@ void GCodeViewer::init() void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) { // avoid processing if called with the same gcode_result +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (m_last_result_id == gcode_result.id && + (m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate))) + return; +#else if (m_last_result_id == gcode_result.id) return; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC m_last_result_id = gcode_result.id; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + m_last_view_type = m_view_type; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // release gpu memory, if used reset(); @@ -955,6 +1040,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); +#if ENABLE_SHOW_TOOLPATHS_COG + flags = set_flag(flags, static_cast(Preview::OptionType::CenterOfGravity), m_cog.is_visible()); +#endif // ENABLE_SHOW_TOOLPATHS_COG flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); #if !ENABLE_PREVIEW_LAYOUT @@ -978,6 +1066,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); +#if ENABLE_SHOW_TOOLPATHS_COG + m_cog.set_visible(is_flag_set(static_cast(Preview::OptionType::CenterOfGravity))); +#endif // ENABLE_SHOW_TOOLPATHS_COG m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); #if !ENABLE_PREVIEW_LAYOUT @@ -1200,9 +1291,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) // add current vertex add_vertex(curr); }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // add starting index indices.push_back(static_cast(indices.size())); buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); @@ -1221,7 +1318,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) }; // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) { +#else + auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, + unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position vertices.push_back(position.x()); @@ -1233,7 +1336,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) vertices.push_back(normal.z()); }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } @@ -1278,8 +1385,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next, - TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, + const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, + IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { +#else + auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, + const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, + IndexBuffer& indices, size_t move_id) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC static Vec3f prev_dir; static Vec3f prev_up; static float sq_prev_length; @@ -1324,7 +1438,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { +#else if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } @@ -1408,7 +1526,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) vbuffer_size += 6; } +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate))) +#else if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // ending cap triangles append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); @@ -1537,6 +1659,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) if (wxGetApp().is_editor()) m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); +#if ENABLE_SHOW_TOOLPATHS_COG + m_cog.reset(); +#endif // ENABLE_SHOW_TOOLPATHS_COG + m_sequential_view.gcode_ids.clear(); for (size_t i = 0; i < gcode_result.moves.size(); ++i) { const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; @@ -1544,6 +1670,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) m_sequential_view.gcode_ids.push_back(move.gcode_id); } +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + std::vector vertices(m_buffers.size()); std::vector indices(m_buffers.size()); std::vector instances(m_buffers.size()); @@ -1551,18 +1681,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) std::vector instances_offsets(m_buffers.size()); std::vector options_zs; - size_t seams_count = 0; std::vector biased_seams_ids; // toolpaths data -> extract vertices from result for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; - if (curr.type == EMoveType::Seam) { - ++seams_count; + if (curr.type == EMoveType::Seam) biased_seams_ids.push_back(i - biased_seams_ids.size() - 1); - } - size_t move_id = i - seams_count; + const size_t move_id = i - biased_seams_ids.size(); // skip first vertex if (i == 0) @@ -1570,6 +1697,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; +#if ENABLE_SHOW_TOOLPATHS_COG + if (curr.type == EMoveType::Extrude && + curr.extrusion_role != erSkirt && + curr.extrusion_role != erSupportMaterial && + curr.extrusion_role != erSupportMaterialInterface && + curr.extrusion_role != erWipeTower && + curr.extrusion_role != erCustom && + curr.extrusion_role != erMixed) { + const Vec3d curr_pos = curr.position.cast(); + const Vec3d prev_pos = prev.position.cast(); + m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm()); + } +#endif // ENABLE_SHOW_TOOLPATHS_COG + // update progress dialog ++progress_count; if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { @@ -1597,7 +1738,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) v_multibuffer.push_back(VertexBuffer()); if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { Path& last_path = t_buffer.paths.back(); +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate)) +#else if (prev.type == curr.type && last_path.matches(curr)) +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); } } @@ -1608,7 +1753,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) { 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; } +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; } +#else case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC case TBuffer::ERenderPrimitiveType::InstancedModel: { add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); @@ -1893,14 +2042,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) using VboIndexList = std::vector; std::vector vbo_indices(m_buffers.size()); - seams_count = 0; + size_t seams_count = 0; for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; if (curr.type == EMoveType::Seam) ++seams_count; - size_t move_id = i - seams_count; + const size_t move_id = i - seams_count; // skip first vertex if (i == 0) @@ -1972,12 +2121,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) break; } case TBuffer::ERenderPrimitiveType::Line: { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); +#else add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); break; } case TBuffer::ERenderPrimitiveType::Triangle: { +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); +#else add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC break; } case TBuffer::ERenderPrimitiveType::BatchedModel: { @@ -4074,15 +4231,6 @@ void GCodeViewer::render_legend(float& legend_height) }; #if ENABLE_LEGEND_TOOLBAR_ICONS -// auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { -// const float margin = 3.0f; -// const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); -// window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); -// }; -// auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { -// const float margin = 3.0f; -// window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); -// }; auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { ImGuiIO& io = ImGui::GetIO(); const ImTextureID tex_id = io.Fonts->TexID; @@ -4091,17 +4239,17 @@ void GCodeViewer::render_legend(float& legend_height) const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id); const ImVec2 uv0 = { static_cast(rect->X) / tex_w, static_cast(rect->Y) / tex_h }; const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; - window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f })); + window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); }; #else - auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { - const float margin = 3.0f; + auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { + const float margin = 3.0f; const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); - window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGuiWrapper::to_ImU32(color), 16); }; - auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) { + auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { const float margin = 3.0f; - window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGuiWrapper::to_ImU32(color), 3.0f); }; #endif // ENABLE_LEGEND_TOOLBAR_ICONS @@ -4190,12 +4338,41 @@ void GCodeViewer::render_legend(float& legend_height) #endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); +#if ENABLE_SHOW_TOOLPATHS_COG +#if ENABLE_LEGEND_TOOLBAR_ICONS + toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { + image_icon(window, pos, size, ImGui::LegendCOG); + }); +#else + toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [](ImGuiWindow& window, const ImVec2& pos, float size) { + const ImU32 black = ImGuiWrapper::to_ImU32({ 0.0f, 0.0f, 0.0f, 1.0f }); + const ImU32 white = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const float margin = 3.0f; + const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); + const float radius = 0.5f * (size - 2.0f * margin); + window.DrawList->PathArcToFast(center, radius, 0, 3); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(black); + window.DrawList->PathArcToFast(center, radius, 3, 6); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(white); + window.DrawList->PathArcToFast(center, radius, 6, 9); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(black); + window.DrawList->PathArcToFast(center, radius, 9, 12); + window.DrawList->PathLineTo(center); + window.DrawList->PathFillConvex(white); + window.DrawList->AddCircle(center, radius, black, 16); + }); +#endif // ENABLE_LEGEND_TOOLBAR_ICONS + ImGui::SameLine(); +#endif // ENABLE_SHOW_TOOLPATHS_COG #if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendShells); #else toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f }); + const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); const float margin = 3.0f; const float proj = 0.25f * size; window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); @@ -4212,11 +4389,11 @@ void GCodeViewer::render_legend(float& legend_height) image_icon(window, pos, size, ImGui::LegendToolMarker); #else toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 0.8f }); + const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 0.8f }); const float margin = 3.0f; const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); - const ImVec2 p2 = ImVec2(p1.x + 0.25f * size, p1.y - 0.25f * size); - const ImVec2 p3 = ImVec2(p1.x - 0.25f * size, p1.y - 0.25f * size); + const ImVec2 p2(p1.x + 0.25f * size, p1.y - 0.25f * size); + const ImVec2 p3(p1.x - 0.25f * size, p1.y - 0.25f * size); window.DrawList->AddTriangleFilled(p1, p2, p3, color); const float mid_x = 0.5f * (pos.x + pos.x + size); window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index ecebb26414..b2b0626d51 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -212,7 +212,11 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; std::vector sub_paths; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const; +#else bool matches(const GCodeProcessorResult::MoveVertex& move) const; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC size_t vertices_count() const { return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; } @@ -380,6 +384,52 @@ class GCodeViewer bool visible{ false }; }; +#if ENABLE_SHOW_TOOLPATHS_COG + // helper to render center of gravity + class COG + { + GLModel m_model; + bool m_visible{ false }; + // whether or not to render the model with fixed screen size + bool m_fixed_size{ true }; + double m_total_mass{ 0.0 }; + Vec3d m_position{ Vec3d::Zero() }; + + public: + void render(); + + void reset() { + m_position = Vec3d::Zero(); + m_total_mass = 0.0; + } + + bool is_visible() const { return m_visible; } + void set_visible(bool visible) { m_visible = visible; } + + void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) { + assert(mass > 0.0); + m_position += mass * 0.5 * (v1 + v2); + m_total_mass += mass; + } + + Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); } + + private: + void init() { + if (m_model.is_initialized()) + return; + + const float radius = m_fixed_size ? 10.0f : 1.0f; + +#if ENABLE_GLBEGIN_GLEND_REMOVAL + m_model.init_from(smooth_sphere(32, radius)); +#else + m_model.init_from(its_make_sphere(radius, PI / 32.0)); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } + }; +#endif // ENABLE_SHOW_TOOLPATHS_COG + // helper to render extrusion paths struct Extrusions { @@ -716,6 +766,9 @@ public: private: bool m_gl_data_initialized{ false }; unsigned int m_last_result_id{ 0 }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + EViewType m_last_view_type{ EViewType::Count }; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC size_t m_moves_count{ 0 }; std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths @@ -734,6 +787,9 @@ private: Extrusions m_extrusions; SequentialView m_sequential_view; Shells m_shells; +#if ENABLE_SHOW_TOOLPATHS_COG + COG m_cog; +#endif // ENABLE_SHOW_TOOLPATHS_COG EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; #if ENABLE_PREVIEW_LAYOUT @@ -779,6 +835,9 @@ public: void reset(); void render(); +#if ENABLE_SHOW_TOOLPATHS_COG + void render_cog() { m_cog.render(); } +#endif // ENABLE_SHOW_TOOLPATHS_COG bool has_data() const { return !m_roles.empty(); } bool can_export_toolpaths() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 84cc2e5552..95600c074a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -368,8 +368,8 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices const float l = bar_rect.get_left(); @@ -428,8 +428,8 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT }; init_data.color = ColorRGBA::BLACK(); - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x; @@ -447,16 +447,20 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) m_profile.profile.reset(); GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::UINT }; + const GLModel::Geometry::EIndexType index_type = (m_layer_height_profile.size() / 2 < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, index_type }; init_data.color = ColorRGBA::BLUE(); - init_data.vertices.reserve(m_layer_height_profile.size() * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(m_layer_height_profile.size() * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(m_layer_height_profile.size() / 2); + init_data.reserve_indices(m_layer_height_profile.size() / 2); // vertices + indices for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) { init_data.add_vertex(Vec2f(bar_rect.get_left() + float(m_layer_height_profile[i + 1]) * scale_x, bar_rect.get_bottom() + float(m_layer_height_profile[i]) * scale_y)); - init_data.add_uint_index(i / 2); + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_index((unsigned short)i / 2); + else + init_data.add_uint_index(i / 2); } m_profile.profile.init_from(std::move(init_data)); @@ -898,6 +902,8 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons unsigned int vertices_counter = 0; for (const ExPolygon& poly : polygons_union) { const std::vector triangulation = triangulate_expolygon_3d(poly); + fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size()); + fill_data.reserve_indices(fill_data.indices_count() + triangulation.size()); for (const Vec3d& v : triangulation) { fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting ++vertices_counter; @@ -1600,6 +1606,10 @@ void GLCanvas3D::render() #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_SHOW_TOOLPATHS_COG + if (!m_main_toolbar.is_enabled()) + _render_gcode_cog(); +#endif // ENABLE_SHOW_TOOLPATHS_COG // we need to set the mouse's scene position here because the depth buffer // could be invalidated by the following gizmo render methods @@ -5234,8 +5244,8 @@ void GLCanvas3D::_render_background() GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f)); @@ -5415,6 +5425,13 @@ void GLCanvas3D::_render_gcode() m_gcode_viewer.render(); } +#if ENABLE_SHOW_TOOLPATHS_COG +void GLCanvas3D::_render_gcode_cog() +{ + m_gcode_viewer.render_cog(); +} +#endif // ENABLE_SHOW_TOOLPATHS_COG + void GLCanvas3D::_render_selection() { float scale_factor = 1.0; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ac1985f0f8..702403e667 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -944,6 +944,9 @@ private: void _render_bed_for_picking(bool bottom); void _render_objects(GLVolumeCollection::ERenderType type); void _render_gcode(); +#if ENABLE_SHOW_TOOLPATHS_COG + void _render_gcode_cog(); +#endif // ENABLE_SHOW_TOOLPATHS_COG void _render_selection(); void _render_sequential_clearance(); #if ENABLE_RENDER_SELECTION_CENTER diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 4d3be069ca..e1dc3305c7 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -18,6 +18,16 @@ namespace Slic3r { namespace GUI { #if ENABLE_GLBEGIN_GLEND_REMOVAL +void GLModel::Geometry::reserve_vertices(size_t vertices_count) +{ + vertices.reserve(vertices_count * vertex_stride_floats(format)); +} + +void GLModel::Geometry::reserve_indices(size_t indices_count) +{ + indices.reserve(indices_count * index_stride_bytes(format)); +} + void GLModel::Geometry::add_vertex(const Vec2f& position) { assert(format.vertex_layout == EVertexLayout::P2); @@ -42,6 +52,16 @@ void GLModel::Geometry::add_vertex(const Vec3f& position) vertices.emplace_back(position.z()); } +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P3T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal) { assert(format.vertex_layout == EVertexLayout::P3N3); @@ -228,6 +248,7 @@ size_t GLModel::Geometry::vertex_stride_floats(const Format& format) case EVertexLayout::P2: { return 2; } case EVertexLayout::P2T2: { return 4; } case EVertexLayout::P3: { return 3; } + case EVertexLayout::P3T2: { return 5; } case EVertexLayout::P3N3: { return 6; } default: { assert(false); return 0; } }; @@ -240,6 +261,7 @@ size_t GLModel::Geometry::position_stride_floats(const Format& format) case EVertexLayout::P2: case EVertexLayout::P2T2: { return 2; } case EVertexLayout::P3: + case EVertexLayout::P3T2: case EVertexLayout::P3N3: { return 3; } default: { assert(false); return 0; } }; @@ -252,6 +274,7 @@ size_t GLModel::Geometry::position_offset_floats(const Format& format) case EVertexLayout::P2: case EVertexLayout::P2T2: case EVertexLayout::P3: + case EVertexLayout::P3T2: case EVertexLayout::P3N3: { return 0; } default: { assert(false); return 0; } }; @@ -279,7 +302,8 @@ size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format) { switch (format.vertex_layout) { - case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: { return 2; } default: { assert(false); return 0; } }; } @@ -289,6 +313,7 @@ size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format) switch (format.vertex_layout) { case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3T2: { return 3; } default: { assert(false); return 0; } }; } @@ -310,6 +335,7 @@ bool GLModel::Geometry::has_position(const Format& format) case EVertexLayout::P2: case EVertexLayout::P2T2: case EVertexLayout::P3: + case EVertexLayout::P3T2: case EVertexLayout::P3N3: { return true; } default: { assert(false); return false; } }; @@ -321,7 +347,8 @@ bool GLModel::Geometry::has_normal(const Format& format) { case EVertexLayout::P2: case EVertexLayout::P2T2: - case EVertexLayout::P3: { return false; } + case EVertexLayout::P3: + case EVertexLayout::P3T2: { return false; } case EVertexLayout::P3N3: { return true; } default: { assert(false); return false; } }; @@ -331,7 +358,8 @@ bool GLModel::Geometry::has_tex_coord(const Format& format) { switch (format.vertex_layout) { - case EVertexLayout::P2T2: { return true; } + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: { return true; } case EVertexLayout::P2: case EVertexLayout::P3: case EVertexLayout::P3N3: { return false; } @@ -452,8 +480,8 @@ void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bb Geometry& data = m_render_data.geometry; data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, Geometry::EIndexType::UINT }; - data.vertices.reserve(3 * its.indices.size() * Geometry::vertex_stride_floats(data.format)); - data.indices.reserve(3 * its.indices.size() * Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(3 * its.indices.size()); + data.reserve_indices(3 * its.indices.size()); // vertices + indices unsigned int vertices_counter = 0; @@ -534,8 +562,8 @@ void GLModel::init_from(const Polygons& polygons, float z) segments_count += polygon.points.size(); } - data.vertices.reserve(2 * segments_count * Geometry::vertex_stride_floats(data.format)); - data.indices.reserve(2 * segments_count * Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(2 * segments_count); + data.reserve_indices(2 * segments_count); // vertices + indices unsigned int vertices_counter = 0; @@ -702,8 +730,8 @@ void GLModel::render() const const Geometry& data = m_render_data.geometry; - GLenum mode = get_primitive_mode(data.format); - GLenum index_type = get_index_type(data.format); + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data.format); const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); const bool position = Geometry::has_position(data.format); @@ -1016,8 +1044,8 @@ GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, fl GLModel::Geometry data; #if ENABLE_GLBEGIN_GLEND_REMOVAL data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; - data.vertices.reserve((6 * resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format)); - data.indices.reserve((6 * resolution * 3) * GLModel::Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(6 * resolution + 2); + data.reserve_indices(6 * resolution * 3); #else GLModel::Geometry::Entity entity; entity.type = GLModel::EPrimitiveType::Triangles; @@ -1157,6 +1185,7 @@ GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, fl data.entities.emplace_back(entity); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + return data; } @@ -1182,8 +1211,8 @@ GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float GLModel::Geometry data; #if ENABLE_GLBEGIN_GLEND_REMOVAL data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; - data.vertices.reserve((8 * (resolution + 1) + 30) * GLModel::Geometry::vertex_stride_floats(data.format)); - data.indices.reserve(((8 * resolution + 16) * 3) * GLModel::Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(8 * (resolution + 1) + 30); + data.reserve_indices((8 * resolution + 16) * 3); #else GLModel::Geometry::Entity entity; entity.type = GLModel::EPrimitiveType::Triangles; @@ -1488,6 +1517,7 @@ GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float data.entities.emplace_back(entity); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + return data; } @@ -1508,8 +1538,8 @@ GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_w GLModel::Geometry data; #if ENABLE_GLBEGIN_GLEND_REMOVAL data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; - data.vertices.reserve(42 * GLModel::Geometry::vertex_stride_floats(data.format)); - data.indices.reserve((24 * 3) * GLModel::Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(42); + data.reserve_indices(72); #else GLModel::Geometry::Entity entity; entity.type = GLModel::EPrimitiveType::Triangles; @@ -1681,6 +1711,7 @@ GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_w data.entities.emplace_back(entity); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + return data; } @@ -1694,8 +1725,8 @@ GLModel::Geometry diamond(unsigned short resolution) GLModel::Geometry data; #if ENABLE_GLBEGIN_GLEND_REMOVAL data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; - data.vertices.reserve((resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format)); - data.indices.reserve(((2 * (resolution + 1)) * 3) * GLModel::Geometry::index_stride_bytes(data.format)); + data.reserve_vertices(resolution + 2); + data.reserve_indices((2 * (resolution + 1)) * 3); #else GLModel::Geometry::Entity entity; entity.type = GLModel::EPrimitiveType::Triangles; @@ -1706,7 +1737,7 @@ GLModel::Geometry diamond(unsigned short resolution) #if ENABLE_GLBEGIN_GLEND_REMOVAL // vertices for (unsigned short i = 0; i < resolution; ++i) { - float ii = float(i) * step; + const float ii = float(i) * step; const Vec3f p = { 0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f }; append_vertex(data, p, p.normalized()); } @@ -1764,8 +1795,77 @@ GLModel::Geometry diamond(unsigned short resolution) data.entities.emplace_back(entity); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL + return data; } +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_SHOW_TOOLPATHS_COG +GLModel::Geometry smooth_sphere(unsigned short resolution, float radius) +{ + resolution = std::max(4, resolution); + resolution = std::min(256, resolution); // ensure no unsigned short overflow of indices + + const unsigned short sectorCount = /*2 **/ resolution; + const unsigned short stackCount = resolution; + + const float sectorStep = float(2.0 * M_PI / sectorCount); + const float stackStep = float(M_PI / stackCount); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT }; + data.reserve_vertices((stackCount - 1) * sectorCount + 2); + data.reserve_indices((2 * (stackCount - 1) * sectorCount) * 3); + + // vertices + for (unsigned short i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + const double stackAngle = 0.5 * M_PI - stackStep * i; + const double xy = double(radius) * ::cos(stackAngle); + const double z = double(radius) * ::sin(stackAngle); + if (i == 0 || i == stackCount) { + const Vec3f v(float(xy), 0.0f, float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + else { + for (unsigned short j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + const double sectorAngle = sectorStep * j; + const Vec3f v(float(xy * std::cos(sectorAngle)), float(xy * std::sin(sectorAngle)), float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + } + } + + // triangles + for (unsigned short i = 0; i < stackCount; ++i) { + // Beginning of current stack. + unsigned short k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + const unsigned short k1_first = k1; + // Beginning of next stack. + unsigned short k2 = (i == 0) ? 1 : (k1 + sectorCount); + const unsigned short k2_first = k2; + for (unsigned short j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + unsigned short k1_next = k1; + unsigned short k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + data.add_ushort_triangle(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + data.add_ushort_triangle(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return data; +} +#endif // ENABLE_SHOW_TOOLPATHS_COG +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 61456f3773..70220a75cb 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -58,6 +58,7 @@ namespace GUI { P2, // position 2 floats P2T2, // position 2 floats + texture coords 2 floats P3, // position 3 floats + P3T2, // position 3 floats + texture coords 2 floats P3N3, // position 3 floats + normal 3 floats }; @@ -79,9 +80,13 @@ namespace GUI { std::vector indices; ColorRGBA color{ ColorRGBA::BLACK() }; + void reserve_vertices(size_t vertices_count); + void reserve_indices(size_t indices_count); + void add_vertex(const Vec2f& position); void add_vertex(const Vec2f& position, const Vec2f& tex_coord); void add_vertex(const Vec3f& position); + void add_vertex(const Vec3f& position, const Vec2f& tex_coord); void add_vertex(const Vec3f& position, const Vec3f& normal); void add_ushort_index(unsigned short id); @@ -101,6 +106,8 @@ namespace GUI { unsigned int extract_uint_index(size_t id) const; unsigned short extract_ushort_index(size_t id) const; + bool is_empty() const { return vertices.empty() || indices.empty(); } + size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); } size_t indices_count() const { return indices.size() / index_stride_bytes(format); } @@ -254,6 +261,14 @@ namespace GUI { // the diamond is contained into a box with size [1, 1, 1] GLModel::Geometry diamond(unsigned short resolution); +#if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_SHOW_TOOLPATHS_COG + // create a sphere with the given resolution and smooth normals + // the origin of the sphere is in its center + GLModel::Geometry smooth_sphere(unsigned short resolution, float radius); +#endif // ENABLE_SHOW_TOOLPATHS_COG +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 7991caaba2..2a3d73d7f5 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -115,8 +115,8 @@ namespace GUI { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(4 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(4); // vertices init_data.add_vertex(Vec2f(left, bottom)); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index ae71e90afe..33ac9b6bcd 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -41,6 +41,10 @@ std::pair GLShadersManager::init() // used to render 3D scene background valid &= append_shader("background", { "background.vs", "background.fs" }); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_SHOW_TOOLPATHS_COG + // used to render toolpaths center of gravity + valid &= append_shader("toolpaths_cog", { "toolpaths_cog.vs", "toolpaths_cog.fs" }); +#endif // ENABLE_SHOW_TOOLPATHS_COG // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 3b99397ad5..340bb78c3c 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -342,8 +342,8 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, #if ENABLE_GLBEGIN_GLEND_REMOVAL GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec2f(left, bottom), Vec2f(uvs.left_bottom.u, uvs.left_bottom.v)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3326547bbc..27dcaa225b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -872,8 +872,8 @@ void GUI_App::init_app_config() { // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. // SetAppName(SLIC3R_APP_KEY); -// SetAppName(SLIC3R_APP_KEY "-alpha"); - SetAppName(SLIC3R_APP_KEY "-beta"); + SetAppName(SLIC3R_APP_KEY "-alpha"); +// SetAppName(SLIC3R_APP_KEY "-beta"); // SetAppDisplayName(SLIC3R_APP_NAME); // Set the Slic3r data directory at the Slic3r XS module. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a8f61d58f1..04685e7cc3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1706,16 +1706,16 @@ void ObjectList::load_shape_object(const std::string& type_name) if (selection.get_object_idx() != -1) return; - const int obj_idx = m_objects->size(); - if (obj_idx < 0) - return; - take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (!m_objects->empty()) + m_objects->back()->volumes.front()->source.is_from_builtin_objects = true; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK wxGetApp().mainframe->update_title(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 4ee7882d25..69e41e8065 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1438,8 +1438,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, parent->set_focused_editor(nullptr); #if ENABLE_OBJECT_MANIPULATOR_FOCUS - // if the widget loosing focus is not a manipulator field, call kill_focus - if (dynamic_cast(e.GetWindow()) == nullptr) + // if the widgets exchanging focus are both manipulator fields, call kill_focus + if (dynamic_cast(e.GetEventObject()) != nullptr && dynamic_cast(e.GetWindow()) != nullptr) #else if (!m_enter_pressed) #endif // ENABLE_OBJECT_MANIPULATOR_FOCUS diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index d9c32e8293..97f8edde33 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -126,6 +126,9 @@ public: ColorChanges, PausePrints, CustomGCodes, +#if ENABLE_SHOW_TOOLPATHS_COG + CenterOfGravity, +#endif // ENABLE_SHOW_TOOLPATHS_COG Shells, ToolMarker, #if !ENABLE_PREVIEW_LAYOUT diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index ab29e90262..ff5d89f5eb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -115,8 +115,8 @@ void GLGizmoCut::on_render() GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); @@ -160,8 +160,8 @@ void GLGizmoCut::on_render() GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; init_data.color = ColorRGBA::YELLOW(); - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices init_data.add_vertex((Vec3f)plane_center.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e40996c362..8e22b923c3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1301,23 +1301,39 @@ void GLGizmoEmboss::draw_advanced() *font_prop.distance : .0f; ImGui::SetNextItemWidth(item_width); if (m_imgui->slider_optional_float(_u8L("Surface distance").c_str(), font_prop.distance, - -font_prop.emboss, font_prop.emboss, "%.2f mm", 1.f, false, _L("Distance from model surface"))) { + -2*font_prop.emboss, 2*font_prop.emboss, "%.2f mm", 1.f, false, _L("Distance from model surface"))) { float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; - float diff = prev_distance - act_distance; + float diff = prev_distance - act_distance; + Vec3d displacement_rot = Vec3d::UnitZ() * diff; - // move with volume by diff size in volume z - Vec3d r = m_volume->get_rotation(); - Eigen::Matrix3d rot_mat = - (Eigen::AngleAxisd(r.z(), Vec3d::UnitZ()) * - Eigen::AngleAxisd(r.y(), Vec3d::UnitY()) * - Eigen::AngleAxisd(r.x(), Vec3d::UnitX())).toRotationMatrix(); - Vec3d displacement_rot = rot_mat * (Vec3d::UnitZ() * diff); - m_volume->translate(displacement_rot); - m_volume->set_new_unique_id(); - /*Selection &s = m_parent.get_selection(); - const GLVolume *v = s.get_volume(*s.get_volume_idxs().begin()); - s.translate(v->get_volume_offset() + displacement_rot*diff, ECoordinatesType::Local);*/ + Selection &selection = m_parent.get_selection(); + selection.start_dragging(); + selection.translate(displacement_rot, ECoordinatesType::Local); + selection.stop_dragging(); + + std::string snapshot_name; // empty meand no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended inside function do_move + //snapshot_name = L("Set surface distance"); + m_parent.do_move(snapshot_name); + } + + float prev_angle = font_prop.angle.has_value() ? *font_prop.angle : .0f; + ImGui::SetNextItemWidth(item_width); + if (m_imgui->slider_optional_float(_u8L("Angle").c_str(), font_prop.angle, + -180.f, 180.f, u8"%.2f °", 1.f, false, _L("Rotation of text"))) { + float act_angle = font_prop.angle.has_value() ? *font_prop.angle : .0f; + float diff = prev_angle - act_angle; + + Selection &selection = m_parent.get_selection(); + selection.start_dragging(); + selection.rotate(Vec3d(0., 0., M_PI / 180.0 * diff), TransformationType::Local); + selection.stop_dragging(); + + std::string snapshot_name; // empty meand no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended inside function do_move + //snapshot_name = L("Set surface distance"); + m_parent.do_rotate(snapshot_name); } // when more collection add selector diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index fd32c68fc1..0956a9047b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,6 +1,9 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" @@ -63,6 +66,14 @@ void GLGizmoFlatten::on_render() { const Selection& selection = m_parent.get_selection(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -76,21 +87,38 @@ void GLGizmoFlatten::on_render() if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + m_planes[i].vbo.render(); +#else glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); if (m_planes[i].vbo.has_VBOs()) m_planes[i].vbo.render(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } glsafe(::glPopMatrix()); } glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->stop_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } void GLGizmoFlatten::on_render_for_picking() { const Selection& selection = m_parent.get_selection(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); @@ -102,13 +130,21 @@ void GLGizmoFlatten::on_render_for_picking() if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_planes[i].vbo.set_color(picking_color_component(i)); +#else glsafe(::glColor4fv(picking_color_component(i).data())); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL m_planes[i].vbo.render(); } glsafe(::glPopMatrix()); } glsafe(::glEnable(GL_CULL_FACE)); + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->stop_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) @@ -324,12 +360,29 @@ void GLGizmoFlatten::update_planes() // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto& plane : m_planes) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel::Geometry init_data; + const GLModel::Geometry::EIndexType index_type = (plane.vertices.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3, index_type }; + init_data.reserve_vertices(plane.vertices.size()); + init_data.reserve_indices(plane.vertices.size()); + // vertices + indices + for (size_t i = 0; i < plane.vertices.size(); ++i) { + init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_index((unsigned short)i); + else + init_data.add_uint_index((unsigned int)i); + } + plane.vbo.init_from(std::move(init_data)); +#else plane.vbo.reserve(plane.vertices.size()); for (const auto& vert : plane.vertices) plane.vbo.push_geometry(vert, plane.normal); for (size_t i=1; i vertices; // should be in fact local in update_planes() +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel vbo; +#else GLIndexedVertexArray vbo; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL Vec3d normal; float area; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 4c204d0d4c..dd9cf0de2e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -589,6 +589,9 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) m_gizmo_scene.render(color_idx); } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + render_paint_contour(); +#else if (m_paint_contour.has_VBO()) { ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); @@ -602,6 +605,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui) contour_shader->stop_using(); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_update_render_data = false; } @@ -636,6 +640,9 @@ void TriangleSelectorMmGui::update_render_data() m_gizmo_scene.finalize_triangle_indices(); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + update_paint_contour(); +#else m_paint_contour.release_geometry(); std::vector contour_edges = this->get_seed_fill_contour(); m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); @@ -654,6 +661,7 @@ void TriangleSelectorMmGui::update_render_data() m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); m_paint_contour.finalize_geometry(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 5224935974..1d8548bda9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -117,8 +117,8 @@ void GLGizmoMove3D::on_render() GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; init_data.color = AXES_COLOR[id]; - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices init_data.add_vertex((Vec3f)center.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 4c76767bd7..0fc57c909d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -18,7 +18,11 @@ namespace Slic3r::GUI { - std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#else +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -27,8 +31,13 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic GLGizmoPainterBase::~GLGizmoPainterBase() { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (s_sphere != nullptr) + s_sphere.reset(); +#else if (s_sphere != nullptr && s_sphere->has_VBOs()) s_sphere->release_geometry(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) @@ -185,8 +194,8 @@ void GLGizmoPainterBase::render_cursor_circle() static const float StepSize = 2.0f * float(PI) / float(StepsCount); init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; - init_data.vertices.reserve(StepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(StepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(StepsCount); + init_data.reserve_indices(StepsCount); // vertices + indices for (unsigned short i = 0; i < StepsCount; ++i) { @@ -220,18 +229,27 @@ void GLGizmoPainterBase::render_cursor_circle() void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { if (s_sphere == nullptr) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + s_sphere = std::make_shared(); + s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); +#else s_sphere = std::make_shared(); s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); s_sphere->finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo.data())); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2))); + glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z())); glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); @@ -243,11 +261,22 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const render_color = this->get_cursor_sphere_left_button_color(); else if (m_button_down == Button::Right) render_color = this->get_cursor_sphere_right_button_color(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->start_using(); + + assert(s_sphere != nullptr); + s_sphere->set_color(render_color); +#else glsafe(::glColor4fv(render_color.data())); assert(s_sphere != nullptr); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL s_sphere->render(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->stop_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (is_left_handed) glFrontFace(GL_CCW); @@ -763,13 +792,28 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) shader->set_uniform("offset_depth_buffer", true); for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), std::make_pair(&m_iva_blockers, blockers_color)}) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + iva.first->set_color(iva.second); + iva.first->render(); +#else if (iva.first->has_VBOs()) { shader->set_uniform("uniform_color", iva.second); iva.first->render(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } - for (auto &iva : m_iva_seed_fills) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (auto& iva : m_iva_seed_fills) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + iva.set_color(color); + iva.render(); + } +#else + for (auto& iva : m_iva_seed_fills) if (iva.has_VBOs()) { size_t color_idx = &iva - &m_iva_seed_fills.front(); const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : @@ -778,7 +822,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) shader->set_uniform("uniform_color", color); iva.render(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLBEGIN_GLEND_REMOVAL + render_paint_contour(); +#else if (m_paint_contour.has_VBO()) { ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); shader->stop_using(); @@ -792,13 +840,14 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) contour_shader->stop_using(); } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) render_debug(imgui); else assert(false); // If you want debug output, pass ptr to ImGuiWrapper. -#endif +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG } void TriangleSelectorGUI::update_render_data() @@ -807,20 +856,44 @@ void TriangleSelectorGUI::update_render_data() int blc_cnt = 0; std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { + iva->reset(); + } + + for (auto& iva : m_iva_seed_fills) { + iva.reset(); + } + + GLModel::Geometry iva_enforcers_data; + iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; + GLModel::Geometry iva_blockers_data; + iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; + std::array iva_seed_fills_data; + for (auto& data : iva_seed_fills_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; +#else for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) iva->release_geometry(); for (auto &iva : m_iva_seed_fills) iva.release_geometry(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL for (const Triangle &tr : m_triangles) { if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) continue; int tr_state = int(tr.get_state()); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; +#else GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt; @@ -830,19 +903,40 @@ void TriangleSelectorGUI::update_render_data() //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort // or the current implementation may be more cache friendly. const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + iva.add_vertex(v0, n); + iva.add_vertex(v1, n); + iva.add_vertex(v2, n); + iva.add_uint_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); +#else iva.push_geometry(v0, n); iva.push_geometry(v1, n); iva.push_geometry(v2, n); iva.push_triangle(cnt, cnt + 1, cnt + 2); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL cnt += 3; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (!iva_enforcers_data.is_empty()) + m_iva_enforcers.init_from(std::move(iva_enforcers_data)); + if (!iva_blockers_data.is_empty()) + m_iva_blockers.init_from(std::move(iva_blockers_data)); + for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { + if (!iva_seed_fills_data[i].is_empty()) + m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); + } +#else for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) iva->finalize_geometry(true); for (auto &iva : m_iva_seed_fills) iva.finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLBEGIN_GLEND_REMOVAL + update_paint_contour(); +#else m_paint_contour.release_geometry(); std::vector contour_edges = this->get_seed_fill_contour(); m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); @@ -861,8 +955,10 @@ void TriangleSelectorGUI::update_render_data() m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); m_paint_contour.finalize_geometry(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } +#if !ENABLE_GLBEGIN_GLEND_REMOVAL void GLPaintContour::render() const { assert(this->m_contour_VBO_id != 0); @@ -920,6 +1016,7 @@ void GLPaintContour::release_geometry() } this->clear(); } +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) @@ -956,45 +1053,111 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) INVALID }; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (auto& va : m_varrays) + va.reset(); +#else for (auto& va : m_varrays) va.release_geometry(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL std::array cnts; ::glScalef(1.01f, 1.01f, 1.01f); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + std::array varrays_data; + for (auto& data : varrays_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (int tr_id=0; tr_idadd_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); + } + va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); +#else + for (int i = 0; i < 3; ++i) va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), double(m_vertices[tr.verts_idxs[i]].v[1]), double(m_vertices[tr.verts_idxs[i]].v[2]), 0., 0., 1.); va->push_triangle(*cnt, - *cnt+1, - *cnt+2); + *cnt + 1, + *cnt + 2); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL *cnt += 3; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (int i = 0; i < 3; ++i) { + if (!varrays_data[i].is_empty()) + m_varrays[i].init_from(std::move(varrays_data[i])); + } +#else + for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) + iva->finalize_geometry(true); + + for (auto& iva : m_iva_seed_fills) + iva.finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); for (vtype i : {ORIGINAL, SPLIT, INVALID}) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel& va = m_varrays[i]; + switch (i) { + case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; + case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; + case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; + } + va.render(); +#else GLIndexedVertexArray& va = m_varrays[i]; va.finalize_geometry(true); if (va.has_VBOs()) { @@ -1005,11 +1168,67 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) } va.render(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } -#endif +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +#if ENABLE_GLBEGIN_GLEND_REMOVAL +void TriangleSelectorGUI::update_paint_contour() +{ + m_paint_contour.reset(); + GLModel::Geometry init_data; + const std::vector contour_edges = this->get_seed_fill_contour(); + const GLModel::Geometry::EIndexType index_type = (2 * contour_edges.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, index_type }; + init_data.reserve_vertices(2 * contour_edges.size()); + init_data.reserve_indices(2 * contour_edges.size()); + // vertices + indices + unsigned int vertices_count = 0; + for (const Vec2i& edge : contour_edges) { + init_data.add_vertex(m_vertices[edge(0)].v); + init_data.add_vertex(m_vertices[edge(1)].v); + vertices_count += 2; + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_line((unsigned short)vertices_count - 2, (unsigned short)vertices_count - 1); + else + init_data.add_uint_line(vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + m_paint_contour.init_from(std::move(init_data)); +} + +void TriangleSelectorGUI::render_paint_contour() +{ + auto* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + auto* contour_shader = wxGetApp().get_shader("mm_contour"); + if (contour_shader != nullptr) { + contour_shader->start_using(); + + glsafe(::glDepthFunc(GL_LEQUAL)); + m_paint_contour.render(); + glsafe(::glDepthFunc(GL_LESS)); + + contour_shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +} +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 079f3f08ed..37c7163e2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -3,7 +3,11 @@ #include "GLGizmoBase.hpp" +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "slic3r/GUI/GLModel.hpp" +#else #include "slic3r/GUI/3DScene.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include "libslic3r/ObjectID.hpp" #include "libslic3r/TriangleSelector.hpp" @@ -28,6 +32,7 @@ enum class PainterGizmoType { MMU_SEGMENTATION }; +#if !ENABLE_GLBEGIN_GLEND_REMOVAL class GLPaintContour { public: @@ -63,6 +68,7 @@ public: GLuint m_contour_VBO_id{0}; GLuint m_contour_EBO_id{0}; }; +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL class TriangleSelectorGUI : public TriangleSelector { public: @@ -75,13 +81,13 @@ public: virtual void render(ImGuiWrapper *imgui); void render() { this->render(nullptr); } - void request_update_render_data() { m_update_render_data = true; }; + void request_update_render_data() { m_update_render_data = true; } #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); bool m_show_triangles{false}; bool m_show_invalid{false}; -#endif +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG protected: bool m_update_render_data = false; @@ -91,13 +97,29 @@ protected: private: void update_render_data(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel m_iva_enforcers; + GLModel m_iva_blockers; + std::array m_iva_seed_fills; +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + std::array m_varrays; +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +#else GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_blockers; std::array m_iva_seed_fills; std::array m_varrays; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL protected: +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel m_paint_contour; + + void update_paint_contour(); + void render_paint_contour(); +#else GLPaintContour m_paint_contour; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; @@ -209,7 +231,11 @@ private: const Camera& camera, const std::vector& trafo_matrices) const; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + static std::shared_ptr s_sphere; +#else static std::shared_ptr s_sphere; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL bool m_internal_stack_active = false; bool m_schedule_update = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 0c922a8360..565e9f2af2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -235,8 +235,8 @@ void GLGizmoRotate::render_circle() const GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(ScaleStepsCount); + init_data.reserve_indices(ScaleStepsCount); // vertices + indices for (unsigned short i = 0; i < ScaleStepsCount; ++i) { @@ -278,8 +278,8 @@ void GLGizmoRotate::render_scale() const GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); // vertices + indices for (unsigned short i = 0; i < ScaleStepsCount; ++i) { @@ -337,8 +337,8 @@ void GLGizmoRotate::render_snap_radii() const GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2 * ScaleStepsCount); + init_data.reserve_indices(2 * ScaleStepsCount); // vertices + indices for (unsigned short i = 0; i < ScaleStepsCount; ++i) { @@ -388,8 +388,8 @@ void GLGizmoRotate::render_reference_radius(const ColorRGBA& color, bool radius_ GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); @@ -429,8 +429,8 @@ void GLGizmoRotate::render_angle() const GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve((1 + AngleResolution) * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve((1 + AngleResolution) * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(1 + AngleResolution); + init_data.reserve_indices(1 + AngleResolution); // vertices + indices for (unsigned short i = 0; i <= AngleResolution; ++i) { @@ -466,8 +466,8 @@ void GLGizmoRotate::render_grabber_connection(const ColorRGBA& color, bool radiu GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index d0afd4caaa..4f7e375df3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -403,8 +403,8 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); // vertices init_data.add_vertex((Vec3f)m_grabbers[id_1].center.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index cc53f267c0..1e49ebc8c3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -213,15 +213,21 @@ void InstancesHider::render_cut() const clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); glsafe(::glPushMatrix()); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (mv->is_model_part()) glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); else { const ColorRGBA color = color_from_model_volume(*mv); glsafe(::glColor4fv(color.data())); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); +#else clipper->render_cut(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPopAttrib()); glsafe(::glPopMatrix()); @@ -417,8 +423,12 @@ void ObjectClipper::render_cut() const clipper->set_transformation(trafo); clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); glsafe(::glPushMatrix()); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); +#else glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPopMatrix()); ++clipper_id; @@ -530,8 +540,12 @@ void SupportsClipper::render_cut() const m_clipper->set_transformation(supports_trafo); glsafe(::glPushMatrix()); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); +#else glsafe(::glColor3f(1.0f, 0.f, 0.37f)); m_clipper->render_cut(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 658105d975..581c191476 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -70,6 +70,9 @@ static const std::map font_icons = { {ImGui::LegendColorChanges , "legend_colorchanges" }, {ImGui::LegendPausePrints , "legend_pauseprints" }, {ImGui::LegendCustomGCodes , "legend_customgcodes" }, +#if ENABLE_SHOW_TOOLPATHS_COG + {ImGui::LegendCOG , "legend_cog" }, +#endif // ENABLE_SHOW_TOOLPATHS_COG {ImGui::LegendShells , "legend_shells" }, {ImGui::LegendToolMarker , "legend_toolmarker" }, #endif // ENABLE_LEGEND_TOOLBAR_ICONS diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index c385eaa78e..ed56bc3576 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -6,6 +6,9 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Model.hpp" +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include "slic3r/GUI/Camera.hpp" #include @@ -66,13 +69,34 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void MeshClipper::render_cut(const ColorRGBA& color) +#else void MeshClipper::render_cut() +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { if (! m_triangles_valid) recalculate_triangles(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + m_model.set_color(color); + m_model.render(); + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#else if (m_vertex_array.has_VBOs()) m_vertex_array.render(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } @@ -161,6 +185,30 @@ void MeshClipper::recalculate_triangles() tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_model.reset(); + + GLModel::Geometry init_data; + const GLModel::Geometry::EIndexType index_type = (m_triangles2d.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, index_type }; + init_data.reserve_vertices(m_triangles2d.size()); + init_data.reserve_indices(m_triangles2d.size()); + + // vertices + indices + for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - m_triangles2d.cbegin(); + if (index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2); + else + init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + m_model.init_from(std::move(init_data)); +#else m_vertex_array.release_geometry(); for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); @@ -170,6 +218,7 @@ void MeshClipper::recalculate_triangles() m_vertex_array.push_triangle(idx, idx+1, idx+2); } m_vertex_array.finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL m_triangles_valid = true; } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index dc4f59cebe..341b505946 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -6,7 +6,11 @@ #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "slic3r/GUI/GLModel.hpp" +#else #include "slic3r/GUI/3DScene.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include @@ -69,7 +73,8 @@ public: // MeshClipper class cuts a mesh and is able to return a triangulated cut. -class MeshClipper { +class MeshClipper +{ public: // Inform MeshClipper about which plane we want to use to cut the mesh // This is supposed to be in world coordinates. @@ -92,7 +97,11 @@ public: // Render the triangulated cut. Transformation matrices should // be set in world coords. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void render_cut(const ColorRGBA& color); +#else void render_cut(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL private: void recalculate_triangles(); @@ -103,7 +112,11 @@ private: ClippingPlane m_plane; ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); std::vector m_triangles2d; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel m_model; +#else GLIndexedVertexArray m_vertex_array; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL bool m_triangles_valid = false; }; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index acdbd249c4..35d68e52e5 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -95,9 +95,9 @@ wxButton* MsgDialog::get_button(wxWindowID btn_id){ void MsgDialog::apply_style(long style) { if (style & wxOK) add_button(wxID_OK, true); - if (style & wxYES) add_button(wxID_YES, true); - if (style & wxNO) add_button(wxID_NO); - if (style & wxCANCEL) add_button(wxID_CANCEL); + if (style & wxYES) add_button(wxID_YES, !(style & wxNO_DEFAULT)); + if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT)); + if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT)); logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : style & wxICON_INFORMATION ? "info" : @@ -299,25 +299,12 @@ wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/) for (size_t i = 0; i < in.size();) { // Overwrite the character (space or newline) starting at ibreak? bool overwrite = false; -#if wxUSE_UNICODE_WCHAR - // On Windows, most likely the internal representation of wxString is wide char. - size_t end = std::min(in.size(), i + line_len); - size_t ibreak = end; - for (size_t j = i; j < end; ++ j) { - if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) { - ibreak = j; - overwrite = true; - if (newline) - break; - } else if (in[j] == '/' || in[j] == '\\') - ibreak = j + 1; - } -#else // UTF8 representation of wxString. // Where to break the line, index of character at the start of a UTF-8 sequence. size_t ibreak = size_t(-1); // Overwrite the character at ibreak (it is a whitespace) or not? - for (size_t cnt = 0, j = i; j < in.size();) { + size_t j = i; + for (size_t cnt = 0; j < in.size();) { if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) { // Overwrite the whitespace. ibreak = j ++; @@ -326,16 +313,23 @@ wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/) break; } else if (in[j] == '/') { // Insert after the slash. - ibreak = ++ j; + ibreak = ++ j; + overwrite = false; } else j += get_utf8_sequence_length(in.c_str() + j, in.size() - j); if (++ cnt == line_len) { - if (ibreak == size_t(-1)) - ibreak = j; + if (ibreak == size_t(-1)) { + ibreak = j; + overwrite = false; + } break; } } -#endif + if (j == in.size()) { + out.append(in.begin() + i, in.end()); + break; + } + assert(ibreak != size_t(-1)); out.append(in.begin() + i, in.begin() + ibreak); out.append('\n'); i = ibreak; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0eda5b424e..6bbbd31f42 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3539,8 +3539,45 @@ void Plater::priv::replace_with_stl() } } +#if ENABLE_RELOAD_FROM_DISK_REWORK +static std::vector> reloadable_volumes(const Model& model, const Selection& selection) +{ + std::vector> ret; + const std::set& selected_volumes_idxs = selection.get_volume_idxs(); + for (unsigned int idx : selected_volumes_idxs) { + const GLVolume& v = *selection.get_volume(idx); + const int o_idx = v.object_idx(); + if (0 <= o_idx && o_idx < int(model.objects.size())) { + const ModelObject* obj = model.objects[o_idx]; + const int v_idx = v.volume_idx(); + if (0 <= v_idx && v_idx < int(obj->volumes.size())) { + const ModelVolume* vol = obj->volumes[v_idx]; + if (!vol->source.is_from_builtin_objects && !vol->source.input_file.empty() && + !fs::path(vol->source.input_file).extension().string().empty()) + ret.push_back({ o_idx, v_idx }); + } + } + } + return ret; +} +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + void Plater::priv::reload_from_disk() { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + + // nothing to reload, return + if (selected_volumes.empty()) + return; + + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); +#else Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); const Selection& selection = get_selection(); @@ -3573,10 +3610,36 @@ void Plater::priv::reload_from_disk() } std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::vector> replace_paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + const ModelObject* object = model.objects[obj_idx]; + const ModelVolume* volume = object->volumes[vol_idx]; + if (fs::exists(volume->source.input_file)) + input_paths.push_back(volume->source.input_file); + else { + // searches the source in the same folder containing the object + bool found = false; + if (!object->input_file.empty()) { + fs::path object_path = fs::path(object->input_file).remove_filename(); + if (!object_path.empty()) { + object_path /= fs::path(volume->source.input_file).filename(); + if (fs::exists(object_path)) { + input_paths.push_back(object_path); + found = true; + } + } + } + if (!found) + missing_input_paths.push_back(volume->source.input_file); + } + } +#else std::vector replace_paths; for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; @@ -3606,6 +3669,7 @@ void Plater::priv::reload_from_disk() else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects) missing_input_paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); @@ -3649,7 +3713,11 @@ void Plater::priv::reload_from_disk() //wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) - replace_paths.push_back(sel_filename_path); +#if ENABLE_RELOAD_FROM_DISK_REWORK + replace_paths.emplace_back(search, sel_filename_path); +#else + replace_paths.emplace_back(sel_filename_path); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK missing_input_paths.pop_back(); } } @@ -3660,6 +3728,10 @@ void Plater::priv::reload_from_disk() std::sort(replace_paths.begin(), replace_paths.end()); replace_paths.erase(std::unique(replace_paths.begin(), replace_paths.end()), replace_paths.end()); +#if ENABLE_RELOAD_FROM_DISK_REWORK + Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + std::vector fail_list; Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas()); @@ -3688,6 +3760,86 @@ void Plater::priv::reload_from_disk() } // update the selected volumes whose source is the current file +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [obj_idx, vol_idx] : selected_volumes) { + ModelObject* old_model_object = model.objects[obj_idx]; + ModelVolume* old_volume = old_model_object->volumes[vol_idx]; + + bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; + + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); + bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); + if (has_source || has_name) { + int new_volume_idx = -1; + int new_object_idx = -1; + bool match_found = false; + // take idxs from the matching volume + if (has_source && old_volume->source.object_idx < int(new_model.objects.size())) { + const ModelObject* obj = new_model.objects[old_volume->source.object_idx]; + if (old_volume->source.volume_idx < int(obj->volumes.size())) { + if (obj->volumes[old_volume->source.volume_idx]->name == old_volume->name) { + new_volume_idx = old_volume->source.volume_idx; + new_object_idx = old_volume->source.object_idx; + match_found = true; + } + } + } + + if (!match_found && has_name) { + // take idxs from the 1st matching volume + for (size_t o = 0; o < new_model.objects.size(); ++o) { + ModelObject* obj = new_model.objects[o]; + bool found = false; + for (size_t v = 0; v < obj->volumes.size(); ++v) { + if (obj->volumes[v]->name == old_volume->name) { + new_volume_idx = (int)v; + new_object_idx = (int)o; + found = true; + break; + } + } + if (found) + break; + } + } + + if (new_object_idx < 0 || int(new_model.objects.size()) <= new_object_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + ModelObject* new_model_object = new_model.objects[new_object_idx]; + if (new_volume_idx < 0 || int(new_model_object->volumes.size()) <= new_volume_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + + old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); + ModelVolume* new_volume = old_model_object->volumes.back(); + new_volume->set_new_unique_id(); + new_volume->config.apply(old_volume->config); + new_volume->set_type(old_volume->type()); + new_volume->set_material_id(old_volume->material_id()); + new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + old_volume->get_transformation().get_matrix(true) * + old_volume->source.transform.get_matrix(true)); + new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + new_volume->source.object_idx = old_volume->source.object_idx; + new_volume->source.volume_idx = old_volume->source.volume_idx; + assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); + if (old_volume->source.is_converted_from_inches) + new_volume->convert_from_imperial_units(); + else if (old_volume->source.is_converted_from_meters) + new_volume->convert_from_meters(); + std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); + old_model_object->delete_volume(old_model_object->volumes.size() - 1); + if (!sinking) + old_model_object->ensure_on_bed(); + old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); + + sla::reproject_points_and_holes(old_model_object); + } + } +#else for (const SelectedVolume& sel_v : selected_volumes) { ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; @@ -3764,10 +3916,19 @@ void Plater::priv::reload_from_disk() sla::reproject_points_and_holes(old_model_object); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } busy.reset(); +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [src, dest] : replace_paths) { + for (auto [obj_idx, vol_idx] : selected_volumes) { + if (boost::algorithm::iequals(model.objects[obj_idx]->volumes[vol_idx]->source.input_file, src.string())) + replace_volume_with_stl(obj_idx, vol_idx, dest, ""); + } + } +#else for (size_t i = 0; i < replace_paths.size(); ++i) { const auto& path = replace_paths[i].string(); for (const SelectedVolume& sel_v : selected_volumes) { @@ -3777,6 +3938,7 @@ void Plater::priv::reload_from_disk() replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, ""); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; @@ -4572,6 +4734,13 @@ bool Plater::priv::can_replace_with_stl() const bool Plater::priv::can_reload_from_disk() const { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + // nothing to reload, return + if (selected_volumes.empty()) + return false; +#else // struct to hold selected ModelVolumes by their indices struct SelectedVolume { @@ -4597,6 +4766,21 @@ bool Plater::priv::can_reload_from_disk() const selected_volumes.push_back({ o_idx, v_idx }); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); + + // collects paths of files to load + std::vector paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + paths.push_back(model.objects[obj_idx]->volumes[vol_idx]->source.input_file); + } +#else std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); @@ -4610,6 +4794,7 @@ bool Plater::priv::can_reload_from_disk() const else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects) paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(paths.begin(), paths.end()); paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); @@ -5375,17 +5560,22 @@ bool Plater::load_files(const wxArrayString& filenames) if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { LoadType load_type = LoadType::Unknown; if (!model().objects.empty()) { - if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { - ProjectDropDialog dlg(filename); - if (dlg.ShowModal() == wxID_OK) { - int choice = dlg.get_action(); - load_type = static_cast(choice); - wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) || + (boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) + load_type = LoadType::OpenProject; + else { + if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { + ProjectDropDialog dlg(filename); + if (dlg.ShowModal() == wxID_OK) { + int choice = dlg.get_action(); + load_type = static_cast(choice); + wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + } } + else + load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } - else - load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), - static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } else load_type = LoadType::OpenProject; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 2af008a52b..75c5c116fd 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -790,10 +790,10 @@ void PreferencesDialog::create_settings_mode_widget() } std::string opt_key = "settings_layout_mode"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); @@ -810,13 +810,13 @@ void PreferencesDialog::create_settings_text_color_widget() if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); std::string opt_key = "text_colors"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a9eea84a7f..9b362c4d49 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1427,10 +1427,10 @@ void Selection::render(float scale_factor) return; m_scale_factor = scale_factor; + // render cumulative bounding box of selected volumes #if ENABLE_GLBEGIN_GLEND_REMOVAL render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); #else - // render cumulative bounding box of selected volumes render_selected_volumes(); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL render_synchronized_volumes(); @@ -1442,6 +1442,14 @@ void Selection::render_center(bool gizmo_is_dragging) if (!m_valid || is_empty()) return; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); glsafe(::glDisable(GL_DEPTH_TEST)); @@ -1450,19 +1458,17 @@ void Selection::render_center(bool gizmo_is_dragging) glsafe(::glTranslated(center.x(), center.y(), center.z())); #if ENABLE_GLBEGIN_GLEND_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL + m_vbo_sphere.set_color(ColorRGBA::WHITE()); +#else m_vbo_sphere.set_color(-1, ColorRGBA::WHITE()); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL m_vbo_sphere.render(); + + glsafe(::glPopMatrix()); + #if ENABLE_GLBEGIN_GLEND_REMOVAL shader->stop_using(); #endif // ENABLE_GLBEGIN_GLEND_REMOVAL - - glsafe(::glPopMatrix()); } #endif // ENABLE_RENDER_SELECTION_CENTER @@ -2037,8 +2043,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(48 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(48 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(48); + init_data.reserve_indices(48); // vertices init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); @@ -2346,8 +2352,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); @@ -2368,8 +2374,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT }; - init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format)); - init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format)); + init_data.reserve_vertices(4); + init_data.reserve_indices(6); // vertices init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4f19ad8067..b97b6cc39c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2324,6 +2324,7 @@ void TabPrinter::build_fff() option = optgroup->get_option("thumbnails"); option.opt.full_width = true; optgroup->append_single_option_line(option); + optgroup->append_single_option_line("thumbnails_format"); optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); @@ -2517,6 +2518,11 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("min_initial_exposure_time"); optgroup->append_single_option_line("max_initial_exposure_time"); + + optgroup = page->new_optgroup(L("Output")); + optgroup->append_single_option_line("sla_archive_format"); + optgroup->append_single_option_line("sla_output_precision"); + build_print_host_upload_group(page.get()); const int notes_field_height = 25; // 250 @@ -4106,7 +4112,10 @@ wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent) }); create_btn(&m_del_all_substitutions_btn, _L("Delete all"), "cross"); - m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { + m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { + if (MessageDialog(parent, _L("Are you sure you want to delete all substitutions?"), SLIC3R_APP_NAME, wxYES_NO | wxICON_QUESTION). + ShowModal() != wxID_YES) + return; m_subst_manager.delete_all(); m_del_all_substitutions_btn->Hide(); }); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index d49b4b1d34..b3b0c15b49 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -656,6 +656,7 @@ void DiffViewCtrl::Clear() { model->Clear(); m_items_map.clear(); + m_has_long_strings = false; } wxString DiffViewCtrl::get_short_string(wxString full_string) @@ -1030,7 +1031,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) { const ConfigOptionDef& def = config.def()->options.at(opt_key); - const std::vector& names = def.enum_labels;//ConfigOptionEnum::get_enum_names(); + const std::vector& names = def.enum_labels.empty() ? def.enum_values : def.enum_labels; int val = config.option(opt_key)->getInt(); // Each infill doesn't use all list of infill declared in PrintConfig.hpp. @@ -1523,8 +1524,8 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border); topSizer->Add(presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border); - topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border); + topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); this->SetMinSize(wxSize(80 * em, 30 * em)); this->SetSizer(topSizer); @@ -1689,12 +1690,17 @@ void DiffPresetDialog::update_tree() left_val, right_val, category_icon_map.at(option.category)); } } + + if (m_tree->has_long_strings()) + bottom_info = _L("Some fields are too long to fit. Right mouse click reveals the full text."); bool tree_was_shown = m_tree->IsShown(); m_tree->Show(show_tree); - if (!show_tree) + + bool show_bottom_info = !show_tree || m_tree->has_long_strings(); + if (show_bottom_info) m_bottom_info_line->SetLabel(bottom_info); - m_bottom_info_line->Show(!show_tree); + m_bottom_info_line->Show(show_bottom_info); if (tree_was_shown == m_tree->IsShown()) Layout(); diff --git a/src/slic3r/Utils/FontManager.cpp b/src/slic3r/Utils/FontManager.cpp index bcb7c8d8a0..5f5dcd3157 100644 --- a/src/slic3r/Utils/FontManager.cpp +++ b/src/slic3r/Utils/FontManager.cpp @@ -375,8 +375,8 @@ void FontManager::create_texture(size_t index, const std::string &text, GLuint& bb2.scale(scale); tex_size.x = bb2.max.x() - bb2.min.x(); tex_size.y = bb2.max.y() - bb2.min.y(); - sla::RasterBase::Resolution resolution(tex_size.x,tex_size.y); - sla::RasterBase::PixelDim dim(1/scale, 1/scale); + sla::Resolution resolution(tex_size.x,tex_size.y); + sla::PixelDim dim(1/scale, 1/scale); const double no_gamma = 1.; std::unique_ptr r = sla::create_raster_grayscale_aa(resolution, dim, no_gamma); @@ -472,8 +472,8 @@ void FontManager::init_style_images(int max_width) { for (Item &item : m_font_list) { double scale = item.font_item.prop.size_in_mm; StyleImage &image = *item.image; - sla::RasterBase::Resolution resolution(image.tex_size.x, image.tex_size.y); - sla::RasterBase::PixelDim dim(1 / scale, 1 / scale); + sla::Resolution resolution(image.tex_size.x, image.tex_size.y); + sla::PixelDim dim(1 / scale, 1 / scale); double gamma = 1.; std::unique_ptr r = sla::create_raster_grayscale_aa(resolution, dim, gamma); size_t index = &item - &m_font_list.front(); diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 049de5400a..533c544a8a 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -21,6 +21,10 @@ #include #include +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "slic3r/GUI/3DScene.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + #include #ifndef NDEBUG diff --git a/tests/catch_main.hpp b/tests/catch_main.hpp index 5ab71fdd74..ca5b47da84 100644 --- a/tests/catch_main.hpp +++ b/tests/catch_main.hpp @@ -3,7 +3,7 @@ #define CATCH_CONFIG_EXTERNAL_INTERFACES #define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" +// #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" #include namespace Catch { diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 3553697acd..32b137175a 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -20,17 +20,17 @@ using namespace Slic3r; -static double area(const sla::RasterBase::PixelDim &pxd) +static double area(const sla::PixelDim &pxd) { return pxd.w_mm * pxd.h_mm; } static Slic3r::sla::RasterGrayscaleAA create_raster( - const sla::RasterBase::Resolution &res, + const sla::Resolution &res, double disp_w = 100., double disp_h = 100.) { - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo; @@ -107,7 +107,7 @@ static void test_expolys(Rst && rst, svg.Close(); double max_rel_err = 0.1; - sla::RasterBase::PixelDim pxd = rst.pixel_dimensions(); + sla::PixelDim pxd = rst.pixel_dimensions(); double max_abs_err = area(pxd) * scaled(1.) * scaled(1.); BoundingBox ref_bb; @@ -175,7 +175,7 @@ TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -205,7 +205,7 @@ TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -321,7 +321,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); - sla::RasterBase::Resolution res{2560, 1440}; + sla::Resolution res{2560, 1440}; double disp_w = 120.96; double disp_h = 68.04; diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index b4fcd6255d..e8229b7163 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -9,9 +9,9 @@ using namespace Slic3r; -static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res) +static sla::RasterGrayscaleAA create_raster(const sla::Resolution &res) { - sla::RasterBase::PixelDim pixdim{1., 1.}; + sla::PixelDim pixdim{1., 1.}; auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)}); sla::RasterBase::Trafo trafo; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index db8c5e93ec..f7b0df339a 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -159,8 +159,8 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); @@ -186,8 +186,8 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; double gamma = 1.; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1082df2007..d98a920372 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -307,8 +307,8 @@ void check_validity(const TriangleMesh &input_mesh, int flags) void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo{o, mirroring}; @@ -400,7 +400,7 @@ double raster_white_area(const sla::RasterGrayscaleAA &raster) return a; } -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 2264ad856c..7171914d43 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -175,7 +175,7 @@ void check_raster_transformations(sla::RasterBase::Orientation o, ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } @@ -183,7 +183,7 @@ inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) double raster_white_area(const sla::RasterGrayscaleAA &raster); long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::PixelDim &pd); sla::SupportPoints calc_support_pts( const TriangleMesh & mesh, diff --git a/version.inc b/version.inc index b976d7d674..93b3763239 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.4.1-beta1") +set(SLIC3R_VERSION "2.5.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,4,1,0") -set(SLIC3R_RC_VERSION_DOTS "2.4.1.0") +set(SLIC3R_RC_VERSION "2,5,0,0") +set(SLIC3R_RC_VERSION_DOTS "2.5.0.0") diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 962e2e04d9..06fc98322f 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -208,5 +208,45 @@ if (MSVC) else () set(PERL_PROVE "${PERL_BIN_PATH}/prove") endif () -add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PERL_LOCAL_LIB_DIR}/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) -add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..) + +set(PERL_ENV_VARS "") +if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT CMAKE_CROSSCOMPILING AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + if (SLIC3R_ASAN OR SLIC3R_UBSAN) + set(PERL_ENV_VARS env) + endif () + + if (SLIC3R_ASAN) + # Find the location of libasan.so for passing it into LD_PRELOAD. It works with GCC and Clang on Linux. + # On Centos 7 calling "gcc -print-file-name=libasan.so" returns path to "ld script" instead of path to shared library. + set(_asan_compiled_bin ${CMAKE_CURRENT_BINARY_DIR}/detect_libasan) + set(_asan_source_file ${_asan_compiled_bin}.c) + # Compile and link simple C application with enabled address sanitizer. + file(WRITE ${_asan_source_file} "int main(){}") + include(GetPrerequisites) + execute_process(COMMAND ${CMAKE_C_COMPILER} ${_asan_source_file} -fsanitize=address -lasan -o ${_asan_compiled_bin}) + # Extract from the compiled application absolute path of libasan. + get_prerequisites(${_asan_compiled_bin} _asan_shared_libraries_list 0 0 "" "") + list(FILTER _asan_shared_libraries_list INCLUDE REGEX libasan) + set(PERL_ENV_VARS ${PERL_ENV_VARS} "LD_PRELOAD=${_asan_shared_libraries_list}") + + # Suppressed memory leak reports that come from Perl. + set(PERL_LEAK_SUPPRESSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/leak_suppression.txt) + file(WRITE ${PERL_LEAK_SUPPRESSION_FILE} + "leak:Perl_safesysmalloc\n" + "leak:Perl_safesyscalloc\n" + "leak:Perl_safesysrealloc\n" + "leak:__newlocale\n") + + # Suppress a few memory leak reports and disable informing about suppressions. + # Print reports about memory leaks but exit with zero exit code when any memory leaks is found to make unit tests pass. + set(PERL_ENV_VARS ${PERL_ENV_VARS} "LSAN_OPTIONS=suppressions=${PERL_LEAK_SUPPRESSION_FILE}:print_suppressions=0:exitcode=0") + endif () + + if (SLIC3R_UBSAN) + # Do not show full stacktrace for reports from UndefinedBehaviorSanitizer in Perl tests. + set(PERL_ENV_VARS ${PERL_ENV_VARS} "UBSAN_OPTIONS=print_stacktrace=0") + endif () +endif () + +add_test (NAME xs COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PERL_LOCAL_LIB_DIR}/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +add_test (NAME integration COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..)