mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 11:37:42 +00:00
Compare commits
13 Commits
feat/ota-o
...
hanif/prev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d7addc9c4 | ||
|
|
a2f0606d32 | ||
|
|
36cd425bb3 | ||
|
|
c2eea1cada | ||
|
|
038e86f02a | ||
|
|
55ef9d2afd | ||
|
|
63d00f7cf1 | ||
|
|
f2143d3c91 | ||
|
|
e0275c4d69 | ||
|
|
77d00c0ee4 | ||
|
|
8fd5d77220 | ||
|
|
7cf9884952 | ||
|
|
c2c572d228 |
@@ -202,6 +202,39 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: simplify the preview while the user is dragging: what is left out and one layer in how many is kept
|
||||
if (get("preview_reduced_detail_while_dragging").empty())
|
||||
set_bool("preview_reduced_detail_while_dragging", false);
|
||||
|
||||
{
|
||||
const std::string mode = get("preview_reduced_detail_mode");
|
||||
if (mode != "layers" && mode != "no_infill" && mode != "shell" && mode != "solid")
|
||||
set("preview_reduced_detail_mode", "no_infill");
|
||||
}
|
||||
|
||||
// ORCA: keep the drawn preview scene while nothing in it changes
|
||||
if (get("preview_cache_static_scene").empty())
|
||||
set_bool("preview_cache_static_scene", false);
|
||||
|
||||
{
|
||||
const std::string mode = get("preview_rest_detail_mode");
|
||||
if (mode != "full" && mode != "no_infill" && mode != "shell")
|
||||
set("preview_rest_detail_mode", "full");
|
||||
}
|
||||
|
||||
if (get("preview_reduced_detail_layer_stride").empty())
|
||||
set("preview_reduced_detail_layer_stride", "4");
|
||||
else {
|
||||
int stride = 4;
|
||||
try {
|
||||
stride = std::stoi(get("preview_reduced_detail_layer_stride"));
|
||||
}
|
||||
catch (...) {
|
||||
stride = 4;
|
||||
}
|
||||
set("preview_reduced_detail_layer_stride", std::to_string(std::max(1, std::min(stride, 20))));
|
||||
}
|
||||
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
if (get("preview_dim_previous_layers").empty())
|
||||
set_bool("preview_dim_previous_layers", false);
|
||||
|
||||
@@ -158,6 +158,26 @@ enum class EGCodeExtrusionRole : uint8_t
|
||||
|
||||
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
|
||||
|
||||
//
|
||||
// ORCA: what the reduced toolpath set drawn while the user is dragging leaves out, on top of
|
||||
// keeping only one layer in every Viewer::get_reduced_detail_layer_stride() layers
|
||||
//
|
||||
enum class EReducedDetailMode : uint8_t
|
||||
{
|
||||
// no reduced set is built and Viewer::set_reduced_detail() has no effect
|
||||
Off,
|
||||
// every role is kept, only layers are skipped
|
||||
LayersOnly,
|
||||
// the interior infill roles are left out
|
||||
NoInternalInfill,
|
||||
// only the segments on the visible surface of the print are kept
|
||||
ShellOnly,
|
||||
// only the bottom and top of the visible layer range are kept, for a caller that draws the
|
||||
// print itself some other way and needs just the two faces the range cuts open
|
||||
EndLayersOnly,
|
||||
COUNT
|
||||
};
|
||||
|
||||
//
|
||||
// Option types
|
||||
//
|
||||
|
||||
@@ -98,6 +98,46 @@ public:
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// ORCA: draw the preview from a reduced set of entities: one layer in every
|
||||
// get_reduced_detail_layer_stride() layers is kept, and on top of that the mode decides which
|
||||
// roles or segments are left out. The bottom and top of the visible layer range are always kept
|
||||
// whole. Meant to be held only while the user drags the camera or a slider: the reduced set is
|
||||
// built alongside the full one, so toggling it never rebuilds anything. Nothing is built while
|
||||
// the mode is EReducedDetailMode::Off. Has no effect on the OpenGL ES path.
|
||||
//
|
||||
void set_reduced_detail(bool value);
|
||||
bool is_reduced_detail() const;
|
||||
EReducedDetailMode get_reduced_detail_mode() const;
|
||||
void set_reduced_detail_mode(EReducedDetailMode mode);
|
||||
uint32_t get_reduced_detail_layer_stride() const;
|
||||
void set_reduced_detail_layer_stride(uint32_t value);
|
||||
//
|
||||
// ORCA: what is left out even at rest, with every layer drawn. Off draws everything.
|
||||
// EReducedDetailMode::LayersOnly means the same as Off here.
|
||||
//
|
||||
EReducedDetailMode get_rest_detail_mode() const;
|
||||
void set_rest_detail_mode(EReducedDetailMode mode);
|
||||
//
|
||||
// ORCA: with EReducedDetailMode::ShellOnly at rest, draw the walls of one layer in every N, each
|
||||
// N layers tall, keeping the exposed surfaces of every layer. Choose N from how many layers fit
|
||||
// in a pixel at the current view and it changes nothing visible.
|
||||
//
|
||||
uint32_t get_rest_layer_stride() const;
|
||||
void set_rest_layer_stride(uint32_t value);
|
||||
//
|
||||
// ORCA: whether the camera is above the print. With layers skipped at rest, only the surfaces
|
||||
// that side of the print can see are kept in the skipped layers.
|
||||
//
|
||||
bool is_rest_view_from_above() const;
|
||||
void set_rest_view_from_above(bool value);
|
||||
//
|
||||
// ORCA: a counter that changes whenever what render() draws changes, and whether an update is
|
||||
// still pending that the next render() will apply. Together they tell a caller whether a frame
|
||||
// it has kept from an earlier render() can be shown again.
|
||||
//
|
||||
uint64_t get_state_version() const;
|
||||
bool has_pending_updates() const;
|
||||
float get_dim_previous_layers_brightness() const;
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
//
|
||||
|
||||
@@ -15,7 +15,12 @@ namespace libvgcode {
|
||||
//| 2--0-------5--7 |
|
||||
//| \ | | / |
|
||||
//| 3-------4 |
|
||||
static constexpr const std::array<uint8_t, 24> VERTEX_DATA = {
|
||||
// The eight corners the vertex shader knows how to place. Each is sent once and
|
||||
// referenced by INDEX_DATA below, so the post-transform cache can reuse it across
|
||||
// the triangles that share it: the shader runs 8 times per segment instead of 24.
|
||||
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
static constexpr const std::array<uint8_t, 24> INDEX_DATA = {
|
||||
0, 1, 2, // front spike
|
||||
0, 2, 3, // front spike
|
||||
0, 3, 4, // right/bottom body
|
||||
@@ -31,7 +36,7 @@ void SegmentTemplate::init()
|
||||
if (m_vao_id != 0)
|
||||
return;
|
||||
|
||||
m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t);
|
||||
m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t);
|
||||
|
||||
int curr_vertex_array;
|
||||
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
||||
@@ -51,12 +56,22 @@ void SegmentTemplate::init()
|
||||
glsafe(glVertexAttribIPointer(0, 1, GL_UNSIGNED_BYTE, 0, (const void*)0));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
// The element buffer binding is part of the vao state, so it is left bound here
|
||||
// and restored together with the vao.
|
||||
glsafe(glGenBuffers(1, &m_ibo_id));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
|
||||
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW));
|
||||
|
||||
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
|
||||
glsafe(glBindVertexArray(curr_vertex_array));
|
||||
}
|
||||
|
||||
void SegmentTemplate::shutdown()
|
||||
{
|
||||
if (m_ibo_id != 0) {
|
||||
glsafe(glDeleteBuffers(1, &m_ibo_id));
|
||||
m_ibo_id = 0;
|
||||
}
|
||||
if (m_vbo_id != 0) {
|
||||
glsafe(glDeleteBuffers(1, &m_vbo_id));
|
||||
m_vbo_id = 0;
|
||||
@@ -71,14 +86,15 @@ void SegmentTemplate::shutdown()
|
||||
|
||||
void SegmentTemplate::render(size_t count)
|
||||
{
|
||||
if (m_vao_id == 0 || m_vbo_id == 0 || count == 0)
|
||||
if (m_vao_id == 0 || m_vbo_id == 0 || m_ibo_id == 0 || count == 0)
|
||||
return;
|
||||
|
||||
int curr_vertex_array;
|
||||
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
||||
|
||||
glsafe(glBindVertexArray(m_vao_id));
|
||||
glsafe(glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast<GLsizei>(VERTEX_DATA.size()), static_cast<GLsizei>(count)));
|
||||
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_BYTE,
|
||||
nullptr, static_cast<GLsizei>(count)));
|
||||
glsafe(glBindVertexArray(curr_vertex_array));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ private:
|
||||
//
|
||||
unsigned int m_vao_id{ 0 };
|
||||
unsigned int m_vbo_id{ 0 };
|
||||
unsigned int m_ibo_id{ 0 };
|
||||
//
|
||||
// Size of the data sent to gpu, in bytes.
|
||||
//
|
||||
|
||||
@@ -25,6 +25,24 @@ struct Settings
|
||||
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
|
||||
float dim_previous_layers_brightness{ 0.4f };
|
||||
bool spiral_vase_mode{ false };
|
||||
// ORCA: while the user drags the camera or a slider, the preview can be drawn from a reduced
|
||||
// set of entities: one layer in every reduced_detail_layer_stride kept, and on top of that
|
||||
// whatever reduced_detail_mode leaves out. The reduced sets are built alongside the full ones
|
||||
// in update_enabled_entities(), so holding reduced_detail costs nothing but a buffer binding.
|
||||
// Ignored on the OpenGL ES path, which keeps a single set of entities.
|
||||
bool reduced_detail{ false };
|
||||
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
|
||||
uint32_t reduced_detail_layer_stride{ 4 };
|
||||
// ORCA: what is left out even at rest, with every layer drawn. Bound whenever the reduced set
|
||||
// above is not. Off draws everything.
|
||||
EReducedDetailMode rest_detail_mode{ EReducedDetailMode::Off };
|
||||
// ORCA: in ShellOnly rest mode, the walls of one layer in this many are drawn, that many layers
|
||||
// tall; the exposed surfaces of every layer stay. Meant to follow how many layers fit in a
|
||||
// pixel at the current view, so that it changes nothing visible.
|
||||
uint32_t rest_layer_stride{ 1 };
|
||||
// ORCA: whether the camera is above the print; decides whether the surfaces kept in the
|
||||
// skipped layers at rest are the ones visible from above or from below
|
||||
bool rest_view_from_above{ true };
|
||||
//
|
||||
// Required update flags
|
||||
//
|
||||
|
||||
@@ -26,6 +26,8 @@ static const char* Segments_Vertex_Shader =
|
||||
"uniform mat4 view_matrix;\n"
|
||||
"uniform mat4 projection_matrix;\n"
|
||||
"uniform vec3 camera_position;\n"
|
||||
"// ORCA: how many layers each drawn segment stands in for while layers are being skipped\n"
|
||||
"uniform float height_scale;\n"
|
||||
"uniform samplerBuffer position_tex;\n"
|
||||
"uniform samplerBuffer height_width_angle_tex;\n"
|
||||
"uniform samplerBuffer color_tex;\n"
|
||||
@@ -112,7 +114,20 @@ static const char* Segments_Vertex_Shader =
|
||||
"#endif\n"
|
||||
" float view_right_sign = sign(dot(-camera_view_dir, line_right_dir));\n"
|
||||
" float view_top_sign = sign(dot(-camera_view_dir, line_up_dir));\n"
|
||||
" float half_height = 0.5 * height_width_angle.x;\n"
|
||||
" // ORCA: the cross-section is a diamond, full width at mid-height and a point at top and bottom.\n"
|
||||
" // A segment grown to stand in for skipped layers would leave a notch that deep between itself\n"
|
||||
" // and the next one, so its four profile points become the corners of a rectangle instead:\n"
|
||||
" // top to top-left, right to top-right, bottom to bottom-right, left to bottom-left, which keeps\n"
|
||||
" // the winding and turns the two drawn faces into a flat top and a flat camera-facing side.\n"
|
||||
" if (height_scale > 1.0) {\n"
|
||||
" if (signs.y > 0.0) signs = vec2(-1.0, 1.0);\n"
|
||||
" else if (signs.y < 0.0) signs = vec2(1.0, -1.0);\n"
|
||||
" else if (signs.x > 0.0) signs = vec2(1.0, 1.0);\n"
|
||||
" else signs = vec2(-1.0, -1.0);\n"
|
||||
" }\n"
|
||||
" // ORCA: a segment standing in for the skipped layers below it grows downward to cover them\n"
|
||||
" endpoint_pos -= (height_scale - 1.0) * 0.5 * height_width_angle.x * line_up_dir;\n"
|
||||
" float half_height = 0.5 * height_scale * height_width_angle.x;\n"
|
||||
" float half_width = 0.5 * height_width_angle.y;\n"
|
||||
" vec3 horizontal_dir = half_width * line_right_dir;\n"
|
||||
" vec3 vertical_dir = half_height * line_up_dir;\n"
|
||||
@@ -133,10 +148,19 @@ static const char* Segments_Vertex_Shader =
|
||||
" pos += sign(height_width_angle.z) * horizontal_dir * cos(abs(height_width_angle.z) * 0.5);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" // ORCA: the grown first layer must not reach below the bed\n"
|
||||
" if (height_scale > 1.0)\n"
|
||||
" pos.z = max(pos.z, 0.0);\n"
|
||||
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
|
||||
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
|
||||
" eye_position.z += bias;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
|
||||
" vec3 normal_dir = normalize(pos - endpoint_pos);\n"
|
||||
" // ORCA: a grown box is lit flat: its camera-facing side through the side normal, its top-left\n"
|
||||
" // corner through the up normal, so the side carries no bright edge every few layers\n"
|
||||
" if (height_scale > 1.0)\n"
|
||||
" normal_dir = (signs.x > 0.0) ? horizontal_sign * line_right_dir :\n"
|
||||
" (signs.y > 0.0) ? vertical_sign * line_up_dir : -horizontal_sign * line_right_dir;\n"
|
||||
" vec3 eye_normal = (view_matrix * vec4(normal_dir, 0.0)).xyz;\n"
|
||||
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
|
||||
" color = color_base * lighting(eye_position, eye_normal);\n"
|
||||
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
|
||||
|
||||
@@ -77,6 +77,76 @@ bool Viewer::is_dim_previous_layers() const
|
||||
return m_impl->is_dim_previous_layers();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail(bool value)
|
||||
{
|
||||
m_impl->set_reduced_detail(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_reduced_detail() const
|
||||
{
|
||||
return m_impl->is_reduced_detail();
|
||||
}
|
||||
|
||||
EReducedDetailMode Viewer::get_reduced_detail_mode() const
|
||||
{
|
||||
return m_impl->get_reduced_detail_mode();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail_mode(EReducedDetailMode mode)
|
||||
{
|
||||
m_impl->set_reduced_detail_mode(mode);
|
||||
}
|
||||
|
||||
uint32_t Viewer::get_reduced_detail_layer_stride() const
|
||||
{
|
||||
return m_impl->get_reduced_detail_layer_stride();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail_layer_stride(uint32_t value)
|
||||
{
|
||||
m_impl->set_reduced_detail_layer_stride(value);
|
||||
}
|
||||
|
||||
EReducedDetailMode Viewer::get_rest_detail_mode() const
|
||||
{
|
||||
return m_impl->get_rest_detail_mode();
|
||||
}
|
||||
|
||||
void Viewer::set_rest_detail_mode(EReducedDetailMode mode)
|
||||
{
|
||||
m_impl->set_rest_detail_mode(mode);
|
||||
}
|
||||
|
||||
uint32_t Viewer::get_rest_layer_stride() const
|
||||
{
|
||||
return m_impl->get_rest_layer_stride();
|
||||
}
|
||||
|
||||
void Viewer::set_rest_layer_stride(uint32_t value)
|
||||
{
|
||||
m_impl->set_rest_layer_stride(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_rest_view_from_above() const
|
||||
{
|
||||
return m_impl->is_rest_view_from_above();
|
||||
}
|
||||
|
||||
void Viewer::set_rest_view_from_above(bool value)
|
||||
{
|
||||
m_impl->set_rest_view_from_above(value);
|
||||
}
|
||||
|
||||
uint64_t Viewer::get_state_version() const
|
||||
{
|
||||
return m_impl->get_state_version();
|
||||
}
|
||||
|
||||
bool Viewer::has_pending_updates() const
|
||||
{
|
||||
return m_impl->has_pending_updates();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers(bool value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <cfloat>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace libvgcode {
|
||||
|
||||
@@ -759,6 +763,7 @@ void ViewerImpl::init(const std::string& opengl_context_version)
|
||||
m_uni_segments_view_matrix_id = glGetUniformLocation(m_segments_shader_id, "view_matrix");
|
||||
m_uni_segments_projection_matrix_id = glGetUniformLocation(m_segments_shader_id, "projection_matrix");
|
||||
m_uni_segments_camera_position_id = glGetUniformLocation(m_segments_shader_id, "camera_position");
|
||||
m_uni_segments_height_scale_id = glGetUniformLocation(m_segments_shader_id, "height_scale");
|
||||
m_uni_segments_positions_tex_id = glGetUniformLocation(m_segments_shader_id, "position_tex");
|
||||
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
|
||||
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
|
||||
@@ -875,6 +880,12 @@ void ViewerImpl::reset()
|
||||
m_travels_time = { 0.0f, 0.0f };
|
||||
m_vertices.clear();
|
||||
m_vertices_colors.clear();
|
||||
// swap rather than clear: these are sized by the print, and a reset means the memory
|
||||
// should go back, not sit reserved until the next load
|
||||
for (std::vector<float>& times : m_cumulative_times)
|
||||
std::vector<float>().swap(times);
|
||||
std::vector<uint32_t>().swap(m_layer_first_vertex);
|
||||
std::vector<float>().swap(m_colors_scratch);
|
||||
m_valid_lines_bitset.clear();
|
||||
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
|
||||
m_cog_marker.reset();
|
||||
@@ -885,9 +896,23 @@ void ViewerImpl::reset()
|
||||
#else
|
||||
m_enabled_segments_count = 0;
|
||||
m_enabled_options_count = 0;
|
||||
m_enabled_segments_reduced_count = 0;
|
||||
m_enabled_options_reduced_count = 0;
|
||||
m_enabled_segments_rest_count = 0;
|
||||
++m_state_version;
|
||||
m_shell_bitset = BitSet<>();
|
||||
m_near_shell_bitset = BitSet<>();
|
||||
m_top_visible_bitset = BitSet<>();
|
||||
m_bottom_visible_bitset = BitSet<>();
|
||||
|
||||
m_settings_used_for_ranges = std::nullopt;
|
||||
|
||||
delete_textures(m_enabled_segments_rest_tex_id);
|
||||
delete_buffers(m_enabled_segments_rest_buf_id);
|
||||
delete_textures(m_enabled_options_reduced_tex_id);
|
||||
delete_buffers(m_enabled_options_reduced_buf_id);
|
||||
delete_textures(m_enabled_segments_reduced_tex_id);
|
||||
delete_buffers(m_enabled_segments_reduced_buf_id);
|
||||
delete_textures(m_enabled_options_tex_id);
|
||||
delete_buffers(m_enabled_options_buf_id);
|
||||
delete_textures(m_enabled_segments_tex_id);
|
||||
@@ -996,6 +1021,8 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
m_tool_colors = std::move(gcode_data.tools_colors);
|
||||
m_color_print_colors = std::move(gcode_data.color_print_colors);
|
||||
m_vertices_colors.resize(m_vertices.size());
|
||||
for (std::vector<float>& times : m_cumulative_times)
|
||||
times.resize(m_vertices.size());
|
||||
|
||||
m_settings.spiral_vase_mode = gcode_data.spiral_vase_mode;
|
||||
|
||||
@@ -1006,6 +1033,9 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
|
||||
for (size_t j = 0; j < TIME_MODES_COUNT; ++j) {
|
||||
m_total_time[j] += v.times[j];
|
||||
// the running total up to and including this vertex is exactly what
|
||||
// get_estimated_time_at() has to return for it
|
||||
m_cumulative_times[j][i] = m_total_time[j];
|
||||
if (v.type == EMoveType::Travel)
|
||||
m_travels_time[j] += v.times[j];
|
||||
}
|
||||
@@ -1048,6 +1078,20 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast<size_t>(v.layer_id));
|
||||
}
|
||||
|
||||
// Index of the first vertex of each layer, walked back to front so that a layer with no
|
||||
// vertex of its own inherits the next layer's index and the array stays non-decreasing.
|
||||
if (!m_layers.empty()) {
|
||||
const uint32_t vertices_count = static_cast<uint32_t>(m_vertices.size());
|
||||
m_layer_first_vertex.assign(m_layers.count(), vertices_count);
|
||||
for (uint32_t i = vertices_count; i > 0; --i) {
|
||||
const uint32_t layer_id = m_vertices[i - 1].layer_id;
|
||||
if (layer_id < m_layer_first_vertex.size())
|
||||
m_layer_first_vertex[layer_id] = i - 1;
|
||||
}
|
||||
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
|
||||
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
|
||||
}
|
||||
|
||||
if (!m_layers.empty())
|
||||
m_layers.set_view_range(0, static_cast<uint32_t>(m_layers.count()) - 1);
|
||||
|
||||
@@ -1116,6 +1160,22 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
glsafe(glGenTextures(1, &m_enabled_options_tex_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
|
||||
|
||||
// create (but do not fill) the reduced counterparts of the two buffers above
|
||||
glsafe(glGenBuffers(1, &m_enabled_segments_reduced_buf_id));
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
|
||||
glsafe(glGenTextures(1, &m_enabled_segments_reduced_tex_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_id));
|
||||
|
||||
glsafe(glGenBuffers(1, &m_enabled_options_reduced_buf_id));
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
|
||||
glsafe(glGenTextures(1, &m_enabled_options_reduced_tex_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_id));
|
||||
|
||||
glsafe(glGenBuffers(1, &m_enabled_segments_rest_buf_id));
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_rest_buf_id));
|
||||
glsafe(glGenTextures(1, &m_enabled_segments_rest_tex_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_rest_tex_id));
|
||||
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
@@ -1127,6 +1187,508 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
update_colors();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
// ORCA: the roles that sit inside the part and are hidden by its walls from every angle
|
||||
static bool is_interior_infill(EGCodeExtrusionRole role)
|
||||
{
|
||||
return role == EGCodeExtrusionRole::InternalInfill ||
|
||||
role == EGCodeExtrusionRole::SolidInfill ||
|
||||
role == EGCodeExtrusionRole::InternalBridgeInfill;
|
||||
}
|
||||
|
||||
// ORCA: what can never be a visible surface whatever the geometry says: the interior roles, and
|
||||
// gap fill, which sits between walls. Short sparse-infill segments hugging a wall would otherwise
|
||||
// pass the geometric test by the thousand.
|
||||
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
|
||||
{
|
||||
return is_interior_infill(role) || role == EGCodeExtrusionRole::GapFill;
|
||||
}
|
||||
|
||||
bool ViewerImpl::reduced_set_keeps(EReducedDetailMode mode, size_t i, const PathVertex& v) const
|
||||
{
|
||||
switch (mode) {
|
||||
case EReducedDetailMode::NoInternalInfill: return !is_interior_infill(v.role);
|
||||
case EReducedDetailMode::ShellOnly:
|
||||
// the first inner wall fills the step of a sloped surface between one layer's outer wall
|
||||
// and the next, too narrow for the grid to see; whatever is the visible top or bottom of a
|
||||
// step stays whatever its role
|
||||
return (!is_hidden_in_shell(v.role) && m_shell_bitset[i]) || m_near_shell_bitset[i] ||
|
||||
m_top_visible_bitset[i] || m_bottom_visible_bitset[i];
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A 2D occupancy grid over the print's footprint, one byte per cell. Only the rectangle a layer
|
||||
// touches is ever cleared or scanned, so a grid the size of the whole print costs no more than
|
||||
// the layer needs.
|
||||
struct OccupancyGrid
|
||||
{
|
||||
int nx{ 0 };
|
||||
int ny{ 0 };
|
||||
std::vector<uint8_t> cells;
|
||||
// bounding rectangle of the set cells, inclusive; empty while min > max
|
||||
int min_x{ 0 };
|
||||
int min_y{ 0 };
|
||||
int max_x{ -1 };
|
||||
int max_y{ -1 };
|
||||
|
||||
OccupancyGrid(int nx, int ny) : nx(nx), ny(ny), cells(static_cast<size_t>(nx) * static_cast<size_t>(ny), 0) {}
|
||||
|
||||
bool empty() const { return min_x > max_x; }
|
||||
uint8_t at(int x, int y) const { return cells[static_cast<size_t>(y) * nx + x]; }
|
||||
uint8_t& at(int x, int y) { return cells[static_cast<size_t>(y) * nx + x]; }
|
||||
|
||||
void set(int x, int y) {
|
||||
at(x, y) = 1;
|
||||
if (empty()) {
|
||||
min_x = max_x = x;
|
||||
min_y = max_y = y;
|
||||
}
|
||||
else {
|
||||
min_x = std::min(min_x, x);
|
||||
max_x = std::max(max_x, x);
|
||||
min_y = std::min(min_y, y);
|
||||
max_y = std::max(max_y, y);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (int y = min_y; y <= max_y; ++y)
|
||||
std::fill_n(&at(min_x, y), max_x - min_x + 1, uint8_t(0));
|
||||
min_x = min_y = 0;
|
||||
max_x = max_y = -1;
|
||||
}
|
||||
|
||||
// grow the bounding rectangle by r cells, staying inside the grid
|
||||
void grow(int r) {
|
||||
if (empty())
|
||||
return;
|
||||
min_x = std::max(0, min_x - r);
|
||||
min_y = std::max(0, min_y - r);
|
||||
max_x = std::min(nx - 1, max_x + r);
|
||||
max_y = std::min(ny - 1, max_y + r);
|
||||
}
|
||||
};
|
||||
|
||||
// Scratch space for close_gaps(), one per worker
|
||||
struct ClosingScratch
|
||||
{
|
||||
// component label per cell: 0 empty, > 0 a component, WILD a tiny fragment, CONTESTED a cell
|
||||
// reached by two components' dilations
|
||||
std::vector<int32_t> labels;
|
||||
std::vector<std::pair<int, int>> frontier;
|
||||
std::vector<std::pair<int, int>> next;
|
||||
std::vector<int> window_sum;
|
||||
std::vector<uint8_t> raw;
|
||||
static constexpr int32_t WILD = -1;
|
||||
static constexpr int32_t CONTESTED = -2;
|
||||
};
|
||||
|
||||
// Morphological closing with a square window of the given radius: fills gaps up to 2 * radius
|
||||
// cells wide, so that sparse infill or support reads as the solid area it is part of. The dilation
|
||||
// is done per connected component, and a cell two components both reach stays empty, so the gap
|
||||
// between two objects standing close together is never bridged and both of their facing walls
|
||||
// stay on the shell. A separable erosion over running window sums then shrinks the result back.
|
||||
static void close_gaps(OccupancyGrid& grid, int radius, ClosingScratch& scratch)
|
||||
{
|
||||
if (grid.empty() || radius <= 0)
|
||||
return;
|
||||
// the dilated area needs room to grow
|
||||
grid.grow(radius);
|
||||
const int nx = grid.nx;
|
||||
const auto idx = [nx](int x, int y) { return static_cast<size_t>(y) * nx + x; };
|
||||
const auto in_rect = [&](int x, int y) { return x >= grid.min_x && x <= grid.max_x && y >= grid.min_y && y <= grid.max_y; };
|
||||
std::vector<int32_t>& labels = scratch.labels;
|
||||
labels.resize(grid.cells.size());
|
||||
for (int y = grid.min_y; y <= grid.max_y; ++y)
|
||||
std::fill_n(&labels[idx(grid.min_x, y)], grid.max_x - grid.min_x + 1, 0);
|
||||
// the raw cells come back at the end: a closing must never lose one, and the erosion below
|
||||
// would eat into a wall that faces a contested gap
|
||||
std::vector<uint8_t>& raw = scratch.raw;
|
||||
raw.resize(grid.cells.size());
|
||||
for (int y = grid.min_y; y <= grid.max_y; ++y)
|
||||
std::copy_n(&grid.at(grid.min_x, y), grid.max_x - grid.min_x + 1, &raw[idx(grid.min_x, y)]);
|
||||
|
||||
// label the 8-connected components of the raw cells; a fragment too small to be a wall does
|
||||
// not spread and is absorbed by whichever component reaches it
|
||||
static constexpr size_t TINY = 8;
|
||||
int32_t next_label = 1;
|
||||
std::vector<std::pair<int, int>>& frontier = scratch.frontier;
|
||||
frontier.clear();
|
||||
for (int y = grid.min_y; y <= grid.max_y; ++y) {
|
||||
for (int x = grid.min_x; x <= grid.max_x; ++x) {
|
||||
if (!grid.at(x, y) || labels[idx(x, y)] != 0)
|
||||
continue;
|
||||
std::vector<std::pair<int, int>>& component = scratch.next;
|
||||
component.clear();
|
||||
component.emplace_back(x, y);
|
||||
labels[idx(x, y)] = next_label;
|
||||
for (size_t head = 0; head < component.size(); ++head) {
|
||||
const auto [cx, cy] = component[head];
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
const int px = cx + dx;
|
||||
const int py = cy + dy;
|
||||
if ((dx == 0 && dy == 0) || !in_rect(px, py) || !grid.at(px, py) || labels[idx(px, py)] != 0)
|
||||
continue;
|
||||
labels[idx(px, py)] = next_label;
|
||||
component.emplace_back(px, py);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (component.size() < TINY) {
|
||||
for (const auto [cx, cy] : component)
|
||||
labels[idx(cx, cy)] = ClosingScratch::WILD;
|
||||
}
|
||||
else {
|
||||
frontier.insert(frontier.end(), component.begin(), component.end());
|
||||
++next_label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dilate: each component claims the cells within radius of it, breadth first; a cell already
|
||||
// claimed by another component is contested and stays empty
|
||||
for (int step = 0; step < radius; ++step) {
|
||||
std::vector<std::pair<int, int>>& next = scratch.next;
|
||||
next.clear();
|
||||
for (const auto [cx, cy] : frontier) {
|
||||
const int32_t label = labels[idx(cx, cy)];
|
||||
if (label <= 0)
|
||||
continue;
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
const int px = cx + dx;
|
||||
const int py = cy + dy;
|
||||
if ((dx == 0 && dy == 0) || !in_rect(px, py))
|
||||
continue;
|
||||
int32_t& other = labels[idx(px, py)];
|
||||
if (other == 0 || other == ClosingScratch::WILD) {
|
||||
other = label;
|
||||
next.emplace_back(px, py);
|
||||
}
|
||||
else if (other != label && other != ClosingScratch::CONTESTED && !grid.at(px, py))
|
||||
other = ClosingScratch::CONTESTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::swap(frontier, next);
|
||||
}
|
||||
for (int y = grid.min_y; y <= grid.max_y; ++y) {
|
||||
for (int x = grid.min_x; x <= grid.max_x; ++x) {
|
||||
if (labels[idx(x, y)] > 0)
|
||||
grid.at(x, y) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// erode by the same radius, separably; cells outside the rectangle are empty, which is what a
|
||||
// shrinking erosion has to see
|
||||
std::vector<int>& window_sum = scratch.window_sum;
|
||||
const auto erode = [&](bool horizontal) {
|
||||
const int outer_n = horizontal ? grid.max_y - grid.min_y + 1 : grid.max_x - grid.min_x + 1;
|
||||
const int inner_n = horizontal ? grid.max_x - grid.min_x + 1 : grid.max_y - grid.min_y + 1;
|
||||
window_sum.assign(inner_n + 1, 0);
|
||||
for (int o = 0; o < outer_n; ++o) {
|
||||
const auto cell = [&](int i) -> uint8_t& {
|
||||
return horizontal ? grid.at(grid.min_x + i, grid.min_y + o) : grid.at(grid.min_x + o, grid.min_y + i);
|
||||
};
|
||||
for (int i = 0; i < inner_n; ++i)
|
||||
window_sum[i + 1] = window_sum[i] + cell(i);
|
||||
for (int i = 0; i < inner_n; ++i) {
|
||||
const int count = window_sum[std::min(inner_n, i + radius + 1)] - window_sum[std::max(0, i - radius)];
|
||||
cell(i) = (count == 2 * radius + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
erode(true);
|
||||
erode(false);
|
||||
for (int y = grid.min_y; y <= grid.max_y; ++y) {
|
||||
for (int x = grid.min_x; x <= grid.max_x; ++x)
|
||||
grid.at(x, y) |= raw[idx(x, y)];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ORCA: mark the extrusion segments that lie on the visible surface of the print, so that
|
||||
// EReducedDetailMode::ShellOnly can leave out everything the walls hide. Each layer is rasterized
|
||||
// into a coarse occupancy grid and closed, so that its footprint is solid whatever the infill;
|
||||
// a cell is then on the shell when it is filled and any of its six neighbours (four in the layer,
|
||||
// the layer below, the layer above) is not. A segment is kept when at least half of the cells it
|
||||
// crosses are shell cells: walls run along the shell, infill only touches it at the ends. Purely
|
||||
// geometric, so it works as well for the wipe tower, whose every segment shares one role, as for
|
||||
// the objects.
|
||||
// The same pass records the highest and lowest layer occupying each cell over the whole print,
|
||||
// which tells the segments that are the topmost or bottommost thing at their place, the only ones
|
||||
// a view from above or below sees of a layer. Exposure to the neighbouring layer alone would also
|
||||
// count whatever sits under an overhang, and the edge of a tower whose footprint lands a cell
|
||||
// differently from one layer to the next.
|
||||
void ViewerImpl::update_shell_bitset()
|
||||
{
|
||||
m_shell_bitset = BitSet<>(m_vertices.size());
|
||||
m_near_shell_bitset = BitSet<>(m_vertices.size());
|
||||
m_top_visible_bitset = BitSet<>(m_vertices.size());
|
||||
m_bottom_visible_bitset = BitSet<>(m_vertices.size());
|
||||
if (m_vertices.size() < 2 || m_layers.empty())
|
||||
return;
|
||||
|
||||
float min_x = FLT_MAX;
|
||||
float min_y = FLT_MAX;
|
||||
float max_x = -FLT_MAX;
|
||||
float max_y = -FLT_MAX;
|
||||
for (const PathVertex& v : m_vertices) {
|
||||
if (!v.is_extrusion())
|
||||
continue;
|
||||
min_x = std::min(min_x, v.position[0]);
|
||||
min_y = std::min(min_y, v.position[1]);
|
||||
max_x = std::max(max_x, v.position[0]);
|
||||
max_y = std::max(max_y, v.position[1]);
|
||||
}
|
||||
if (min_x > max_x)
|
||||
return;
|
||||
|
||||
// Half a millimetre separates a wall from the wall behind it; a print too large for that at
|
||||
// 1024 cells across gets coarser cells rather than a bigger grid. Gaps of up to 5 mm read as
|
||||
// solid: wide enough to swallow sparse infill, narrow enough to leave real holes open.
|
||||
static constexpr int MAX_CELLS = 1024;
|
||||
const float cell = std::max(0.5f, std::max(max_x - min_x, max_y - min_y) / static_cast<float>(MAX_CELLS));
|
||||
const int radius = static_cast<int>(std::ceil(2.5f / cell));
|
||||
// room for the closing to grow into, plus the neighbour lookups
|
||||
const int margin = radius + 2;
|
||||
const float origin_x = min_x - static_cast<float>(margin) * cell;
|
||||
const float origin_y = min_y - static_cast<float>(margin) * cell;
|
||||
const int nx = static_cast<int>((max_x - min_x) / cell) + 1 + 2 * margin;
|
||||
const int ny = static_cast<int>((max_y - min_y) / cell) + 1 + 2 * margin;
|
||||
|
||||
const auto cell_index = [nx](int x, int y) { return static_cast<size_t>(y) * nx + x; };
|
||||
const auto cell_of = [&](float x, float y) {
|
||||
const int cx = std::clamp(static_cast<int>((x - origin_x) / cell), margin, nx - 1 - margin);
|
||||
const int cy = std::clamp(static_cast<int>((y - origin_y) / cell), margin, ny - 1 - margin);
|
||||
return std::make_pair(cx, cy);
|
||||
};
|
||||
|
||||
// calls f(cx, cy) once per cell the segment starting at vertex i passes through
|
||||
const auto for_each_cell = [&](size_t i, auto&& f) {
|
||||
const Vec3& a = m_vertices[i].position;
|
||||
const Vec3& b = m_vertices[i + 1].position;
|
||||
const float dx = b[0] - a[0];
|
||||
const float dy = b[1] - a[1];
|
||||
const int steps = static_cast<int>(std::sqrt(dx * dx + dy * dy) / (0.5f * cell)) + 1;
|
||||
int last_x = -1;
|
||||
int last_y = -1;
|
||||
for (int s = 0; s <= steps; ++s) {
|
||||
const float t = static_cast<float>(s) / static_cast<float>(steps);
|
||||
const auto [cx, cy] = cell_of(a[0] + t * dx, a[1] + t * dy);
|
||||
if (cx != last_x || cy != last_y) {
|
||||
f(cx, cy);
|
||||
last_x = cx;
|
||||
last_y = cy;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const size_t layers_count = m_layers.count();
|
||||
// the segments of a layer: [first, last), where segment i runs from vertex i to vertex i + 1
|
||||
const auto layer_segments = [&](size_t layer) {
|
||||
const size_t first = m_layer_first_vertex[layer];
|
||||
const size_t last = (layer + 1 < layers_count) ? m_layer_first_vertex[layer + 1] : m_vertices.size() - 1;
|
||||
return std::make_pair(first, std::min(last, m_vertices.size() - 1));
|
||||
};
|
||||
const auto is_drawn_extrusion = [&](size_t i) { return m_vertices[i].is_extrusion() && m_valid_lines_bitset[i]; };
|
||||
|
||||
const OccupancyGrid nothing(nx, ny);
|
||||
|
||||
// Classifies the layers in [first_layer, last_layer) and returns the segments kept, plus the
|
||||
// highest and lowest of these layers occupying each cell. Each call owns its grids, so the layer
|
||||
// range can be split across threads.
|
||||
static constexpr int32_t NO_LAYER = -1;
|
||||
struct Kept {
|
||||
std::vector<uint32_t> shell;
|
||||
std::vector<uint32_t> near_shell;
|
||||
std::vector<int32_t> top;
|
||||
std::vector<int32_t> bottom;
|
||||
// the rectangle of cells these layers touched, inclusive; empty while min > max
|
||||
int min_x{ 0 };
|
||||
int min_y{ 0 };
|
||||
int max_x{ -1 };
|
||||
int max_y{ -1 };
|
||||
};
|
||||
const size_t cells_count = static_cast<size_t>(nx) * static_cast<size_t>(ny);
|
||||
const auto classify_layers = [&](size_t first_layer, size_t last_layer) {
|
||||
Kept kept;
|
||||
kept.top.assign(cells_count, NO_LAYER);
|
||||
kept.bottom.assign(cells_count, NO_LAYER);
|
||||
std::vector<OccupancyGrid> footprints(3, OccupancyGrid(nx, ny));
|
||||
OccupancyGrid shell_cells(nx, ny);
|
||||
// the outer wall segments of the layer, by every cell they cross
|
||||
std::unordered_map<size_t, std::vector<uint32_t>> outer_walls_by_cell;
|
||||
ClosingScratch scratch;
|
||||
const auto footprint = [&](size_t layer) -> OccupancyGrid& { return footprints[layer % 3]; };
|
||||
const auto prepare = [&](size_t layer) {
|
||||
OccupancyGrid& g = footprint(layer);
|
||||
g.clear();
|
||||
const auto [first, last] = layer_segments(layer);
|
||||
for (size_t i = first; i < last; ++i) {
|
||||
if (is_drawn_extrusion(i))
|
||||
for_each_cell(i, [&](int x, int y) { g.set(x, y); });
|
||||
}
|
||||
close_gaps(g, radius, scratch);
|
||||
};
|
||||
|
||||
if (first_layer > 0)
|
||||
prepare(first_layer - 1);
|
||||
prepare(first_layer);
|
||||
for (size_t layer = first_layer; layer < last_layer; ++layer) {
|
||||
if (layer + 1 < layers_count)
|
||||
prepare(layer + 1);
|
||||
const OccupancyGrid& below = (layer > 0) ? footprint(layer - 1) : nothing;
|
||||
const OccupancyGrid& cur = footprint(layer);
|
||||
const OccupancyGrid& above = (layer + 1 < layers_count) ? footprint(layer + 1) : nothing;
|
||||
|
||||
shell_cells.clear();
|
||||
if (!cur.empty()) {
|
||||
kept.min_x = (kept.max_x < kept.min_x) ? cur.min_x : std::min(kept.min_x, cur.min_x);
|
||||
kept.min_y = (kept.max_y < kept.min_y) ? cur.min_y : std::min(kept.min_y, cur.min_y);
|
||||
kept.max_x = std::max(kept.max_x, cur.max_x);
|
||||
kept.max_y = std::max(kept.max_y, cur.max_y);
|
||||
}
|
||||
for (int y = cur.min_y; y <= cur.max_y; ++y) {
|
||||
for (int x = cur.min_x; x <= cur.max_x; ++x) {
|
||||
if (!cur.at(x, y))
|
||||
continue;
|
||||
// layers come in ascending order, so the first occupant is the lowest
|
||||
int32_t& top = kept.top[cell_index(x, y)];
|
||||
int32_t& bottom = kept.bottom[cell_index(x, y)];
|
||||
top = static_cast<int32_t>(layer);
|
||||
if (bottom == NO_LAYER)
|
||||
bottom = static_cast<int32_t>(layer);
|
||||
if (!below.at(x, y) || !above.at(x, y) ||
|
||||
!cur.at(x - 1, y) || !cur.at(x + 1, y) || !cur.at(x, y - 1) || !cur.at(x, y + 1))
|
||||
shell_cells.set(x, y);
|
||||
}
|
||||
}
|
||||
const auto [first, last] = layer_segments(layer);
|
||||
outer_walls_by_cell.clear();
|
||||
for (size_t i = first; i < last; ++i) {
|
||||
const EGCodeExtrusionRole role = m_vertices[i].role;
|
||||
if (is_drawn_extrusion(i) && (role == EGCodeExtrusionRole::ExternalPerimeter || role == EGCodeExtrusionRole::OverhangPerimeter))
|
||||
for_each_cell(i, [&](int x, int y) { outer_walls_by_cell[cell_index(x, y)].push_back(static_cast<uint32_t>(i)); });
|
||||
}
|
||||
// an inner wall segment is the first inner wall when its midpoint lies within a line
|
||||
// and a half of an outer wall segment of the same layer
|
||||
const auto beside_outer_wall = [&](size_t i) {
|
||||
const Vec3& a = m_vertices[i].position;
|
||||
const Vec3& b = m_vertices[i + 1].position;
|
||||
const float mx = 0.5f * (a[0] + b[0]);
|
||||
const float my = 0.5f * (a[1] + b[1]);
|
||||
const float reach = 1.5f * m_vertices[i].width;
|
||||
const auto [cx, cy] = cell_of(mx, my);
|
||||
for (int dy = -1; dy <= 1; ++dy) {
|
||||
for (int dx = -1; dx <= 1; ++dx) {
|
||||
const auto it = outer_walls_by_cell.find(cell_index(cx + dx, cy + dy));
|
||||
if (it == outer_walls_by_cell.end())
|
||||
continue;
|
||||
for (uint32_t o : it->second) {
|
||||
const Vec3& p = m_vertices[o].position;
|
||||
const Vec3& q = m_vertices[o + 1].position;
|
||||
const float ex = q[0] - p[0];
|
||||
const float ey = q[1] - p[1];
|
||||
const float len2 = ex * ex + ey * ey;
|
||||
const float t = (len2 > 0.0f) ? std::clamp(((mx - p[0]) * ex + (my - p[1]) * ey) / len2, 0.0f, 1.0f) : 0.0f;
|
||||
const float ddx = mx - (p[0] + t * ex);
|
||||
const float ddy = my - (p[1] + t * ey);
|
||||
if (ddx * ddx + ddy * ddy <= reach * reach)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (size_t i = first; i < last; ++i) {
|
||||
if (!is_drawn_extrusion(i))
|
||||
continue;
|
||||
int total = 0;
|
||||
int on_shell = 0;
|
||||
for_each_cell(i, [&](int x, int y) {
|
||||
++total;
|
||||
on_shell += shell_cells.at(x, y);
|
||||
});
|
||||
if (2 * on_shell >= total)
|
||||
kept.shell.push_back(static_cast<uint32_t>(i));
|
||||
if (m_vertices[i].role == EGCodeExtrusionRole::Perimeter && beside_outer_wall(i))
|
||||
kept.near_shell.push_back(static_cast<uint32_t>(i));
|
||||
}
|
||||
}
|
||||
return kept;
|
||||
};
|
||||
|
||||
const size_t workers = std::clamp<size_t>(std::thread::hardware_concurrency(), 1, 8);
|
||||
const size_t chunk = std::max<size_t>(16, (layers_count + workers - 1) / workers);
|
||||
std::vector<std::future<Kept>> futures;
|
||||
for (size_t first = 0; first < layers_count; first += chunk)
|
||||
futures.emplace_back(std::async(std::launch::async, classify_layers, first, std::min(layers_count, first + chunk)));
|
||||
std::vector<int32_t> top_layer(cells_count, NO_LAYER);
|
||||
std::vector<int32_t> bottom_layer(cells_count, NO_LAYER);
|
||||
for (auto& f : futures) {
|
||||
const Kept kept = f.get();
|
||||
for (uint32_t i : kept.shell)
|
||||
m_shell_bitset.set(i);
|
||||
for (uint32_t i : kept.near_shell)
|
||||
m_near_shell_bitset.set(i);
|
||||
for (int y = kept.min_y; y <= kept.max_y; ++y) {
|
||||
for (int x = kept.min_x; x <= kept.max_x; ++x) {
|
||||
const size_t c = cell_index(x, y);
|
||||
if (kept.top[c] == NO_LAYER)
|
||||
continue;
|
||||
top_layer[c] = std::max(top_layer[c], kept.top[c]);
|
||||
bottom_layer[c] = (bottom_layer[c] == NO_LAYER) ? kept.bottom[c] : std::min(bottom_layer[c], kept.bottom[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A segment is visible from straight above when its layer is the topmost occupant of any of its
|
||||
// cells, and from below likewise with the bottommost: the exposed band of a sloped surface is
|
||||
// narrower than the infill chords that fill it, so touching it is what counts.
|
||||
struct Visible { std::vector<uint32_t> top; std::vector<uint32_t> bottom; };
|
||||
const auto find_visible = [&](size_t first_layer, size_t last_layer) {
|
||||
Visible visible;
|
||||
for (size_t layer = first_layer; layer < last_layer; ++layer) {
|
||||
const auto [first, last] = layer_segments(layer);
|
||||
for (size_t i = first; i < last; ++i) {
|
||||
if (!is_drawn_extrusion(i))
|
||||
continue;
|
||||
int total = 0;
|
||||
int on_top = 0;
|
||||
int on_bottom = 0;
|
||||
for_each_cell(i, [&](int x, int y) {
|
||||
++total;
|
||||
on_top += top_layer[cell_index(x, y)] == static_cast<int32_t>(layer);
|
||||
on_bottom += bottom_layer[cell_index(x, y)] == static_cast<int32_t>(layer);
|
||||
});
|
||||
if (on_top > 0)
|
||||
visible.top.push_back(static_cast<uint32_t>(i));
|
||||
if (on_bottom > 0)
|
||||
visible.bottom.push_back(static_cast<uint32_t>(i));
|
||||
}
|
||||
}
|
||||
return visible;
|
||||
};
|
||||
std::vector<std::future<Visible>> visible_futures;
|
||||
for (size_t first = 0; first < layers_count; first += chunk)
|
||||
visible_futures.emplace_back(std::async(std::launch::async, find_visible, first, std::min(layers_count, first + chunk)));
|
||||
for (auto& f : visible_futures) {
|
||||
const Visible visible = f.get();
|
||||
for (uint32_t i : visible.top)
|
||||
m_top_visible_bitset.set(i);
|
||||
for (uint32_t i : visible.bottom)
|
||||
m_bottom_visible_bitset.set(i);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
void ViewerImpl::update_enabled_entities()
|
||||
{
|
||||
if (m_vertices.empty())
|
||||
@@ -1134,6 +1696,26 @@ void ViewerImpl::update_enabled_entities()
|
||||
|
||||
std::vector<uint32_t> enabled_segments;
|
||||
std::vector<uint32_t> enabled_options;
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
// ORCA: the reduced sets are filled by the same walk, so switching to them costs no rebuild.
|
||||
const bool build_reduced = m_settings.reduced_detail_mode != EReducedDetailMode::Off;
|
||||
const bool build_rest = build_rest_set();
|
||||
std::vector<uint32_t> enabled_segments_reduced;
|
||||
std::vector<uint32_t> enabled_options_reduced;
|
||||
std::vector<uint32_t> enabled_segments_rest;
|
||||
const uint32_t layer_stride = std::max<uint32_t>(1, m_settings.reduced_detail_layer_stride);
|
||||
// Only the shell mode knows which segments are exposed surfaces, so only it can skip layers at
|
||||
// rest, and in either set only it can keep the surfaces of the layers it skips.
|
||||
const bool shell_reduced = build_reduced && m_settings.reduced_detail_mode == EReducedDetailMode::ShellOnly;
|
||||
const bool shell_rest = build_rest && m_settings.rest_detail_mode == EReducedDetailMode::ShellOnly;
|
||||
const uint32_t rest_stride = shell_rest ? std::max<uint32_t>(1, m_settings.rest_layer_stride) : 1;
|
||||
// Whatever else is dropped, both ends of the visible layer range are kept whole: the top is the
|
||||
// surface the user is looking at, and the only layer drawn at full color in top-layer-only
|
||||
// mode; the bottom is exposed whenever the range is cut short.
|
||||
const Interval& layers_range = m_layers.get_view_range();
|
||||
if ((shell_reduced || shell_rest) && m_shell_bitset.size != m_vertices.size())
|
||||
update_shell_bitset();
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
Interval range = m_view_range.get_visible();
|
||||
|
||||
// when top layer only visualization is enabled, we need to render
|
||||
@@ -1181,6 +1763,38 @@ void ViewerImpl::update_enabled_entities()
|
||||
enabled_options.push_back(static_cast<uint32_t>(i));
|
||||
else
|
||||
enabled_segments.push_back(static_cast<uint32_t>(i));
|
||||
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
const bool whole_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
|
||||
const bool keep_anyway = whole_layer || !v.is_extrusion();
|
||||
const bool classified = v.is_extrusion() && m_top_visible_bitset.size == m_vertices.size();
|
||||
const bool visible_from_above = classified && m_top_visible_bitset[i];
|
||||
const bool visible_from_below = classified && m_bottom_visible_bitset[i];
|
||||
if (build_rest && !v.is_option()) {
|
||||
const bool skipped = !whole_layer && rest_stride > 1 && (v.layer_id % rest_stride) != 0;
|
||||
// a skipped layer keeps only what the camera's side of the print can see of it
|
||||
const bool visible = m_settings.rest_view_from_above ? visible_from_above : visible_from_below;
|
||||
if (skipped ? visible : (keep_anyway || reduced_set_keeps(m_settings.rest_detail_mode, i, v)))
|
||||
enabled_segments_rest.push_back(static_cast<uint32_t>(i));
|
||||
}
|
||||
if (!build_reduced)
|
||||
continue;
|
||||
if (m_settings.reduced_detail_mode == EReducedDetailMode::EndLayersOnly) {
|
||||
if (whole_layer)
|
||||
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
|
||||
continue;
|
||||
}
|
||||
if (!whole_layer && (v.layer_id % layer_stride) != 0) {
|
||||
// the surfaces of a skipped layer that either side can see stay, so that a step does not vanish
|
||||
if (shell_reduced && (visible_from_above || visible_from_below))
|
||||
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
|
||||
continue;
|
||||
}
|
||||
if (v.is_option())
|
||||
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
|
||||
else if (keep_anyway || reduced_set_keeps(m_settings.reduced_detail_mode, i, v))
|
||||
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
@@ -1209,10 +1823,34 @@ void ViewerImpl::update_enabled_entities()
|
||||
else
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
|
||||
|
||||
m_enabled_segments_reduced_count = enabled_segments_reduced.size();
|
||||
m_enabled_options_reduced_count = enabled_options_reduced.size();
|
||||
m_enabled_segments_rest_count = enabled_segments_rest.size();
|
||||
|
||||
if (build_rest) {
|
||||
assert(m_enabled_segments_rest_buf_id > 0);
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_rest_buf_id));
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments_rest.size() * sizeof(uint32_t),
|
||||
enabled_segments_rest.empty() ? nullptr : enabled_segments_rest.data(), GL_STATIC_DRAW));
|
||||
}
|
||||
|
||||
if (build_reduced) {
|
||||
assert(m_enabled_segments_reduced_buf_id > 0);
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments_reduced.size() * sizeof(uint32_t),
|
||||
enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW));
|
||||
|
||||
assert(m_enabled_options_reduced_buf_id > 0);
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options_reduced.size() * sizeof(uint32_t),
|
||||
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
|
||||
}
|
||||
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
m_settings.update_enabled_entities = false;
|
||||
++m_state_version;
|
||||
}
|
||||
|
||||
static float encode_color(const Color& color) {
|
||||
@@ -1261,7 +1899,10 @@ void ViewerImpl::update_colors_texture()
|
||||
|
||||
// Based on current settings and slider position, we might want to render some
|
||||
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
|
||||
std::vector<float> colors(m_vertices_colors.size());
|
||||
// Reused across calls: this runs on every slider tick, and the allocation alone is
|
||||
// 4 bytes per vertex of the whole print each time.
|
||||
std::vector<float>& colors = m_colors_scratch;
|
||||
colors.resize(m_vertices_colors.size());
|
||||
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
|
||||
for (size_t i=0; i<m_vertices.size(); ++i) {
|
||||
const PathVertex& v = m_vertices[i];
|
||||
@@ -1319,6 +1960,7 @@ void ViewerImpl::update_colors()
|
||||
|
||||
update_colors_texture();
|
||||
m_settings.update_colors = false;
|
||||
++m_state_version;
|
||||
}
|
||||
|
||||
void ViewerImpl::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix)
|
||||
@@ -1384,6 +2026,54 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// ORCA: what the reduced set leaves out, and how many layers it keeps one of. Either changes which
|
||||
// vertices land in the reduced set, so the sets have to be rebuilt.
|
||||
void ViewerImpl::set_reduced_detail_mode(EReducedDetailMode mode)
|
||||
{
|
||||
if (m_settings.reduced_detail_mode == mode)
|
||||
return;
|
||||
m_settings.reduced_detail_mode = mode;
|
||||
m_settings.update_enabled_entities = true;
|
||||
}
|
||||
|
||||
void ViewerImpl::set_reduced_detail_layer_stride(uint32_t value)
|
||||
{
|
||||
value = std::max<uint32_t>(1, value);
|
||||
if (m_settings.reduced_detail_layer_stride == value)
|
||||
return;
|
||||
m_settings.reduced_detail_layer_stride = value;
|
||||
m_settings.update_enabled_entities = true;
|
||||
}
|
||||
|
||||
void ViewerImpl::set_rest_detail_mode(EReducedDetailMode mode)
|
||||
{
|
||||
if (m_settings.rest_detail_mode == mode)
|
||||
return;
|
||||
m_settings.rest_detail_mode = mode;
|
||||
m_settings.update_enabled_entities = true;
|
||||
}
|
||||
|
||||
void ViewerImpl::set_rest_layer_stride(uint32_t value)
|
||||
{
|
||||
value = std::max<uint32_t>(1, value);
|
||||
if (m_settings.rest_layer_stride == value)
|
||||
return;
|
||||
m_settings.rest_layer_stride = value;
|
||||
// only the shell rest set is built from it
|
||||
if (m_settings.rest_detail_mode == EReducedDetailMode::ShellOnly)
|
||||
m_settings.update_enabled_entities = true;
|
||||
}
|
||||
|
||||
void ViewerImpl::set_rest_view_from_above(bool value)
|
||||
{
|
||||
if (m_settings.rest_view_from_above == value)
|
||||
return;
|
||||
m_settings.rest_view_from_above = value;
|
||||
// it only matters while the shell rest set skips layers
|
||||
if (m_settings.rest_detail_mode == EReducedDetailMode::ShellOnly && m_settings.rest_layer_stride > 1)
|
||||
m_settings.update_enabled_entities = true;
|
||||
}
|
||||
|
||||
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
|
||||
void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
{
|
||||
@@ -1516,8 +2206,10 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
|
||||
|
||||
float ViewerImpl::get_estimated_time_at(size_t id) const
|
||||
{
|
||||
return std::accumulate(m_vertices.begin(), m_vertices.begin() + id + 1, 0.0f,
|
||||
[this](float a, const PathVertex& v) { return a + v.times[static_cast<size_t>(m_settings.time_mode)]; });
|
||||
const size_t mode = static_cast<size_t>(m_settings.time_mode);
|
||||
if (mode >= TIME_MODES_COUNT || id >= m_cumulative_times[mode].size())
|
||||
return 0.0f;
|
||||
return m_cumulative_times[mode][id];
|
||||
}
|
||||
|
||||
Color ViewerImpl::get_vertex_color(const PathVertex& v) const
|
||||
@@ -1722,6 +2414,10 @@ size_t ViewerImpl::get_used_cpu_memory() const
|
||||
ret += sizeof(m_extrusion_roles_colors);
|
||||
ret += sizeof(m_options_colors);
|
||||
ret += STDVEC_MEMSIZE(m_vertices, PathVertex);
|
||||
for (const std::vector<float>& times : m_cumulative_times)
|
||||
ret += STDVEC_MEMSIZE(times, float);
|
||||
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
|
||||
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
|
||||
ret += m_valid_lines_bitset.size_in_bytes_cpu();
|
||||
ret += m_height_range.size_in_bytes_cpu();
|
||||
ret += m_width_range.size_in_bytes_cpu();
|
||||
@@ -1787,7 +2483,13 @@ void ViewerImpl::update_view_full_range()
|
||||
const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)];
|
||||
const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)];
|
||||
|
||||
// Every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the
|
||||
// loop below would skip all of them on its first condition alone. Starting there turns a scan
|
||||
// from vertex 0 on every slider tick into a scan of the visible part only; what the loop
|
||||
// settles on is unchanged.
|
||||
auto first_it = m_vertices.begin();
|
||||
if (layers_range[0] < m_layer_first_vertex.size())
|
||||
first_it += m_layer_first_vertex[layers_range[0]];
|
||||
while (first_it != m_vertices.end() &&
|
||||
(first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) {
|
||||
++first_it;
|
||||
@@ -1868,6 +2570,7 @@ void ViewerImpl::update_view_full_range()
|
||||
}
|
||||
|
||||
m_settings.update_view_full_range = false;
|
||||
++m_state_version;
|
||||
}
|
||||
|
||||
void ViewerImpl::update_color_ranges()
|
||||
@@ -1974,7 +2677,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
if (m_texture_data.get_enabled_segments_count() == 0)
|
||||
#else
|
||||
if (m_enabled_segments_count == 0)
|
||||
if (active_segments_count() == 0)
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
return;
|
||||
|
||||
@@ -1994,6 +2697,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
|
||||
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
|
||||
glsafe(glUniform1f(m_uni_segments_height_scale_id, active_height_scale()));
|
||||
|
||||
glsafe(glDisable(GL_CULL_FACE));
|
||||
|
||||
@@ -2033,10 +2737,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
|
||||
glsafe(glActiveTexture(GL_TEXTURE3));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, active_segments_tex_id()));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, active_segments_buf_id()));
|
||||
|
||||
m_segment_template.render(m_enabled_segments_count);
|
||||
m_segment_template.render(active_segments_count());
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
if (curr_cull_face)
|
||||
@@ -2062,7 +2766,7 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
if (m_texture_data.get_enabled_options_count() == 0)
|
||||
#else
|
||||
if (m_enabled_options_count == 0)
|
||||
if (active_options_count() == 0)
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
return;
|
||||
|
||||
@@ -2120,10 +2824,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
|
||||
glsafe(glActiveTexture(GL_TEXTURE3));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, active_options_tex_id()));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, active_options_buf_id()));
|
||||
|
||||
m_option_template.render(m_enabled_options_count);
|
||||
m_option_template.render(active_options_count());
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
if (!curr_cull_face)
|
||||
|
||||
@@ -91,6 +91,31 @@ public:
|
||||
// 0.0 = black
|
||||
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// Draw the preview from the reduced set of entities. Meant to be held only while the user is
|
||||
// dragging; the sets are already built, so this is just a buffer binding and never rebuilds.
|
||||
//
|
||||
void set_reduced_detail(bool value) {
|
||||
if (m_settings.reduced_detail != value) {
|
||||
m_settings.reduced_detail = value;
|
||||
++m_state_version;
|
||||
}
|
||||
}
|
||||
bool is_reduced_detail() const { return m_settings.reduced_detail; }
|
||||
EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; }
|
||||
void set_reduced_detail_mode(EReducedDetailMode mode);
|
||||
uint32_t get_reduced_detail_layer_stride() const { return m_settings.reduced_detail_layer_stride; }
|
||||
void set_reduced_detail_layer_stride(uint32_t value);
|
||||
EReducedDetailMode get_rest_detail_mode() const { return m_settings.rest_detail_mode; }
|
||||
void set_rest_detail_mode(EReducedDetailMode mode);
|
||||
uint32_t get_rest_layer_stride() const { return m_settings.rest_layer_stride; }
|
||||
void set_rest_layer_stride(uint32_t value);
|
||||
bool is_rest_view_from_above() const { return m_settings.rest_view_from_above; }
|
||||
void set_rest_view_from_above(bool value);
|
||||
uint64_t get_state_version() const { return m_state_version; }
|
||||
bool has_pending_updates() const {
|
||||
return m_settings.update_view_full_range || m_settings.update_enabled_entities || m_settings.update_colors;
|
||||
}
|
||||
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
|
||||
@@ -234,6 +259,27 @@ private:
|
||||
//
|
||||
std::array<float, TIME_MODES_COUNT> m_total_time{ 0.0f, 0.0f };
|
||||
//
|
||||
// Running sum of the vertex estimated times, one entry per vertex for each time mode.
|
||||
// get_estimated_time_at() answers from this instead of re-accumulating the whole vertex
|
||||
// array, which it was doing once per frame from the tool marker tooltip. The sums are
|
||||
// built by the same left-to-right addition the accumulate performed, so the value handed
|
||||
// back is bit-identical, float rounding included.
|
||||
//
|
||||
std::array<std::vector<float>, TIME_MODES_COUNT> m_cumulative_times;
|
||||
//
|
||||
// For each layer L, the index of the first vertex whose layer_id is >= L (m_vertices.size()
|
||||
// if there is none). Every vertex before it is guaranteed to belong to an earlier layer, so
|
||||
// the scan in update_view_full_range() can start there rather than at vertex 0. Derived from
|
||||
// the vertices themselves rather than from Layers, which buckets an out-of-order vertex into
|
||||
// the layer that happens to be open, so this stays exact whatever order the vertices arrive in.
|
||||
//
|
||||
std::vector<uint32_t> m_layer_first_vertex;
|
||||
//
|
||||
// Scratch buffer for update_colors_texture(), kept alive so that a slider drag does not
|
||||
// allocate and free one float per vertex of the print on every step.
|
||||
//
|
||||
std::vector<float> m_colors_scratch;
|
||||
//
|
||||
// Detected travel moves times
|
||||
//
|
||||
std::array<float, TIME_MODES_COUNT> m_travels_time{ 0.0f, 0.0f };
|
||||
@@ -288,6 +334,26 @@ private:
|
||||
// Variables used for toolpaths visibiliity
|
||||
//
|
||||
BitSet<> m_valid_lines_bitset;
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
//
|
||||
// ORCA: bit set for the extrusion segments that lie on the visible surface of the print,
|
||||
// computed on demand by update_shell_bitset() for EReducedDetailMode::ShellOnly
|
||||
//
|
||||
BitSet<> m_shell_bitset;
|
||||
// the segments that are the topmost, or the bottommost, thing at their place in the whole
|
||||
// print: what a view from above, or below, sees of a layer, kept even while layers are skipped
|
||||
BitSet<> m_top_visible_bitset;
|
||||
BitSet<> m_bottom_visible_bitset;
|
||||
// the inner wall segments that run right beside an outer wall: the first inner wall, which
|
||||
// fills the step of a sloped surface between one layer's outer wall and the next, too narrow
|
||||
// for the grid to see
|
||||
BitSet<> m_near_shell_bitset;
|
||||
//
|
||||
// ORCA: bumped whenever what render() draws changes: on every applied update, on a change of
|
||||
// the bound set, on load and reset
|
||||
//
|
||||
uint64_t m_state_version{ 0 };
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
//
|
||||
// Variables used for toolpaths coloring
|
||||
//
|
||||
@@ -326,6 +392,7 @@ private:
|
||||
int m_uni_segments_view_matrix_id{ -1 };
|
||||
int m_uni_segments_projection_matrix_id{ -1 };
|
||||
int m_uni_segments_camera_position_id{ -1 };
|
||||
int m_uni_segments_height_scale_id{ -1 };
|
||||
int m_uni_segments_positions_tex_id{ -1 };
|
||||
int m_uni_segments_height_width_angle_tex_id{ -1 };
|
||||
int m_uni_segments_colors_tex_id{ -1 };
|
||||
@@ -460,6 +527,22 @@ private:
|
||||
unsigned int m_enabled_options_tex_id{ 0 };
|
||||
size_t m_enabled_options_count{ 0 };
|
||||
//
|
||||
// OpenGL buffers to store the reduced sets drawn while Settings::reduced_detail is set
|
||||
//
|
||||
unsigned int m_enabled_segments_reduced_buf_id{ 0 };
|
||||
unsigned int m_enabled_segments_reduced_tex_id{ 0 };
|
||||
size_t m_enabled_segments_reduced_count{ 0 };
|
||||
unsigned int m_enabled_options_reduced_buf_id{ 0 };
|
||||
unsigned int m_enabled_options_reduced_tex_id{ 0 };
|
||||
size_t m_enabled_options_reduced_count{ 0 };
|
||||
//
|
||||
// OpenGL buffer to store the segments drawn at rest while Settings::rest_detail_mode is not Off.
|
||||
// Every layer is drawn at rest, so the options are the full set and need no counterpart.
|
||||
//
|
||||
unsigned int m_enabled_segments_rest_buf_id{ 0 };
|
||||
unsigned int m_enabled_segments_rest_tex_id{ 0 };
|
||||
size_t m_enabled_segments_rest_count{ 0 };
|
||||
//
|
||||
// Caches for size of data sent to gpu, in bytes
|
||||
//
|
||||
size_t m_positions_tex_size{ 0 };
|
||||
@@ -467,6 +550,49 @@ private:
|
||||
size_t m_colors_tex_size{ 0 };
|
||||
size_t m_enabled_segments_tex_size{ 0 };
|
||||
size_t m_enabled_options_tex_size{ 0 };
|
||||
|
||||
// The set the next draw reads from: the reduced one only while the user is dragging, and only
|
||||
// if a reduced set is being built at all; otherwise the rest set, if one is being built. A rest
|
||||
// set already smaller than the reduced one, as it is with layers merged looking from above,
|
||||
// stays bound through the drag: it was right for the camera the drag started from.
|
||||
bool build_rest_set() const {
|
||||
return m_settings.rest_detail_mode != EReducedDetailMode::Off && m_settings.rest_detail_mode != EReducedDetailMode::LayersOnly;
|
||||
}
|
||||
bool use_reduced_set() const {
|
||||
return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off &&
|
||||
!(build_rest_set() && m_enabled_segments_rest_count < m_enabled_segments_reduced_count);
|
||||
}
|
||||
bool use_rest_set() const { return !use_reduced_set() && build_rest_set(); }
|
||||
// how many layers each drawn segment of the bound set stands in for
|
||||
float active_height_scale() const {
|
||||
if (use_reduced_set() && m_settings.reduced_detail_mode != EReducedDetailMode::EndLayersOnly)
|
||||
return static_cast<float>(std::max<uint32_t>(1, m_settings.reduced_detail_layer_stride));
|
||||
if (use_rest_set() && m_settings.rest_detail_mode == EReducedDetailMode::ShellOnly)
|
||||
return static_cast<float>(std::max<uint32_t>(1, m_settings.rest_layer_stride));
|
||||
return 1.0f;
|
||||
}
|
||||
size_t active_segments_count() const {
|
||||
return use_reduced_set() ? m_enabled_segments_reduced_count : use_rest_set() ? m_enabled_segments_rest_count : m_enabled_segments_count;
|
||||
}
|
||||
unsigned int active_segments_buf_id() const {
|
||||
return use_reduced_set() ? m_enabled_segments_reduced_buf_id : use_rest_set() ? m_enabled_segments_rest_buf_id : m_enabled_segments_buf_id;
|
||||
}
|
||||
unsigned int active_segments_tex_id() const {
|
||||
return use_reduced_set() ? m_enabled_segments_reduced_tex_id : use_rest_set() ? m_enabled_segments_rest_tex_id : m_enabled_segments_tex_id;
|
||||
}
|
||||
size_t active_options_count() const {
|
||||
return use_reduced_set() ? m_enabled_options_reduced_count : m_enabled_options_count;
|
||||
}
|
||||
unsigned int active_options_buf_id() const {
|
||||
return use_reduced_set() ? m_enabled_options_reduced_buf_id : m_enabled_options_buf_id;
|
||||
}
|
||||
unsigned int active_options_tex_id() const {
|
||||
return use_reduced_set() ? m_enabled_options_reduced_tex_id : m_enabled_options_tex_id;
|
||||
}
|
||||
|
||||
// Whether the segment starting at vertex i belongs to the set built under the given mode
|
||||
bool reduced_set_keeps(EReducedDetailMode mode, size_t i, const PathVertex& v) const;
|
||||
void update_shell_bitset();
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
void update_view_full_range();
|
||||
|
||||
@@ -1172,6 +1172,29 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
if (current_top_layer_only != required_top_layer_only)
|
||||
m_viewer.toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: simplify the preview while the user is dragging
|
||||
m_reduced_detail_while_dragging = get_app_config()->get_bool("preview_reduced_detail_while_dragging");
|
||||
m_reduced_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_reduced_detail_mode"));
|
||||
m_solid_model_while_dragging = m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly;
|
||||
m_reduced_detail_layer_stride = static_cast<unsigned int>(std::max(1, std::stoi(get_app_config()->get("preview_reduced_detail_layer_stride"))));
|
||||
m_rest_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_rest_detail_mode"));
|
||||
apply_reduced_detail_settings();
|
||||
++m_scene_version;
|
||||
// the median z step between layers, robust to the first layer and to variable layer height
|
||||
{
|
||||
std::vector<float> steps;
|
||||
for (size_t i = 1; i < m_viewer.get_layers_count(); ++i) {
|
||||
const float step = m_viewer.get_layer_z(i) - m_viewer.get_layer_z(i - 1);
|
||||
if (step > 0.0f)
|
||||
steps.push_back(step);
|
||||
}
|
||||
m_typical_layer_height = 0.0f;
|
||||
if (!steps.empty()) {
|
||||
std::nth_element(steps.begin(), steps.begin() + steps.size() / 2, steps.end());
|
||||
m_typical_layer_height = steps[steps.size() / 2];
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
|
||||
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
|
||||
@@ -1573,11 +1596,21 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
|
||||
|
||||
void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig *config)
|
||||
{
|
||||
++m_scene_version;
|
||||
if (config != nullptr)
|
||||
m_shells.volumes.update_colors_by_extruder(config, false);
|
||||
}
|
||||
|
||||
void GCodeViewer::set_shell_transparency(float alpha) { m_shells.volumes.set_transparency(alpha); }
|
||||
void GCodeViewer::set_shell_transparency(float alpha)
|
||||
{
|
||||
m_shells.volumes.set_transparency(alpha);
|
||||
++m_scene_version;
|
||||
}
|
||||
|
||||
std::array<uint64_t, 2> GCodeViewer::scene_version() const
|
||||
{
|
||||
return { (m_scene_version << 2) | (m_shells.visible ? 1u : 0u) | (m_no_render_path ? 2u : 0u), m_viewer.get_state_version() };
|
||||
}
|
||||
|
||||
//BBS: always load shell at preview
|
||||
void GCodeViewer::reset_shell()
|
||||
@@ -1589,6 +1622,7 @@ void GCodeViewer::reset_shell()
|
||||
|
||||
void GCodeViewer::reset()
|
||||
{
|
||||
++m_scene_version;
|
||||
//BBS: should also reset the result id
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": current result id %1% ")%m_last_result_id;
|
||||
m_last_result_id = -1;
|
||||
@@ -1617,15 +1651,26 @@ void GCodeViewer::reset()
|
||||
}
|
||||
|
||||
//BBS: GUI refactor: add canvas width and height
|
||||
void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin)
|
||||
void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin, bool draw_scene)
|
||||
{
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
render_shells(canvas_width, canvas_height);
|
||||
// while dragging in the solid model mode the objects stand in for their toolpaths, cut to the
|
||||
// visible layer range; the toolpath set then holds only the range's bottom and top layers
|
||||
const bool solid_model = m_interacting && m_solid_model_while_dragging && m_viewer.is_reduced_detail();
|
||||
if (draw_scene) {
|
||||
if (solid_model)
|
||||
render_solid_model(canvas_width, canvas_height);
|
||||
else
|
||||
render_shells(canvas_width, canvas_height);
|
||||
}
|
||||
|
||||
if (m_viewer.get_extrusion_roles().empty())
|
||||
if (m_viewer.get_extrusion_roles_count() == 0)
|
||||
return;
|
||||
|
||||
render_toolpaths();
|
||||
update_rest_layer_stride();
|
||||
|
||||
if (draw_scene)
|
||||
render_toolpaths();
|
||||
|
||||
float legend_height = 0.0f;
|
||||
render_legend(legend_height, canvas_width, canvas_height, right_margin);
|
||||
@@ -1907,6 +1952,92 @@ void GCodeViewer::update_layers_slider_mode()
|
||||
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
|
||||
}
|
||||
|
||||
void GCodeViewer::set_interacting(bool interacting)
|
||||
{
|
||||
m_interacting = interacting;
|
||||
m_viewer.set_reduced_detail(m_reduced_detail_while_dragging && interacting);
|
||||
}
|
||||
|
||||
// ORCA: with the shell drawn at rest, layers thinner than a couple of pixels on screen are merged:
|
||||
// the walls of one layer in N are drawn N layers tall, which looks the same and costs 1/N. N follows
|
||||
// the view, from 1 side-on and zoomed in to the cap looking straight down, where the walls are edge-on
|
||||
// and every layer's exposed surfaces are all there is to see. Changing N rebuilds the sets, so it is
|
||||
// left alone while the user is dragging.
|
||||
void GCodeViewer::update_rest_layer_stride()
|
||||
{
|
||||
if (m_interacting || m_rest_detail_mode != libvgcode::EReducedDetailMode::ShellOnly || m_typical_layer_height <= 0.0f)
|
||||
return;
|
||||
static constexpr double MERGE_BELOW_PX = 2.0;
|
||||
static constexpr unsigned int MAX_STRIDE = 64;
|
||||
Camera& camera = wxGetApp().plater()->get_camera();
|
||||
// how tall one layer is on screen: a step of one layer height projected through the camera's
|
||||
// own matrices at the centre of the toolpaths and at the point the camera looks at, whichever
|
||||
// is taller. Perspective makes the near corners of a tall print larger than that, but merging
|
||||
// is judged where the user looks, not at the worst corner.
|
||||
const Matrix4d view_projection = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
const auto to_pixels = [&](const Vec3d& p) {
|
||||
const Vec4d clip = view_projection * Vec4d(p.x(), p.y(), p.z(), 1.0);
|
||||
const double w = (std::abs(clip.w()) < 1e-9) ? 1e-9 : clip.w();
|
||||
return Vec2d(0.5 * viewport[2] * clip.x() / w, 0.5 * viewport[3] * clip.y() / w);
|
||||
};
|
||||
const Vec3d step(0.0, 0.0, static_cast<double>(m_typical_layer_height));
|
||||
double layer_px = 0.0;
|
||||
for (const Vec3d& p : { m_paths_bounding_box.center(), camera.get_target() })
|
||||
layer_px = std::max(layer_px, (to_pixels(p + step) - to_pixels(p)).norm());
|
||||
const unsigned int stride = (layer_px * MAX_STRIDE <= MERGE_BELOW_PX) ? MAX_STRIDE :
|
||||
std::clamp(static_cast<unsigned int>(MERGE_BELOW_PX / layer_px), 1u, MAX_STRIDE);
|
||||
m_viewer.set_rest_layer_stride(stride);
|
||||
m_viewer.set_rest_view_from_above(camera.get_dir_forward().z() < 0.0);
|
||||
}
|
||||
|
||||
// ORCA: libvgcode only builds a reduced set while its mode is not Off, so the preference switch is
|
||||
// folded into the mode it is given. Every setter goes through here.
|
||||
void GCodeViewer::apply_reduced_detail_settings()
|
||||
{
|
||||
m_viewer.set_reduced_detail_mode(m_reduced_detail_while_dragging ? m_reduced_detail_mode : libvgcode::EReducedDetailMode::Off);
|
||||
m_viewer.set_reduced_detail_layer_stride(m_reduced_detail_layer_stride);
|
||||
m_viewer.set_rest_detail_mode(m_rest_detail_mode);
|
||||
}
|
||||
|
||||
void GCodeViewer::set_rest_detail_mode(const std::string& mode)
|
||||
{
|
||||
m_rest_detail_mode = reduced_detail_mode_from_string(mode);
|
||||
apply_reduced_detail_settings();
|
||||
}
|
||||
|
||||
void GCodeViewer::set_reduced_detail_while_dragging(bool value)
|
||||
{
|
||||
m_reduced_detail_while_dragging = value;
|
||||
apply_reduced_detail_settings();
|
||||
}
|
||||
|
||||
void GCodeViewer::set_reduced_detail_mode(const std::string& mode)
|
||||
{
|
||||
m_reduced_detail_mode = reduced_detail_mode_from_string(mode);
|
||||
m_solid_model_while_dragging = m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly;
|
||||
apply_reduced_detail_settings();
|
||||
}
|
||||
|
||||
void GCodeViewer::set_reduced_detail_layer_stride(unsigned int value)
|
||||
{
|
||||
m_reduced_detail_layer_stride = std::max(1u, value);
|
||||
apply_reduced_detail_settings();
|
||||
}
|
||||
|
||||
libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const std::string& mode)
|
||||
{
|
||||
if (mode == "full")
|
||||
return libvgcode::EReducedDetailMode::Off;
|
||||
if (mode == "solid")
|
||||
return libvgcode::EReducedDetailMode::EndLayersOnly;
|
||||
if (mode == "layers")
|
||||
return libvgcode::EReducedDetailMode::LayersOnly;
|
||||
if (mode == "shell")
|
||||
return libvgcode::EReducedDetailMode::ShellOnly;
|
||||
return libvgcode::EReducedDetailMode::NoInternalInfill;
|
||||
}
|
||||
|
||||
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
|
||||
{
|
||||
m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1]));
|
||||
@@ -2230,6 +2361,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
||||
|
||||
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
|
||||
{
|
||||
++m_scene_version;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
|
||||
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
|
||||
//BBS: update force previewing logic
|
||||
@@ -2294,6 +2426,19 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
|
||||
object_count++;
|
||||
}
|
||||
|
||||
// the prime tower as it was sliced, so that the solid model shows what the print shows; it
|
||||
// keeps its opaque colour and so never appears among the translucent shells
|
||||
if (print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data) {
|
||||
const PrintConfig& config = print.config();
|
||||
const int plate_idx = print.get_plate_index();
|
||||
const Vec3d plate_origin = print.get_plate_origin();
|
||||
const float x = static_cast<float>(config.wipe_tower_x.get_at(plate_idx) + plate_origin.x());
|
||||
const float y = static_cast<float>(config.wipe_tower_y.get_at(plate_idx) + plate_origin.y());
|
||||
m_shells.volumes.load_real_wipe_tower_preview(1000, x, y, print.wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
|
||||
print.wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh, true,
|
||||
static_cast<float>(config.wipe_tower_rotation_angle), false, initialized);
|
||||
}
|
||||
|
||||
// Orca: disable wipe tower shell
|
||||
// if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
|
||||
// // BBS: adds wipe tower's volume
|
||||
@@ -2544,6 +2689,47 @@ void GCodeViewer::render_shells(int canvas_width, int canvas_height)
|
||||
glsafe(::glDepthMask(GL_TRUE));
|
||||
}
|
||||
|
||||
// The sliced objects and the prime tower drawn opaque, in their filament colours, cut to the
|
||||
// visible layer range by the shader's z range. The toolpaths of the range's bottom and top layers
|
||||
// are drawn afterwards and cap the cut.
|
||||
void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
|
||||
{
|
||||
if (m_shells.volumes.empty())
|
||||
return;
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
|
||||
const float z_top = m_viewer.get_layer_z(layers[1]) - m_z_offset + 0.001f;
|
||||
const float z_bottom = (layers[0] > 0) ? m_viewer.get_layer_z(layers[0] - 1) - m_z_offset - 0.001f : -FLT_MAX;
|
||||
|
||||
std::vector<float> alphas;
|
||||
alphas.reserve(m_shells.volumes.volumes.size());
|
||||
for (GLVolume* volume : m_shells.volumes.volumes) {
|
||||
alphas.push_back(volume->color.a());
|
||||
volume->color.a(1.0f);
|
||||
volume->set_render_color();
|
||||
}
|
||||
m_shells.volumes.set_z_range(z_bottom, z_top);
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("z_far", camera.get_far_z());
|
||||
shader->set_uniform("z_near", camera.get_near_z());
|
||||
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
shader->stop_using();
|
||||
|
||||
m_shells.volumes.set_z_range(-FLT_MAX, FLT_MAX);
|
||||
size_t k = 0;
|
||||
for (GLVolume* volume : m_shells.volumes.volumes) {
|
||||
volume->color.a(alphas[k++]);
|
||||
volume->set_render_color();
|
||||
}
|
||||
}
|
||||
|
||||
//BBS
|
||||
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
|
||||
if (!show)
|
||||
@@ -3408,6 +3594,12 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret;
|
||||
ret.reserve(custom_gcode_per_print_z.size());
|
||||
|
||||
// Loop invariant, but built lazily: this lambda runs once per extruder on every frame
|
||||
// and most prints reach neither colour change below, so fetching it up front would cost
|
||||
// more than the per-item fetch it replaces.
|
||||
std::vector<float> zs;
|
||||
bool zs_built = false;
|
||||
|
||||
for (const auto& item : custom_gcode_per_print_z) {
|
||||
if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
|
||||
continue;
|
||||
@@ -3415,7 +3607,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
if (item.type != ColorChange)
|
||||
continue;
|
||||
|
||||
const std::vector<float> zs = m_viewer.get_layers_zs();
|
||||
if (!zs_built) {
|
||||
zs = m_viewer.get_layers_zs();
|
||||
zs_built = true;
|
||||
}
|
||||
auto lower_b = std::lower_bound(zs.begin(), zs.end(),
|
||||
static_cast<float>(item.print_z - epsilon()));
|
||||
if (lower_b == zs.end())
|
||||
@@ -4562,6 +4757,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
|
||||
// ORCA: Get layer Zs as doubles
|
||||
std::vector<double> layer_zs = get_layers_zs();
|
||||
// loop invariant, same reason as the layer Zs above
|
||||
const std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
|
||||
|
||||
for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) {
|
||||
ImGui::Dummy({window_padding, window_padding});
|
||||
@@ -4581,7 +4778,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
imgui.text(buf);
|
||||
ImGui::SameLine(max_len * 1.5);
|
||||
|
||||
std::vector<float> layer_times = m_viewer.get_layers_estimated_times();
|
||||
float custom_gcode_time = 0;
|
||||
if (layer > 0)
|
||||
{
|
||||
@@ -4630,7 +4826,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
std::string print_str = _u8L("Model printing time");
|
||||
std::string total_str = _u8L("Total time");
|
||||
float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x;
|
||||
if (m_viewer.get_layers_estimated_times().empty())
|
||||
if (m_viewer.get_layers_count() == 0)
|
||||
max_len += ImGui::CalcTextSize(total_str.c_str()).x;
|
||||
else {
|
||||
if (m_viewer.get_view_type() == libvgcode::EViewType::FeatureType)
|
||||
|
||||
@@ -230,6 +230,20 @@ private:
|
||||
|
||||
bool m_legend_visible{ true };
|
||||
bool m_legend_enabled{ true };
|
||||
// ORCA: the reduced-detail-while-dragging preferences, pushed to libvgcode by apply_reduced_detail_settings()
|
||||
bool m_reduced_detail_while_dragging{ false };
|
||||
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::NoInternalInfill };
|
||||
unsigned int m_reduced_detail_layer_stride{ 4 };
|
||||
libvgcode::EReducedDetailMode m_rest_detail_mode{ libvgcode::EReducedDetailMode::Off };
|
||||
// ORCA: draw the sliced objects as solid shapes instead of toolpaths while dragging
|
||||
bool m_solid_model_while_dragging{ false };
|
||||
void render_solid_model(int canvas_width, int canvas_height);
|
||||
void apply_reduced_detail_settings();
|
||||
// whether the user is dragging or a wheel burst is settling, as told by set_interacting()
|
||||
bool m_interacting{ false };
|
||||
// the print's typical layer height, for how many layers fit in a pixel at the current view
|
||||
float m_typical_layer_height{ 0.0f };
|
||||
void update_rest_layer_stride();
|
||||
|
||||
float m_legend_height;
|
||||
PrintEstimatedStatistics m_print_statistics;
|
||||
@@ -240,6 +254,8 @@ private:
|
||||
|
||||
bool m_contained_in_bed{ true };
|
||||
mutable bool m_no_render_path { false };
|
||||
// ORCA: bumped on every change to what the scene pass draws that libvgcode does not track
|
||||
uint64_t m_scene_version{ 0 };
|
||||
bool m_is_dark = false;
|
||||
|
||||
libvgcode::Viewer m_viewer;
|
||||
@@ -272,7 +288,12 @@ public:
|
||||
//BBS: add all plates filament statistics
|
||||
void render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show = true) const;
|
||||
//BBS: GUI refactor: add canvas width and height
|
||||
void render(int canvas_width, int canvas_height, int right_margin);
|
||||
// draw_scene = false records the legend, the sliders and the marker only, for a frame whose
|
||||
// toolpaths are shown again from a kept image
|
||||
void render(int canvas_width, int canvas_height, int right_margin, bool draw_scene = true);
|
||||
// ORCA: what the scene pass draws, as two counters that change whenever it would look different
|
||||
std::array<uint64_t, 2> scene_version() const;
|
||||
bool scene_update_pending() const { return m_viewer.has_pending_updates(); }
|
||||
//BBS
|
||||
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
|
||||
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
|
||||
@@ -339,6 +360,18 @@ public:
|
||||
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
|
||||
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
|
||||
|
||||
// ORCA: while the user drags the camera or a slider, draw the preview from libvgcode's reduced
|
||||
// toolpath set, if the preference asks for one
|
||||
void set_interacting(bool interacting);
|
||||
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
|
||||
void set_reduced_detail_while_dragging(bool value);
|
||||
// the preference's string value: "layers", "no_infill" or "shell"
|
||||
void set_reduced_detail_mode(const std::string& mode);
|
||||
void set_reduced_detail_layer_stride(unsigned int value);
|
||||
// what is left out even at rest: "full", "no_infill" or "shell"
|
||||
void set_rest_detail_mode(const std::string& mode);
|
||||
static libvgcode::EReducedDetailMode reduced_detail_mode_from_string(const std::string& mode);
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
|
||||
|
||||
@@ -1224,6 +1224,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
||||
|
||||
GLCanvas3D::~GLCanvas3D()
|
||||
{
|
||||
_scene_cache_release();
|
||||
if (_set_current()) {
|
||||
if (m_fxaa_texture_id != 0) {
|
||||
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
|
||||
@@ -1328,6 +1329,7 @@ bool GLCanvas3D::init()
|
||||
|
||||
void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) {
|
||||
m_is_dark = is_dark;
|
||||
++m_scene_version;
|
||||
// Bed color
|
||||
m_bed.on_change_color_mode(is_dark);
|
||||
// GcodeViewer color
|
||||
@@ -2050,6 +2052,48 @@ void GLCanvas3D::render(bool only_init)
|
||||
}
|
||||
|
||||
// draw scene
|
||||
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
|
||||
|
||||
// ORCA: while the preference is on and nothing the scene pass draws has changed since the
|
||||
// last full frame, that frame is shown again and only the overlays are drawn on top of it
|
||||
const bool preview_scene = m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data();
|
||||
if (preview_scene)
|
||||
_update_preview_interaction();
|
||||
bool scene_from_cache = false;
|
||||
bool scene_to_cache = false;
|
||||
if (preview_scene && _scene_cache_enabled() && !m_scene_cache.broken) {
|
||||
const int current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate_index();
|
||||
const std::array<uint64_t, 2> viewer_version = m_gcode_viewer.scene_version();
|
||||
const bool unchanged = m_scene_cache.valid &&
|
||||
m_scene_cache.width == cnv_size.get_width() && m_scene_cache.height == cnv_size.get_height() &&
|
||||
m_scene_cache.view == camera.get_view_matrix().matrix() && m_scene_cache.projection == camera.get_projection_matrix().matrix() &&
|
||||
m_scene_cache.hover_plate == hover_id && m_scene_cache.current_plate == current_plate &&
|
||||
m_scene_cache.world_axes == m_show_world_axes && m_scene_cache.canvas_version == m_scene_version &&
|
||||
m_scene_cache.viewer_version == viewer_version && !m_gcode_viewer.scene_update_pending();
|
||||
if (unchanged) {
|
||||
_scene_cache_blit(false);
|
||||
scene_from_cache = !m_scene_cache.broken;
|
||||
}
|
||||
if (!scene_from_cache && _scene_cache_prepare(cnv_size.get_width(), cnv_size.get_height())) {
|
||||
m_scene_cache.valid = false;
|
||||
m_scene_cache.view = camera.get_view_matrix().matrix();
|
||||
m_scene_cache.projection = camera.get_projection_matrix().matrix();
|
||||
m_scene_cache.hover_plate = hover_id;
|
||||
m_scene_cache.current_plate = current_plate;
|
||||
m_scene_cache.world_axes = m_show_world_axes;
|
||||
m_scene_cache.canvas_version = m_scene_version;
|
||||
m_scene_cache.viewer_version = viewer_version;
|
||||
scene_to_cache = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_scene_cache.valid = false;
|
||||
|
||||
if (scene_from_cache) {
|
||||
// the preview's panels and its marker are drawn on top of the frame shown again
|
||||
_render_gcode(cnv_size.get_width(), cnv_size.get_height(), false);
|
||||
}
|
||||
else {
|
||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
|
||||
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
|
||||
@@ -2070,7 +2114,6 @@ void GLCanvas3D::render(bool only_init)
|
||||
show_grid = false;
|
||||
|
||||
/* view3D render*/
|
||||
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
||||
if (!no_partplate)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
|
||||
@@ -2147,6 +2190,13 @@ void GLCanvas3D::render(bool only_init)
|
||||
if (_is_fxaa_enabled())
|
||||
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||
|
||||
// ORCA: keep the finished scene, with its depth, for the frames that follow
|
||||
if (scene_to_cache) {
|
||||
_scene_cache_blit(true);
|
||||
m_scene_cache.valid = !m_scene_cache.broken;
|
||||
}
|
||||
}
|
||||
|
||||
// draw overlays
|
||||
_render_overlays();
|
||||
|
||||
@@ -3240,6 +3290,12 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
||||
m_dirty |= imgui_requires_extra_frame;
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
|
||||
// ORCA: the render timer only wakes the idle loop; the frame that puts the preview's full detail
|
||||
// back after a wheel burst has to be asked for here, once the settle time is really up
|
||||
if (m_preview_settle_pending && std::chrono::steady_clock::now() >= m_preview_interaction_until) {
|
||||
m_preview_settle_pending = false;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
if (!m_dirty)
|
||||
return;
|
||||
@@ -3945,6 +4001,9 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
evt.SetY(evt.GetY() * scale);
|
||||
#endif
|
||||
|
||||
if (m_canvas_type == CanvasPreview)
|
||||
note_preview_interaction();
|
||||
|
||||
if (wxGetApp().imgui()->update_mouse_data(evt)) {
|
||||
if (m_canvas_type == CanvasPreview) {
|
||||
IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider();
|
||||
@@ -4051,6 +4110,11 @@ void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
|
||||
}
|
||||
|
||||
|
||||
void GLCanvas3D::note_preview_interaction()
|
||||
{
|
||||
m_preview_interaction_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(150);
|
||||
}
|
||||
|
||||
void GLCanvas3D::schedule_extra_frame(int milliseconds)
|
||||
{
|
||||
// Schedule idle event right now
|
||||
@@ -5609,6 +5673,9 @@ void GLCanvas3D::mouse_up_cleanup()
|
||||
m_mouse.ignore_left_up = false;
|
||||
m_mouse.ignore_right_up = false;
|
||||
m_dirty = true;
|
||||
// ORCA: the frame that follows a release is the one that puts the preview's full detail back,
|
||||
// and on some platforms no idle event follows a button release until the next input
|
||||
wxWakeUpIdle();
|
||||
|
||||
if (m_canvas->HasCapture())
|
||||
m_canvas->ReleaseMouse();
|
||||
@@ -8508,12 +8575,125 @@ void GLCanvas3D::_render_wireframe_overlay()
|
||||
}
|
||||
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
|
||||
// ORCA: dragging the camera, the navigator or either slider is when the preview has to keep up
|
||||
// with continuous input, so that is when the reduced toolpath set earns its visible coarseness.
|
||||
// A wheel step has no duration, so it holds the reduced set for a settle time instead, and the
|
||||
// frame that restores the full detail is scheduled for when that time runs out. The level is
|
||||
// chosen before the draw and before the scene cache is consulted, so a change lands in this very
|
||||
// frame and never shows a kept frame of the other level.
|
||||
void GLCanvas3D::_update_preview_interaction()
|
||||
{
|
||||
IMSlider* layers_slider = m_gcode_viewer.get_layers_slider();
|
||||
IMSlider* moves_slider = m_gcode_viewer.get_moves_slider();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool settling = now < m_preview_interaction_until;
|
||||
const bool dragging = m_mouse.dragging || m_navigator_dragging || layers_slider->is_dragging() || moves_slider->is_dragging();
|
||||
m_gcode_viewer.set_interacting(dragging || settling);
|
||||
if (settling && !dragging && m_gcode_viewer.is_reduced_detail()) {
|
||||
m_preview_settle_pending = true;
|
||||
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_scene_cache_enabled() const
|
||||
{
|
||||
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool("preview_cache_static_scene");
|
||||
}
|
||||
|
||||
// Makes the cache's framebuffer match the window: same size, same sample count, a depth buffer of
|
||||
// the same format, so that colour and depth can be blitted both ways. Returns false when it cannot.
|
||||
bool GLCanvas3D::_scene_cache_prepare(int width, int height)
|
||||
{
|
||||
SceneCache& cache = m_scene_cache;
|
||||
glsafe(::glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &cache.default_fbo));
|
||||
int samples = 0;
|
||||
glsafe(::glGetIntegerv(GL_SAMPLES, &samples));
|
||||
int depth_bits = 0;
|
||||
int stencil_bits = 0;
|
||||
const GLenum depth_query = (cache.default_fbo == 0) ? GL_DEPTH : GL_DEPTH_ATTACHMENT;
|
||||
const GLenum stencil_query = (cache.default_fbo == 0) ? GL_STENCIL : GL_DEPTH_ATTACHMENT;
|
||||
glsafe(::glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, depth_query, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depth_bits));
|
||||
glsafe(::glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, stencil_query, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencil_bits));
|
||||
const bool stencil = stencil_bits > 0;
|
||||
if (cache.fbo != 0 && cache.width == width && cache.height == height && cache.samples == samples && cache.stencil == stencil)
|
||||
return true;
|
||||
|
||||
_scene_cache_release();
|
||||
const GLenum depth_format = stencil ? GL_DEPTH24_STENCIL8 : (depth_bits <= 16) ? GL_DEPTH_COMPONENT16 : (depth_bits >= 32) ? GL_DEPTH_COMPONENT32 : GL_DEPTH_COMPONENT24;
|
||||
glsafe(::glGenRenderbuffers(1, &cache.color));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, cache.color));
|
||||
if (samples > 0)
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width, height));
|
||||
else
|
||||
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height));
|
||||
glsafe(::glGenRenderbuffers(1, &cache.depth));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, cache.depth));
|
||||
if (samples > 0)
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, depth_format, width, height));
|
||||
else
|
||||
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, depth_format, width, height));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, 0));
|
||||
glsafe(::glGenFramebuffers(1, &cache.fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, cache.fbo));
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cache.color));
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, cache.depth));
|
||||
const GLenum status = ::glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(cache.default_fbo)));
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preview scene cache disabled: framebuffer incomplete, status " << status;
|
||||
_scene_cache_release();
|
||||
cache.broken = true;
|
||||
return false;
|
||||
}
|
||||
cache.width = width;
|
||||
cache.height = height;
|
||||
cache.samples = samples;
|
||||
cache.stencil = stencil;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copies colour and depth from the window into the cache (store) or back (restore). A driver that
|
||||
// refuses the blit marks the cache broken, and the preview falls back to drawing every frame.
|
||||
void GLCanvas3D::_scene_cache_blit(bool store)
|
||||
{
|
||||
SceneCache& cache = m_scene_cache;
|
||||
if (cache.fbo == 0)
|
||||
return;
|
||||
const GLuint window = static_cast<GLuint>(cache.default_fbo);
|
||||
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, store ? window : cache.fbo));
|
||||
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, store ? cache.fbo : window));
|
||||
while (::glGetError() != GL_NO_ERROR) {}
|
||||
::glBlitFramebuffer(0, 0, cache.width, cache.height, 0, 0, cache.width, cache.height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
const GLenum error = ::glGetError();
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, window));
|
||||
if (error != GL_NO_ERROR) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preview scene cache disabled: framebuffer blit failed with GL error " << error;
|
||||
cache.broken = true;
|
||||
_scene_cache_release();
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::_scene_cache_release()
|
||||
{
|
||||
SceneCache& cache = m_scene_cache;
|
||||
if (cache.fbo != 0)
|
||||
glsafe(::glDeleteFramebuffers(1, &cache.fbo));
|
||||
if (cache.color != 0)
|
||||
glsafe(::glDeleteRenderbuffers(1, &cache.color));
|
||||
if (cache.depth != 0)
|
||||
glsafe(::glDeleteRenderbuffers(1, &cache.depth));
|
||||
cache.fbo = cache.color = cache.depth = 0;
|
||||
cache.width = cache.height = 0;
|
||||
cache.valid = false;
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height, bool draw_scene)
|
||||
{
|
||||
m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE);
|
||||
IMSlider *layers_slider = m_gcode_viewer.get_layers_slider();
|
||||
IMSlider *moves_slider = m_gcode_viewer.get_moves_slider();
|
||||
|
||||
m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE, draw_scene);
|
||||
|
||||
if (layers_slider->is_need_post_tick_event()) {
|
||||
auto evt = new wxCommandEvent(EVT_CUSTOMEVT_TICKSCHANGED, m_canvas->GetId());
|
||||
evt->SetInt((int)layers_slider->get_post_tick_event_type());
|
||||
|
||||
@@ -591,6 +591,10 @@ private:
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
// ORCA: until when a discrete preview interaction (a wheel step) keeps the reduced toolpath set bound
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
|
||||
// whether the frame that restores the full detail once that time is up is still owed
|
||||
bool m_preview_settle_pending{ false };
|
||||
|
||||
//BBS:add plate related logic
|
||||
mutable std::vector<int> m_hover_volume_idxs;
|
||||
@@ -732,6 +736,33 @@ public:
|
||||
std::array<unsigned int, 2> m_fxaa_texture_size{ 0, 0 };
|
||||
unsigned int m_ssao_color_texture_id{ 0 };
|
||||
unsigned int m_ssao_depth_texture_id{ 0 };
|
||||
|
||||
// ORCA: the last fully drawn preview scene, shown again while nothing it draws has changed
|
||||
struct SceneCache
|
||||
{
|
||||
unsigned int fbo{ 0 };
|
||||
unsigned int color{ 0 };
|
||||
unsigned int depth{ 0 };
|
||||
int default_fbo{ 0 };
|
||||
int width{ 0 };
|
||||
int height{ 0 };
|
||||
int samples{ 0 };
|
||||
bool stencil{ false };
|
||||
// the kept image matches the key below
|
||||
bool valid{ false };
|
||||
// a blit failed on this driver: never try again this session
|
||||
bool broken{ false };
|
||||
Matrix4d view{ Matrix4d::Zero() };
|
||||
Matrix4d projection{ Matrix4d::Zero() };
|
||||
int hover_plate{ -1 };
|
||||
int current_plate{ -1 };
|
||||
bool world_axes{ false };
|
||||
uint64_t canvas_version{ 0 };
|
||||
std::array<uint64_t, 2> viewer_version{ 0, 0 };
|
||||
};
|
||||
SceneCache m_scene_cache;
|
||||
// bumped on changes to what the scene pass draws that neither the camera nor the viewer track
|
||||
uint64_t m_scene_version{ 0 };
|
||||
std::array<unsigned int, 2> m_ssao_texture_size{ { 0, 0 } };
|
||||
GLModel m_plate_shadow_mask;
|
||||
std::string m_plate_shadow_mask_key;
|
||||
@@ -1138,6 +1169,9 @@ public:
|
||||
void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
|
||||
|
||||
void request_extra_frame() { m_extra_frame_requested = true; }
|
||||
// ORCA: a wheel step is over before the next frame, so it holds the reduced preview for a short
|
||||
// settle time instead: a burst of steps stays cheap and the full frame lands once they stop
|
||||
void note_preview_interaction();
|
||||
|
||||
void schedule_extra_frame(int milliseconds);
|
||||
|
||||
@@ -1273,7 +1307,15 @@ private:
|
||||
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);
|
||||
void _render_wireframe_overlay();
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void _render_gcode(int canvas_width, int canvas_height);
|
||||
void _render_gcode(int canvas_width, int canvas_height, bool draw_scene = true);
|
||||
// ORCA: decides whether the preview draws its reduced set this frame; runs before the scene
|
||||
// cache is consulted, since the decision changes what the scene pass draws
|
||||
void _update_preview_interaction();
|
||||
// ORCA: scene cache, see SceneCache
|
||||
bool _scene_cache_enabled() const;
|
||||
bool _scene_cache_prepare(int width, int height);
|
||||
void _scene_cache_blit(bool store);
|
||||
void _scene_cache_release();
|
||||
//BBS: render a plane for assemble
|
||||
void _render_plane() const;
|
||||
void _render_selection();
|
||||
|
||||
@@ -482,6 +482,11 @@ void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& g
|
||||
ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth());
|
||||
}
|
||||
|
||||
bool IMSlider::is_dragging() const
|
||||
{
|
||||
return GImGui != nullptr && m_imgui_id != 0 && GImGui->ActiveId == m_imgui_id && GImGui->IO.MouseDown[0];
|
||||
}
|
||||
|
||||
bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale)
|
||||
{
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
@@ -490,6 +495,7 @@ bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int
|
||||
|
||||
ImGuiContext& context = *GImGui;
|
||||
const ImGuiID id = window->GetID(str_id);
|
||||
m_imgui_id = id;
|
||||
|
||||
const ImVec2 pos = window->DC.CursorPos;
|
||||
const ImRect draw_region(pos, pos + size);
|
||||
@@ -882,6 +888,7 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
|
||||
|
||||
ImGuiContext& context = *GImGui;
|
||||
const ImGuiID id = window->GetID(str_id);
|
||||
m_imgui_id = id;
|
||||
|
||||
const ImVec2 pos = window->DC.CursorPos;
|
||||
const ImRect draw_region(pos, pos + size);
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
|
||||
//BBS update scroll value changed
|
||||
bool is_dirty() { return m_dirty; }
|
||||
// ORCA: whether the mouse is currently holding this slider's handle. Read from ImGui's active
|
||||
// id rather than from the dirty flag, which is raised and consumed inside a single frame.
|
||||
bool is_dragging() const;
|
||||
void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
|
||||
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
|
||||
void reset_post_tick_event(bool val = false) {
|
||||
@@ -182,6 +185,8 @@ private:
|
||||
int m_higher_value;
|
||||
int m_one_layer_value; // ORCA
|
||||
bool m_dirty = false;
|
||||
// ORCA: the ImGui id of the slider widget, as of its last render
|
||||
unsigned int m_imgui_id = 0;
|
||||
|
||||
bool m_render_as_disabled{ false };
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
|
||||
return sizer;
|
||||
}
|
||||
|
||||
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url)
|
||||
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange, const wxString wiki_url)
|
||||
{
|
||||
assert(vlist.size() == config_name_index.size());
|
||||
unsigned int current_index = 0;
|
||||
@@ -333,9 +333,16 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
|
||||
|
||||
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
|
||||
|
||||
// ORCA: this one is only meaningful while the simplification it configures is enabled
|
||||
if (param == "preview_reduced_detail_mode") {
|
||||
m_reduced_detail_mode_combo = combobox;
|
||||
combobox->Enable(app_config->get_bool("preview_reduced_detail_while_dragging"));
|
||||
}
|
||||
|
||||
//// save config
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index, onchange](wxCommandEvent& e) {
|
||||
app_config->set(param, config_name_index[e.GetSelection()]);
|
||||
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
@@ -700,11 +707,15 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit
|
||||
auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param)));
|
||||
input->SetToolTip(tip);
|
||||
|
||||
// ORCA: this one is only meaningful while the dimming it controls is enabled
|
||||
// ORCA: these are only meaningful while the option they belong to is enabled
|
||||
if (param == "preview_dim_previous_layers_brightness") {
|
||||
m_dim_previous_layers_brightness_input = input;
|
||||
input->Enable(app_config->get_bool("preview_dim_previous_layers"));
|
||||
}
|
||||
else if (param == "preview_reduced_detail_layer_stride") {
|
||||
m_reduced_detail_layer_stride_input = input;
|
||||
input->Enable(app_config->get_bool("preview_reduced_detail_while_dragging"));
|
||||
}
|
||||
|
||||
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
@@ -1056,6 +1067,20 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
// ORCA: apply the reduced-detail preference immediately to the currently loaded preview
|
||||
else if (param == "preview_reduced_detail_while_dragging") {
|
||||
if (m_reduced_detail_mode_combo)
|
||||
m_reduced_detail_mode_combo->Enable(app_config->get_bool(param));
|
||||
if (m_reduced_detail_layer_stride_input)
|
||||
m_reduced_detail_layer_stride_input->Enable(app_config->get_bool(param));
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_reduced_detail_while_dragging(app_config->get_bool(param));
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
else if (param == "preview_dim_previous_layers") {
|
||||
if (m_dim_previous_layers_brightness_input)
|
||||
@@ -1938,6 +1963,93 @@ void PreferencesDialog::create_items()
|
||||
//// GRAPHICS > G-code Preview
|
||||
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
|
||||
|
||||
auto item_reduced_detail_while_dragging = create_item_checkbox(
|
||||
_L("Simplify preview while dragging"),
|
||||
_L("While dragging the camera or a preview slider, or zooming with the mouse wheel, draw only part of the toolpaths so that large prints stay responsive. "
|
||||
"The two options below choose what is left out. The full detail is restored as soon as you let go."),
|
||||
"preview_reduced_detail_while_dragging"
|
||||
);
|
||||
g_sizer->Add(item_reduced_detail_while_dragging);
|
||||
|
||||
auto item_reduced_detail_mode = create_item_combobox(
|
||||
_L("Simplification"),
|
||||
_L("What the simplified preview leaves out while dragging, on top of skipping layers.\n"
|
||||
"Skip layers only: every toolpath of the drawn layers is kept.\n"
|
||||
"Skip internal infill: sparse and solid infill hidden inside the walls is left out.\n"
|
||||
"Shell only: only the toolpaths on the visible surface of the print are drawn, including the outside of the prime tower. "
|
||||
"Removes the most of the toolpath modes, and holes narrower than 5 mm are treated as solid.\n"
|
||||
"Solid model: the sliced objects and the prime tower are drawn as solid shapes in their filament colours instead of toolpaths, "
|
||||
"cut to the visible layer range with the range's bottom and top layers drawn on top. Cheapest of all; supports are not shown, and the layer setting below does not apply."),
|
||||
"preview_reduced_detail_mode",
|
||||
{_L("Skip layers only"), _L("Skip internal infill"), _L("Shell only"), _L("Solid model")},
|
||||
{"layers", "no_infill", "shell", "solid"},
|
||||
// ORCA: apply the new mode immediately to the currently loaded preview
|
||||
[](std::string value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_reduced_detail_mode(value);
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_reduced_detail_mode);
|
||||
|
||||
auto item_reduced_detail_layer_stride = create_item_spinctrl(
|
||||
_L("Draw one layer in every"),
|
||||
"",
|
||||
_L("layers"),
|
||||
_L("How many layers the simplified preview keeps one of while dragging. 1 draws every layer, 4 draws every fourth. "
|
||||
"The bottom and top of the visible layer range are always drawn whole."),
|
||||
"preview_reduced_detail_layer_stride",
|
||||
1,
|
||||
20,
|
||||
// ORCA: apply the new stride immediately to the currently loaded preview
|
||||
[](int value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_reduced_detail_layer_stride(static_cast<unsigned int>(value));
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_reduced_detail_layer_stride);
|
||||
|
||||
auto item_rest_detail_mode = create_item_combobox(
|
||||
_L("Always leave out"),
|
||||
_L("What the preview leaves out at all times, dragging or not, with every layer drawn. "
|
||||
"Use it when a plate of large objects is slow to draw even when the view is not moving.\n"
|
||||
"Nothing: the full preview.\n"
|
||||
"Internal infill: sparse and solid infill hidden inside the walls is left out.\n"
|
||||
"Everything but the shell: only the toolpaths on the visible surface of the print are drawn, and layers thinner than a couple of pixels on screen are merged, "
|
||||
"so that looking from above costs little more than the top surfaces. Holes narrower than 5 mm are treated as solid."),
|
||||
"preview_rest_detail_mode",
|
||||
{_L("Nothing"), _L("Internal infill"), _L("Everything but the shell")},
|
||||
{"full", "no_infill", "shell"},
|
||||
// ORCA: apply the new mode immediately to the currently loaded preview
|
||||
[](std::string value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_rest_detail_mode(value);
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_rest_detail_mode);
|
||||
|
||||
auto item_cache_static_scene = create_item_checkbox(
|
||||
_L("Keep the drawn preview while nothing moves"),
|
||||
_L("Keep the last drawn preview and show it again for frames in which neither the camera nor the toolpaths changed, "
|
||||
"so that hovering, tooltips and notifications no longer redraw a large print. The scene is redrawn as soon as anything in it changes."),
|
||||
"preview_cache_static_scene"
|
||||
);
|
||||
g_sizer->Add(item_cache_static_scene);
|
||||
|
||||
auto item_dim_previous_layers = create_item_checkbox(
|
||||
_L("Dim lower layers"),
|
||||
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
|
||||
|
||||
@@ -73,6 +73,8 @@ public:
|
||||
::CheckBox * m_bambu_cloud_checkbox = {nullptr};
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::SpinInput *m_dim_previous_layers_brightness_input = {nullptr};
|
||||
::ComboBox * m_reduced_detail_mode_combo = {nullptr};
|
||||
::SpinInput *m_reduced_detail_layer_stride_input = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
std::vector<NetworkLibraryVersionInfo> m_available_versions;
|
||||
|
||||
@@ -86,7 +88,7 @@ public:
|
||||
wxBoxSizer *create_item_title(wxString title);
|
||||
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange = {}, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
|
||||
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
|
||||
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);
|
||||
|
||||
Reference in New Issue
Block a user