diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 35bbfa50a..c0f768088 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -56,8 +56,7 @@ public: // Color mapping to convert a float into a smooth rainbow of 10 colors. class RangeBase { - public: - + public: virtual void reset() = 0; virtual bool empty() const = 0; virtual float min() const = 0; @@ -73,7 +72,7 @@ public: // Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors. class Range : public RangeBase { - public: + public: Range(); // RangeBase Overrides @@ -97,8 +96,7 @@ public: template class MultiRange : public RangeBase { - public: - + public: void reset() override { bounds = decltype(bounds){}; @@ -160,8 +158,7 @@ public: mode.set(static_cast(range_type_value), enable); } - private: - + private: // Interval bounds struct Bounds { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1c828198d..10282642c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -59,14 +59,38 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role) +void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height) { unsigned int id = static_cast(data.size()); - paths.push_back({ type, role, id, id }); + paths.push_back({ type, role, id, id, height }); +} + +std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const +{ + // Input value scaled to the color range + const float step = step_size(); + const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f + + const size_t color_max_idx = colors.size() - 1; + + // Compute the two colors just below (low) and above (high) the input value + const size_t color_low_idx = std::clamp(static_cast(global_t), std::size_t{ 0 }, color_max_idx); + const size_t color_high_idx = std::clamp(color_low_idx + 1, std::size_t{ 0 }, color_max_idx); + + // Compute how far the value is between the low and high colors so that they can be interpolated + const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f + + // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get + std::array ret; + for (unsigned int i = 0; i < 4; ++i) + { + ret[i] = lerp(colors[color_low_idx][i], colors[color_high_idx][i], local_t); + } + return ret; } const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ - { 0.00f, 0.00f, 0.00f, 1.0f }, // erNone + { 1.00f, 1.00f, 1.00f, 1.0f }, // erNone { 1.00f, 1.00f, 0.40f, 1.0f }, // erPerimeter { 1.00f, 0.65f, 0.00f, 1.0f }, // erExternalPerimeter { 0.00f, 0.00f, 1.00f, 1.0f }, // erOverhangPerimeter @@ -83,6 +107,19 @@ const std::array, erCount> GCodeViewer::Default_Extrusion_R { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; +const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ + { 0.043f, 0.173f, 0.478f, 1.0f }, + { 0.075f, 0.349f, 0.522f, 1.0f }, + { 0.110f, 0.533f, 0.569f, 1.0f }, + { 0.016f, 0.839f, 0.059f, 1.0f }, + { 0.667f, 0.949f, 0.000f, 1.0f }, + { 0.988f, 0.975f, 0.012f, 1.0f }, + { 0.961f, 0.808f, 0.039f, 1.0f }, + { 0.890f, 0.533f, 0.125f, 1.0f }, + { 0.820f, 0.408f, 0.188f, 1.0f }, + { 0.761f, 0.322f, 0.235f, 1.0f } +}}; + void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { if (m_last_result_id == gcode_result.id) @@ -108,6 +145,7 @@ void GCodeViewer::reset() m_bounding_box = BoundingBoxf3(); m_extrusions.reset_role_visibility_flags(); + m_extrusions.reset_ranges(); m_shells.volumes.clear(); m_layers_zs = std::vector(); m_roles = std::vector(); @@ -241,17 +279,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - buffer.add_path(curr.type, curr.extrusion_role); + buffer.add_path(curr.type, curr.extrusion_role, curr.height); buffer.data.push_back(static_cast(i)); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - if (prev.type != curr.type) + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr.type, curr.extrusion_role); + buffer.add_path(curr.type, curr.extrusion_role, curr.height); buffer.data.push_back(static_cast(i - 1)); + + if (curr.type == GCodeProcessor::EMoveType::Extrude) + { + m_extrusions.ranges.height.update_from(curr.height); + } } buffer.paths.back().last = static_cast(buffer.data.size()); @@ -375,6 +418,10 @@ void GCodeViewer::render_toolpaths() const break; } case EViewType::Height: + { + color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); + break; + } case EViewType::Width: case EViewType::Feedrate: case EViewType::FanSpeed: @@ -513,8 +560,9 @@ void GCodeViewer::render_shells() const void GCodeViewer::render_overlay() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const float ICON_BORDER_SIZE = 20.0f; + static const float ICON_BORDER_SIZE = 25.0f; static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + static const float GAP_ICON_TEXT = 5.0f; if (!m_legend_enabled || m_roles.empty()) return; @@ -551,16 +599,34 @@ void GCodeViewer::render_overlay() const { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& role_color = m_extrusions.role_colors[static_cast(role)]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(role_color[0], role_color[1], role_color[2], role_color[3])); + const std::array& color = m_extrusions.role_colors[static_cast(role)]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + 4.0f); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); ImGui::AlignTextToFramePadding(); imgui.text(Slic3r::I18N::translate(ExtrusionEntity::role_to_string(role))); } break; } - case EViewType::Height: { break; } + case EViewType::Height: + { + float step_size = m_extrusions.ranges.height.step_size(); + for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) + { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + const std::array& color = m_extrusions.ranges.colors[i]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); + ImGui::AlignTextToFramePadding(); + char buf[1024]; + ::sprintf(buf, "%.*f", 3, m_extrusions.ranges.height.min + static_cast(i) * step_size); + imgui.text(buf); + } + + break; + } case EViewType::Width: { break; } case EViewType::Feedrate: { break; } case EViewType::FanSpeed: { break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 4ee187144..5185f5409 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -7,6 +7,8 @@ #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" +#include + namespace Slic3r { class Print; namespace GUI { @@ -14,6 +16,8 @@ namespace GUI { class GCodeViewer { static const std::array, erCount> Default_Extrusion_Role_Colors; + static const size_t Default_Range_Colors_Count = 10; + static const std::array, Default_Range_Colors_Count> Default_Range_Colors; // buffer containing vertices data struct VBuffer @@ -29,14 +33,16 @@ class GCodeViewer static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; + // Used to identify different toolpath sub-types inside a IBuffer struct Path { GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; ExtrusionRole role{ erNone }; unsigned int first{ 0 }; unsigned int last{ 0 }; + float height{ 0.0f }; - bool matches(GCodeProcessor::EMoveType type, ExtrusionRole role) const { return this->type == type && this->role == role; } + bool matches(const GCodeProcessor::MoveVertex& move) const { return type == move.type && role == move.extrusion_role && height == move.height; } }; // buffer containing indices data and shader for a specific toolpath type @@ -52,7 +58,7 @@ class GCodeViewer void reset(); bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); - void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role); + void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height); }; struct Shells @@ -62,10 +68,55 @@ class GCodeViewer Shader shader; }; + // helper to render extrusion paths struct Extrusions { + struct Range + { + float min; + float max; + + Range() { reset(); } + + void update_from(const float value) + { + min = std::min(min, value); + max = std::max(max, value); + } + + void reset() + { + min = FLT_MAX; + max = -FLT_MAX; + } + + float step_size() const { return (max - min) / static_cast(Default_Range_Colors_Count); } + std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; + }; + + struct Ranges + { + std::array, Default_Range_Colors_Count> colors; + + // Color mapping by layer height. + Range height; +// // Color mapping by extrusion width. +// Range width; +// // Color mapping by feedrate. +// MultiRange feedrate; +// // Color mapping by fan speed. +// Range fan_speed; +// // Color mapping by volumetric extrusion rate. +// Range volumetric_rate; + + void reset() { + height.reset(); + } + }; + std::array, erCount> role_colors; unsigned int role_visibility_flags{ 0 }; + Ranges ranges; void reset_role_visibility_flags() { role_visibility_flags = 0; @@ -75,6 +126,8 @@ class GCodeViewer } } + void reset_ranges() { ranges.reset(); } + static bool is_role_visible(unsigned int flags, ExtrusionRole role) { return role < erCount && (flags & (1 << role)) != 0; } @@ -113,6 +166,7 @@ public: bool init() { m_extrusions.role_colors = Default_Extrusion_Role_Colors; + m_extrusions.ranges.colors = Default_Range_Colors; set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); }