mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-22 08:22:58 +00:00
Offer the Sliced Objects as a Solid Model While Dragging
The G-code preview draws every toolpath segment as an instanced box, and its frame cost is linear in the number of segments drawn. On a plate of large objects that is enough that dragging the camera or a preview slider cannot keep up, and no amount of per-segment work changes that; only drawing fewer segments does. The objects themselves are cheaper: a mesh costs its triangles once, however many layers it has, and the preview already loads the objects as shells for its translucent ghost. A new preference, "Only render solid model when dragging" (off by default), draws those shells opaque in their filament colours instead of the toolpaths while the user drags. The visible layer range still holds. The shells are cut at the range's top and bottom through the gouraud shader's z range, and libvgcode keeps a second index buffer holding just the range's bottom and top layers, filled in the same walk as the full one, which is drawn afterwards so that it caps the cut with what was really printed there. Switching between the two sets is a buffer binding, never a rebuild. The prime tower is added to the shells from its sliced mesh while the preference is on, positioned as the print placed it; it keeps its opaque colour and so stays out of the translucent ghost. Supports have no mesh and are not shown. Dragging is the camera, the navigator or either slider being held; a slider reports it from ImGui's active id, since its dirty flag is raised and consumed inside one frame. A wheel step holds the solid model for a 150 ms settle time, with the frame that restores the toolpaths scheduled for when it runs out. The switch is decided at the top of the frame, before the cached scene is consulted, and a frame that switches redraws the scene. A drag cut short by focus or capture loss is ended explicitly, and a button release wakes the idle loop, since on some platforms no idle event follows it until the next input. With the preference off, nothing is built and the preview is unchanged.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# G-code preview while dragging
|
||||
|
||||
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
|
||||
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
|
||||
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
|
||||
keep up. With the `preview_solid_model_while_dragging` preference (*Graphics > G-code Preview*,
|
||||
off by default), the preview draws the sliced objects as solid meshes while the user drags and
|
||||
puts the toolpaths back when they let go. A mesh costs its triangles once, however many layers it
|
||||
has.
|
||||
|
||||
`GCodeViewer` draws the solid model, libvgcode (`src/libvgcode`) keeps the toolpaths that cap it,
|
||||
and `GLCanvas3D` decides when the user is dragging. The OpenGL ES path ignores the preference.
|
||||
|
||||
## The solid model
|
||||
|
||||
The preview already loads the sliced objects as shells for its translucent ghost.
|
||||
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colours, with the
|
||||
`gouraud` shader, whose z range cuts them to the visible layer range. The shells hold only the
|
||||
objects, so while the preference is on the prime tower is added from its sliced mesh, positioned
|
||||
as the print placed it. It is added or removed on its own when the preference changes, without
|
||||
reloading the objects, keeps its opaque colour so that it never appears among the translucent
|
||||
shells, and stays out of their bounding box. Supports have no mesh and are not shown.
|
||||
|
||||
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
|
||||
only the end layers.
|
||||
|
||||
## End-layer set
|
||||
|
||||
The cut faces of the solid model are capped with what was really printed there: the toolpaths of
|
||||
the bottom and top layers of the visible range. While the preference is on,
|
||||
`ViewerImpl::update_enabled_entities()` fills a second, **reduced** index buffer holding just those
|
||||
two layers, in the same walk that fills the full one. Building both together is what makes
|
||||
switching free: starting or ending a drag is a buffer binding, never a rebuild.
|
||||
|
||||
## Deciding that the user is dragging
|
||||
|
||||
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
|
||||
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
|
||||
is the camera, the navigator or either slider being held; a slider reports this from ImGui's active
|
||||
id rather than its dirty flag, which is raised and consumed inside one frame. A wheel step has no
|
||||
duration, so it holds the solid model for a 150 ms settle time instead, and the frame that restores
|
||||
the toolpaths is scheduled for when that time runs out, since the render timer only wakes the idle
|
||||
loop. A drag cut short by focus or capture loss is ended explicitly, and a button release wakes the
|
||||
idle loop, because on some platforms nothing else would until the next input.
|
||||
|
||||
## Reused scene frames
|
||||
|
||||
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
|
||||
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
|
||||
that reuses the scene must never be one on which the solid model is switched.
|
||||
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
|
||||
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
|
||||
drags, so no solid-model frame outlives a drag, and the frame that ends a wheel's settle time is
|
||||
requested as a full frame.
|
||||
|
||||
## Per-frame lookups
|
||||
|
||||
The segment template draws its box from 8 corners through an index buffer, so the vertex shader
|
||||
runs at most once per corner. `get_estimated_time_at()`, which the tool marker tooltip calls every
|
||||
frame, starts from the running time at the first vertex of the vertex's layer, kept per layer at
|
||||
load, and adds only that layer's vertices. The sum runs in vertex order, so it matches a full
|
||||
accumulation exactly while costing memory per layer rather than per vertex.
|
||||
@@ -205,6 +205,10 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// draw the sliced objects instead of their toolpaths while the user drags the preview
|
||||
if (get("preview_solid_model_while_dragging").empty())
|
||||
set_bool("preview_solid_model_while_dragging", false);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -98,6 +98,15 @@ public:
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// The reduced set holds only the bottom and top layers of the visible range, for a caller that
|
||||
// draws the print itself some other way while the user drags. While enabled it is built
|
||||
// alongside the full set, so set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
|
||||
//
|
||||
void set_reduced_detail_enabled(bool value);
|
||||
bool is_reduced_detail_enabled() const;
|
||||
void set_reduced_detail(bool value);
|
||||
bool is_reduced_detail() const;
|
||||
float get_dim_previous_layers_brightness() const;
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
//
|
||||
|
||||
@@ -25,6 +25,10 @@ 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 };
|
||||
// whether the reduced set (the visible range's end layers) is built, and whether it is drawn.
|
||||
// Ignored on the OpenGL ES path.
|
||||
bool reduced_detail_enabled{ false };
|
||||
bool reduced_detail{ false };
|
||||
//
|
||||
// Required update flags
|
||||
//
|
||||
|
||||
@@ -77,6 +77,26 @@ 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();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail_enabled(bool value)
|
||||
{
|
||||
m_impl->set_reduced_detail_enabled(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_reduced_detail_enabled() const
|
||||
{
|
||||
return m_impl->is_reduced_detail_enabled();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers(bool value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
|
||||
@@ -877,7 +877,7 @@ void ViewerImpl::reset()
|
||||
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)
|
||||
for (std::vector<float>& times : m_layer_start_times)
|
||||
std::vector<float>().swap(times);
|
||||
std::vector<uint32_t>().swap(m_layer_first_vertex);
|
||||
std::vector<float>().swap(m_colors_scratch);
|
||||
@@ -891,9 +891,15 @@ 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_settings_used_for_ranges = std::nullopt;
|
||||
|
||||
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);
|
||||
@@ -1002,8 +1008,6 @@ 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;
|
||||
|
||||
@@ -1014,9 +1018,6 @@ 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];
|
||||
}
|
||||
@@ -1071,6 +1072,23 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
}
|
||||
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]);
|
||||
|
||||
// the running time at each layer's first vertex, summed in vertex order so that
|
||||
// get_estimated_time_at() matches a full accumulation exactly
|
||||
std::array<float, TIME_MODES_COUNT> running{};
|
||||
for (std::vector<float>& times : m_layer_start_times)
|
||||
times.assign(m_layer_first_vertex.size(), 0.0f);
|
||||
size_t layer = 0;
|
||||
for (size_t i = 0; i <= m_vertices.size(); ++i) {
|
||||
for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) {
|
||||
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
|
||||
m_layer_start_times[j][layer] = running[j];
|
||||
}
|
||||
if (i < m_vertices.size()) {
|
||||
for (size_t j = 0; j < TIME_MODES_COUNT; ++j)
|
||||
running[j] += m_vertices[i].times[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_layers.empty())
|
||||
@@ -1141,6 +1159,17 @@ 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(glBindBuffer(GL_TEXTURE_BUFFER, 0));
|
||||
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
@@ -1159,6 +1188,14 @@ void ViewerImpl::update_enabled_entities()
|
||||
|
||||
std::vector<uint32_t> enabled_segments;
|
||||
std::vector<uint32_t> enabled_options;
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
// the reduced set is filled by the same walk, so switching to it costs no rebuild. It keeps the
|
||||
// bottom and top layers of the visible range: the surfaces the range cuts open
|
||||
const bool build_reduced = m_settings.reduced_detail_enabled;
|
||||
std::vector<uint32_t> enabled_segments_reduced;
|
||||
std::vector<uint32_t> enabled_options_reduced;
|
||||
const Interval& layers_range = m_layers.get_view_range();
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
Interval range = m_view_range.get_visible();
|
||||
|
||||
// when top layer only visualization is enabled, we need to render
|
||||
@@ -1206,6 +1243,11 @@ 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
|
||||
if (build_reduced && (v.layer_id == layers_range[0] || v.layer_id == layers_range[1]))
|
||||
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
@@ -1234,6 +1276,21 @@ 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();
|
||||
|
||||
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
|
||||
|
||||
@@ -1412,6 +1469,14 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
void ViewerImpl::set_reduced_detail_enabled(bool value)
|
||||
{
|
||||
if (m_settings.reduced_detail_enabled == value)
|
||||
return;
|
||||
m_settings.reduced_detail_enabled = value;
|
||||
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)
|
||||
{
|
||||
@@ -1545,9 +1610,18 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
|
||||
float ViewerImpl::get_estimated_time_at(size_t id) const
|
||||
{
|
||||
const size_t mode = static_cast<size_t>(m_settings.time_mode);
|
||||
if (mode >= TIME_MODES_COUNT || id >= m_cumulative_times[mode].size())
|
||||
if (mode >= TIME_MODES_COUNT || id >= m_vertices.size())
|
||||
return 0.0f;
|
||||
return m_cumulative_times[mode][id];
|
||||
size_t first = 0;
|
||||
float time = 0.0f;
|
||||
const size_t layer = static_cast<size_t>(m_vertices[id].layer_id);
|
||||
if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) {
|
||||
first = m_layer_first_vertex[layer];
|
||||
time = m_layer_start_times[mode][layer];
|
||||
}
|
||||
for (size_t i = first; i <= id; ++i)
|
||||
time += m_vertices[i].times[mode];
|
||||
return time;
|
||||
}
|
||||
|
||||
Color ViewerImpl::get_vertex_color(const PathVertex& v) const
|
||||
@@ -1752,7 +1826,7 @@ 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)
|
||||
for (const std::vector<float>& times : m_layer_start_times)
|
||||
ret += STDVEC_MEMSIZE(times, float);
|
||||
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
|
||||
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
|
||||
@@ -1821,10 +1895,8 @@ 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.
|
||||
// every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop
|
||||
// below would skip all of them anyway
|
||||
auto first_it = m_vertices.begin();
|
||||
if (layers_range[0] < m_layer_first_vertex.size())
|
||||
first_it += m_layer_first_vertex[layers_range[0]];
|
||||
@@ -2014,7 +2086,8 @@ 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)
|
||||
const ActiveSet segments = active_segments();
|
||||
if (segments.count == 0)
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
return;
|
||||
|
||||
@@ -2073,10 +2146,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, segments.tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, segments.buf_id));
|
||||
|
||||
m_segment_template.render(m_enabled_segments_count);
|
||||
m_segment_template.render(segments.count);
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
if (curr_cull_face)
|
||||
@@ -2102,7 +2175,8 @@ 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)
|
||||
const ActiveSet options = active_options();
|
||||
if (options.count == 0)
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
return;
|
||||
|
||||
@@ -2160,10 +2234,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, options.tex_id));
|
||||
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, options.buf_id));
|
||||
|
||||
m_option_template.render(m_enabled_options_count);
|
||||
m_option_template.render(options.count);
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
if (!curr_cull_face)
|
||||
|
||||
@@ -91,6 +91,19 @@ public:
|
||||
// 0.0 = black
|
||||
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// Draw from the reduced set; it is already built, so this is just a buffer binding.
|
||||
//
|
||||
void set_reduced_detail(bool value) {
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
// no reduced set is built on OpenGL ES
|
||||
value = false;
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
m_settings.reduced_detail = value;
|
||||
}
|
||||
bool is_reduced_detail() const { return m_settings.reduced_detail; }
|
||||
bool is_reduced_detail_enabled() const { return m_settings.reduced_detail_enabled; }
|
||||
void set_reduced_detail_enabled(bool value);
|
||||
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
|
||||
@@ -234,24 +247,17 @@ 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.
|
||||
// Running sum of the vertex estimated times at each layer's first vertex, for each time mode,
|
||||
// so that get_estimated_time_at() only accumulates the vertices of one layer.
|
||||
//
|
||||
std::array<std::vector<float>, TIME_MODES_COUNT> m_cumulative_times;
|
||||
std::array<std::vector<float>, TIME_MODES_COUNT> m_layer_start_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.
|
||||
// if there is none). Derived from the vertices, so it stays exact whatever order they 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.
|
||||
// Scratch buffer for update_colors_texture(), kept alive across slider steps
|
||||
//
|
||||
std::vector<float> m_colors_scratch;
|
||||
//
|
||||
@@ -481,6 +487,15 @@ private:
|
||||
unsigned int m_enabled_options_tex_id{ 0 };
|
||||
size_t m_enabled_options_count{ 0 };
|
||||
//
|
||||
// OpenGL buffers to store the reduced set 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 };
|
||||
//
|
||||
// Caches for size of data sent to gpu, in bytes
|
||||
//
|
||||
size_t m_positions_tex_size{ 0 };
|
||||
@@ -488,6 +503,25 @@ 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 while dragging, if one is built.
|
||||
bool use_reduced_set() const { return m_settings.reduced_detail && m_settings.reduced_detail_enabled; }
|
||||
struct ActiveSet
|
||||
{
|
||||
size_t count{ 0 };
|
||||
unsigned int buf_id{ 0 };
|
||||
unsigned int tex_id{ 0 };
|
||||
};
|
||||
ActiveSet active_segments() const {
|
||||
if (use_reduced_set())
|
||||
return { m_enabled_segments_reduced_count, m_enabled_segments_reduced_buf_id, m_enabled_segments_reduced_tex_id };
|
||||
return { m_enabled_segments_count, m_enabled_segments_buf_id, m_enabled_segments_tex_id };
|
||||
}
|
||||
ActiveSet active_options() const {
|
||||
if (use_reduced_set())
|
||||
return { m_enabled_options_reduced_count, m_enabled_options_reduced_buf_id, m_enabled_options_reduced_tex_id };
|
||||
return { m_enabled_options_count, m_enabled_options_buf_id, m_enabled_options_tex_id };
|
||||
}
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
void update_view_full_range();
|
||||
|
||||
@@ -1177,6 +1177,8 @@ 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();
|
||||
|
||||
read_solid_model_preference();
|
||||
|
||||
// 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")));
|
||||
@@ -1589,6 +1591,7 @@ void GCodeViewer::reset_shell()
|
||||
{
|
||||
m_shells.volumes.clear();
|
||||
m_shells.print_id = -1;
|
||||
m_shells.with_wipe_tower = false;
|
||||
m_shell_bounding_box = BoundingBoxf3();
|
||||
}
|
||||
|
||||
@@ -1625,7 +1628,12 @@ void GCodeViewer::reset()
|
||||
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
|
||||
{
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
render_shells(canvas_width, canvas_height);
|
||||
// while dragging with the solid model on, 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
|
||||
if (m_viewer.is_reduced_detail())
|
||||
render_solid_model(canvas_width, canvas_height);
|
||||
else
|
||||
render_shells(canvas_width, canvas_height);
|
||||
|
||||
if (m_viewer.get_extrusion_roles_count() == 0)
|
||||
return;
|
||||
@@ -1925,6 +1933,41 @@ void GCodeViewer::update_layers_slider_mode()
|
||||
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
|
||||
}
|
||||
|
||||
void GCodeViewer::set_interacting(bool interacting)
|
||||
{
|
||||
// with no shells to stand in for the toolpaths, the solid model would leave only the end layers
|
||||
m_viewer.set_reduced_detail(m_solid_model_while_dragging && interacting && !m_shells.volumes.empty());
|
||||
}
|
||||
|
||||
void GCodeViewer::set_solid_model_while_dragging(bool value)
|
||||
{
|
||||
const bool was_enabled = m_solid_model_while_dragging;
|
||||
m_solid_model_while_dragging = value;
|
||||
m_viewer.set_reduced_detail_enabled(value);
|
||||
reload_shells_if_solid_model_changed(was_enabled);
|
||||
}
|
||||
|
||||
void GCodeViewer::read_solid_model_preference()
|
||||
{
|
||||
m_solid_model_while_dragging = get_app_config()->get_bool("preview_solid_model_while_dragging");
|
||||
m_viewer.set_reduced_detail_enabled(m_solid_model_while_dragging);
|
||||
}
|
||||
|
||||
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
|
||||
{
|
||||
if (was_enabled == m_solid_model_while_dragging || m_shells.print_id == -1)
|
||||
return;
|
||||
// only the prime tower comes and goes with the mode: a full reload would drop the shells
|
||||
// whenever the print has moved on since they were loaded, leaving the solid model nothing to draw
|
||||
if (wxGetApp().plater() == nullptr)
|
||||
return;
|
||||
// the shells are loaded from the current plate's print, which is not the plater's own
|
||||
const Print& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
|
||||
if (static_cast<int>(print.id().id) != m_shells.print_id)
|
||||
return;
|
||||
update_shell_wipe_tower(print, m_gl_data_initialized);
|
||||
}
|
||||
|
||||
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]));
|
||||
@@ -2249,7 +2292,11 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
||||
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
|
||||
// the shells can load before the first G-code does, so the preferences are read here as well
|
||||
read_solid_model_preference();
|
||||
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
|
||||
// the prime tower comes and goes on its own, without reloading the objects
|
||||
update_shell_wipe_tower(print, initialized);
|
||||
//BBS: update force previewing logic
|
||||
if (force_previewing)
|
||||
m_shells.previewing = force_previewing;
|
||||
@@ -2358,10 +2405,45 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
|
||||
m_shells.print_id = print.id().id;
|
||||
m_shells.print_modify_count = print.get_modified_count();
|
||||
m_shells.previewing = true;
|
||||
update_shell_wipe_tower(print, initialized);
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
|
||||
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
|
||||
}
|
||||
|
||||
// The prime tower as it was sliced, so that the solid model shows what the print shows. It keeps its
|
||||
// opaque colour, so it never appears among the translucent shells, and stays out of their bounding box.
|
||||
void GCodeViewer::update_shell_wipe_tower(const Print& print, bool initialized)
|
||||
{
|
||||
const bool with_wipe_tower = m_solid_model_while_dragging && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
|
||||
if (with_wipe_tower == m_shells.with_wipe_tower)
|
||||
return;
|
||||
m_shells.with_wipe_tower = with_wipe_tower;
|
||||
GLVolumePtrs& volumes = m_shells.volumes.volumes;
|
||||
if (!with_wipe_tower) {
|
||||
volumes.erase(std::remove_if(volumes.begin(), volumes.end(), [](GLVolume* volume) {
|
||||
if (!volume->is_wipe_tower)
|
||||
return false;
|
||||
delete volume;
|
||||
return true;
|
||||
}), volumes.end());
|
||||
return;
|
||||
}
|
||||
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());
|
||||
const size_t first_new = volumes.size();
|
||||
m_shells.volumes.load_real_wipe_tower_preview(1000 + plate_idx, 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);
|
||||
for (size_t i = first_new; i < volumes.size(); ++i) {
|
||||
volumes[i]->zoom_to_volumes = false;
|
||||
volumes[i]->force_native_color = true;
|
||||
volumes[i]->set_render_color();
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeViewer::render_toolpaths()
|
||||
{
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
@@ -2562,6 +2644,50 @@ 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;
|
||||
// gouraud_light has no z range, so it could not cut the model
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
|
||||
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);
|
||||
// gouraud also clips by this plane, which nothing else sets on the shells
|
||||
m_shells.volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data());
|
||||
|
||||
shader->start_using();
|
||||
// the 3D view leaves its shadow settings on the shared program
|
||||
shader->set_uniform("shadow_intensity", 0.0f);
|
||||
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->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)
|
||||
|
||||
@@ -174,6 +174,8 @@ public:
|
||||
int print_id{-1};
|
||||
int print_modify_count{-1};
|
||||
bool previewing{false};
|
||||
// the prime tower was loaded with the objects, for the solid model
|
||||
bool with_wipe_tower{false};
|
||||
};
|
||||
//BBS
|
||||
ConflictResultOpt m_conflict_result;
|
||||
@@ -233,6 +235,13 @@ private:
|
||||
|
||||
bool m_legend_visible{ true };
|
||||
bool m_legend_enabled{ true };
|
||||
// while dragging, the sliced objects are drawn as solid shapes instead of toolpaths
|
||||
bool m_solid_model_while_dragging{ false };
|
||||
void read_solid_model_preference();
|
||||
void render_solid_model(int canvas_width, int canvas_height);
|
||||
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
|
||||
void reload_shells_if_solid_model_changed(bool was_enabled);
|
||||
void update_shell_wipe_tower(const Print& print, bool initialized);
|
||||
|
||||
float m_legend_height;
|
||||
PrintEstimatedStatistics m_print_statistics;
|
||||
@@ -283,7 +292,7 @@ public:
|
||||
// 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);
|
||||
// void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
|
||||
bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); }
|
||||
bool has_data() const { return m_viewer.get_extrusion_roles_count() != 0; }
|
||||
|
||||
bool can_export_toolpaths() const;
|
||||
std::vector<int> get_plater_extruder();
|
||||
@@ -345,6 +354,11 @@ 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(); }
|
||||
|
||||
// while the user drags the camera or a slider, draw the solid model, if the preference asks for it
|
||||
void set_interacting(bool interacting);
|
||||
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
|
||||
void set_solid_model_while_dragging(bool value);
|
||||
|
||||
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; }
|
||||
|
||||
@@ -2054,6 +2054,11 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
|
||||
const bool overlay_tick = m_fps_overlay_tick;
|
||||
m_fps_overlay_tick = false;
|
||||
|
||||
// Whether the preview draws the solid model is decided before the cached scene is consulted,
|
||||
// since switching changes what the scene pass draws.
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data() && _update_preview_interaction())
|
||||
scene_dirty = true;
|
||||
|
||||
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
|
||||
// below once it is known whether the frame differs from the one on screen.
|
||||
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
|
||||
@@ -3233,9 +3238,16 @@ void GLCanvas3D::bind_event_handlers()
|
||||
if (m_selection_edit.kind != SelectionEdit::None)
|
||||
finish_selection_edit();
|
||||
ImGui::SetWindowFocus(nullptr);
|
||||
// a drag cut short never sees its button release, which would leave the solid model drawn
|
||||
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
|
||||
mouse_up_cleanup();
|
||||
render();
|
||||
evt.Skip();
|
||||
});
|
||||
m_canvas->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
|
||||
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
|
||||
mouse_up_cleanup();
|
||||
});
|
||||
m_event_handlers_bound = true;
|
||||
|
||||
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
||||
@@ -3308,6 +3320,17 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
||||
m_overlay_dirty |= imgui_requires_extra_frame;
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
|
||||
// the render timer only wakes the idle loop; the frame that puts the preview's toolpaths back
|
||||
// after a wheel burst has to be asked for here, once the settle time is really up
|
||||
if (m_preview_settle_pending) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= m_preview_interaction_until) {
|
||||
m_preview_settle_pending = false;
|
||||
m_dirty = true;
|
||||
}
|
||||
else // the timer fired early
|
||||
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
|
||||
}
|
||||
|
||||
if (!m_dirty && !m_overlay_dirty)
|
||||
return;
|
||||
@@ -3838,6 +3861,10 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
return;
|
||||
}
|
||||
|
||||
// only a wheel the panels did not take moves the camera
|
||||
if (m_canvas_type == CanvasPreview)
|
||||
note_preview_interaction();
|
||||
|
||||
#ifdef __WXMSW__
|
||||
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
|
||||
// if the event is not allowed to be passed further.
|
||||
@@ -3938,6 +3965,11 @@ void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -5554,6 +5586,9 @@ void GLCanvas3D::mouse_up_cleanup()
|
||||
m_mouse.ignore_left_up = false;
|
||||
m_mouse.ignore_right_up = false;
|
||||
m_dirty = true;
|
||||
// the frame that follows a release puts the preview's toolpaths back, and on some platforms
|
||||
// no idle event follows a button release until the next input
|
||||
wxWakeUpIdle();
|
||||
|
||||
if (m_canvas->HasCapture())
|
||||
m_canvas->ReleaseMouse();
|
||||
@@ -8738,6 +8773,26 @@ void GLCanvas3D::_render_wireframe_overlay()
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// The solid model is drawn while the camera, the navigator or either slider is dragged. A wheel
|
||||
// step has no duration, so it holds the solid model for a settle time instead, and the frame that
|
||||
// restores the toolpaths is scheduled for when that time runs out. Returns whether what the scene
|
||||
// pass draws changed, since a frame that reuses the cached scene would hide the change.
|
||||
bool 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();
|
||||
const bool was_reduced = m_gcode_viewer.is_reduced_detail();
|
||||
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);
|
||||
}
|
||||
return m_gcode_viewer.is_reduced_detail() != was_reduced;
|
||||
}
|
||||
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
|
||||
{
|
||||
|
||||
@@ -649,6 +649,10 @@ private:
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
// until when a wheel step keeps the preview's solid model drawn
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
|
||||
// whether the frame that restores the toolpaths 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;
|
||||
@@ -1215,6 +1219,8 @@ public:
|
||||
void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
|
||||
|
||||
void request_extra_frame() { m_extra_frame_requested = true; }
|
||||
// a wheel step is over before the next frame, so it holds the preview's solid model for a settle time
|
||||
void note_preview_interaction();
|
||||
|
||||
void schedule_extra_frame(int milliseconds);
|
||||
|
||||
@@ -1361,6 +1367,9 @@ private:
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void _render_gcode(int canvas_width, int canvas_height);
|
||||
void _render_gcode_overlay(int canvas_width, int canvas_height);
|
||||
// decides whether the preview draws its solid model this frame and returns whether what the scene
|
||||
// pass draws changed; runs before the cached scene is consulted
|
||||
bool _update_preview_interaction();
|
||||
//BBS: render a plane for assemble
|
||||
void _render_plane() const;
|
||||
void _render_selection();
|
||||
|
||||
@@ -483,6 +483,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();
|
||||
@@ -491,6 +496,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);
|
||||
@@ -883,6 +889,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; }
|
||||
// whether the mouse is 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;
|
||||
// the ImGui id of the slider widget, as of its last render
|
||||
unsigned int m_imgui_id = 0;
|
||||
|
||||
bool m_render_as_disabled{ false };
|
||||
|
||||
|
||||
@@ -1049,6 +1049,16 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
// apply the solid model preference immediately to the currently loaded preview
|
||||
else if (param == "preview_solid_model_while_dragging") {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_solid_model_while_dragging(app_config->get_bool(param));
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (param == "preview_dim_previous_layers") {
|
||||
if (m_dim_previous_layers_brightness_input)
|
||||
m_dim_previous_layers_brightness_input->Enable(app_config->get_bool(param));
|
||||
@@ -2019,6 +2029,15 @@ void PreferencesDialog::create_items()
|
||||
//// GRAPHICS > G-code Preview
|
||||
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
|
||||
|
||||
auto item_solid_model_while_dragging = create_item_checkbox(
|
||||
_L("Only render solid model when dragging"),
|
||||
_L("While dragging the camera or a preview slider, or zooming with the mouse wheel, draw the sliced objects and the prime tower as solid shapes "
|
||||
"in their filament colours instead of toolpaths, so that large prints stay responsive. They are cut to the visible layer range, with its bottom "
|
||||
"and top layers drawn as toolpaths. Supports are not shown. The toolpaths are restored as soon as you let go."),
|
||||
"preview_solid_model_while_dragging"
|
||||
);
|
||||
g_sizer->Add(item_solid_model_while_dragging);
|
||||
|
||||
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."),
|
||||
|
||||
Reference in New Issue
Block a user