From 1d0af50a94dadf22126a3ebe2b9035f05644a1aa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Feb 2022 10:41:38 +0100 Subject: [PATCH 01/33] GCodeProcessor - Added processing of gcode line M220 --- src/libslic3r/GCode/GCodeProcessor.cpp | 20 ++++++++++++++++++-- src/libslic3r/GCode/GCodeProcessor.hpp | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 58e8ed787a..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; } @@ -2498,7 +2500,7 @@ 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; @@ -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; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 7e4bb831d8..25375f61b6 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -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); From cca1454c38ece7f1361edf1effb0087647086e61 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Feb 2022 12:06:12 +0100 Subject: [PATCH 02/33] Tech ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - Recalculate toolpaths when switching to/from volumetric rate visualization --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 82 ++++++++++++++++++++++++++++++++-- src/slic3r/GUI/GCodeViewer.hpp | 7 +++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6b4274b11f..8e7fba1264 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -76,6 +76,8 @@ #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) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a4808a0cf0..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; @@ -698,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(); @@ -1266,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); @@ -1287,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()); @@ -1299,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; } @@ -1344,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; @@ -1390,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; } @@ -1474,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); @@ -1614,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()); @@ -1678,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); } } @@ -1689,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); @@ -2053,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: { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 22e866fe6e..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; } @@ -762,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 From fa1ff1c35768e09fdb7270f563f62abb558cd966 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Feb 2022 14:39:02 +0100 Subject: [PATCH 03/33] Tech ENABLE_GLBEGIN_GLEND_REMOVAL - Another refactoring to simplify client code of GLModel::Geometry --- src/slic3r/GUI/3DBed.cpp | 10 ++++------ src/slic3r/GUI/3DScene.cpp | 5 ++--- src/slic3r/GUI/GLCanvas3D.cpp | 5 ++--- src/slic3r/GUI/GLModel.cpp | 5 +++++ src/slic3r/GUI/GLModel.hpp | 12 +++++++----- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 5 ++--- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 5 ++--- src/slic3r/GUI/MeshUtils.cpp | 5 ++--- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 8488c1148f..0facce33c4 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -312,8 +312,7 @@ void Bed3D::init_triangles() 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.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2, GLModel::Geometry::index_type(triangles.size()) }; init_data.reserve_vertices(triangles.size()); init_data.reserve_indices(triangles.size() / 3); @@ -338,7 +337,7 @@ void Bed3D::init_triangles() 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) + if (init_data.format.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); @@ -381,8 +380,7 @@ void Bed3D::init_gridlines() 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.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::index_type(2 * gridlines.size()) }; init_data.reserve_vertices(2 * gridlines.size()); init_data.reserve_indices(2 * gridlines.size()); @@ -390,7 +388,7 @@ void Bed3D::init_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) + if (init_data.format.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); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 339754790f..c9b647b07c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -400,8 +400,7 @@ void GLVolume::NonManifoldEdges::update() if (!edges.empty()) { GUI::GLModel::Geometry init_data; #if ENABLE_GLBEGIN_GLEND_REMOVAL - 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.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::index_type(2 * edges.size()) }; init_data.reserve_vertices(2 * edges.size()); init_data.reserve_indices(2 * edges.size()); @@ -411,7 +410,7 @@ 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; - if (index_type == GUI::GLModel::Geometry::EIndexType::USHORT) + if (init_data.format.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); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 95600c074a..ee16aefef7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -447,8 +447,7 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) m_profile.profile.reset(); GLModel::Geometry init_data; - 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.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::index_type(m_layer_height_profile.size() / 2) }; init_data.color = ColorRGBA::BLUE(); init_data.reserve_vertices(m_layer_height_profile.size() / 2); init_data.reserve_indices(m_layer_height_profile.size() / 2); @@ -457,7 +456,7 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) 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)); - if (index_type == GLModel::Geometry::EIndexType::USHORT) + if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) init_data.add_ushort_index((unsigned short)i / 2); else init_data.add_uint_index(i / 2); diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index e1dc3305c7..28a6a1ce7a 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -328,6 +328,11 @@ size_t GLModel::Geometry::index_stride_bytes(const Format& format) }; } +GLModel::Geometry::EIndexType GLModel::Geometry::index_type(size_t vertices_count) +{ + return (vertices_count < 65536) ? EIndexType::USHORT : EIndexType::UINT; +} + bool GLModel::Geometry::has_position(const Format& format) { switch (format.vertex_layout) diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 70220a75cb..72c50ee11c 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -83,11 +83,11 @@ namespace GUI { 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_vertex(const Vec2f& position); // EVertexLayout::P2 + void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2 + void add_vertex(const Vec3f& position); // EVertexLayout::P3 + void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2 + void add_vertex(const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 void add_ushort_index(unsigned short id); void add_uint_index(unsigned int id); @@ -135,6 +135,8 @@ namespace GUI { static size_t index_stride_bytes(const Format& format); + static EIndexType index_type(size_t vertices_count); + static bool has_position(const Format& format); static bool has_normal(const Format& format); static bool has_tex_coord(const Format& format); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 0956a9047b..ea15deb169 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -362,14 +362,13 @@ void GLGizmoFlatten::update_planes() 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.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(plane.vertices.size()) }; 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) + if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) init_data.add_ushort_index((unsigned short)i); else init_data.add_uint_index((unsigned int)i); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 0fc57c909d..82a816d1b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1189,8 +1189,7 @@ void TriangleSelectorGUI::update_paint_contour() 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.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::index_type(2 * contour_edges.size()) }; init_data.reserve_vertices(2 * contour_edges.size()); init_data.reserve_indices(2 * contour_edges.size()); // vertices + indices @@ -1199,7 +1198,7 @@ void TriangleSelectorGUI::update_paint_contour() 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) + if (init_data.format.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); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 68762c67a6..3787abb2f7 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -189,8 +189,7 @@ void MeshClipper::recalculate_triangles() 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.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(m_triangles2d.size()) }; init_data.reserve_vertices(m_triangles2d.size()); init_data.reserve_indices(m_triangles2d.size()); @@ -200,7 +199,7 @@ void MeshClipper::recalculate_triangles() 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) + if (init_data.format.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); From a28dea0617d0342c79cadf99f64f72b5d06d0286 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Feb 2022 15:11:22 +0100 Subject: [PATCH 04/33] Follow-up of 6b041429f6f137add530c900c3160d431286366f - Fixed uvmapping of printbed texture --- src/slic3r/GUI/3DBed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 0facce33c4..244a802dbc 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -334,7 +334,7 @@ void Bed3D::init_triangles() 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()); + init_data.add_vertex(p, (Vec2f)(v - min).cwiseProduct(inv_size).eval()); ++vertices_counter; if (vertices_counter % 3 == 0) { if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) From aa4714a239d9d224708156c6c15a0d239a7c8a71 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Feb 2022 15:17:24 +0100 Subject: [PATCH 05/33] Tech ENABLE_GLBEGIN_GLEND_REMOVAL - Removed OpenGL legacy calls from GLCanvas3D::init() --- src/slic3r/GUI/GLCanvas3D.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ee16aefef7..398eef1de8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1157,6 +1157,7 @@ bool GLCanvas3D::init() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +#if !ENABLE_GLBEGIN_GLEND_REMOVAL // Set antialiasing / multisampling glsafe(::glDisable(GL_LINE_SMOOTH)); glsafe(::glDisable(GL_POLYGON_SMOOTH)); @@ -1186,6 +1187,7 @@ bool GLCanvas3D::init() // A handy trick -- have surface material mirror the color. glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)); glsafe(::glEnable(GL_COLOR_MATERIAL)); +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); @@ -5868,11 +5870,11 @@ void GLCanvas3D::_render_sla_slices() if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { for (const SLAPrintObject::Instance& inst : obj->instances()) { glsafe(::glPushMatrix()); - glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0)); - glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0)); + glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); + glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); if (obj->is_left_handed()) // The polygons are mirrored by X. - glsafe(::glScalef(-1.0, 1.0, 1.0)); + glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); if (!bottom_obj_triangles.empty()) { From c9ff260d13690eb32527900599487712a77e1657 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Feb 2022 09:15:56 +0100 Subject: [PATCH 06/33] Tech ENABLE_GLBEGIN_GLEND_REMOVAL - SLA caps rendered using GLModel --- src/slic3r/GUI/GLCanvas3D.cpp | 100 ++++++++++++++++++++++++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 13 ++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 398eef1de8..db15bdce5f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5802,32 +5802,81 @@ void GLCanvas3D::_render_sla_slices() if (!obj->is_step_done(slaposSliceSupports)) continue; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + SlaCap::ObjectIdToModelsMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); + SlaCap::ObjectIdToModelsMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#else SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL { if (it_caps_bottom == m_sla_caps[0].triangles.end()) it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; if (!m_sla_caps[0].matches(clip_min_z)) { m_sla_caps[0].z = clip_min_z; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + it_caps_bottom->second.object.reset(); + it_caps_bottom->second.supports.reset(); +#else it_caps_bottom->second.object.clear(); it_caps_bottom->second.supports.clear(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } if (it_caps_top == m_sla_caps[1].triangles.end()) it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; if (!m_sla_caps[1].matches(clip_max_z)) { m_sla_caps[1].z = clip_max_z; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + it_caps_top->second.object.reset(); + it_caps_top->second.supports.reset(); +#else it_caps_top->second.object.clear(); it_caps_top->second.supports.clear(); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLModel& bottom_obj_triangles = it_caps_bottom->second.object; + GLModel& bottom_sup_triangles = it_caps_bottom->second.supports; + GLModel& top_obj_triangles = it_caps_top->second.object; + GLModel& top_sup_triangles = it_caps_top->second.supports; +#else Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object; Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports; Pointf3s &top_obj_triangles = it_caps_top->second.object; Pointf3s &top_sup_triangles = it_caps_top->second.supports; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_GLBEGIN_GLEND_REMOVAL + auto init_model = [](GLModel& model, const Pointf3s& triangles, const ColorRGBA& color) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::index_type(triangles.size()) }; + init_data.reserve_vertices(triangles.size()); + init_data.reserve_indices(triangles.size() / 3); + init_data.color = color; + + unsigned int vertices_count = 0; + for (const Vec3d& v : triangles) { + init_data.add_vertex((Vec3f)v.cast()); + ++vertices_count; + if (vertices_count % 3 == 0) { + if (init_data.format.index_type == GLModel::Geometry::EIndexType::USHORT) + init_data.add_ushort_triangle((unsigned short)vertices_count - 3, (unsigned short)vertices_count - 2, (unsigned short)vertices_count - 1); + else + init_data.add_uint_triangle(vertices_count - 3, vertices_count - 2, vertices_count - 1); + } + } + + if (!init_data.is_empty()) + model.init_from(std::move(init_data)); + }; + + if ((!bottom_obj_triangles.is_initialized() || !bottom_sup_triangles.is_initialized() || + !top_obj_triangles.is_initialized() || !top_sup_triangles.is_initialized()) && !obj->get_slice_index().empty()) { +#else if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - !obj->get_slice_index().empty()) - { + !obj->get_slice_index().empty()) { +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL double layer_height = print->default_object_config().layer_height.value; double initial_layer_height = print->material_config().initial_layer_height.value; bool left_handed = obj->is_left_handed(); @@ -5842,31 +5891,73 @@ void GLCanvas3D::_render_sla_slices() const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON)); // Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts. - double plane_shift_z = 0.002; + const double plane_shift_z = 0.002; if (slice_low.is_valid()) { const ExPolygons& obj_bottom = slice_low.get_slice(soModel); const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // calculate model bottom cap + if (!bottom_obj_triangles.is_initialized() && !obj_bottom.empty()) + init_model(bottom_obj_triangles, triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support bottom cap + if (!bottom_sup_triangles.is_initialized() && !sup_bottom.empty()) + init_model(bottom_sup_triangles, triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else // calculate model bottom cap if (bottom_obj_triangles.empty() && !obj_bottom.empty()) bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); // calculate support bottom cap if (bottom_sup_triangles.empty() && !sup_bottom.empty()) - bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, ! left_handed); + bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, !left_handed); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } if (slice_high.is_valid()) { const ExPolygons& obj_top = slice_high.get_slice(soModel); const ExPolygons& sup_top = slice_high.get_slice(soSupport); +#if ENABLE_GLBEGIN_GLEND_REMOVAL + // calculate model top cap + if (!top_obj_triangles.is_initialized() && !obj_top.empty()) + init_model(top_obj_triangles, triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.37f, 0.0f, 1.0f }); + // calculate support top cap + if (!top_sup_triangles.is_initialized() && !sup_top.empty()) + init_model(top_sup_triangles, triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed), { 1.0f, 0.0f, 0.37f, 1.0f }); +#else // calculate model top cap if (top_obj_triangles.empty() && !obj_top.empty()) top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); // calculate support top cap if (top_sup_triangles.empty() && !sup_top.empty()) top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } } +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + + for (const SLAPrintObject::Instance& inst : obj->instances()) { + glsafe(::glPushMatrix()); + glsafe(::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0.0)); + glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0f, 0.0f, 1.0f)); + if (obj->is_left_handed()) + // The polygons are mirrored by X. + glsafe(::glScalef(-1.0f, 1.0f, 1.0f)); + + bottom_obj_triangles.render(); + top_obj_triangles.render(); + bottom_sup_triangles.render(); + top_sup_triangles.render(); + + glsafe(::glPopMatrix()); + } + + shader->stop_using(); + } +#else if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { for (const SLAPrintObject::Instance& inst : obj->instances()) { glsafe(::glPushMatrix()); @@ -5898,6 +5989,7 @@ void GLCanvas3D::_render_sla_slices() glsafe(::glPopMatrix()); } } +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 33e17d5dd2..57f173c7b2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -335,6 +335,16 @@ class GLCanvas3D struct SlaCap { +#if ENABLE_GLBEGIN_GLEND_REMOVAL + struct Triangles + { + GLModel object; + GLModel supports; + }; + typedef std::map ObjectIdToModelsMap; + double z; + ObjectIdToModelsMap triangles; +#else struct Triangles { Pointf3s object; @@ -343,6 +353,7 @@ class GLCanvas3D typedef std::map ObjectIdToTrianglesMap; double z; ObjectIdToTrianglesMap triangles; +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL SlaCap() { reset(); } void reset() { z = DBL_MAX; triangles.clear(); } @@ -473,7 +484,7 @@ private: std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; - SlaCap m_sla_caps[2]; + std::array m_sla_caps; std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() From 0c74081f7dd33ebeb4e6f2c179e728520b19febb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Feb 2022 11:29:00 +0100 Subject: [PATCH 07/33] Follow-up of afcac6e2ea306fbbb12428e360c0fe70b2a3f12d - Fixed GLGizmoSlaSupports::render_points() --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 3406c58a29..fa0b27269a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -127,6 +127,14 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) if (! has_points && ! has_holes) return; +#if ENABLE_GLBEGIN_GLEND_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) shader->start_using(); @@ -134,6 +142,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) if (shader != nullptr) shader->stop_using(); }); +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); @@ -177,11 +186,12 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) #if ENABLE_GLBEGIN_GLEND_REMOVAL m_cone.set_color(render_color); m_sphere.set_color(render_color); + if (!picking) #else m_cone.set_color(-1, render_color); m_sphere.set_color(-1, render_color); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL if (shader && !picking) +#endif // ENABLE_GLBEGIN_GLEND_REMOVAL shader->set_uniform("emission_factor", 0.5f); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. @@ -236,9 +246,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) m_cylinder.set_color(render_color); #else m_cylinder.set_color(-1, render_color); + if (shader != nu) #endif // ENABLE_GLBEGIN_GLEND_REMOVAL - if (shader) - shader->set_uniform("emission_factor", 0.5f); + shader->set_uniform("emission_factor", 0.5f); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; From a1ff2bb7d7a3c26d3f4341e9fe1f4d47fad50e67 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 Feb 2022 11:38:36 +0100 Subject: [PATCH 08/33] Fix for #6364 - MacOS / Minimize window / Shortcut CMD + M There was a bug in wxWidgets 3.1.4. (wxWidgets/wxWidgets#18998) So, we cherry-picked fixing commit from the upstream wxWidgets to our "v3.1.4-patched" Note: It's update of the Sha256 for wxWidgets in master branch --- deps/wxWidgets/wxWidgets.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 2730854ebc..1ca6735cc3 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -12,8 +12,8 @@ endif() prusaslicer_add_cmake_project(wxWidgets # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" # GIT_TAG tm_cross_compile #${_wx_git_tag} - URL https://github.com/prusa3d/wxWidgets/archive/73f029adfcc82fb3aa4b01220a013f716e57d110.zip - URL_HASH SHA256=c35fe0187db497b6a3f477e24ed5e307028657ff0c2554385810b6e7961ad2e4 + URL https://github.com/prusa3d/wxWidgets/archive/489f6118256853cf5b299d595868641938566cdb.zip + URL_HASH SHA256=5b22d465377cedd8044bba69bea958b248953fd3628c1de4913a84d4e6f6175b DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG CMAKE_ARGS -DwxBUILD_PRECOMP=ON From 5089d1460d172e29164e313e806457181a67daef Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Feb 2022 11:47:29 +0100 Subject: [PATCH 09/33] Removed obsolete member variable GLCanvas3D::m_color_by --- src/slic3r/GUI/3DScene.cpp | 8 ++------ src/slic3r/GUI/3DScene.hpp | 2 -- src/slic3r/GUI/GCodeViewer.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 10 ++-------- src/slic3r/GUI/GLCanvas3D.hpp | 4 ---- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c9b647b07c..23ca974dd7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -684,13 +684,12 @@ std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, - const std::string &color_by, bool opengl_initialized) { std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized)); + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, opengl_initialized)); return volumes_idx; } @@ -699,16 +698,13 @@ int GLVolumeCollection::load_object_volume( int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, bool opengl_initialized) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); const ModelInstance *instance = model_object->instances[instance_idx]; const TriangleMesh &mesh = model_volume->mesh(); - ColorRGBA color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4]; - color.a(model_volume->is_model_part() ? 1.0f : 0.5f); - this->volumes.emplace_back(new GLVolume(color)); + this->volumes.emplace_back(new GLVolume()); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); #if ENABLE_SMOOTH_NORMALS diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 5aaa759f2f..c2e4e587ce 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -593,7 +593,6 @@ public: const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, - const std::string &color_by, bool opengl_initialized); int load_object_volume( @@ -601,7 +600,6 @@ public: int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, bool opengl_initialized); // Load SLA auxiliary GLVolumes (for support trees or pad). diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index d54c28293f..c86d16a1f0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2306,7 +2306,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } size_t current_volumes_count = m_shells.volumes.volumes.size(); - m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized); + m_shells.volumes.load_object(model_obj, object_id, instance_ids, initialized); // adjust shells' z if raft is present const SlicingParameters& slicing_parameters = obj->slicing_parameters(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index db15bdce5f..732c75589e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1106,7 +1106,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) , m_moving(false) , m_tab_down(false) , m_cursor_type(Standard) - , m_color_by("volume") , m_reload_delayed(false) #if ENABLE_RENDER_PICKING_PASS , m_show_picking_texture(false) @@ -1358,11 +1357,6 @@ void GLCanvas3D::bed_shape_changed() m_dirty = true; } -void GLCanvas3D::set_color_by(const std::string& value) -{ - m_color_by = value; -} - void GLCanvas3D::refresh_camera_scene_box() { wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box()); @@ -1807,7 +1801,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.emplace_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -2032,7 +2026,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh // later in this function. it->volume_idx = m_volumes.volumes.size(); - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized); + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 57f173c7b2..a3c302baef 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -522,8 +522,6 @@ private: // I just don't want to do it now before a release (Lukas Matena 24.3.2019) bool m_render_sla_auxiliaries; - std::string m_color_by; - bool m_reload_delayed; #if ENABLE_RENDER_PICKING_PASS @@ -692,8 +690,6 @@ public: bool get_use_clipping_planes() const { return m_use_clipping_planes; } const std::array &get_clipping_planes() const { return m_clipping_planes; }; - void set_color_by(const std::string& value); - void refresh_camera_scene_box(); BoundingBoxf3 volumes_bounding_box() const; From 0835e117d588f98da14c0636ad514882ee9313f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Feb 2022 12:06:48 +0100 Subject: [PATCH 10/33] Refactoring in GLCanvas3D::LayersEditing::render_volumes() --- src/slic3r/GUI/GLCanvas3D.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 732c75589e..edd62ce0ab 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -495,17 +495,17 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { assert(this->is_allowed()); assert(this->last_object_id != -1); + + GLShaderProgram* current_shader = wxGetApp().get_current_shader(); + ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); }); + if (current_shader != nullptr) + current_shader->stop_using(); + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) return; - GLShaderProgram* current_shader = wxGetApp().get_current_shader(); - if (shader->get_id() != current_shader->get_id()) - // The layer editing shader is not yet active. Activate it. - shader->start_using(); - else - // The layer editing shader was already active. - current_shader = nullptr; + shader->start_using(); generate_layer_height_texture(); @@ -516,10 +516,10 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G shader->set_uniform("z_cursor_band_width", float(this->band_width)); // Initialize the layer height texture mapping. - GLsizei w = (GLsizei)m_layers_texture.width; - GLsizei h = (GLsizei)m_layers_texture.height; - GLsizei half_w = w / 2; - GLsizei half_h = h / 2; + const GLsizei w = (GLsizei)m_layers_texture.width; + const GLsizei h = (GLsizei)m_layers_texture.height; + const GLsizei half_w = w / 2; + const GLsizei half_h = h / 2; glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); @@ -528,17 +528,15 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); for (const GLVolume* glvolume : volumes.volumes) { // Render the object using the layer editing shader and texture. - if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) + if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); - shader->set_uniform("object_max_z", GLfloat(0)); + shader->set_uniform("object_max_z", 0.0f); glvolume->render(); } // Revert back to the previous shader. glBindTexture(GL_TEXTURE_2D, 0); - if (current_shader != nullptr) - current_shader->start_using(); } void GLCanvas3D::LayersEditing::adjust_layer_height_profile() From 7685e707434613e1a8ba50a189da61061e1a26d0 Mon Sep 17 00:00:00 2001 From: ole00 Date: Fri, 11 Feb 2022 22:06:47 +0000 Subject: [PATCH 11/33] SLA: added pwmx format exporter Pwmx format is used by Anycubic Photon Mono X printers. File format structure and rle encoding implemented according to: https://github.com/sn4k3/UVtools https://github.com/ole00/pwmx_info --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/pwmx.cpp | 509 ++++++++++++++++++++ src/libslic3r/Format/pwmx.hpp | 36 ++ src/libslic3r/SLA/RasterBase.cpp | 48 ++ src/libslic3r/SLA/RasterBase.hpp | 4 + src/libslic3r/SLAPrint.cpp | 18 + src/libslic3r/SLAPrint.hpp | 38 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 26 +- src/slic3r/GUI/GUI_App.cpp | 2 +- 9 files changed, 647 insertions(+), 36 deletions(-) create mode 100644 src/libslic3r/Format/pwmx.cpp create mode 100644 src/libslic3r/Format/pwmx.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d4c8d7edc1..ea663f2e1f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -98,6 +98,8 @@ set(SLIC3R_SOURCES Format/SL1.cpp Format/SL1_SVG.hpp Format/SL1_SVG.cpp + Format/pwmx.hpp + Format/pwmx.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp diff --git a/src/libslic3r/Format/pwmx.cpp b/src/libslic3r/Format/pwmx.cpp new file mode 100644 index 0000000000..99785b8d08 --- /dev/null +++ b/src/libslic3r/Format/pwmx.cpp @@ -0,0 +1,509 @@ +#include "pwmx.hpp" +#include "GCode/ThumbnailData.hpp" + +#include +#include +#include + + +#define TAG_INTRO "ANYCUBIC\0\0\0\0" +#define TAG_HEADER "HEADER\0\0\0\0\0\0" +#define TAG_PREVIEW "PREVIEW\0\0\0\0\0" +#define TAG_LAYERS "LAYERDEF\0\0\0\0" + +#define CFG_LIFT_DISTANCE "LIFT_DISTANCE" +#define CFG_LIFT_SPEED "LIFT_SPEED" +#define CFG_RETRACT_SPEED "RETRACT_SPEED" +#define CFG_DELAY_BEFORE_EXPOSURE "DELAY_BEFORE_EXPOSURE" +#define CFG_BOTTOM_LIFT_SPEED "BOTTOM_LIFT_SPEED" +#define CFG_BOTTOM_LIFT_DISTANCE "BOTTOM_LIFT_DISTANCE" + +#define PREV_W 224 +#define PREV_H 168 +#define PREV_DPI 42 + +#define LAYER_SIZE_ESTIMATE (32 * 1024) + + +namespace Slic3r { + +using ConfMap = std::map; + +typedef struct pwmx_format_intro +{ + char tag[12]; + std::uint32_t version; // value 1 + std::uint32_t area_num; // unknown - usually 4 + std::uint32_t header_data_offset; + std::float_t intro24; // unknown - usually 0 + std::uint32_t preview_data_offset; + std::float_t intro32; // unknown + std::uint32_t layer_data_offset; + std::float_t intro40; // unknown + std::uint32_t image_data_offset; +} pwmx_format_intro; + +typedef struct pwmx_format_header +{ + char tag[12]; + std::uint32_t payload_size; + std::float_t pixel_size_um; + std::float_t layer_height_mm; + std::float_t exposure_time_s; + std::float_t delay_before_exposure_s; + std::float_t bottom_exposure_time_s; + std::float_t bottom_layer_count; + std::float_t lift_distance_mm; + std::float_t lift_speed_mms; + std::float_t retract_speed_mms; + std::float_t volume_ml; + std::uint32_t antialiasing; + std::uint32_t res_x; + std::uint32_t res_y; + std::float_t weight_g; + std::float_t price; + std::uint32_t price_currency; + std::uint32_t per_layer_override; // ? unknown meaning ? + std::uint32_t print_time_s; + std::uint32_t transition_layer_count; + std::uint32_t unknown; // ? usually 0 ? + +} pwmx_format_header; + +typedef struct pwmx_format_preview +{ + char tag[12]; + std::uint32_t payload_size; + std::uint32_t preview_w; + std::uint32_t preview_dpi; + std::uint32_t preview_h; + // raw image data in BGR565 format + std::uint8_t pixels[PREV_W * PREV_H * 2]; +} pwmx_format_preview; + +typedef struct pwmx_format_layers_header +{ + char tag[12]; + std::uint32_t payload_size; + std::uint32_t layer_count; +} pwmx_format_layers_header; + +typedef struct pwmx_format_layer +{ + std::uint32_t image_offset; + std::uint32_t image_size; + std::float_t lift_distance_mm; + std::float_t lift_speed_mms; + std::float_t exposure_time_s; + std::float_t layer_height_mm; + std::float_t layer44; // unkown - usually 0 + std::float_t layer48; // unkown - usually 0 +} pwmx_format_layer; + +typedef struct pwmx_format_misc +{ + std::float_t bottom_layer_height_mm; + std::float_t bottom_lift_distance_mm; + std::float_t bottom_lift_speed_mms; + +} pwmx_format_misc; + +class PwmxFormatConfigDef : public ConfigDef +{ +public: + PwmxFormatConfigDef() + { + add(CFG_LIFT_DISTANCE, coFloat); + add(CFG_LIFT_SPEED, coFloat); + add(CFG_RETRACT_SPEED, coFloat); + add(CFG_DELAY_BEFORE_EXPOSURE, coFloat); + add(CFG_BOTTOM_LIFT_DISTANCE, coFloat); + add(CFG_BOTTOM_LIFT_SPEED, coFloat); + } +}; + +class PwmxFormatDynamicConfig : public DynamicConfig +{ +public: + PwmxFormatDynamicConfig(){}; + const ConfigDef *def() const override { return &config_def; } + +private: + PwmxFormatConfigDef config_def; +}; + +namespace { + +const char *get_cfg_value(const DynamicConfig &cfg, + const std::string & key, + const std::string & def = "0") +{ + std::string ret; + + if (cfg.has(key)) { + auto opt = cfg.option(key); + if (opt) { + ret = opt->serialize(); + } else { + return def.c_str(); + } + } else { + return def.c_str(); + } + + return ret.c_str(); +} + +template void crop_value(T &val, T val_min, T val_max) +{ + if (val < val_min) { + val = val_min; + } else if (val > val_max) { + val = val_max; + } +} + +void fill_preview(pwmx_format_preview &p, + pwmx_format_misc &m, + ThumbnailsList &thumbnails) +{ + + p.preview_w = PREV_W; + p.preview_h = PREV_H; + p.preview_dpi = PREV_DPI; + p.payload_size = sizeof(p) - sizeof(p.tag) - sizeof(p.payload_size); + + std::memset(p.pixels, 0 , sizeof(p.pixels)); + if (!thumbnails.empty()) { + std::uint32_t dst_index; + std::uint32_t i = 0; + size_t len; + size_t pixel_x = 0; + auto t = thumbnails[0]; //use the first thumbnail + len = t.pixels.size(); + //sanity check + if (len != PREV_W * PREV_H * 4) { + printf("incorrect thumbnail size. expected %ix%i\n", PREV_W, PREV_H); + return; + } + // rearange pixels: they seem to be stored from bottom to top. + dst_index = (PREV_W * (PREV_H - 1) * 2); + while (i < len) { + std::uint32_t pixel; + std::uint32_t r = t.pixels[i++]; + std::uint32_t g = t.pixels[i++]; + std::uint32_t b = t.pixels[i++]; + i++; // Alpha + // convert to BGRA565 + pixel = ((b >> 3) << 11) | ((g >>2) << 5) | (r >> 3); + p.pixels[dst_index++] = pixel & 0xFF; + p.pixels[dst_index++] = (pixel >> 8) & 0xFF; + pixel_x++; + if (pixel_x == PREV_W) { + pixel_x = 0; + dst_index -= (PREV_W * 4); + } + } + } +} + + +void fill_header(pwmx_format_header &h, + pwmx_format_misc &m, + const SLAPrint &print, + std::uint32_t layer_count) +{ + std::float_t bottle_weight_g; + std::float_t bottle_volume_ml; + std::float_t bottle_cost; + std::float_t material_density; + auto & cfg = print.full_print_config(); + std::string mnotes = cfg.option("material_notes")->serialize(); + // create a config parser from the material notes + Slic3r::PwmxFormatDynamicConfig mat_cfg; + SLAPrintStatistics stats = print.print_statistics(); + + // sanitize the string config + boost::replace_all(mnotes, "\\n", "\n"); + boost::replace_all(mnotes, "\\r", "\r"); + mat_cfg.load_from_ini_string(mnotes, + ForwardCompatibilitySubstitutionRule::Enable); + + h.layer_height_mm = std::atof(get_cfg_value(cfg, "layer_height")); + m.bottom_layer_height_mm = std::atof( + get_cfg_value(cfg, "initial_layer_height")); + h.exposure_time_s = std::atof(get_cfg_value(cfg, "exposure_time")); + h.bottom_exposure_time_s = std::atof( + get_cfg_value(cfg, "initial_exposure_time")); + h.bottom_layer_count = std::atof(get_cfg_value(cfg, "faded_layers")); + if (layer_count < h.bottom_layer_count) { + h.bottom_layer_count = layer_count; + } + h.res_x = std::atol(get_cfg_value(cfg, "display_pixels_x")); + h.res_y = std::atol(get_cfg_value(cfg, "display_pixels_y")); + bottle_weight_g = std::atof(get_cfg_value(cfg, "bottle_weight")) * 1000.0f; + bottle_volume_ml = std::atof(get_cfg_value(cfg, "bottle_volume")); + bottle_cost = std::atof(get_cfg_value(cfg, "bottle_cost")); + material_density = bottle_weight_g / bottle_volume_ml; + + h.volume_ml = (stats.objects_used_material + stats.support_used_material) / 1000; + h.weight_g = h.volume_ml * material_density; + h.price = (h.volume_ml * bottle_cost) / bottle_volume_ml; + h.price_currency = '$'; + h.antialiasing = 1; + h.per_layer_override = 0; + + // TODO - expose these variables to the UI rather than using material notes + h.delay_before_exposure_s = std::atof( + get_cfg_value(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, "0.5")); + crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f); + + h.lift_distance_mm = std::atof( + get_cfg_value(mat_cfg, CFG_LIFT_DISTANCE, "8.0")); + crop_value(h.lift_distance_mm, 0.0f, 100.0f); + + if (mat_cfg.has(CFG_BOTTOM_LIFT_DISTANCE)) { + m.bottom_lift_distance_mm = std::atof( + get_cfg_value(mat_cfg, CFG_BOTTOM_LIFT_DISTANCE, "8.0")); + crop_value(h.lift_distance_mm, 0.0f, 100.0f); + } else { + m.bottom_lift_distance_mm = h.lift_distance_mm; + } + + + h.lift_speed_mms = std::atof( + get_cfg_value(mat_cfg, CFG_LIFT_SPEED, "2.0")); + crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f); + + if (mat_cfg.has(CFG_BOTTOM_LIFT_SPEED)) { + m.bottom_lift_speed_mms = std::atof( + get_cfg_value(mat_cfg, CFG_BOTTOM_LIFT_SPEED, "2.0")); + crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f); + } else { + m.bottom_lift_speed_mms = h.lift_speed_mms; + } + + h.retract_speed_mms = std::atof( + get_cfg_value(mat_cfg, CFG_RETRACT_SPEED, "3.0")); + crop_value(h.lift_speed_mms, 0.1f, 20.0f); + + h.print_time_s = (h.bottom_layer_count * h.bottom_exposure_time_s) + + ((layer_count - h.bottom_layer_count) * + h.exposure_time_s) + + (layer_count * h.lift_distance_mm / h.retract_speed_mms) + + (layer_count * h.lift_distance_mm / h.lift_speed_mms) + + (layer_count * h.delay_before_exposure_s); + + + h.payload_size = sizeof(h) - sizeof(h.tag) - sizeof(h.payload_size); + h.pixel_size_um = 50; +} + +} // namespace + +std::unique_ptr PwmxArchive::create_raster() const +{ + sla::Resolution res; + sla::PixelDim pxdim; + std::array mirror; + + double w = m_cfg.display_width.getFloat(); + double h = m_cfg.display_height.getFloat(); + auto pw = size_t(m_cfg.display_pixels_x.getInt()); + auto ph = size_t(m_cfg.display_pixels_y.getInt()); + + mirror[X] = m_cfg.display_mirror_x.getBool(); + mirror[Y] = m_cfg.display_mirror_y.getBool(); + + auto ro = m_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(pw, 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(); + + return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); +} + +sla::RasterEncoder PwmxArchive::get_encoder() const +{ + return sla::PWXRasterEncoder{}; +} + +// Endian safe write of little endian 32bit ints +static void pwmx_write_int32(std::ofstream &out, std::uint32_t val) +{ + const char i1 = (val & 0xFF); + const char i2 = (val >> 8) & 0xFF; + const char i3 = (val >> 16) & 0xFF; + const char i4 = (val >> 24) & 0xFF; + + out.write((const char *) &i1, 1); + out.write((const char *) &i2, 1); + out.write((const char *) &i3, 1); + out.write((const char *) &i4, 1); +} +static void pwmx_write_float(std::ofstream &out, std::float_t val) +{ + std::uint32_t *f = (std::uint32_t *) &val; + pwmx_write_int32(out, *f); +} + +static void pwmx_write_intro(std::ofstream &out, pwmx_format_intro &i) +{ + out.write(TAG_INTRO, sizeof(i.tag)); + pwmx_write_int32(out, i.version); + pwmx_write_int32(out, i.area_num); + pwmx_write_int32(out, i.header_data_offset); + pwmx_write_int32(out, i.intro24); + pwmx_write_int32(out, i.preview_data_offset); + pwmx_write_int32(out, i.intro32); + pwmx_write_int32(out, i.layer_data_offset); + pwmx_write_int32(out, i.intro40); + pwmx_write_int32(out, i.image_data_offset); +} + +static void pwmx_write_header(std::ofstream &out, pwmx_format_header &h) +{ + out.write(TAG_HEADER, sizeof(h.tag)); + pwmx_write_int32(out, h.payload_size); + pwmx_write_float(out, h.pixel_size_um); + pwmx_write_float(out, h.layer_height_mm); + pwmx_write_float(out, h.exposure_time_s); + pwmx_write_float(out, h.delay_before_exposure_s); + pwmx_write_float(out, h.bottom_exposure_time_s); + pwmx_write_float(out, h.bottom_layer_count); + pwmx_write_float(out, h.lift_distance_mm); + pwmx_write_float(out, h.lift_speed_mms); + pwmx_write_float(out, h.retract_speed_mms); + pwmx_write_float(out, h.volume_ml); + pwmx_write_int32(out, h.antialiasing); + pwmx_write_int32(out, h.res_x); + pwmx_write_int32(out, h.res_y); + pwmx_write_float(out, h.weight_g); + pwmx_write_float(out, h.price); + pwmx_write_int32(out, h.price_currency); + pwmx_write_int32(out, h.per_layer_override); + pwmx_write_int32(out, h.print_time_s); + pwmx_write_int32(out, h.transition_layer_count); + pwmx_write_int32(out, h.unknown); +} + +static void pwmx_write_preview(std::ofstream &out, pwmx_format_preview &p) +{ + out.write(TAG_PREVIEW, sizeof(p.tag)); + pwmx_write_int32(out, p.payload_size); + pwmx_write_int32(out, p.preview_w); + pwmx_write_int32(out, p.preview_dpi); + pwmx_write_int32(out, p.preview_h); + out.write((const char*) p.pixels, sizeof(p.pixels)); +} + +static void pwmx_write_layers_header(std::ofstream &out, pwmx_format_layers_header &h) +{ + out.write(TAG_LAYERS, sizeof(h.tag)); + pwmx_write_int32(out, h.payload_size); + pwmx_write_int32(out, h.layer_count); +} + +static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l) +{ + pwmx_write_int32(out, l.image_offset); + pwmx_write_int32(out, l.image_size); + pwmx_write_float(out, l.lift_distance_mm); + pwmx_write_float(out, l.lift_speed_mms); + pwmx_write_float(out, l.exposure_time_s); + pwmx_write_float(out, l.layer_height_mm); + pwmx_write_float(out, l.layer44); + pwmx_write_float(out, l.layer48); +} + +void PwmxArchive::export_print(const std::string fname, + const SLAPrint & print, + ThumbnailsList & thumbnails, + const std::string &prjname) +{ + std::uint32_t layer_count = m_layers.size(); + + pwmx_format_intro intro = {0}; + pwmx_format_header header = {0}; + pwmx_format_preview preview = {0}; + pwmx_format_layers_header layers_header = {0}; + pwmx_format_misc misc = {0}; + std::vector layer_images; + std::uint32_t image_offset; + + intro.version = 1; + intro.area_num = 4; + intro.header_data_offset = sizeof(intro); + intro.preview_data_offset = sizeof(intro) + sizeof(header); + intro.layer_data_offset = intro.preview_data_offset + sizeof(preview); + intro.image_data_offset = intro.layer_data_offset + + sizeof(layers_header) + + (sizeof(pwmx_format_layer) * layer_count); + + fill_header(header, misc, print, layer_count); + fill_preview(preview, misc, thumbnails); + + try { + // open the file and write the contents + std::ofstream out; + out.open(fname, std::ios::binary | std::ios::out | std::ios::trunc); + pwmx_write_intro(out, intro); + pwmx_write_header(out, header); + pwmx_write_preview(out, preview); + + layers_header.payload_size = intro.image_data_offset - intro.layer_data_offset - + sizeof(layers_header.tag) - sizeof(layers_header.payload_size); + layers_header.layer_count = layer_count; + pwmx_write_layers_header(out, layers_header); + + //layers + layer_images.reserve(layer_count * LAYER_SIZE_ESTIMATE); + image_offset = intro.image_data_offset; + size_t i = 0; + for (const sla::EncodedRaster &rst : m_layers) { + pwmx_format_layer l; + std::memset(&l, 0, sizeof(l)); + l.image_offset = image_offset; + l.image_size = rst.size(); + if (i < header.bottom_layer_count) { + l.exposure_time_s = header.bottom_exposure_time_s; + l.layer_height_mm = misc.bottom_layer_height_mm; + l.lift_distance_mm = misc.bottom_lift_distance_mm; + l.lift_speed_mms = misc.bottom_lift_speed_mms; + } else { + l.exposure_time_s = header.exposure_time_s; + l.layer_height_mm = header.layer_height_mm; + l.lift_distance_mm = header.lift_distance_mm; + l.lift_speed_mms = header.lift_speed_mms; + } + image_offset += l.image_size; + pwmx_write_layer(out, l); + // add the rle encoded layer image into the buffer + const char* img_start = reinterpret_cast(rst.data()); + const char* img_end = img_start + rst.size(); + std::copy(img_start, img_end, std::back_inserter(layer_images)); + i++; + } + const char* img_buffer = reinterpret_cast(layer_images.data()); + out.write(img_buffer, layer_images.size()); + out.close(); + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } + +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp new file mode 100644 index 0000000000..69cec100c3 --- /dev/null +++ b/src/libslic3r/Format/pwmx.hpp @@ -0,0 +1,36 @@ +#ifndef _SLIC3R_FORMAT_PWMX_HPP_ +#define _SLIC3R_FORMAT_PWMX_HPP_ + +#include + +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +class PwmxArchive: public SLAArchive { + SLAPrinterConfig m_cfg; + +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: + + PwmxArchive() = default; + explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} + explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} + + void export_print(const std::string fname, + const SLAPrint &print, + ThumbnailsList &thumbnails, + const std::string &projectname = "") override; + bool uses_zipper_export() override {return false;} +}; + + +} // namespace Slic3r::sla + +#endif // _SLIC3R_FORMAT_PWMX_HPP_ diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 0b6c45eff3..fd3f3e062d 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -16,6 +16,54 @@ const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; + + +static void pwx_get_pixel_span(const std::uint8_t* ptr, const std::uint8_t* end, + std::uint8_t& pixel, size_t& span_len) +{ + size_t max_len; + + span_len = 0; + pixel = (*ptr) & 0xF0; + // the maximum length of the span depends on the pixel color + max_len = (pixel == 0 || pixel == 0xF0) ? 0xFFF : 0xF; + while (((*ptr) & 0xF0) == pixel && ptr < end && span_len < max_len) { + span_len++; + ptr++; + } +} + +EncodedRaster PWXRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector dst; + size_t span_len; + std::uint8_t pixel; + auto size = w * h * num_components; + dst.reserve(size); + + const std::uint8_t* src = reinterpret_cast(ptr); + const std::uint8_t* src_end = src + size; + while (src < src_end) { + pwx_get_pixel_span(src, src_end, pixel, span_len); + src += span_len; + // fully transparent of fully opaque pixel + if (pixel == 0 || pixel == 0xF0) { + pixel = pixel | (span_len >> 8); + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + pixel = span_len & 0xFF; + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + } + // antialiased pixel + else { + pixel = pixel | span_len; + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + } + } + + return EncodedRaster(std::move(dst), "pwx"); +} + EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, size_t num_components) { diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 657fc865c2..9d22591cc7 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -97,6 +97,10 @@ public: virtual EncodedRaster encode(RasterEncoder encoder) const = 0; }; +struct PWXRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + struct PNGRasterEncoder { EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 7b78dfea2f..23502c44e3 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -3,6 +3,7 @@ #include "Format/SL1.hpp" #include "Format/SL1_SVG.hpp" +#include "Format/pwmx.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -16,6 +17,8 @@ #include #include +#include + // #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK @@ -249,6 +252,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con 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); + else if (m_printer_config.sla_archive_format.value == "pwmx") { + m_archiver = std::make_unique(m_printer_config); + } } struct ModelObjectStatus { @@ -1265,4 +1271,16 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, p.set_status(int(std::round(st)), msg, flags); } + +void SLAPrint::write_thumbnail(Zipper& zipper, const ThumbnailData& data) +{ + 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) + { + zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size); + mz_free(png_data); + } +} + } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index b2df0c4e92..8d5b54376a 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -9,6 +9,7 @@ #include "Point.hpp" #include "MTUtils.hpp" #include "Zipper.hpp" +#include "GCode/ThumbnailData.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" @@ -422,12 +423,16 @@ public: } // 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; + const std::string &projectname = "") {}; + // Export the print into an archive using the provided filename. + virtual void export_print(const std::string fname, + const SLAPrint &print, + ThumbnailsList &thumbnails, + const std::string &projectname = "") {}; + // By default the exporters use zipper export. Return false to use file export. + virtual bool uses_zipper_export() { return true; } }; /** @@ -533,16 +538,27 @@ 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 export_print(Zipper &zipper, const std::string &projectname = "") - { - m_archiver->export_print(zipper, *this, projectname); - } + + void write_thumbnail(Zipper& zipper, const ThumbnailData& data); void export_print(const std::string &fname, const std::string &projectname = "") { - Zipper zipper(fname); - export_print(zipper, projectname); + Slic3r::ThumbnailsList thumbnails; //empty thumbnail list + export_print(fname, thumbnails, projectname); + } + + void export_print(const std::string &fname, Slic3r::ThumbnailsList &thumbnails, const std::string &projectname = "") + { + if (m_archiver->uses_zipper_export()) { + Zipper zipper(fname); + m_archiver->export_print(zipper, *this, projectname); + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) + write_thumbnail(zipper, data); + zipper.finalize(); + } else { + m_archiver->export_print(fname, *this, thumbnails, projectname); + } } private: diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 5d3d47c202..37e527d649 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -165,17 +165,6 @@ void BackgroundSlicingProcess::process_fff() } } -static void write_thumbnail(Zipper& zipper, const ThumbnailData& data) -{ - 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) - { - zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size); - mz_free(png_data); - } -} - void BackgroundSlicingProcess::process_sla() { assert(m_print == m_sla_print); @@ -189,12 +178,7 @@ void BackgroundSlicingProcess::process_sla() ThumbnailsList thumbnails = this->render_thumbnails( ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); - Zipper zipper(export_path); - m_sla_print->export_print(zipper); - for (const ThumbnailData& data : thumbnails) - if (data.is_valid()) - write_thumbnail(zipper, data); - zipper.finalize(); + m_sla_print->export_print(export_path, thumbnails); m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { @@ -739,13 +723,7 @@ void BackgroundSlicingProcess::prepare_upload() ThumbnailsList thumbnails = this->render_thumbnails( 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_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); - zipper.finalize(); + m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.string()); } m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str()); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b55ba0d75e..b914d8db47 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -496,7 +496,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } }, + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, }; // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. From df0751716c2cc031cc8b3ffabd272ac55c1b0b6f Mon Sep 17 00:00:00 2001 From: ole00 Date: Sat, 12 Feb 2022 01:13:58 +0000 Subject: [PATCH 12/33] Added resources and settings for Anycubic Photon Mono X SLA printer --- resources/profiles/Anycubic.ini | 100 ++++++++++++++++++ .../Anycubic/PHOTON MONO X_thumbnail.png | Bin 0 -> 45953 bytes 2 files changed, 100 insertions(+) create mode 100644 resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index 053aecbd59..a67ba0962f 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -64,6 +64,13 @@ technology = FFF family = PREDATOR default_materials = Generic PLA @PREDATOR; Generic PETG @PREDATOR; Generic ABS @PREDATOR +[printer_model:PHOTON MONO X] +name = Photon Mono X +variants = default +technology = SLA +family = PHOTON MONO +default_materials = Generic Blue Resin MONO @0.05 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -1898,3 +1905,96 @@ default_print_profile = 0.24mm 0.8 nozzle DETAILED QUALITY @PREDATOR ######################################### ########## end printer presets ########## ######################################### + +######################################### +########## SLA printer presets ########## +######################################### + + +[sla_print:*common print ANYCUBIC SLA*] +compatible_printers_condition = family=="PHOTON MONO" +layer_height = 0.05 +output_filename_format = [input_filename_base].pwmx +pad_edge_radius = 0.5 +pad_enable = 0 +pad_max_merge_distance = 50 +pad_wall_height = 0 +pad_wall_thickness = 1 +pad_wall_slope = 45 +faded_layers = 8 +slice_closing_radius = 0.005 +support_base_diameter = 3 +support_base_height = 1 +support_critical_angle = 45 +support_density_at_45 = 250 +support_density_at_horizontal = 500 +support_head_front_diameter = 0.4 +support_head_penetration = 0.4 +support_head_width = 3 +support_max_bridge_length = 10 +support_minimal_z = 0 +support_object_elevation = 5 +support_pillar_diameter = 1 +support_pillar_connection_mode = zigzag +support_pillar_widening_factor = 0 +supports_enable = 1 +support_small_pillar_diameter_percent = 60% + +[sla_print:0.05 ANYCUBIC SLA] +inherits = *common print ANYCUBIC SLA* +layer_height = 0.05 + + +########### Materials + +[sla_material:*common ANYCUBIC SLA*] +compatible_printers_condition = family=="PHOTON MONO" +compatible_prints_condition = layer_height == 0.05 +exposure_time = 7 +initial_exposure_time = 40 +initial_layer_height = 0.05 +material_correction = 1,1,1 +material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5 + +[sla_material:*common 0.05 ANYCUBIC SLA*] +inherits = *common ANYCUBIC SLA* + +[sla_material:Generic Blue Resin MONO @0.05] +inherits = *common 0.05 ANYCUBIC SLA* +exposure_time = 2.5 +initial_exposure_time = 40 +material_type = Tough +material_vendor = Generic +material_colour = #6080EC + +########## Printers + +[printer:Anycubic Photon Mono X] +printer_technology = SLA +printer_model = PHOTON MONO X +printer_variant = default +default_sla_material_profile = Generic Blue Resin MONO @0.05 +default_sla_print_profile = 0.05 ANYCUBIC SLA +thumbnails = 224x168 +sla_archive_format = pwmx +bed_shape = 1.48x1.02,193.48x1.02,193.48x121.02,1.48x121.02 +display_height = 120 +display_orientation = landscape +display_mirror_x = 1 +display_mirror_y = 0 +display_pixels_x = 3840 +display_pixels_y = 2400 +display_width = 192 +max_print_height = 245 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +min_exposure_time = 1 +max_exposure_time = 120 +min_initial_exposure_time = 1 +max_initial_exposure_time = 300 +printer_correction = 1,1,1 +gamma_correction = 1 +area_fill = 45 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n + + diff --git a/resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png b/resources/profiles/Anycubic/PHOTON MONO X_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..70ad47b63defdbd22c913d211d284a1bed729860 GIT binary patch literal 45953 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

?9hL)5+9SL}$(eIdYR*>(Nn1RXY00 z_y?`E?Af!*=GK;9okh|fAvy@noZ}L$pR6G6jMJbHt59MnO^?Eq7+qpai7pC^DKJI+ zDZ@{Xvg}iq;rYf;7KVOV(u+TZp^T3g1-%Afzw|IWdyZ(#3%gZ#&z|K~h>&lh;wH+(bC{MPT{ zS(>Rx;U=5>KfkshS%|mm;E!ehM#=#5AvE{{x>WwFY}3y{V6~G(wq6#Z~IRE z(R03w5B`t$bIZ@af=l-A;|rhrEdS)2zJa>(RvMrD>rb<=xWxP4`)+>bm9ONVJokHe z_IEs&|MBj3@Y-MeIhIyd(zb8K@pJ$B7XJO4-pG4@|6TmiAAW$Jc*QFj42Rjuq~^Cx z=XO(7#}BpRRT(vU99{p$zzS&L&(>cyWEw@>6(2tK@cCPz^Of3(OSzPYAfXsjS(`U* z*HM2X=AP~2A;-)gSN?ek0SS6@Y3oezf%5gFQk4A?0S+8IM1Of7Tk9tQp)3bHc;6j- z@ZG=3bXw8Wj%qUE!|!=J|LP65a_QAK@cl3QC9eP9{vm(#&i~B!{L>%h;rs97@dqE^ z|9JlQ@h9)Too9X9^ZB+Hyp$`h`#b#P>u=>l?|wU58z*`G5B~(c;gDC|{5_mFeuT2W z&=n_h!gM<2H-Gm%-2bJ!x%!Grx%;lWSkkb#xX7)){;T}6AN>(-`jMA%{S9Bot^e)a zyyJI%hf^mvc;#zf&(m(ak!StAujAo|9-ve(7!G*rfBX;p)KC8$FTCj{p7!Z^zlT-EW(Qr#|iJ{FmQ)8~^mCAL1=-~KfV!vWv_W50lH8mzUPI&qu_?z)5F z;ws`iS6aK8UVj+!N}<2Biq@LZ6sDtf8Z46uEG;d2PIOa&cp-ADG=sqcx4!w+yy)hi z=i`6+N4)XX|2NWK@;)lzD*;sfgU~MOrvJPytBA zb9XFEP$GpA=oRG<;Jz>2E9M*u!za2z$H-gPTK|X zryuPl1adJ*t&M-o7?d(-rO{|_Z6-C({MP64vRD5ad-h+3wGAlY#;1Q1|ME4z#^TCe z#5o3w%RJ}#H}Te6Ue4OlNBGb?-^S-Y`4Rr{cfSx@JF0q0caqm5M@ru{Jg%|$FPcR*iXP`YWrr^Yh6HKNRH~r|3 zBO;7}Q)_Gd%rD%+AO67~@VeK$iof~GkMjdB_&$E|mRmr>#`;O_yz7g+@jv}1-uQ+$ zP*-D4ojl2_|DV_Mw%cyy^}l=zfB6?5;YBySh+lm5EuA!QVPTQqzV+AnzVCf5Z+`1- zjMq=_(?9hueaexYV69sGU8~mHTg3-7Db-7c@G7OYfLH9>x9?fkTyxE!wC)tx`IXa$!$hH{OsT z;tMTwmUj`9ptJ;W!aS){?G~Ajf{_q56tJ+k!hu8AQcb6fH`i(E8dDTpas7=nP0i-1 zlkDApkb~EK9pkMHzV{_R$JajnncVrAPw<7m`8Y58rC;HSuX!4y%~M?a)TeXZ)1JX- zYlD3U4)W~hy@=6xlP}+QFW+<1kMq1A`Y}dZ8$9EizmqGj`Wn_w9Os8#`qTWJ;KStqR5z7~}J5 zi%{7HE0P)jKIjxmX>FY?aFRI9nFeigDu|7q*61o{2&xb(s+8QUNGl3sP)cJxaU$t8 zt7w-&+w%9%K%*$dxuEf^0=+fZImaR_Skj3SuaTND+WbhbXi%kJY(=!z!4BHBtEX0M zw|`FZj{bd|Q%dUslooN4Fm`$Hn8~aq+l#Ts$rw7mtg_#pB{}@wj+gJpO-k{C}%O`IChc3wZzl002ovPDHLk FV1nTQC-eXS literal 0 HcmV?d00001 From 69019ae4bb16473a33ad681c2a8cb57887e5c830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 14 Feb 2022 08:20:57 +0100 Subject: [PATCH 13/33] Added a missing include (GCC11.1 without PCH). --- src/slic3r/GUI/MeshUtils.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 11ebf93864..cc961ee8f1 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -3,6 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" From 41e31c31b60280c89f03bc3f8348555285f9fe99 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Mon, 14 Feb 2022 09:33:40 +0100 Subject: [PATCH 14/33] Updated Anycubic Photon structure. --- resources/profiles/Anycubic.ini | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index a67ba0962f..821d87462c 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -69,7 +69,7 @@ name = Photon Mono X variants = default technology = SLA family = PHOTON MONO -default_materials = Generic Blue Resin MONO @0.05 +default_materials = Generic Blue Resin @MONO 0.05 # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -1940,15 +1940,14 @@ support_pillar_widening_factor = 0 supports_enable = 1 support_small_pillar_diameter_percent = 60% -[sla_print:0.05 ANYCUBIC SLA] +[sla_print:0.05 Normal @ANYCUBIC] inherits = *common print ANYCUBIC SLA* layer_height = 0.05 - ########### Materials [sla_material:*common ANYCUBIC SLA*] -compatible_printers_condition = family=="PHOTON MONO" +compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ compatible_prints_condition = layer_height == 0.05 exposure_time = 7 initial_exposure_time = 40 @@ -1959,13 +1958,14 @@ material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LI [sla_material:*common 0.05 ANYCUBIC SLA*] inherits = *common ANYCUBIC SLA* -[sla_material:Generic Blue Resin MONO @0.05] +[sla_material:Generic Blue Resin @MONO 0.05] inherits = *common 0.05 ANYCUBIC SLA* exposure_time = 2.5 initial_exposure_time = 40 material_type = Tough material_vendor = Generic material_colour = #6080EC +compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/ ########## Printers @@ -1973,8 +1973,8 @@ material_colour = #6080EC printer_technology = SLA printer_model = PHOTON MONO X printer_variant = default -default_sla_material_profile = Generic Blue Resin MONO @0.05 -default_sla_print_profile = 0.05 ANYCUBIC SLA +default_sla_material_profile = Generic Blue Resin @MONO 0.05 +default_sla_print_profile = 0.05 Normal @ANYCUBIC thumbnails = 224x168 sla_archive_format = pwmx bed_shape = 1.48x1.02,193.48x1.02,193.48x121.02,1.48x121.02 @@ -1996,5 +1996,3 @@ printer_correction = 1,1,1 gamma_correction = 1 area_fill = 45 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n - - From 05087540334682dfe657f039fe7d94a5cc47e6cb Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Feb 2022 09:53:59 +0100 Subject: [PATCH 15/33] SLA SVG export: Using string_view literals to avoid unnecessary std::string allocations and faster string additions (length of a string_view literal is known without having to iterate to the trailing NULL). --- src/libslic3r/Format/SL1_SVG.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 0ea2306119..a7a8c9ed6b 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +using namespace std::literals; namespace Slic3r { @@ -77,20 +79,23 @@ void append_svg(std::string &buf, const Polygon &poly) char intbuf[coord_t_bufsize]; - buf += std::string(" data; - constexpr const char finish[] = "\n"; + constexpr auto finish = "\n"sv; 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)); + std::copy(finish.begin(), finish.end() - 1, std::back_inserter(data)); return sla::EncodedRaster{std::move(data), "svg"}; } From 5e78203241e343610e072f54e0f2498723087c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 14 Feb 2022 10:47:39 +0100 Subject: [PATCH 16/33] Added missing includes (GCC11.1 without PCH). --- src/libslic3r/Format/pwmx.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libslic3r/Format/pwmx.cpp b/src/libslic3r/Format/pwmx.cpp index 99785b8d08..3be3c3d573 100644 --- a/src/libslic3r/Format/pwmx.cpp +++ b/src/libslic3r/Format/pwmx.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #define TAG_INTRO "ANYCUBIC\0\0\0\0" #define TAG_HEADER "HEADER\0\0\0\0\0\0" From 5fcb618f96064d8dbd5803dc3d773b37b81cde49 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 14:23:07 +0100 Subject: [PATCH 17/33] Tech ENABLE_GLBEGIN_GLEND_REMOVAL - Fixed bug in rendering gizmo rotate --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 362e253090..dca578bd76 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -134,15 +134,14 @@ void GLGizmoRotate::on_render() if (shader != nullptr) { shader->start_using(); - const float radius = Offset + m_parent.get_selection().get_bounding_box().radius(); - const bool radius_changed = std::abs(m_old_radius - radius) > EPSILON; - m_old_radius = radius; + const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON; + m_old_radius = m_radius; ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color); render_circle(color, radius_changed); if (m_hover_id != -1) { - const bool hover_radius_changed = std::abs(m_old_hover_radius - radius) > EPSILON; - m_old_hover_radius = radius; + const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON; + m_old_hover_radius = m_radius; render_scale(color, hover_radius_changed); render_snap_radii(color, hover_radius_changed); From b6fe41d5abdb78149ae15a7e87c3d60625f0f47e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 14 Feb 2022 13:27:06 +0100 Subject: [PATCH 18/33] Fixes for pwmx format PR --- src/libslic3r/Format/SL1.cpp | 36 +++++++- src/libslic3r/Format/SL1.hpp | 7 +- src/libslic3r/Format/pwmx.cpp | 154 ++++++++++++++++++++----------- src/libslic3r/Format/pwmx.hpp | 11 +-- src/libslic3r/SLA/RasterBase.cpp | 48 ---------- src/libslic3r/SLA/RasterBase.hpp | 4 - src/libslic3r/SLAPrint.cpp | 14 --- src/libslic3r/SLAPrint.hpp | 33 ++----- 8 files changed, 154 insertions(+), 153 deletions(-) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 6f1e95528a..df10ce0465 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -20,11 +20,14 @@ #include "libslic3r/miniz_extension.hpp" #include "libslic3r/PNGReadWrite.hpp" #include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" #include #include #include +#include + namespace marchsq { template<> struct _RasterTraits { @@ -482,10 +485,31 @@ sla::RasterEncoder SL1Archive::get_encoder() const return sla::PNGRasterEncoder{}; } -void SL1Archive::export_print(Zipper& zipper, - const SLAPrint &print, - const std::string &prjname) +static void write_thumbnail(Zipper &zipper, const ThumbnailData &data) { + 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) { + zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + + "x" + std::to_string(data.height) + ".png", + static_cast(png_data), + png_size); + + mz_free(png_data); + } +} + +void SL1Archive::export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &prjname) +{ + Zipper zipper{fname}; + std::string project = prjname.empty() ? boost::filesystem::path(zipper.get_filename()).stem().string() : @@ -512,6 +536,12 @@ void SL1Archive::export_print(Zipper& zipper, zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); } + + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) + write_thumbnail(zipper, data); + + zipper.finalize(); } catch(std::exception& e) { BOOST_LOG_TRIVIAL(error) << e.what(); // Rethrow the exception diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index e6c0ff0896..874786eb9c 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -23,8 +23,11 @@ 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 = "") override; + + void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") override; }; ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); diff --git a/src/libslic3r/Format/pwmx.cpp b/src/libslic3r/Format/pwmx.cpp index 3be3c3d573..5b420a49ec 100644 --- a/src/libslic3r/Format/pwmx.cpp +++ b/src/libslic3r/Format/pwmx.cpp @@ -1,5 +1,6 @@ #include "pwmx.hpp" #include "GCode/ThumbnailData.hpp" +#include "SLA/RasterBase.hpp" #include #include @@ -27,9 +28,59 @@ #define LAYER_SIZE_ESTIMATE (32 * 1024) - namespace Slic3r { +static void pwx_get_pixel_span(const std::uint8_t* ptr, const std::uint8_t* end, + std::uint8_t& pixel, size_t& span_len) +{ + size_t max_len; + + span_len = 0; + pixel = (*ptr) & 0xF0; + // the maximum length of the span depends on the pixel color + max_len = (pixel == 0 || pixel == 0xF0) ? 0xFFF : 0xF; + while (ptr < end && span_len < max_len && ((*ptr) & 0xF0) == pixel) { + span_len++; + ptr++; + } +} + +struct PWXRasterEncoder +{ + sla::EncodedRaster operator()(const void *ptr, + size_t w, + size_t h, + size_t num_components) + { + std::vector dst; + size_t span_len; + std::uint8_t pixel; + auto size = w * h * num_components; + dst.reserve(size); + + const std::uint8_t *src = reinterpret_cast(ptr); + const std::uint8_t *src_end = src + size; + while (src < src_end) { + pwx_get_pixel_span(src, src_end, pixel, span_len); + src += span_len; + // fully transparent of fully opaque pixel + if (pixel == 0 || pixel == 0xF0) { + pixel = pixel | (span_len >> 8); + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + pixel = span_len & 0xFF; + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + } + // antialiased pixel + else { + pixel = pixel | span_len; + std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); + } + } + + return sla::EncodedRaster(std::move(dst), "pwx"); + } +}; + using ConfMap = std::map; typedef struct pwmx_format_intro @@ -137,24 +188,28 @@ private: namespace { -const char *get_cfg_value(const DynamicConfig &cfg, - const std::string & key, - const std::string & def = "0") +std::float_t get_cfg_value_f(const DynamicConfig &cfg, + const std::string &key, + const std::float_t &def = 0.f) { - std::string ret; - if (cfg.has(key)) { - auto opt = cfg.option(key); - if (opt) { - ret = opt->serialize(); - } else { - return def.c_str(); - } - } else { - return def.c_str(); + if (auto opt = cfg.option(key)) + return opt->getFloat(); } - return ret.c_str(); + return def; +} + +int get_cfg_value_i(const DynamicConfig &cfg, + const std::string &key, + const int &def = 0) +{ + if (cfg.has(key)) { + if (auto opt = cfg.option(key)) + return opt->getInt(); + } + + return def; } template void crop_value(T &val, T val_min, T val_max) @@ -167,8 +222,8 @@ template void crop_value(T &val, T val_min, T val_max) } void fill_preview(pwmx_format_preview &p, - pwmx_format_misc &m, - ThumbnailsList &thumbnails) + pwmx_format_misc &/*m*/, + const ThumbnailsList &thumbnails) { p.preview_w = PREV_W; @@ -216,6 +271,8 @@ void fill_header(pwmx_format_header &h, const SLAPrint &print, std::uint32_t layer_count) { + CNumericLocalesSetter locales_setter; + std::float_t bottle_weight_g; std::float_t bottle_volume_ml; std::float_t bottle_cost; @@ -232,21 +289,19 @@ void fill_header(pwmx_format_header &h, mat_cfg.load_from_ini_string(mnotes, ForwardCompatibilitySubstitutionRule::Enable); - h.layer_height_mm = std::atof(get_cfg_value(cfg, "layer_height")); - m.bottom_layer_height_mm = std::atof( - get_cfg_value(cfg, "initial_layer_height")); - h.exposure_time_s = std::atof(get_cfg_value(cfg, "exposure_time")); - h.bottom_exposure_time_s = std::atof( - get_cfg_value(cfg, "initial_exposure_time")); - h.bottom_layer_count = std::atof(get_cfg_value(cfg, "faded_layers")); + h.layer_height_mm = get_cfg_value_f(cfg, "layer_height"); + m.bottom_layer_height_mm = get_cfg_value_f(cfg, "initial_layer_height"); + h.exposure_time_s = get_cfg_value_f(cfg, "exposure_time"); + h.bottom_exposure_time_s = get_cfg_value_f(cfg, "initial_exposure_time"); + h.bottom_layer_count = get_cfg_value_i(cfg, "faded_layers"); if (layer_count < h.bottom_layer_count) { h.bottom_layer_count = layer_count; } - h.res_x = std::atol(get_cfg_value(cfg, "display_pixels_x")); - h.res_y = std::atol(get_cfg_value(cfg, "display_pixels_y")); - bottle_weight_g = std::atof(get_cfg_value(cfg, "bottle_weight")) * 1000.0f; - bottle_volume_ml = std::atof(get_cfg_value(cfg, "bottle_volume")); - bottle_cost = std::atof(get_cfg_value(cfg, "bottle_cost")); + h.res_x = get_cfg_value_i(cfg, "display_pixels_x"); + h.res_y = get_cfg_value_i(cfg, "display_pixels_y"); + bottle_weight_g = get_cfg_value_f(cfg, "bottle_weight") * 1000.0f; + bottle_volume_ml = get_cfg_value_f(cfg, "bottle_volume"); + bottle_cost = get_cfg_value_f(cfg, "bottle_cost"); material_density = bottle_weight_g / bottle_volume_ml; h.volume_ml = (stats.objects_used_material + stats.support_used_material) / 1000; @@ -257,37 +312,32 @@ void fill_header(pwmx_format_header &h, h.per_layer_override = 0; // TODO - expose these variables to the UI rather than using material notes - h.delay_before_exposure_s = std::atof( - get_cfg_value(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, "0.5")); + h.delay_before_exposure_s = get_cfg_value_f(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, 0.5f); crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f); - h.lift_distance_mm = std::atof( - get_cfg_value(mat_cfg, CFG_LIFT_DISTANCE, "8.0")); + h.lift_distance_mm = get_cfg_value_f(mat_cfg, CFG_LIFT_DISTANCE, 8.0f); crop_value(h.lift_distance_mm, 0.0f, 100.0f); if (mat_cfg.has(CFG_BOTTOM_LIFT_DISTANCE)) { - m.bottom_lift_distance_mm = std::atof( - get_cfg_value(mat_cfg, CFG_BOTTOM_LIFT_DISTANCE, "8.0")); + m.bottom_lift_distance_mm = get_cfg_value_f(mat_cfg, + CFG_BOTTOM_LIFT_DISTANCE, + 8.0f); crop_value(h.lift_distance_mm, 0.0f, 100.0f); } else { m.bottom_lift_distance_mm = h.lift_distance_mm; } - - h.lift_speed_mms = std::atof( - get_cfg_value(mat_cfg, CFG_LIFT_SPEED, "2.0")); + h.lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_LIFT_SPEED, 2.0f); crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f); if (mat_cfg.has(CFG_BOTTOM_LIFT_SPEED)) { - m.bottom_lift_speed_mms = std::atof( - get_cfg_value(mat_cfg, CFG_BOTTOM_LIFT_SPEED, "2.0")); + m.bottom_lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_BOTTOM_LIFT_SPEED, 2.0f); crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f); } else { m.bottom_lift_speed_mms = h.lift_speed_mms; } - h.retract_speed_mms = std::atof( - get_cfg_value(mat_cfg, CFG_RETRACT_SPEED, "3.0")); + h.retract_speed_mms = get_cfg_value_f(mat_cfg, CFG_RETRACT_SPEED, 3.0f); crop_value(h.lift_speed_mms, 0.1f, 20.0f); h.print_time_s = (h.bottom_layer_count * h.bottom_exposure_time_s) + @@ -339,7 +389,7 @@ std::unique_ptr PwmxArchive::create_raster() const sla::RasterEncoder PwmxArchive::get_encoder() const { - return sla::PWXRasterEncoder{}; + return PWXRasterEncoder{}; } // Endian safe write of little endian 32bit ints @@ -430,18 +480,18 @@ static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l) pwmx_write_float(out, l.layer48); } -void PwmxArchive::export_print(const std::string fname, - const SLAPrint & print, - ThumbnailsList & thumbnails, - const std::string &prjname) +void PwmxArchive::export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &/*projectname*/) { std::uint32_t layer_count = m_layers.size(); - pwmx_format_intro intro = {0}; - pwmx_format_header header = {0}; - pwmx_format_preview preview = {0}; - pwmx_format_layers_header layers_header = {0}; - pwmx_format_misc misc = {0}; + pwmx_format_intro intro = {}; + pwmx_format_header header = {}; + pwmx_format_preview preview = {}; + pwmx_format_layers_header layers_header = {}; + pwmx_format_misc misc = {}; std::vector layer_images; std::uint32_t image_offset; diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp index 69cec100c3..d1179c0144 100644 --- a/src/libslic3r/Format/pwmx.hpp +++ b/src/libslic3r/Format/pwmx.hpp @@ -22,12 +22,11 @@ public: PwmxArchive() = default; explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - - void export_print(const std::string fname, - const SLAPrint &print, - ThumbnailsList &thumbnails, - const std::string &projectname = "") override; - bool uses_zipper_export() override {return false;} + + void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") override; }; diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index fd3f3e062d..0b6c45eff3 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -16,54 +16,6 @@ const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; - - -static void pwx_get_pixel_span(const std::uint8_t* ptr, const std::uint8_t* end, - std::uint8_t& pixel, size_t& span_len) -{ - size_t max_len; - - span_len = 0; - pixel = (*ptr) & 0xF0; - // the maximum length of the span depends on the pixel color - max_len = (pixel == 0 || pixel == 0xF0) ? 0xFFF : 0xF; - while (((*ptr) & 0xF0) == pixel && ptr < end && span_len < max_len) { - span_len++; - ptr++; - } -} - -EncodedRaster PWXRasterEncoder::operator()(const void *ptr, size_t w, size_t h, - size_t num_components) -{ - std::vector dst; - size_t span_len; - std::uint8_t pixel; - auto size = w * h * num_components; - dst.reserve(size); - - const std::uint8_t* src = reinterpret_cast(ptr); - const std::uint8_t* src_end = src + size; - while (src < src_end) { - pwx_get_pixel_span(src, src_end, pixel, span_len); - src += span_len; - // fully transparent of fully opaque pixel - if (pixel == 0 || pixel == 0xF0) { - pixel = pixel | (span_len >> 8); - std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); - pixel = span_len & 0xFF; - std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); - } - // antialiased pixel - else { - pixel = pixel | span_len; - std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst)); - } - } - - return EncodedRaster(std::move(dst), "pwx"); -} - EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, size_t num_components) { diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 9d22591cc7..657fc865c2 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -97,10 +97,6 @@ public: virtual EncodedRaster encode(RasterEncoder encoder) const = 0; }; -struct PWXRasterEncoder { - EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); -}; - struct PNGRasterEncoder { EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 23502c44e3..2622b6a23c 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -17,8 +17,6 @@ #include #include -#include - // #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK @@ -1271,16 +1269,4 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, p.set_status(int(std::round(st)), msg, flags); } - -void SLAPrint::write_thumbnail(Zipper& zipper, const ThumbnailData& data) -{ - 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) - { - zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size); - mz_free(png_data); - } -} - } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 8d5b54376a..2d49139a4f 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -422,17 +422,11 @@ public: execution::max_concurrency(ep)); } - // Export the print into an archive using the provided zipper. - virtual void export_print(Zipper &zipper, - const SLAPrint &print, - const std::string &projectname = "") {}; // Export the print into an archive using the provided filename. - virtual void export_print(const std::string fname, - const SLAPrint &print, - ThumbnailsList &thumbnails, - const std::string &projectname = "") {}; - // By default the exporters use zipper export. Return false to use file export. - virtual bool uses_zipper_export() { return true; } + virtual void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") = 0; }; /** @@ -538,27 +532,18 @@ 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 write_thumbnail(Zipper& zipper, const ThumbnailData& data); void export_print(const std::string &fname, const std::string &projectname = "") { - Slic3r::ThumbnailsList thumbnails; //empty thumbnail list + ThumbnailsList thumbnails; //empty thumbnail list export_print(fname, thumbnails, projectname); } - void export_print(const std::string &fname, Slic3r::ThumbnailsList &thumbnails, const std::string &projectname = "") + void export_print(const std::string &fname, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") { - if (m_archiver->uses_zipper_export()) { - Zipper zipper(fname); - m_archiver->export_print(zipper, *this, projectname); - for (const ThumbnailData& data : thumbnails) - if (data.is_valid()) - write_thumbnail(zipper, data); - zipper.finalize(); - } else { - m_archiver->export_print(fname, *this, thumbnails, projectname); - } + m_archiver->export_print(fname, *this, thumbnails, projectname); } private: From 3633112148e5da0c192b4ad0f099af171beaac3c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Feb 2022 10:17:57 +0100 Subject: [PATCH 19/33] Follow-up of 7150e0adda2673797b624e314782a81ca8279bb7 - Fix in clamping max bounding box to avoid z-fighting in gcode preview --- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index edd62ce0ab..1df16e6b75 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5025,9 +5025,9 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be // clamp max bb size with respect to bed bb size if (!m_picking_enabled) { - static const double max_scale_factor = 1.5; + static const double max_scale_factor = 2.0; const Vec3d bb_size = bb.size(); - const Vec3d bed_bb_size = bed_bb.size(); + const Vec3d bed_bb_size = m_bed.build_volume().bounding_volume().size(); if (bb_size.x() > max_scale_factor * bed_bb_size.x() || bb_size.y() > max_scale_factor * bed_bb_size.y() || bb_size.z() > max_scale_factor * bed_bb_size.z()) { @@ -5037,6 +5037,9 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + std::cout << to_string(bb.size()) << "\n"; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return bb; } From 6c397e291cc199d4da8d9ffc23dff1384584049d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Feb 2022 10:18:47 +0100 Subject: [PATCH 20/33] Removed debug code committed by mistake --- src/slic3r/GUI/GLCanvas3D.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1df16e6b75..865190e465 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5037,9 +5037,6 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - std::cout << to_string(bb.size()) << "\n"; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return bb; } From 66f60de5560ba0bc9577c0ed85565e03f865b7ac Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Feb 2022 12:48:17 +0100 Subject: [PATCH 21/33] SLAArchive framework refactor --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/SL1.hpp | 4 +- src/libslic3r/Format/SLAArchive.cpp | 74 ++++++++++++++++++++ src/libslic3r/Format/SLAArchive.hpp | 64 +++++++++++++++++ src/libslic3r/Format/pwmx.cpp | 6 +- src/libslic3r/Format/pwmx.hpp | 4 +- src/libslic3r/SLAPrint.cpp | 11 +-- src/libslic3r/SLAPrint.hpp | 40 +---------- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 1 + tests/libnest2d/CMakeLists.txt | 4 +- tests/sla_print/CMakeLists.txt | 3 +- tests/sla_print/sla_archive_export_tests.cpp | 37 ++++++++++ tests/slic3rutils/CMakeLists.txt | 4 +- 13 files changed, 199 insertions(+), 55 deletions(-) create mode 100644 src/libslic3r/Format/SLAArchive.cpp create mode 100644 src/libslic3r/Format/SLAArchive.hpp create mode 100644 tests/sla_print/sla_archive_export_tests.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ea663f2e1f..046aba7631 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -94,6 +94,8 @@ set(SLIC3R_SOURCES Format/objparser.hpp Format/STL.cpp Format/STL.hpp + Format/SLAArchive.hpp + Format/SLAArchive.cpp Format/SL1.hpp Format/SL1.cpp Format/SL1_SVG.hpp diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 874786eb9c..8ed9dc3299 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -3,8 +3,10 @@ #include +#include "SLAArchive.hpp" + #include "libslic3r/Zipper.hpp" -#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/Format/SLAArchive.cpp b/src/libslic3r/Format/SLAArchive.cpp new file mode 100644 index 0000000000..8e2bf2824f --- /dev/null +++ b/src/libslic3r/Format/SLAArchive.cpp @@ -0,0 +1,74 @@ +#include "SLAArchive.hpp" + +#include "SL1.hpp" +#include "SL1_SVG.hpp" +#include "pwmx.hpp" + +#include "libslic3r/libslic3r.h" + +#include +#include +#include +#include + +namespace Slic3r { + +using ArchiveFactory = std::function(const SLAPrinterConfig&)>; + +struct ArchiveEntry { + const char *ext; + ArchiveFactory factoryfn; +}; + +static const std::map REGISTERED_ARCHIVES { + { + "SL1", + { "sl1", [] (const auto &cfg) { return std::make_unique(cfg); } } + }, + { + "SL2", + { "sl2", [] (const auto &cfg) { return std::make_unique(cfg); } } + }, + { + "pwmx", + { "pwmx", [] (const auto &cfg) { return std::make_unique(cfg); } } + } +}; + +std::unique_ptr +SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg) +{ + auto entry = REGISTERED_ARCHIVES.find(archtype); + + if (entry != REGISTERED_ARCHIVES.end()) + return entry->second.factoryfn(cfg); + + return nullptr; +} + +const std::vector& SLAArchive::registered_archives() +{ + static std::vector archnames; + + if (archnames.empty()) { + archnames.reserve(REGISTERED_ARCHIVES.size()); + + for (auto &[name, _] : REGISTERED_ARCHIVES) + archnames.emplace_back(name.c_str()); + } + + return archnames; +} + +const char *SLAArchive::get_extension(const char *archtype) +{ + static const char* DEFAULT_EXT = "zip"; + + auto entry = REGISTERED_ARCHIVES.find(archtype); + if (entry != REGISTERED_ARCHIVES.end()) + return entry->second.ext; + + return DEFAULT_EXT; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SLAArchive.hpp b/src/libslic3r/Format/SLAArchive.hpp new file mode 100644 index 0000000000..971d7ba7b8 --- /dev/null +++ b/src/libslic3r/Format/SLAArchive.hpp @@ -0,0 +1,64 @@ +#ifndef SLAARCHIVE_HPP +#define SLAARCHIVE_HPP + +#include + +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" + +namespace Slic3r { + +class SLAPrint; +class SLAPrinterConfig; + +class SLAArchive { +protected: + std::vector m_layers; + + virtual std::unique_ptr create_raster() const = 0; + virtual sla::RasterEncoder get_encoder() const = 0; + +public: + virtual ~SLAArchive() = default; + + // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); + template + void draw_layers( + size_t layer_num, + Fn && drawfn, + CancelFn cancelfn = []() { return false; }, + const EP & ep = {}) + { + m_layers.resize(layer_num); + execution::for_each( + ep, size_t(0), m_layers.size(), + [this, &drawfn, &cancelfn](size_t idx) { + if (cancelfn()) return; + + sla::EncodedRaster &enc = m_layers[idx]; + auto rst = create_raster(); + drawfn(*rst, idx); + enc = rst->encode(get_encoder()); + }, + execution::max_concurrency(ep)); + } + + // Export the print into an archive using the provided filename. + virtual void export_print(const std::string fname, + const SLAPrint &print, + const ThumbnailsList &thumbnails, + const std::string &projectname = "") = 0; + + // Factory method to create an archiver instance + static std::unique_ptr create(const std::string &archtype, const SLAPrinterConfig&); + + // Get the names of currently known archiver implementations + static const std::vector & registered_archives(); + + // Get the default file extension belonging to an archive format + static const char *get_extension(const char *archtype); +}; + +} // namespace Slic3r +#endif // SLAARCHIVE_HPP diff --git a/src/libslic3r/Format/pwmx.cpp b/src/libslic3r/Format/pwmx.cpp index 5b420a49ec..f3f0f6802a 100644 --- a/src/libslic3r/Format/pwmx.cpp +++ b/src/libslic3r/Format/pwmx.cpp @@ -1,6 +1,7 @@ #include "pwmx.hpp" #include "GCode/ThumbnailData.hpp" #include "SLA/RasterBase.hpp" +#include "libslic3r/SLAPrint.hpp" #include #include @@ -277,8 +278,9 @@ void fill_header(pwmx_format_header &h, std::float_t bottle_volume_ml; std::float_t bottle_cost; std::float_t material_density; - auto & cfg = print.full_print_config(); - std::string mnotes = cfg.option("material_notes")->serialize(); + auto &cfg = print.full_print_config(); + auto mat_opt = cfg.option("material_notes"); + std::string mnotes = mat_opt? cfg.option("material_notes")->serialize() : ""; // create a config parser from the material notes Slic3r::PwmxFormatDynamicConfig mat_cfg; SLAPrintStatistics stats = print.print_statistics(); diff --git a/src/libslic3r/Format/pwmx.hpp b/src/libslic3r/Format/pwmx.hpp index d1179c0144..65fb199102 100644 --- a/src/libslic3r/Format/pwmx.hpp +++ b/src/libslic3r/Format/pwmx.hpp @@ -3,7 +3,9 @@ #include -#include "libslic3r/SLAPrint.hpp" +#include "SLAArchive.hpp" + +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 2622b6a23c..72cb96dd04 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -245,15 +245,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); - 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); - else if (m_printer_config.sla_archive_format.value == "pwmx") { - m_archiver = std::make_unique(m_printer_config); - } - } + if (!m_archiver || !printer_diff.empty()) + m_archiver = SLAArchive::create(m_printer_config.sla_archive_format.value.c_str(), m_printer_config); struct ModelObjectStatus { enum Status { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 2d49139a4f..0723382b39 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -9,6 +9,7 @@ #include "Point.hpp" #include "MTUtils.hpp" #include "Zipper.hpp" +#include "Format/SLAArchive.hpp" #include "GCode/ThumbnailData.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" @@ -390,45 +391,6 @@ struct SLAPrintStatistics } }; -class SLAArchive { -protected: - std::vector m_layers; - - virtual std::unique_ptr create_raster() const = 0; - virtual sla::RasterEncoder get_encoder() const = 0; - -public: - virtual ~SLAArchive() = default; - - // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); - template - void draw_layers( - size_t layer_num, - Fn && drawfn, - CancelFn cancelfn = []() { return false; }, - const EP & ep = {}) - { - m_layers.resize(layer_num); - execution::for_each( - ep, size_t(0), m_layers.size(), - [this, &drawfn, &cancelfn](size_t idx) { - if (cancelfn()) return; - - sla::EncodedRaster &enc = m_layers[idx]; - auto rst = create_raster(); - drawfn(*rst, idx); - enc = rst->encode(get_encoder()); - }, - execution::max_concurrency(ep)); - } - - // Export the print into an archive using the provided filename. - virtual void export_print(const std::string fname, - const SLAPrint &print, - const ThumbnailsList &thumbnails, - const std::string &projectname = "") = 0; -}; - /** * @brief This class is the high level FSM for the SLA printing process. * diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 1bb8cdf6c6..96702d158f 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -1,5 +1,6 @@ #include "SLAImportJob.hpp" +#include "libslic3r/SLAPrint.hpp" #include "libslic3r/Format/SL1.hpp" #include "slic3r/GUI/GUI.hpp" diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt index bcb7594520..9bafe84a04 100644 --- a/tests/libnest2d/CMakeLists.txt +++ b/tests/libnest2d/CMakeLists.txt @@ -4,4 +4,6 @@ target_link_libraries(${_TEST_NAME}_tests test_common libnest2d ) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") -add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]") +set(_catch_args "exclude:[NotWorking]") +list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") +add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index dc583f1a1c..26d5765495 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -3,7 +3,8 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp - sla_raycast_tests.cpp) + sla_raycast_tests.cpp + sla_archive_export_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") diff --git a/tests/sla_print/sla_archive_export_tests.cpp b/tests/sla_print/sla_archive_export_tests.cpp new file mode 100644 index 0000000000..586b0eb673 --- /dev/null +++ b/tests/sla_print/sla_archive_export_tests.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Format/SLAArchive.hpp" + +#include + +using namespace Slic3r; + +TEST_CASE("Archive export test", "[sla_archives]") { + constexpr const char *PNAME = "20mm_cube"; + + for (auto &archname : SLAArchive::registered_archives()) { + INFO(std::string("Testing archive type: ") + archname); + SLAPrint print; + SLAFullPrintConfig fullcfg; + + auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(PNAME) + ".obj", nullptr); + + fullcfg.set("sla_archive_format", archname); + + DynamicPrintConfig cfg; + cfg.apply(fullcfg); + + print.apply(m, cfg); + print.process(); + + ThumbnailsList thumbnails; + auto outputfname = std::string("output.") + SLAArchive::get_extension(archname); + + print.export_print(outputfname, thumbnails, PNAME); + + // Not much can be checked about the archives... + REQUIRE(boost::filesystem::exists(outputfname)); + } +} diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index be1b645d70..256e6efd68 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -15,4 +15,6 @@ if (WIN32) endif() # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") -add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "${CATCH_EXTRA_ARGS} exclude:[NotWorking]") +set(_catch_args "exclude:[NotWorking]") +list(APPEND _catch_args "${CATCH_EXTRA_ARGS}") +add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args}) From b407540f268e949d2efc8769000aa1b6ea763778 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Feb 2022 13:43:11 +0100 Subject: [PATCH 22/33] Downgrade log message from sla support tree creation --- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 6134e1f5ab..aa69fdc777 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -982,7 +982,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) double w = dist - 2 * head.r_pin_mm - head.r_back_mm; if (w < 0.) { - BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!"; + BOOST_LOG_TRIVIAL(warning) << "Pinhead width is negative!"; w = 0.; } From b8718f46015c3e09ad6042ee6ce24cca636b88b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Feb 2022 14:05:45 +0100 Subject: [PATCH 23/33] Silence sla print messages for archive export tests --- tests/sla_print/sla_archive_export_tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sla_print/sla_archive_export_tests.cpp b/tests/sla_print/sla_archive_export_tests.cpp index 586b0eb673..4cfdd0cdc2 100644 --- a/tests/sla_print/sla_archive_export_tests.cpp +++ b/tests/sla_print/sla_archive_export_tests.cpp @@ -23,6 +23,7 @@ TEST_CASE("Archive export test", "[sla_archives]") { DynamicPrintConfig cfg; cfg.apply(fullcfg); + print.set_status_callback([](const PrintBase::SlicingStatus&) {}); print.apply(m, cfg); print.process(); From 4f2772fef493a98ac05b11d89c36047b02485dd1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 18 Feb 2022 14:29:09 +0100 Subject: [PATCH 24/33] Fix sla archive tests --- tests/sla_print/CMakeLists.txt | 4 ++++ tests/sla_print/sla_archive_export_tests.cpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 26d5765495..88c1cd1866 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -8,5 +8,9 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") +if (WIN32) + prusaslicer_copy_dlls(${_TEST_NAME}_tests) +endif() + # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) diff --git a/tests/sla_print/sla_archive_export_tests.cpp b/tests/sla_print/sla_archive_export_tests.cpp index 4cfdd0cdc2..9dbe5bc64f 100644 --- a/tests/sla_print/sla_archive_export_tests.cpp +++ b/tests/sla_print/sla_archive_export_tests.cpp @@ -19,6 +19,8 @@ TEST_CASE("Archive export test", "[sla_archives]") { auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(PNAME) + ".obj", nullptr); fullcfg.set("sla_archive_format", archname); + fullcfg.set("supports_enable", false); + fullcfg.set("pad_enable", false); DynamicPrintConfig cfg; cfg.apply(fullcfg); From fe14c547fe95bed94a8ddef33c876f6f88e3d4d3 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 19 Feb 2022 12:49:23 +0100 Subject: [PATCH 25/33] Show GLEW error string on glewInit() initialization errors --- src/slic3r/GUI/OpenGLManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 6616cc20d7..41a11cff6e 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -233,8 +233,9 @@ OpenGLManager::~OpenGLManager() bool OpenGLManager::init_gl() { if (!m_gl_initialized) { - if (glewInit() != GLEW_OK) { - BOOST_LOG_TRIVIAL(error) << "Unable to init glew library"; + GLenum err = glewInit(); + if (err != GLEW_OK) { + BOOST_LOG_TRIVIAL(error) << "Unable to init glew library: " << glewGetErrorString(err); return false; } m_gl_initialized = true; From 3a821d6752fa78880c99e2387a8a832d617c9d95 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 19 Feb 2022 12:53:37 +0100 Subject: [PATCH 26/33] Add missing declaration/includes (fix non-PCH build) --- src/libslic3r/Format/SL1.hpp | 2 ++ src/libslic3r/Format/SL1_SVG.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 8ed9dc3299..493550db4b 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -8,6 +8,8 @@ #include "libslic3r/Zipper.hpp" #include "libslic3r/PrintConfig.hpp" +struct indexed_triangle_set; + namespace Slic3r { class SL1Archive: public SLAArchive { diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index a7a8c9ed6b..372348283a 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -2,6 +2,7 @@ #include "SLA/RasterBase.hpp" #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/BoundingBox.hpp" #include #include From 0ffcfd8393457fd035576436752267c9a1e6bbcc Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Mon, 24 Jan 2022 20:21:24 +0100 Subject: [PATCH 27/33] Use namespaces to link against libcereal >= 1.3.1 Create an alias for previous versions. --- CMakeLists.txt | 3 +++ src/CMakeLists.txt | 2 +- src/libslic3r/CMakeLists.txt | 2 +- src/slic3r/CMakeLists.txt | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72fd87d224..ba458ec962 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,6 +506,9 @@ endif () # Find the Cereal serialization library find_package(cereal REQUIRED) +if (NOT TARGET cereal::cereal) + add_library(cereal::cereal ALIAS cereal) +endif () # l10n set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab1c7b9645..bd5eb94a5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,7 +126,7 @@ if (NOT WIN32 AND NOT APPLE) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () -target_link_libraries(PrusaSlicer libslic3r cereal) +target_link_libraries(PrusaSlicer libslic3r cereal::cereal) if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 046aba7631..36344ebab9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -358,7 +358,7 @@ find_package(JPEG REQUIRED) target_link_libraries(libslic3r libnest2d admesh - cereal + cereal::cereal libigl miniz boost_libs diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 34c0efd014..b23db9867f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -270,7 +270,7 @@ endforeach() encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude cereal::cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) From cc788ebb643b6d4048f3550235ac3e9d3697ada0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 22 Feb 2022 11:36:27 +0100 Subject: [PATCH 28/33] Fix and merge PR #7809 --- CMakeLists.txt | 7 +++++-- src/CMakeLists.txt | 2 +- src/libslic3r/CMakeLists.txt | 2 +- src/slic3r/CMakeLists.txt | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba458ec962..6c6afe6710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,9 +506,12 @@ endif () # Find the Cereal serialization library find_package(cereal REQUIRED) +add_library(libcereal INTERFACE) if (NOT TARGET cereal::cereal) - add_library(cereal::cereal ALIAS cereal) -endif () + target_link_libraries(libcereal INTERFACE cereal) +else() + target_link_libraries(libcereal INTERFACE cereal::cereal) +endif() # l10n set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd5eb94a5e..61a2a90d8b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,7 +126,7 @@ if (NOT WIN32 AND NOT APPLE) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () -target_link_libraries(PrusaSlicer libslic3r cereal::cereal) +target_link_libraries(PrusaSlicer libslic3r libcereal) if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 36344ebab9..5e8d681f1a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -358,7 +358,7 @@ find_package(JPEG REQUIRED) target_link_libraries(libslic3r libnest2d admesh - cereal::cereal + libcereal libigl miniz boost_libs diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index b23db9867f..022bba2a8a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -270,7 +270,7 @@ endforeach() encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude cereal::cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) From 4ed411f211ea4ef4efa0aeb82aae9c9c0d991b8c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 22 Feb 2022 12:41:51 +0100 Subject: [PATCH 29/33] Fix tbb assert when grainsize is lower then 1 --- src/libslic3r/Execution/Execution.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp index 62e49cfeb3..dcfd86bde8 100644 --- a/src/libslic3r/Execution/Execution.hpp +++ b/src/libslic3r/Execution/Execution.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" @@ -44,7 +45,8 @@ size_t max_concurrency(const EP &ep) template> void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1) { - AsTraits::for_each(ep, from, to, std::forward(fn), granularity); + AsTraits::for_each(ep, from, to, std::forward(fn), + std::max(granularity, size_t(1))); } // A reduce operation with the execution policy passed as argument. @@ -68,7 +70,7 @@ T reduce(const EP & ep, return AsTraits::reduce(ep, from, to, init, std::forward(mergefn), std::forward(accessfn), - granularity); + std::max(granularity, size_t(1))); } // An overload of reduce method to be used with iterators as 'from' and 'to' @@ -87,7 +89,7 @@ T reduce(const EP &ep, { return reduce( ep, from, to, init, std::forward(mergefn), - [](const auto &i) { return i; }, granularity); + [](const auto &i) { return i; }, std::max(granularity, size_t(1))); } template{}, - std::forward(accessfn), granularity); + std::forward(accessfn), + std::max(granularity, size_t(1))); } @@ -119,7 +122,7 @@ T accumulate(const EP &ep, { return reduce( ep, from, to, init, std::plus{}, [](const auto &i) { return i; }, - granularity); + std::max(granularity, size_t(1))); } } // namespace execution_policy From 33fe1c44b47afa08ae5b1d426be65f944e9aab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 22 Feb 2022 17:34:09 +0100 Subject: [PATCH 30/33] Fixed 'illegal qualified name in member declaration' reported in #7959. --- src/slic3r/GUI/Notebook.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index ff5020b9c4..af03a6a080 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -245,7 +245,7 @@ public: GetBtnsListCtrl()->Rescale(); } - void Notebook::OnNavigationKey(wxNavigationKeyEvent& event) + void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { // change pages From 1eac357739d44fcdcfbbb742b888b51ce8858bba Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 23 Feb 2022 13:39:54 +0100 Subject: [PATCH 31/33] Tech ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL - Replace GLIndexedVertexArray with GLModel: GLVolume geometry + removed class GLIndexedVertexArray from codebase --- src/libslic3r/BuildVolume.hpp | 6 + src/slic3r/GUI/3DScene.cpp | 722 +++++++++++++++++++++++- src/slic3r/GUI/3DScene.hpp | 88 ++- src/slic3r/GUI/GCodeViewer.cpp | 28 + src/slic3r/GUI/GCodeViewer.hpp | 8 + src/slic3r/GUI/GLCanvas3D.cpp | 330 +++++++++-- src/slic3r/GUI/GLModel.cpp | 271 ++++++++- src/slic3r/GUI/GLModel.hpp | 67 ++- src/slic3r/GUI/GLSelectionRectangle.cpp | 2 +- src/slic3r/GUI/GalleryDialog.cpp | 6 +- src/slic3r/GUI/ImGuiWrapper.cpp | 18 +- 11 files changed, 1467 insertions(+), 79 deletions(-) diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 6b928d48b0..be8d224c39 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -94,6 +94,12 @@ public: // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const; + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; } + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + private: // Source definition of the print bed geometry (PrintConfig::bed_shape) std::vector m_bed_shape; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 23ca974dd7..da09a236f4 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,10 +1,12 @@ #include +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #if ENABLE_SMOOTH_NORMALS #include #include #include #endif // ENABLE_SMOOTH_NORMALS +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include "3DScene.hpp" #include "GLShader.hpp" @@ -69,6 +71,7 @@ void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char namespace Slic3r { +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #if ENABLE_SMOOTH_NORMALS static void smooth_normals_corner(TriangleMesh& mesh, std::vector& normals) { @@ -287,6 +290,7 @@ void GLIndexedVertexArray::render( glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL const float GLVolume::SinkingContours::HalfWidth = 0.25f; @@ -483,7 +487,9 @@ GLVolume::GLVolume(float r, float g, float b, float a) , force_neutral_color(false) , force_sinking_contours(false) , tverts_range(0, size_t(-1)) +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL , qverts_range(0, size_t(-1)) +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { color = { r, g, b, a }; set_render_color(color); @@ -599,6 +605,36 @@ const BoundingBoxf3& GLVolume::transformed_non_sinking_bounding_box() const return *m_transformed_non_sinking_bounding_box; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GLVolume::set_range(double min_z, double max_z) +{ + this->tverts_range.first = 0; + this->tverts_range.second = this->model.indices_count(); + + if (!this->print_zs.empty()) { + // The Z layer range is specified. + // First test whether the Z span of this object is not out of (min_z, max_z) completely. + if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) + this->tverts_range.second = 0; + else { + // Then find the lowest layer to be displayed. + size_t i = 0; + for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++i); + if (i == this->print_zs.size()) + // This shall not happen. + this->tverts_range.second = 0; + else { + // Remember start of the layer. + this->tverts_range.first = this->offsets[i]; + // Some layers are above $min_z. Which? + for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++i); + if (i < this->print_zs.size()) + this->tverts_range.second = this->offsets[i]; + } + } + } +} +#else void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -611,7 +647,8 @@ void GLVolume::set_range(double min_z, double max_z) if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) { this->qverts_range.second = 0; this->tverts_range.second = 0; - } else { + } + else { // Then find the lowest layer to be displayed. size_t i = 0; for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++ i); @@ -619,7 +656,8 @@ void GLVolume::set_range(double min_z, double max_z) // This shall not happen. this->qverts_range.second = 0; this->tverts_range.second = 0; - } else { + } + else { // Remember start of the layer. this->qverts_range.first = this->offsets[i * 2]; this->tverts_range.first = this->offsets[i * 2 + 1]; @@ -633,8 +671,9 @@ void GLVolume::set_range(double min_z, double max_z) } } } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL -void GLVolume::render() const +void GLVolume::render() { if (!is_active) return; @@ -645,7 +684,14 @@ void GLVolume::render() const glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(world_matrix().data())); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (tverts_range == std::make_pair(0, -1)) + model.render(); + else + model.render(this->tverts_range); +#else this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL glsafe(::glPopMatrix()); if (this->is_left_handed()) @@ -680,25 +726,44 @@ void GLVolume::render_non_manifold_edges() } #endif // ENABLE_SHOW_NON_MANIFOLD_EDGES +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +std::vector GLVolumeCollection::load_object( + const ModelObject* model_object, + int obj_idx, + const std::vector& instance_idxs) +#else std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, bool opengl_initialized) +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx)); +#else volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, opengl_initialized)); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL return volumes_idx; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +int GLVolumeCollection::load_object_volume( + const ModelObject* model_object, + int obj_idx, + int volume_idx, + int instance_idx) +#else int GLVolumeCollection::load_object_volume( const ModelObject *model_object, int obj_idx, int volume_idx, int instance_idx, bool opengl_initialized) +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); @@ -706,6 +771,14 @@ int GLVolumeCollection::load_object_volume( const TriangleMesh &mesh = model_volume->mesh(); this->volumes.emplace_back(new GLVolume()); GLVolume& v = *this->volumes.back(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_SMOOTH_NORMALS + v.model.init_from(mesh, true); +#else + v.model.init_from(mesh); +#endif // ENABLE_SMOOTH_NORMALS + v.model.set_color(color_from_model_volume(*model_volume)); +#else v.set_color(color_from_model_volume(*model_volume)); #if ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.load_mesh(mesh, true); @@ -713,9 +786,9 @@ int GLVolumeCollection::load_object_volume( v.indexed_vertex_array.load_mesh(mesh); #endif // ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.finalize_geometry(opengl_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); - if (model_volume->is_model_part()) - { + if (model_volume->is_model_part()) { // GLVolume will reference a convex hull from model_volume! v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) @@ -732,6 +805,16 @@ int GLVolumeCollection::load_object_volume( // Load SLA auxiliary GLVolumes (for support trees or pad). // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GLVolumeCollection::load_object_auxiliary( + const SLAPrintObject* print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp) +#else void GLVolumeCollection::load_object_auxiliary( const SLAPrintObject *print_object, int obj_idx, @@ -741,6 +824,7 @@ void GLVolumeCollection::load_object_auxiliary( // Timestamp of the last change of the milestone size_t timestamp, bool opengl_initialized) +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); @@ -753,12 +837,21 @@ void GLVolumeCollection::load_object_auxiliary( const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_SMOOTH_NORMALS + v.model.init_from(mesh, true); +#else + v.model.init_from(mesh); +#endif // ENABLE_SMOOTH_NORMALS + v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR); +#else #if ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.load_mesh(mesh, true); #else v.indexed_vertex_array.load_mesh(mesh); #endif // ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.finalize_geometry(opengl_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. @@ -774,6 +867,17 @@ void GLVolumeCollection::load_object_auxiliary( } } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +int GLVolumeCollection::load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, + float rotation_angle, bool size_unknown, float brim_width) +#else +int GLVolumeCollection::load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, + float rotation_angle, bool size_unknown, float brim_width) +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL int GLVolumeCollection::load_wipe_tower_preview( float pos_x, float pos_y, float width, float depth, float height, @@ -783,6 +887,7 @@ int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized) #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -839,9 +944,16 @@ int GLVolumeCollection::load_wipe_tower_preview( volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + v.model.init_from(mesh); + v.model.set_color(color); +#else v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.set_convex_hull(mesh.convex_hull_3d()); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.indexed_vertex_array.finalize_geometry(opengl_initialized); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL @@ -856,6 +968,22 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(volumes.size() - 1); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba) +{ + GLVolume* out = new_nontoolpath_volume(rgba); + out->is_extrusion_path = true; + return out; +} + +GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba) +{ + GLVolume* out = new GLVolume(rgba); + out->is_extrusion_path = false; + this->volumes.emplace_back(out); + return out; +} +#else GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats) { GLVolume *out = new_nontoolpath_volume(rgba, reserve_vbo_floats); @@ -872,6 +1000,7 @@ GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba, size this->volumes.emplace_back(out); return out; } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) { @@ -960,7 +1089,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - shader->set_uniform("uniform_color", volume.first->render_color); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (!volume.first->model.is_initialized()) +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->set_uniform("uniform_color", volume.first->render_color); shader->set_uniform("z_range", m_z_range, 2); shader->set_uniform("clipping_plane", m_clipping_plane, 4); shader->set_uniform("print_volume.type", static_cast(m_print_volume.type)); @@ -980,6 +1112,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab #endif // ENABLE_ENVIRONMENT_MAP glcheck(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (volume.first->model.is_initialized()) + volume.first->model.set_color(volume.first->render_color); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL volume.first->render(); #if ENABLE_ENVIRONMENT_MAP @@ -1215,6 +1351,466 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +static void thick_lines_to_geometry( + const Lines& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + double top_z, + GUI::GLModel::Geometry& geometry) +{ + assert(!lines.empty()); + if (lines.empty()) + return; + + enum Direction : unsigned char + { + Left, + Right, + Top, + Bottom + }; + + // right, left, top, bottom + std::array idx_prev = { -1, -1, -1, -1 }; + std::array idx_initial = { -1, -1, -1, -1 }; + + double bottom_z_prev = 0.0; + Vec2d b1_prev(Vec2d::Zero()); + Vec2d v_prev(Vec2d::Zero()); + double len_prev = 0.0; + double width_initial = 0.0; + double bottom_z_initial = 0.0; + + // loop once more in case of closed loops + const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ii) { + const size_t i = (ii == lines.size()) ? 0 : ii; + const Line& line = lines[i]; + const double bottom_z = top_z - heights[i]; + const double middle_z = 0.5 * (top_z + bottom_z); + const double width = widths[i]; + + const bool is_first = (ii == 0); + const bool is_last = (ii == lines_end - 1); + const bool is_closing = closed && is_last; + + const Vec2d v = unscale(line.vector()).normalized(); + const double len = unscale(line.length()); + + const Vec2d a = unscale(line.a); + const Vec2d b = unscale(line.b); + Vec2d a1 = a; + Vec2d a2 = a; + Vec2d b1 = b; + Vec2d b2 = b; + { + const double dist = 0.5 * width; // scaled + const double dx = dist * v.x(); + const double dy = dist * v.y(); + a1 += Vec2d(+dy, -dx); + a2 += Vec2d(-dy, +dx); + b1 += Vec2d(+dy, -dx); + b2 += Vec2d(-dy, +dx); + } + + // calculate new XY normals + const Vec2d xy_right_normal = unscale(line.normal()).normalized(); + + std::array idx_a = { 0, 0, 0, 0 }; + std::array idx_b = { 0, 0, 0, 0 }; + int idx_last = int(geometry.vertices_count()); + + const bool bottom_z_different = bottom_z_prev != bottom_z; + bottom_z_prev = bottom_z; + + if (!is_first && bottom_z_different) { + // Found a change of the layer thickness -> Add a cap at the end of the previous segment. + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); + } + + // Share top / bottom vertices if possible. + if (is_first) { + idx_a[Top] = idx_last++; + geometry.add_vertex(Vec3f(a.x(), a.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f)); + } + else + idx_a[Top] = idx_prev[Top]; + + if (is_first || bottom_z_different) { + // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. + idx_a[Bottom] = idx_last++; + geometry.add_vertex(Vec3f(a.x(), a.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f)); + idx_a[Left] = idx_last++; + geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + idx_a[Right] = idx_last++; + geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); + } + else + idx_a[Bottom] = idx_prev[Bottom]; + + if (is_first) { + // Start of the 1st line segment. + width_initial = width; + bottom_z_initial = bottom_z; + idx_initial = idx_a; + } + else { + // Continuing a previous segment. + // Share left / right vertices if possible. + const double v_dot = v_prev.dot(v); + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + const double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + const bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); + if (sharp) { + if (!bottom_z_different) { + // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. + idx_a[Right] = idx_last++; + geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); + idx_a[Left] = idx_last++; + geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + if (cross2(v_prev, v) > 0.0) { + // Right turn. Fill in the right turn wedge. + geometry.add_uint_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]); + geometry.add_uint_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]); + } + else { + // Left turn. Fill in the left turn wedge. + geometry.add_uint_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]); + geometry.add_uint_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]); + } + } + } + else { + if (!bottom_z_different) { + // The two successive segments are nearly collinear. + idx_a[Left] = idx_prev[Left]; + idx_a[Right] = idx_prev[Right]; + } + } + if (is_closing) { + if (!sharp) { + if (!bottom_z_different) { + // Closing a loop with smooth transition. Unify the closing left / right vertices. + geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left])); + geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right])); + geometry.remove_vertex(geometry.vertices_count() - 1); + geometry.remove_vertex(geometry.vertices_count() - 1); + // Replace the left / right vertex indices to point to the start of the loop. + const size_t indices_count = geometry.indices_count(); + for (size_t u = indices_count - 24; u < indices_count; ++u) { + const unsigned int id = geometry.extract_uint_index(u); + if (id == (unsigned int)idx_prev[Left]) + geometry.set_uint_index(u, (unsigned int)idx_initial[Left]); + else if (id == (unsigned int)idx_prev[Right]) + geometry.set_uint_index(u, (unsigned int)idx_initial[Right]); + } + } + } + // This is the last iteration, only required to solve the transition. + break; + } + } + + // Only new allocate top / bottom vertices, if not closing a loop. + if (is_closing) + idx_b[Top] = idx_initial[Top]; + else { + idx_b[Top] = idx_last++; + geometry.add_vertex(Vec3f(b.x(), b.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f)); + } + + if (is_closing && width == width_initial && bottom_z == bottom_z_initial) + idx_b[Bottom] = idx_initial[Bottom]; + else { + idx_b[Bottom] = idx_last++; + geometry.add_vertex(Vec3f(b.x(), b.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f)); + } + // Generate new vertices for the end of this line segment. + idx_b[Left] = idx_last++; + geometry.add_vertex(Vec3f(b2.x(), b2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f)); + idx_b[Right] = idx_last++; + geometry.add_vertex(Vec3f(b1.x(), b1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f)); + + idx_prev = idx_b; + bottom_z_prev = bottom_z; + b1_prev = b1; + v_prev = v; + len_prev = len; + + if (bottom_z_different && (closed || (!is_first && !is_last))) { + // Found a change of the layer thickness -> Add a cap at the beginning of this segment. + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); + } + + if (!closed) { + // Terminate open paths with caps. + if (is_first) { + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); + } + // We don't use 'else' because both cases are true if we have only one line. + if (is_last) { + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); + } + } + + // Add quads for a straight hollow tube-like segment. + // bottom-right face + geometry.add_uint_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]); + geometry.add_uint_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]); + // top-right face + geometry.add_uint_triangle(idx_a[Right], idx_b[Right], idx_b[Top]); + geometry.add_uint_triangle(idx_a[Right], idx_b[Top], idx_a[Top]); + // top-left face + geometry.add_uint_triangle(idx_a[Top], idx_b[Top], idx_b[Left]); + geometry.add_uint_triangle(idx_a[Top], idx_b[Left], idx_a[Left]); + // bottom-left face + geometry.add_uint_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]); + geometry.add_uint_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]); + } +} + +// caller is responsible for supplying NO lines with zero length +static void thick_lines_to_geometry( + const Lines3& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + GUI::GLModel::Geometry& geometry) +{ + assert(!lines.empty()); + if (lines.empty()) + return; + + enum Direction : unsigned char + { + Left, + Right, + Top, + Bottom + }; + + // left, right, top, bottom + std::array idx_prev = { -1, -1, -1, -1 }; + std::array idx_initial = { -1, -1, -1, -1 }; + + double z_prev = 0.0; + double len_prev = 0.0; + Vec3d n_right_prev = Vec3d::Zero(); + Vec3d n_top_prev = Vec3d::Zero(); + Vec3d unit_v_prev = Vec3d::Zero(); + double width_initial = 0.0; + + // new vertices around the line endpoints + // left, right, top, bottom + std::array a = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + std::array b = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + + // loop once more in case of closed loops + const size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ii) { + const size_t i = (ii == lines.size()) ? 0 : ii; + + const Line3& line = lines[i]; + const double height = heights[i]; + const double width = widths[i]; + + const Vec3d unit_v = unscale(line.vector()).normalized(); + const double len = unscale(line.length()); + + Vec3d n_top = Vec3d::Zero(); + Vec3d n_right = Vec3d::Zero(); + + if (line.a.x() == line.b.x() && line.a.y() == line.b.y()) { + // vertical segment + n_top = Vec3d::UnitY(); + n_right = Vec3d::UnitX(); + if (line.a.z() < line.b.z()) + n_right = -n_right; + } + else { + // horizontal segment + n_right = unit_v.cross(Vec3d::UnitZ()).normalized(); + n_top = n_right.cross(unit_v).normalized(); + } + + const Vec3d rl_displacement = 0.5 * width * n_right; + const Vec3d tb_displacement = 0.5 * height * n_top; + const Vec3d l_a = unscale(line.a); + const Vec3d l_b = unscale(line.b); + + a[Right] = l_a + rl_displacement; + a[Left] = l_a - rl_displacement; + a[Top] = l_a + tb_displacement; + a[Bottom] = l_a - tb_displacement; + b[Right] = l_b + rl_displacement; + b[Left] = l_b - rl_displacement; + b[Top] = l_b + tb_displacement; + b[Bottom] = l_b - tb_displacement; + + const Vec3d n_bottom = -n_top; + const Vec3d n_left = -n_right; + + std::array idx_a = { 0, 0, 0, 0}; + std::array idx_b = { 0, 0, 0, 0 }; + int idx_last = int(geometry.vertices_count()); + + const bool z_different = (z_prev != l_a.z()); + z_prev = l_b.z(); + + // Share top / bottom vertices if possible. + if (ii == 0) { + idx_a[Top] = idx_last++; + geometry.add_vertex((Vec3f)a[Top].cast(), (Vec3f)n_top.cast()); + } + else + idx_a[Top] = idx_prev[Top]; + + if (ii == 0 || z_different) { + // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. + idx_a[Bottom] = idx_last++; + geometry.add_vertex((Vec3f)a[Bottom].cast(), (Vec3f)n_bottom.cast()); + idx_a[Left] = idx_last++; + geometry.add_vertex((Vec3f)a[Left].cast(), (Vec3f)n_left.cast()); + idx_a[Right] = idx_last++; + geometry.add_vertex((Vec3f)a[Right].cast(), (Vec3f)n_right.cast()); + } + else + idx_a[Bottom] = idx_prev[Bottom]; + + if (ii == 0) { + // Start of the 1st line segment. + width_initial = width; + idx_initial = idx_a; + } + else { + // Continuing a previous segment. + // Share left / right vertices if possible. + const double v_dot = unit_v_prev.dot(unit_v); + const bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; + + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + const double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + const bool is_sharp = v_dot < 0.707 || len_prev > len_threshold || len > len_threshold; + if (is_sharp) { + // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. + idx_a[Right] = idx_last++; + geometry.add_vertex((Vec3f)a[Right].cast(), (Vec3f)n_right.cast()); + idx_a[Left] = idx_last++; + geometry.add_vertex((Vec3f)a[Left].cast(), (Vec3f)n_left.cast()); + + if (is_right_turn) { + // Right turn. Fill in the right turn wedge. + geometry.add_uint_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]); + geometry.add_uint_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]); + } + else { + // Left turn. Fill in the left turn wedge. + geometry.add_uint_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]); + geometry.add_uint_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]); + } + } + else { + // The two successive segments are nearly collinear. + idx_a[Left] = idx_prev[Left]; + idx_a[Right] = idx_prev[Right]; + } + + if (ii == lines.size()) { + if (!is_sharp) { + // Closing a loop with smooth transition. Unify the closing left / right vertices. + geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left])); + geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right])); + geometry.remove_vertex(geometry.vertices_count() - 1); + geometry.remove_vertex(geometry.vertices_count() - 1); + // Replace the left / right vertex indices to point to the start of the loop. + const size_t indices_count = geometry.indices_count(); + for (size_t u = indices_count - 24; u < indices_count; ++u) { + const unsigned int id = geometry.extract_uint_index(u); + if (id == (unsigned int)idx_prev[Left]) + geometry.set_uint_index(u, (unsigned int)idx_initial[Left]); + else if (id == (unsigned int)idx_prev[Right]) + geometry.set_uint_index(u, (unsigned int)idx_initial[Right]); + } + } + + // This is the last iteration, only required to solve the transition. + break; + } + } + + // Only new allocate top / bottom vertices, if not closing a loop. + if (closed && ii + 1 == lines.size()) + idx_b[Top] = idx_initial[Top]; + else { + idx_b[Top] = idx_last++; + geometry.add_vertex((Vec3f)b[Top].cast(), (Vec3f)n_top.cast()); + } + + if (closed && ii + 1 == lines.size() && width == width_initial) + idx_b[Bottom] = idx_initial[Bottom]; + else { + idx_b[Bottom] = idx_last++; + geometry.add_vertex((Vec3f)b[Bottom].cast(), (Vec3f)n_bottom.cast()); + } + + // Generate new vertices for the end of this line segment. + idx_b[Left] = idx_last++; + geometry.add_vertex((Vec3f)b[Left].cast(), (Vec3f)n_left.cast()); + idx_b[Right] = idx_last++; + geometry.add_vertex((Vec3f)b[Right].cast(), (Vec3f)n_right.cast()); + + idx_prev = idx_b; + n_right_prev = n_right; + n_top_prev = n_top; + unit_v_prev = unit_v; + len_prev = len; + + if (!closed) { + // Terminate open paths with caps. + if (i == 0) { + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]); + geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]); + } + + // We don't use 'else' because both cases are true if we have only one line. + if (i + 1 == lines.size()) { + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]); + geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]); + } + } + + // Add quads for a straight hollow tube-like segment. + // bottom-right face + geometry.add_uint_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]); + geometry.add_uint_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]); + // top-right face + geometry.add_uint_triangle(idx_a[Right], idx_b[Right], idx_b[Top]); + geometry.add_uint_triangle(idx_a[Right], idx_b[Top], idx_a[Top]); + // top-left face + geometry.add_uint_triangle(idx_a[Top], idx_b[Top], idx_b[Left]); + geometry.add_uint_triangle(idx_a[Top], idx_b[Left], idx_a[Left]); + // bottom-left face + geometry.add_uint_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]); + geometry.add_uint_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]); + } +} +#else // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, @@ -1724,7 +2320,30 @@ static void point_to_indexed_vertex_array(const Vec3crd& point, volume.push_triangle(idxs[3], idxs[1], idxs[4]); volume.push_triangle(idxs[0], idxs[3], idxs[4]); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::thick_lines_to_verts( + const Lines& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + double top_z, + GUI::GLModel::Geometry& geometry) +{ + thick_lines_to_geometry(lines, widths, heights, closed, top_z, geometry); +} + +void _3DScene::thick_lines_to_verts( + const Lines3& lines, + const std::vector& widths, + const std::vector& heights, + bool closed, + GUI::GLModel::Geometry& geometry) +{ + thick_lines_to_geometry(lines, widths, heights, closed, geometry); +} +#else void _3DScene::thick_lines_to_verts( const Lines &lines, const std::vector &widths, @@ -1766,8 +2385,21 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, flo { extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Fill in the qverts and tverts with quads and triangles for the extrusion_path. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) +{ + Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(copy); + const Lines lines = polyline.lines(); + std::vector widths(lines.size(), extrusion_path.width); + std::vector heights(lines.size(), extrusion_path.height); + thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); +} +#else void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume) { Polyline polyline = extrusion_path.polyline; @@ -1778,8 +2410,27 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, flo std::vector heights(lines.size(), extrusion_path.height); thick_lines_to_verts(lines, widths, heights, false, print_z, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Fill in the qverts and tverts with quads and triangles for the extrusion_loop. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) +{ + Lines lines; + std::vector widths; + std::vector heights; + for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) { + Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(copy); + const Lines lines_this = polyline.lines(); + append(lines, lines_this); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height); + } + thick_lines_to_verts(lines, widths, heights, true, print_z, geometry); +} +#else void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) { Lines lines; @@ -1796,8 +2447,27 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, flo } thick_lines_to_verts(lines, widths, heights, true, print_z, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) +{ + Lines lines; + std::vector widths; + std::vector heights; + for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) { + Polyline polyline = extrusion_path.polyline; + polyline.remove_duplicate_points(); + polyline.translate(copy); + const Lines lines_this = polyline.lines(); + append(lines, lines_this); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height); + } + thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); +} +#else void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) { Lines lines; @@ -1814,13 +2484,49 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_mult } thick_lines_to_verts(lines, widths, heights, false, print_z, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) +{ + for (const ExtrusionEntity* extrusion_entity : extrusion_entity_collection.entities) + extrusionentity_to_verts(extrusion_entity, print_z, copy, geometry); +} +#else void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) { for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities) extrusionentity_to_verts(extrusion_entity, print_z, copy, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void _3DScene::extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry) +{ + if (extrusion_entity != nullptr) { + auto* extrusion_path = dynamic_cast(extrusion_entity); + if (extrusion_path != nullptr) + extrusionentity_to_verts(*extrusion_path, print_z, copy, geometry); + else { + auto* extrusion_loop = dynamic_cast(extrusion_entity); + if (extrusion_loop != nullptr) + extrusionentity_to_verts(*extrusion_loop, print_z, copy, geometry); + else { + auto* extrusion_multi_path = dynamic_cast(extrusion_entity); + if (extrusion_multi_path != nullptr) + extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, geometry); + else { + auto* extrusion_entity_collection = dynamic_cast(extrusion_entity); + if (extrusion_entity_collection != nullptr) + extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, geometry); + else + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); + } + } + } + } +} +#else void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) { if (extrusion_entity != nullptr) { @@ -1839,9 +2545,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); - else { + else throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); - } } } } @@ -1860,5 +2565,6 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height { thick_point_to_verts(point, width, height, volume); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index c2e4e587ce..ed2aa804eb 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -46,6 +46,7 @@ enum ModelInstanceEPrintVolumeState : unsigned char; // Return appropriate color based on the ModelVolume. extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { @@ -246,6 +247,7 @@ public: private: BoundingBox m_bounding_box; }; +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL class GLVolume { public: @@ -388,11 +390,17 @@ public: // Is mouse or rectangle selection over this object to select/deselect it ? EHoverState hover; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GUI::GLModel model; +#else // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Ranges of triangle and quad indices to be rendered. std::pair tverts_range; +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL std::pair qverts_range; +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts // of the extrusions per layer. @@ -402,13 +410,17 @@ public: // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box() const { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + return this->model.get_bounding_box(); +#else BoundingBoxf3 out; - if (! this->indexed_vertex_array.bounding_box().isEmpty()) { + if (!this->indexed_vertex_array.bounding_box().isEmpty()) { out.min = this->indexed_vertex_array.bounding_box().min().cast(); out.max = this->indexed_vertex_array.bounding_box().max().cast(); out.defined = true; - }; + } return out; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } void set_color(const ColorRGBA& rgba) { color = rgba; } @@ -498,14 +510,20 @@ public: // convex hull const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + bool empty() const { return this->model.is_empty(); } +#else bool empty() const { return this->indexed_vertex_array.empty(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void set_range(double low, double high); - void render() const; + void render(); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); @@ -524,12 +542,20 @@ public: #endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { - //FIXME what to do wih m_convex_hull? + size_t cpu_memory_used() const { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } +#else + //FIXME what to do wih m_convex_hull? return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); } // Return an estimate of the memory held by GPU vertex buffers. size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } }; @@ -589,6 +615,36 @@ public: GLVolumeCollection() { set_default_slope_normal_z(); } ~GLVolumeCollection() { clear(); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + std::vector load_object( + const ModelObject* model_object, + int obj_idx, + const std::vector& instance_idxs); + + int load_object_volume( + const ModelObject* model_object, + int obj_idx, + int volume_idx, + int instance_idx); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject* print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp); + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#else + int load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else std::vector load_object( const ModelObject *model_object, int obj_idx, @@ -620,13 +676,20 @@ public: int load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLVolume* new_toolpath_volume(const ColorRGBA& rgba); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); +#else GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Render the volumes by OpenGL. void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. @@ -634,11 +697,12 @@ public: // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Clear the geometry void clear() { for (auto *v : volumes) delete v; volumes.clear(); } bool empty() const { return volumes.empty(); } - void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } + void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } @@ -683,9 +747,18 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo struct _3DScene { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); +#else static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); - static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); @@ -694,6 +767,7 @@ struct _3DScene static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL }; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c86d16a1f0..cfe6fe418a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -711,7 +711,11 @@ void GCodeViewer::init() m_gl_data_initialized = true; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print) +#else void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { // avoid processing if called with the same gcode_result #if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC @@ -750,7 +754,11 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr m_filament_densities = gcode_result.filament_densities; if (wxGetApp().is_editor()) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + load_shells(print); +#else load_shells(print, initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL else { Pointfs bed_shape; std::string texture; @@ -2289,7 +2297,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) progress_dialog->Destroy(); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GCodeViewer::load_shells(const Print& print) +#else void GCodeViewer::load_shells(const Print& print, bool initialized) +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL { if (print.objects().empty()) // no shells, return @@ -2306,7 +2318,11 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } size_t current_volumes_count = m_shells.volumes.volumes.size(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_shells.volumes.load_object(model_obj, object_id, instance_ids); +#else m_shells.volumes.load_object(model_obj, object_id, instance_ids, initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // adjust shells' z if raft is present const SlicingParameters& slicing_parameters = obj->slicing_parameters(); @@ -2330,6 +2346,15 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) const float depth = print.wipe_tower_data(extruders_count).depth; const float brim_width = print.wipe_tower_data(extruders_count).brim_width; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width); +#else + m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, !print.is_step_done(psWipeTower), brim_width, initialized); @@ -2337,6 +2362,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, !print.is_step_done(psWipeTower), brim_width, initialized); #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } @@ -3199,6 +3225,7 @@ void GCodeViewer::render_shells() if (shader == nullptr) return; +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // when the background processing is enabled, it may happen that the shells data have been loaded // before opengl has been initialized for the preview canvas. // when this happens, the volumes' data have not been sent to gpu yet. @@ -3206,6 +3233,7 @@ void GCodeViewer::render_shells() if (!v->indexed_vertex_array.has_VBOs()) v->finalize_geometry(true); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // glsafe(::glDepthMask(GL_FALSE)); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index b2b0626d51..bdb3ed983e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -823,7 +823,11 @@ public: void init(); // extract rendering data from the given parameters +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void load(const GCodeProcessorResult& gcode_result, const Print& print); +#else void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // recalculate ranges in dependence of what is visible and sets tool/print colors void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); #if ENABLE_PREVIEW_LAYOUT @@ -883,7 +887,11 @@ public: private: void load_toolpaths(const GCodeProcessorResult& gcode_result); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void load_shells(const Print& print); +#else void load_shells(const Print& print, bool initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #if !ENABLE_PREVIEW_LAYOUT void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; #endif // !ENABLE_PREVIEW_LAYOUT diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index edd62ce0ab..93c3c848fc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -526,7 +526,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (const GLVolume* glvolume : volumes.volumes) { + for (GLVolume* glvolume : volumes.volumes) { // Render the object using the layer editing shader and texture. if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; @@ -1192,9 +1192,11 @@ bool GLCanvas3D::init() if (m_main_toolbar.is_enabled()) m_layers_editing.init(); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() m_volumes.finalize_geometry(true); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (m_gizmos.is_enabled() && !m_gizmos.init()) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; @@ -1799,7 +1801,11 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.emplace_back(i); } } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + return m_volumes.load_object(&model_object, obj_idx, instance_idxs); +#else return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -2024,7 +2030,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh // later in this function. it->volume_idx = m_volumes.volumes.size(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx); +#else m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { @@ -2081,31 +2091,55 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re GLVolume &volume = *m_volumes.volumes[it->volume_idx]; if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) { // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.model.reset(); +#else volume.indexed_vertex_array.release_geometry(); - if (state.step[istep].state == PrintStateBase::DONE) { +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (state.step[istep].state == PrintStateBase::DONE) { TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); assert(! mesh.empty()); mesh.transform(sla_print->sla_trafo(*m_model->objects[volume.object_idx()]).inverse()); #if ENABLE_SMOOTH_NORMALS +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.model.init_from(mesh, true); +#else volume.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#else +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.model.init_from(mesh); #else volume.indexed_vertex_array.load_mesh(mesh); -#endif // ENABLE_SMOOTH_NORMALS - } else { - // Reload the original volume. -#if ENABLE_SMOOTH_NORMALS - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); -#else - volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #endif // ENABLE_SMOOTH_NORMALS } + else { + // Reload the original volume. +#if ENABLE_SMOOTH_NORMALS +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#else +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#else + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#endif // ENABLE_SMOOTH_NORMALS + } +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL volume.finalize_geometry(true); - } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables // of various concenrs (model vs. 3D print path). volume.offsets = { state.step[istep].timestamp }; - } else if (state.step[istep].state == PrintStateBase::DONE) { + } + else if (state.step[istep].state == PrintStateBase::DONE) { // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created. ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); @@ -2117,7 +2151,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); else shift_zs[object_idx] = 0.; - } else { + } + else { // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); @@ -2127,7 +2162,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re for (size_t istep = 0; istep < sla_steps.size(); ++istep) if (!instances[istep].empty()) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); +#else m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed @@ -2157,6 +2196,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float depth = print->wipe_tower_data(extruders_count).depth; float brim_width = print->wipe_tower_data(extruders_count).brim_width; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width); +#else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), @@ -2166,6 +2216,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), brim_width, m_initialized); #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -2225,9 +2276,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_dirty = true; } +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE) { - // Assign the large pre-allocated buffers to the new GLVolume. + // Assign the large pre-allocated buffers to the new GLVolume. vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array); // Copy the content back to the old GLVolume. vol_old.indexed_vertex_array = vol_new.indexed_vertex_array; @@ -2239,10 +2291,15 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& // Finalize the old geometry, possibly move data to the graphics card. vol_old.finalize_geometry(gl_initialized); } +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + m_gcode_viewer.load(gcode_result, *this->fff_print()); +#else m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (wxGetApp().is_editor()) { m_gcode_viewer.update_shells_color_by_extruder(m_config); @@ -4356,7 +4413,11 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const shader->set_uniform("emission_factor", 0.0f); for (GLVolume* vol : visible_volumes) { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + vol->model.set_color((vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#else shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : ColorRGBA::ORANGE()) : ColorRGBA::GRAY()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // the volume may have been deactivated by an active gizmo bool is_active = vol->is_active; vol->is_active = true; @@ -5562,6 +5623,12 @@ void GLCanvas3D::_render_overlays() void GLCanvas3D::_render_volumes_for_picking() const { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); @@ -5577,9 +5644,17 @@ void GLCanvas3D::_render_volumes_for_picking() const // we reserve color = (0,0,0) for occluders (as the printbed) // so we shift volumes' id by 1 to get the proper color const unsigned int id = 1 + volume.second.first; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume.first->model.set_color(picking_decode(id)); + shader->start_using(); +#else glsafe(::glColor4fv(picking_decode(id).data())); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL volume.first->render(); - } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + shader->stop_using(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -6165,23 +6240,48 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) skirt_height = std::min(skirt_height, print_zs.size()); print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLVolume* volume = m_volumes.new_toolpath_volume(color); + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; +#else GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL for (size_t i = 0; i < skirt_height; ++ i) { volume->print_zs.emplace_back(print_zs[i]); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume->offsets.emplace_back(init_data.indices_count()); + if (i == 0) + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data); +#else volume->offsets.emplace_back(volume->indexed_vertex_array.quad_indices.size()); volume->offsets.emplace_back(volume->indexed_vertex_array.triangle_indices.size()); if (i == 0) _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), *volume); _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + volume->model.init_from(std::move(init_data)); +#else if (volume->indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - GLVolume &vol = *volume; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLVolume &vol = *volume; volume = m_volumes.new_toolpath_volume(vol.color); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume->model.init_from(std::move(init_data)); + volume->is_outside = !contains(build_volume, volume->model); +#else volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); volume->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -6353,7 +6453,12 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c // Allocate the volume before locking. GLVolume *volume = new GLVolume(color); volume->is_extrusion_path = true; - tbb::spin_mutex::scoped_lock lock; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + tbb::spin_mutex::scoped_lock lock; // Lock by ROII, so if the emplace_back() fails, the lock will be released. lock.acquire(new_volume_mutex); m_volumes.volumes.emplace_back(volume); @@ -6365,31 +6470,57 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range& range) { GLVolumePtrs vols; - auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { - return *vols[ctxt.color_by_color_print()? +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + std::vector geometries; + auto select_geometry = [&ctxt, &geometries](size_t layer_idx, int extruder, int feature) -> GLModel::Geometry& { + return geometries[ctxt.color_by_color_print() ? ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : - ctxt.color_by_tool() ? - std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : - feature - ]; + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; }; +#else + auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) + for (size_t i = 0; i < ctxt.number_tools(); ++i) { vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } } - else + else { vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + geometries = { GLModel::Geometry(), GLModel::Geometry(), GLModel::Geometry() }; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; + } +#else for (GLVolume *vol : vols) // Reserving number of vertices (3x position + 3x color) vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; - if (is_selected_separate_extruder) - { + if (is_selected_separate_extruder) { bool at_least_one_has_correct_extruder = false; - for (const LayerRegion* layerm : layer->regions()) - { + for (const LayerRegion* layerm : layer->regions()) { if (layerm->slices.surfaces.empty()) continue; const PrintRegionConfig& cfg = layerm->region().config(); @@ -6404,17 +6535,27 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c continue; } - for (GLVolume *vol : vols) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume* vol = vols[i]; + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.emplace_back(layer->print_z); + vol->offsets.emplace_back(geometries[i].indices_count()); + } + } +#else + for (GLVolume* vol : vols) if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { vol->print_zs.emplace_back(layer->print_z); vol->offsets.emplace_back(vol->indexed_vertex_array.quad_indices.size()); vol->offsets.emplace_back(vol->indexed_vertex_array.triangle_indices.size()); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (const PrintInstance &instance : *ctxt.shifted_copies) { const Point © = instance.shift; for (const LayerRegion *layerm : layer->regions()) { - if (is_selected_separate_extruder) - { + if (is_selected_separate_extruder) { const PrintRegionConfig& cfg = layerm->region().config(); if (cfg.perimeter_extruder.value != m_selected_extruder || cfg.infill_extruder.value != m_selected_extruder || @@ -6422,19 +6563,31 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c continue; } if (ctxt.has_perimeters) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#else _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + select_geometry(idx_layer, is_solid_infill(fill->entities.front()->role()) ? + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, 1)); +#else _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, volume(idx_layer, is_solid_infill(fill->entities.front()->role()) ? layerm->region().config().solid_infill_extruder : layerm->region().config().infill_extruder, 1)); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } } @@ -6442,28 +6595,50 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c const SupportLayer *support_layer = dynamic_cast(layer); if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + select_geometry(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, 2)); +#else _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, volume(idx_layer, (extrusion_entity->role() == erSupportMaterial) ? support_layer->object()->config().support_material_extruder : support_layer->object()->config().support_material_interface_extruder, 2)); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } } // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { - vols[i] = new_volume(vol.color); - reserve_new_volume_finalize_old_volume(*vols[i], vol, false); - } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + vols[i] = new_volume(vol.color); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } } } + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else for (GLVolume *vol : vols) // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, // but this code runs in parallel and the OpenGL driver is not thread safe. vol->indexed_vertex_array.shrink_to_fit(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL }); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); @@ -6478,8 +6653,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); @@ -6499,10 +6680,10 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con struct Ctxt { - const Print *print; - const std::vector* tool_colors; - Vec2f wipe_tower_pos; - float wipe_tower_angle; + const Print *print; + const std::vector *tool_colors; + Vec2f wipe_tower_pos; + float wipe_tower_angle; static ColorRGBA color_support() { return ColorRGBA::GREENISH(); } @@ -6544,6 +6725,11 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con auto new_volume = [this, &new_volume_mutex](const ColorRGBA& color) { auto *volume = new GLVolume(color); volume->is_extrusion_path = true; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + // to prevent sending data to gpu (in the main thread) while + // editing the model geometry + volume->model.disable_render(); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL tbb::spin_mutex::scoped_lock lock; lock.acquire(new_volume_mutex); m_volumes.volumes.emplace_back(volume); @@ -6557,23 +6743,46 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con [&ctxt, &new_volume](const tbb::blocked_range& range) { // Bounding box of this slab of a wipe tower. GLVolumePtrs vols; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + std::vector geometries; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) + for (size_t i = 0; i < ctxt.number_tools(); ++i) { vols.emplace_back(new_volume(ctxt.color_tool(i))); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + geometries.emplace_back(GLModel::Geometry()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } } - else + else { vols = { new_volume(ctxt.color_support()) }; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + geometries = { GLModel::Geometry() }; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + } + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + assert(vols.size() == geometries.size()); + for (GLModel::Geometry& g : geometries) { + g.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; + } +#else for (GLVolume *volume : vols) // Reserving number of vertices (3x position + 3x color) volume->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { const std::vector &layer = ctxt.tool_change(idx_layer); for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { vol.print_zs.emplace_back(layer.front().print_z); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + vol.offsets.emplace_back(geometries[i].indices_count()); +#else vol.offsets.emplace_back(vol.indexed_vertex_array.quad_indices.size()); vol.offsets.emplace_back(vol.indexed_vertex_array.triangle_indices.size()); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } for (const WipeTower::ToolChangeResult &extrusions : layer) { @@ -6615,21 +6824,42 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con e_prev = e; } + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + geometries[ctxt.volume_idx(e.tool, 0)]); +#else _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.volume_idx(e.tool, 0)]); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } } for (size_t i = 0; i < vols.size(); ++i) { GLVolume &vol = *vols[i]; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + if (geometries[i].vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) { + vol.model.init_from(std::move(geometries[i])); +#else if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) { +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL vols[i] = new_volume(vol.color); +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL reserve_new_volume_finalize_old_volume(*vols[i], vol, false); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } } + +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + for (size_t i = 0; i < vols.size(); ++i) { + if (!geometries[i].is_empty()) + vols[i]->model.init_from(std::move(geometries[i])); + } +#else for (GLVolume *vol : vols) vol->indexed_vertex_array.shrink_to_fit(); - }); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + }); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. @@ -6643,8 +6873,14 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, con } for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + v->is_outside = !contains(build_volume, v->model); + // We are done editinig the model, now it can be sent to gpu + v->model.enable_render(); +#else v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); v->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); @@ -6668,11 +6904,21 @@ void GLCanvas3D::_load_sla_shells() m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); #if ENABLE_SMOOTH_NORMALS +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + v.model.init_from(mesh, true); +#else v.indexed_vertex_array.load_mesh(mesh, true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#else +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + v.model.init_from(mesh); #else v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #endif // ENABLE_SMOOTH_NORMALS +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.indexed_vertex_array.finalize_geometry(m_initialized); +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0)); diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 28a6a1ce7a..8620c7eaf8 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -8,15 +8,56 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #include #include +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_SMOOTH_NORMALS +#include +#include +#include +#endif // ENABLE_SMOOTH_NORMALS +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + #include namespace Slic3r { namespace GUI { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +#if ENABLE_SMOOTH_NORMALS +static void smooth_normals_corner(const TriangleMesh& mesh, std::vector& normals) +{ + using MapMatrixXfUnaligned = Eigen::Map>; + using MapMatrixXiUnaligned = Eigen::Map>; + + std::vector face_normals = its_face_normals(mesh.its); + + Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), + Eigen::Index(mesh.its.vertices.size()), 3).cast(); + Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), + Eigen::Index(mesh.its.indices.size()), 3); + Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(), + Eigen::Index(face_normals.size()), 3).cast(); + Eigen::MatrixXd out_normals; + + igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals); + + normals = std::vector(mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast(); + } + } +} +#endif // ENABLE_SMOOTH_NORMALS +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + #if ENABLE_GLBEGIN_GLEND_REMOVAL void GLModel::Geometry::reserve_vertices(size_t vertices_count) { @@ -207,6 +248,37 @@ Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const return { *(start + 0), *(start + 1) }; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GLModel::Geometry::set_vertex(size_t id, const Vec3f& position, const Vec3f& normal) +{ + assert(format.vertex_layout == EVertexLayout::P3N3); + assert(id < vertices_count()); + if (id < vertices_count()) { + float* start = &vertices[id * vertex_stride_floats(format)]; + *(start + 0) = position.x(); + *(start + 1) = position.y(); + *(start + 2) = position.z(); + *(start + 3) = normal.x(); + *(start + 4) = normal.y(); + *(start + 5) = normal.z(); + } +} + +void GLModel::Geometry::set_ushort_index(size_t id, unsigned short index) +{ + assert(id < indices_count()); + if (id < indices_count()) + ::memcpy(indices.data() + id * sizeof(unsigned short), &index, sizeof(unsigned short)); +} + +void GLModel::Geometry::set_uint_index(size_t id, unsigned int index) +{ + assert(id < indices_count()); + if (id < indices_count()) + ::memcpy(indices.data() + id * sizeof(unsigned int), &index, sizeof(unsigned int)); +} +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + unsigned int GLModel::Geometry::extract_uint_index(size_t id) const { if (format.index_type != EIndexType::UINT) { @@ -219,7 +291,7 @@ unsigned int GLModel::Geometry::extract_uint_index(size_t id) const return -1; } - unsigned int ret = -1; + unsigned int ret = (unsigned int)-1; ::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned int)); return ret; } @@ -236,11 +308,23 @@ unsigned short GLModel::Geometry::extract_ushort_index(size_t id) const return -1; } - unsigned short ret = -1; + unsigned short ret = (unsigned short)-1; ::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned short)); return ret; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GLModel::Geometry::remove_vertex(size_t id) +{ + assert(id < vertices_count()); + if (id < vertices_count()) { + size_t stride = vertex_stride_floats(format); + std::vector::iterator it = vertices.begin() + id * stride; + vertices.erase(it, it + stride); + } +} +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + size_t GLModel::Geometry::vertex_stride_floats(const Format& format) { switch (format.vertex_layout) @@ -461,10 +545,58 @@ void GLModel::init_from(const Geometry& data) } #if ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_SMOOTH_NORMALS +void GLModel::init_from(const TriangleMesh& mesh, bool smooth_normals) +{ + if (smooth_normals) { + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (mesh.its.vertices.empty() || mesh.its.indices.empty()) { + assert(false); + return; + } + + std::vector normals; + smooth_normals_corner(mesh, normals); + + const indexed_triangle_set& its = mesh.its; + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(3 * its.indices.size()) }; + data.reserve_vertices(3 * its.indices.size()); + data.reserve_indices(3 * its.indices.size()); + + // vertices + for (size_t i = 0; i < its.vertices.size(); ++i) { + data.add_vertex(its.vertices[i], normals[i]); + } + + // indices + for (size_t i = 0; i < its.indices.size(); ++i) { + const stl_triangle_vertex_indices& idx = its.indices[i]; + if (data.format.index_type == GLModel::Geometry::EIndexType::USHORT) + data.add_ushort_triangle((unsigned short)idx(0), (unsigned short)idx(1), (unsigned short)idx(2)); + else + data.add_uint_triangle((unsigned int)idx(0), (unsigned int)idx(1), (unsigned int)idx(2)); + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + } + } + else + init_from(mesh.its); +} +#else void GLModel::init_from(const TriangleMesh& mesh) { init_from(mesh.its); } +#endif // ENABLE_SMOOTH_NORMALS void GLModel::init_from(const indexed_triangle_set& its) #else @@ -484,21 +616,24 @@ 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.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(3 * its.indices.size()) }; data.reserve_vertices(3 * its.indices.size()); data.reserve_indices(3 * its.indices.size()); // vertices + indices unsigned int vertices_counter = 0; for (uint32_t i = 0; i < its.indices.size(); ++i) { - stl_triangle_vertex_indices face = its.indices[i]; - stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; - stl_vertex n = face_normal_normalized(vertex); + const stl_triangle_vertex_indices face = its.indices[i]; + const stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; + const stl_vertex n = face_normal_normalized(vertex); for (size_t j = 0; j < 3; ++j) { data.add_vertex(vertex[j], n); } vertices_counter += 3; - data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + if (data.format.index_type == GLModel::Geometry::EIndexType::USHORT) + data.add_ushort_triangle((unsigned short)vertices_counter - 3, (unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1); + else + data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } // update bounding box @@ -721,6 +856,9 @@ void GLModel::render() void GLModel::render() const #endif // ENABLE_GLBEGIN_GLEND_REMOVAL { +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + render(std::make_pair(0, indices_count())); +#else GLShaderProgram* shader = wxGetApp().get_current_shader(); #if ENABLE_GLBEGIN_GLEND_REMOVAL @@ -809,8 +947,71 @@ void GLModel::render() const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +void GLModel::render(const std::pair& range) +{ + if (m_render_disabled) + return; + + if (range.second == range.first) + return; + + GLShaderProgram* shader = wxGetApp().get_current_shader(); + + if (shader == nullptr) + return; + + // sends data to gpu if not done yet + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (m_render_data.geometry.vertices_count() > 0 && m_render_data.geometry.indices_count() > 0 && !send_to_gpu()) + return; + } + + const Geometry& data = m_render_data.geometry; + + 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); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + if (position) { + glsafe(::glVertexPointer(Geometry::position_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + } + if (normal) { + glsafe(::glNormalPointer(GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } + if (tex_coord) { + glsafe(::glTexCoordPointer(Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + } + + shader->set_uniform("uniform_color", data.color); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElements(mode, range.second - range.first + 1, index_type, (const void*)(range.first * Geometry::index_stride_bytes(data.format)))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (tex_coord) + glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); + if (normal) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + if (position) + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + #if ENABLE_GLBEGIN_GLEND_REMOVAL void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) #else @@ -1027,6 +1228,62 @@ static void append_triangle(GLModel::Geometry& data, unsigned short v1, unsigned } #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +template +inline bool all_vertices_inside(const GLModel::Geometry& geometry, Fn fn) +{ + const size_t position_stride_floats = geometry.position_stride_floats(geometry.format); + const size_t position_offset_floats = geometry.position_offset_floats(geometry.format); + assert(position_stride_floats == 3); + if (geometry.vertices.empty() || position_stride_floats != 3) + return false; + + for (auto it = geometry.vertices.begin(); it != geometry.vertices.end(); ) { + it += position_offset_floats; + if (!fn({ *it, *(it + 1), *(it + 2) })) + return false; + it += (geometry.vertex_stride_floats(geometry.format) - position_offset_floats - position_stride_floats); + } + return true; +} + +bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom) +{ + static constexpr const double epsilon = BuildVolume::BedEpsilon; + switch (volume.type()) { + case BuildVolume::Type::Rectangle: + { + BoundingBox3Base build_volume = volume.bounding_volume().inflated(epsilon); + if (volume.max_print_height() == 0.0) + build_volume.max.z() = std::numeric_limits::max(); + if (ignore_bottom) + build_volume.min.z() = -std::numeric_limits::max(); + const BoundingBoxf3& model_box = model.get_bounding_box(); + return build_volume.contains(model_box.min) && build_volume.contains(model_box.max); + } + case BuildVolume::Type::Circle: + { + const Geometry::Circled& circle = volume.circle(); + const Vec2f c = unscaled(circle.center); + const float r = unscaled(circle.radius) + float(epsilon); + const float r2 = sqr(r); + return volume.max_print_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [c, r2](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2; }) : + + all_vertices_inside(model.get_geometry(), [c, r2, z = volume.max_print_height() + epsilon](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; }); + } + case BuildVolume::Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case BuildVolume::Type::Custom: + return volume.max_print_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [&volume](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()); }) : + all_vertices_inside(model.get_geometry(), [&volume, z = volume.max_print_height() + epsilon](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()) && p.z() <= z; }); + default: + return true; + } +} +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) { #if !ENABLE_GLBEGIN_GLEND_REMOVAL diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 72c50ee11c..3b268dab7e 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -14,6 +14,9 @@ namespace Slic3r { class TriangleMesh; class Polygon; using Polygons = std::vector; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL +class BuildVolume; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL namespace GUI { @@ -89,6 +92,13 @@ namespace GUI { void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2 void add_vertex(const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 + + void set_ushort_index(size_t id, unsigned short index); + void set_uint_index(size_t id, unsigned int index); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void add_ushort_index(unsigned short id); void add_uint_index(unsigned int id); @@ -106,7 +116,11 @@ 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(); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void remove_vertex(size_t id); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + + bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; } size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); } size_t indices_count() const { return indices.size() / index_stride_bytes(format); } @@ -179,6 +193,16 @@ namespace GUI { std::vector m_render_data; #endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + // By default the vertex and index buffers data are sent to gpu at the first call to render() method. + // If you need to initialize a model from outside the main thread, so that a call to render() may happen + // before the initialization is complete, use the methods: + // disable_render() + // ... do your initialization ... + // enable_render() + // to keep the data on cpu side until needed. + bool m_render_disabled{ false }; +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL BoundingBoxf3 m_bounding_box; std::string m_filename; @@ -197,8 +221,16 @@ namespace GUI { size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry.format); } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + const Geometry& get_geometry() const { return m_render_data.geometry; } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void init_from(Geometry&& data); +#if ENABLE_SMOOTH_NORMALS + void init_from(const TriangleMesh& mesh, bool smooth_normals = false); +#else void init_from(const TriangleMesh& mesh); +#endif // ENABLE_SMOOTH_NORMALS #else void init_from(const Geometry& data); void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox); @@ -219,9 +251,15 @@ namespace GUI { void reset(); #if ENABLE_GLBEGIN_GLEND_REMOVAL void render(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + void render(const std::pair& range); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL void render_instanced(unsigned int instances_vbo, unsigned int instances_count); bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + bool is_empty() const { return m_render_data.geometry.is_empty(); } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL #else void render() const; void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; @@ -232,6 +270,29 @@ namespace GUI { const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::string& get_filename() const { return m_filename; } +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + bool is_render_disabled() const { return m_render_disabled; } + void enable_render() { m_render_disabled = false; } + void disable_render() { m_render_disabled = true; } + + size_t cpu_memory_used() const { + size_t ret = 0; + if (!m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (!m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } + size_t gpu_memory_used() const { + size_t ret = 0; + if (m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + private: #if ENABLE_GLBEGIN_GLEND_REMOVAL bool send_to_gpu(); @@ -240,6 +301,10 @@ namespace GUI { #endif // ENABLE_GLBEGIN_GLEND_REMOVAL }; +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom = true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution // the origin of the arrow is in the center of the stem cap // the arrow has its axis of symmetry along the Z axis and is pointing upward diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 515da6de3a..8cf3247cb7 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -98,7 +98,7 @@ namespace GUI { color[1] = (m_state == Select) ? 1.0f : 0.3f; color[2] = 0.3f; glsafe(::glColor3fv(color)); -#endif // ENABLE_GLBEGIN_GLEND_REMOVAL +#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL glsafe(::glDisable(GL_DEPTH_TEST)); diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 1191e5c2e6..0bc741c962 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -274,9 +274,13 @@ static void generate_thumbnail_from_model(const std::string& filename) GLVolumeCollection volumes; volumes.volumes.push_back(new GLVolume()); - GLVolume* volume = volumes.volumes[0]; + GLVolume* volume = volumes.volumes.back(); +#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL + volume->model.init_from(model.mesh()); +#else volume->indexed_vertex_array.load_mesh(model.mesh()); volume->indexed_vertex_array.finalize_geometry(true); +#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL volume->set_instance_transformation(model.objects[0]->instances[0]->get_transformation()); volume->set_volume_transformation(model.objects[0]->volumes[0]->get_transformation()); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e70c1111b9..4b75befb6c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1444,8 +1444,8 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) ImGuiIO& io = ImGui::GetIO(); - int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); + const int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); + const int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); if (fb_width == 0 || fb_height == 0) return; draw_data->ScaleClipRects(io.DisplayFramebufferScale); @@ -1488,8 +1488,7 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) // Render command lists ImVec2 pos = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { + for (int n = 0; n < draw_data->CmdListsCount; ++n) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; @@ -1497,19 +1496,14 @@ void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) glsafe(::glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv)))); glsafe(::glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col)))); - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; ++cmd_i) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback) - { // User callback (registered via ImDrawList::AddCallback) pcmd->UserCallback(cmd_list, pcmd); - } - else - { + else { ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y); - if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) - { + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Apply scissor/clipping rectangle glsafe(::glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y))); From 0a280be8d09b77b3e49cdde0a8298b8d30f6ebc9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 3 Feb 2022 21:58:52 +0100 Subject: [PATCH 32/33] Placeholder parser fixes --- src/libslic3r/GCode.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fb6dee37ba..9ce55d0bb3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -274,7 +274,6 @@ namespace Slic3r { // Otherwise, leave control to the user completely. std::string toolchange_gcode_str; const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; -// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z); if (! toolchange_gcode.empty()) { DynamicConfig config; int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; @@ -283,7 +282,7 @@ namespace Slic3r { config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); -// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); check_add_eol(toolchange_gcode_str); } @@ -305,6 +304,9 @@ namespace Slic3r { if (!start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); check_add_eol(start_filament_gcode_str); @@ -1274,15 +1276,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write the custom start G-code file.writeln(start_gcode); - // Process filament-specific gcode. - /* if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the start_filament_gcode. - } else { - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - file.writeln(this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } -*/ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); @@ -1899,6 +1892,8 @@ namespace ProcessLayer // && !MMU1 ) { //! FIXME_in_fw show message during print pause + // FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? Why is that not + // passed to color_change_gcode below? DynamicConfig cfg; cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer)); gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg); @@ -2109,10 +2104,10 @@ GCode::LayerResult GCode::process_layer( DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); gcode += this->placeholder_parser_process("layer_gcode", print.config().layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); } if (! first_layer && ! m_second_layer_things_done) { @@ -3146,7 +3141,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the filament. - gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); } gcode += m_writer.toolchange(extruder_id); @@ -3215,7 +3212,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the new filament. - gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config); check_add_eol(gcode); } // Set the new extruder to the operating temperature. From 148ed7fee48d16c7f5f8f46ec78b1e7b7c2525d2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 23 Feb 2022 14:27:10 +0100 Subject: [PATCH 33/33] Follow-up of 1eac357739d44fcdcfbbb742b888b51ce8858bba - Fixed warning --- src/slic3r/GUI/GLCanvas3D.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a37d320749..acf0a98852 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -87,9 +87,11 @@ static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f // Number of floats static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB // Reserve size in number of floats. +#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB // Reserve size in number of floats, maximum sum of all preallocated buffers. //static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB +#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL namespace Slic3r { namespace GUI {