Let the User Choose What the Simplified Preview Leaves Out While Dragging

The reduced toolpath set was only ever bound during a camera drag. The slider
check read is_dirty(), which the slider raises inside its own render and
_render_gcode() consumes at the end of the same frame, so it was never true at
the start of the next one; the only time it was true was the frame after a
keyboard or wheel step, which then drew one coarse frame followed by one full
frame. The navigator cube and the mouse wheel never engaged it at all, and the
extra frame requested on every level change was a full-detail frame drawn for
nothing, since the level is chosen before the draw. The reduced buffers were
also built and uploaded on every slider tick with the preference off.

Dragging is now read from ImGui's active id for the sliders and from the
navigator's own flag, a wheel step holds the reduced set for a 150 ms settle
time with the restoring frame scheduled for when it runs out, and nothing is
built while the preference is off.

What the reduced set leaves out is now a choice: skip layers only, skip the
internal infill roles, or keep the shell only. The shell is found geometrically
at load, once per print: every layer is rasterized into a half-millimetre
occupancy grid and closed so that sparse infill reads as solid, a cell is on the
shell when any of its six neighbours is empty, and a segment is kept when at
least half of its cells are. Being geometric it works for the wipe tower, whose
every segment shares one role and which is the bulk of a tall print, where
dropping roles could not. The layer stride is a preference too, one layer in
every N with both ends of the visible range always whole.

On the 636-layer three-filament fixture, frames during a drag against 448467
segments drawn in 334 ms at full detail:

  skip layers only, 1 in 4        110691 segments   83 ms
  skip internal infill, 1 in 4     85661 segments   68 ms
  shell only, 1 in 4               54382 segments   52 ms
  skip internal infill, every layer 347208 segments  255 ms
  shell only, every layer          215797 segments  170 ms

The shell classification takes 63 ms once per load, split across threads. At
rest the preview is pixel identical to before in every configuration, and the
frame after a release or a settled wheel burst is the full one.
This commit is contained in:
Hanif Koh
2026-09-10 16:39:17 +08:00
parent 8fd5d77220
commit 77d00c0ee4
15 changed files with 539 additions and 55 deletions

View File

@@ -202,10 +202,30 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty()) if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1"); set("seq_top_layer_only", "1");
// ORCA: darken the layers the preview layer slider is not scrubbed to // 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()) if (get("preview_reduced_detail_while_dragging").empty())
set_bool("preview_reduced_detail_while_dragging", false); 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")
set("preview_reduced_detail_mode", "no_infill");
}
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()) if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false); set_bool("preview_dim_previous_layers", false);

View File

@@ -158,6 +158,23 @@ enum class EGCodeExtrusionRole : uint8_t
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT); 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,
COUNT
};
// //
// Option types // Option types
// //

View File

@@ -99,14 +99,18 @@ public:
bool is_dim_previous_layers() const; bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value); void set_dim_previous_layers(bool value);
// //
// ORCA: draw the preview from a reduced set of entities - the interior infill roles dropped and // ORCA: draw the preview from a reduced set of entities: one layer in every
// one layer in every set_reduced_detail_layer_stride() kept, always keeping the top of the // get_reduced_detail_layer_stride() layers is kept, and on top of that the mode decides which
// visible layer range. Meant to be held only while the user drags the camera or a slider: the // roles or segments are left out. The bottom and top of the visible layer range are always kept
// reduced set is built alongside the full one, so toggling it never rebuilds anything. // whole. Meant to be held only while the user drags the camera or a slider: the reduced set is
// Has no effect on the OpenGL ES path. // 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); void set_reduced_detail(bool value);
bool is_reduced_detail() const; 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); void set_reduced_detail_layer_stride(uint32_t value);
float get_dim_previous_layers_brightness() const; float get_dim_previous_layers_brightness() const;
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);

View File

@@ -26,11 +26,12 @@ struct Settings
float dim_previous_layers_brightness{ 0.4f }; float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false }; bool spiral_vase_mode{ false };
// ORCA: while the user drags the camera or a slider, the preview can be drawn from a reduced // ORCA: while the user drags the camera or a slider, the preview can be drawn from a reduced
// set of entities: the interior infill roles dropped and one layer in every // set of entities: one layer in every reduced_detail_layer_stride kept, and on top of that
// reduced_detail_layer_stride kept. The reduced sets are built alongside the full ones in // whatever reduced_detail_mode leaves out. The reduced sets are built alongside the full ones
// update_enabled_entities(), so turning this on and off costs nothing but a buffer binding. // 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. // Ignored on the OpenGL ES path, which keeps a single set of entities.
bool reduced_detail{ false }; bool reduced_detail{ false };
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
uint32_t reduced_detail_layer_stride{ 4 }; uint32_t reduced_detail_layer_stride{ 4 };
// //
// Required update flags // Required update flags

View File

@@ -87,6 +87,21 @@ bool Viewer::is_reduced_detail() const
return m_impl->is_reduced_detail(); 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) void Viewer::set_reduced_detail_layer_stride(uint32_t value)
{ {
m_impl->set_reduced_detail_layer_stride(value); m_impl->set_reduced_detail_layer_stride(value);

View File

@@ -17,6 +17,9 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <cfloat>
#include <future>
#include <thread>
namespace libvgcode { namespace libvgcode {
@@ -893,6 +896,7 @@ void ViewerImpl::reset()
m_enabled_options_count = 0; m_enabled_options_count = 0;
m_enabled_segments_reduced_count = 0; m_enabled_segments_reduced_count = 0;
m_enabled_options_reduced_count = 0; m_enabled_options_reduced_count = 0;
m_shell_bitset = BitSet<>();
m_settings_used_for_ranges = std::nullopt; m_settings_used_for_ranges = std::nullopt;
@@ -1170,14 +1174,254 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
} }
#ifndef ENABLE_OPENGL_ES #ifndef ENABLE_OPENGL_ES
// ORCA: the roles that sit inside the part and are hidden by its walls from every angle. Dropping // ORCA: the roles that sit inside the part and are hidden by its walls from every angle
// them is the least visible half of the reduced set built below.
static bool is_interior_infill(EGCodeExtrusionRole role) static bool is_interior_infill(EGCodeExtrusionRole role)
{ {
return role == EGCodeExtrusionRole::InternalInfill || return role == EGCodeExtrusionRole::InternalInfill ||
role == EGCodeExtrusionRole::SolidInfill || role == EGCodeExtrusionRole::SolidInfill ||
role == EGCodeExtrusionRole::InternalBridgeInfill; role == EGCodeExtrusionRole::InternalBridgeInfill;
} }
bool ViewerImpl::reduced_set_keeps(size_t i, const PathVertex& v) const
{
switch (m_settings.reduced_detail_mode) {
case EReducedDetailMode::NoInternalInfill: return !is_interior_infill(v.role);
case EReducedDetailMode::ShellOnly: return m_shell_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);
}
};
// 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. Separable
// passes over running window sums keep the cost linear in the rectangle's area.
static void close_gaps(OccupancyGrid& grid, int radius, std::vector<int>& window_sum)
{
if (grid.empty() || radius <= 0)
return;
// the dilated area needs room to grow
grid.grow(radius);
const auto pass = [&](bool horizontal, bool dilate) {
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)];
// cells outside the rectangle are empty, which is what a shrinking erosion has to see
cell(i) = dilate ? (count > 0) : (count == 2 * radius + 1);
}
}
};
pass(true, true);
pass(false, true);
pass(true, false);
pass(false, false);
}
} // 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.
void ViewerImpl::update_shell_bitset()
{
m_shell_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_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. Each call
// owns its grids, so the layer range can be split across threads.
const auto classify_layers = [&](size_t first_layer, size_t last_layer) {
std::vector<uint32_t> kept;
std::vector<OccupancyGrid> footprints(3, OccupancyGrid(nx, ny));
OccupancyGrid shell_cells(nx, ny);
std::vector<int> window_sum;
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, window_sum);
};
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();
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;
if (!cur.at(x - 1, y) || !cur.at(x + 1, y) || !cur.at(x, y - 1) || !cur.at(x, y + 1) ||
!below.at(x, y) || !above.at(x, y))
shell_cells.set(x, y);
}
}
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_shell = 0;
for_each_cell(i, [&](int x, int y) { ++total; on_shell += shell_cells.at(x, y); });
if (2 * on_shell >= total)
kept.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<std::vector<uint32_t>>> 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)));
for (auto& f : futures) {
for (uint32_t i : f.get())
m_shell_bitset.set(i);
}
}
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities() void ViewerImpl::update_enabled_entities()
@@ -1189,12 +1433,17 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_options; std::vector<uint32_t> enabled_options;
#ifndef ENABLE_OPENGL_ES #ifndef ENABLE_OPENGL_ES
// ORCA: the reduced sets are filled by the same walk, so switching to them costs no rebuild. // 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;
std::vector<uint32_t> enabled_segments_reduced; std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced; std::vector<uint32_t> enabled_options_reduced;
const uint32_t layer_stride = std::max<uint32_t>(1, m_settings.reduced_detail_layer_stride); const uint32_t layer_stride = std::max<uint32_t>(1, m_settings.reduced_detail_layer_stride);
// Whatever else is dropped, the top of the visible layer range is kept whole: it is the surface // Whatever else is dropped, both ends of the visible layer range are kept whole: the top is the
// the user is looking at, and in top-layer-only mode it is the only layer drawn at full color. // surface the user is looking at, and the only layer drawn at full color in top-layer-only
const uint32_t kept_layer = m_layers.get_view_range()[1]; // mode; the bottom is exposed whenever the range is cut short.
const Interval& layers_range = m_layers.get_view_range();
if (build_reduced && m_settings.reduced_detail_mode == EReducedDetailMode::ShellOnly &&
m_shell_bitset.size != m_vertices.size())
update_shell_bitset();
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible(); Interval range = m_view_range.get_visible();
@@ -1245,11 +1494,14 @@ void ViewerImpl::update_enabled_entities()
enabled_segments.push_back(static_cast<uint32_t>(i)); enabled_segments.push_back(static_cast<uint32_t>(i));
#ifndef ENABLE_OPENGL_ES #ifndef ENABLE_OPENGL_ES
if (v.layer_id != kept_layer && (v.layer_id % layer_stride) != 0) if (!build_reduced)
continue;
const bool whole_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
if (!whole_layer && (v.layer_id % layer_stride) != 0)
continue; continue;
if (v.is_option()) if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i)); enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!(v.is_extrusion() && is_interior_infill(v.role))) else if (whole_layer || !v.is_extrusion() || reduced_set_keeps(i, v))
enabled_segments_reduced.push_back(static_cast<uint32_t>(i)); enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
} }
@@ -1283,15 +1535,17 @@ void ViewerImpl::update_enabled_entities()
m_enabled_segments_reduced_count = enabled_segments_reduced.size(); m_enabled_segments_reduced_count = enabled_segments_reduced.size();
m_enabled_options_reduced_count = enabled_options_reduced.size(); m_enabled_options_reduced_count = enabled_options_reduced.size();
assert(m_enabled_segments_reduced_buf_id > 0); if (build_reduced) {
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id)); assert(m_enabled_segments_reduced_buf_id > 0);
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_segments_reduced.size() * sizeof(uint32_t), glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW)); 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); assert(m_enabled_options_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id)); glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options_reduced.size() * sizeof(uint32_t), glsafe(glBufferData(GL_TEXTURE_BUFFER, enabled_options_reduced.size() * sizeof(uint32_t),
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW)); enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
}
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0)); glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
@@ -1471,8 +1725,16 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture(); update_colors_texture();
} }
// ORCA: how many layers the reduced set keeps one of. Changing it changes which vertices land in // ORCA: what the reduced set leaves out, and how many layers it keeps one of. Either changes which
// the reduced set, so the sets have to be rebuilt. // 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) void ViewerImpl::set_reduced_detail_layer_stride(uint32_t value)
{ {
value = std::max<uint32_t>(1, value); value = std::max<uint32_t>(1, value);

View File

@@ -97,6 +97,9 @@ public:
// //
void set_reduced_detail(bool value) { m_settings.reduced_detail = value; } void set_reduced_detail(bool value) { m_settings.reduced_detail = value; }
bool is_reduced_detail() const { return m_settings.reduced_detail; } 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); void set_reduced_detail_layer_stride(uint32_t value);
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; } float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);
@@ -316,6 +319,13 @@ private:
// Variables used for toolpaths visibiliity // Variables used for toolpaths visibiliity
// //
BitSet<> m_valid_lines_bitset; 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;
#endif // ENABLE_OPENGL_ES
// //
// Variables used for toolpaths coloring // Variables used for toolpaths coloring
// //
@@ -505,25 +515,33 @@ private:
size_t m_enabled_segments_tex_size{ 0 }; size_t m_enabled_segments_tex_size{ 0 };
size_t m_enabled_options_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. // 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.
bool use_reduced_set() const {
return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off;
}
size_t active_segments_count() const { size_t active_segments_count() const {
return m_settings.reduced_detail ? m_enabled_segments_reduced_count : m_enabled_segments_count; return use_reduced_set() ? m_enabled_segments_reduced_count : m_enabled_segments_count;
} }
unsigned int active_segments_buf_id() const { unsigned int active_segments_buf_id() const {
return m_settings.reduced_detail ? m_enabled_segments_reduced_buf_id : m_enabled_segments_buf_id; return use_reduced_set() ? m_enabled_segments_reduced_buf_id : m_enabled_segments_buf_id;
} }
unsigned int active_segments_tex_id() const { unsigned int active_segments_tex_id() const {
return m_settings.reduced_detail ? m_enabled_segments_reduced_tex_id : m_enabled_segments_tex_id; return use_reduced_set() ? m_enabled_segments_reduced_tex_id : m_enabled_segments_tex_id;
} }
size_t active_options_count() const { size_t active_options_count() const {
return m_settings.reduced_detail ? m_enabled_options_reduced_count : m_enabled_options_count; return use_reduced_set() ? m_enabled_options_reduced_count : m_enabled_options_count;
} }
unsigned int active_options_buf_id() const { unsigned int active_options_buf_id() const {
return m_settings.reduced_detail ? m_enabled_options_reduced_buf_id : m_enabled_options_buf_id; return use_reduced_set() ? m_enabled_options_reduced_buf_id : m_enabled_options_buf_id;
} }
unsigned int active_options_tex_id() const { unsigned int active_options_tex_id() const {
return m_settings.reduced_detail ? m_enabled_options_reduced_tex_id : m_enabled_options_tex_id; 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 reduced set under the current mode
bool reduced_set_keeps(size_t i, const PathVertex& v) const;
void update_shell_bitset();
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
void update_view_full_range(); void update_view_full_range();

View File

@@ -1172,8 +1172,13 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only) if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range(); m_viewer.toggle_top_layer_only_view_range();
// ORCA: darken the layers the preview layer slider is not scrubbed to // 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_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_reduced_detail_layer_stride = static_cast<unsigned int>(std::max(1, std::stoi(get_app_config()->get("preview_reduced_detail_layer_stride"))));
apply_reduced_detail_settings();
// 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(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"))); m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
@@ -1908,13 +1913,44 @@ void GCodeViewer::update_layers_slider_mode()
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); // TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
} }
bool GCodeViewer::set_interacting(bool interacting) void GCodeViewer::set_interacting(bool interacting)
{ {
const bool reduced = m_reduced_detail_while_dragging && interacting; m_viewer.set_reduced_detail(m_reduced_detail_while_dragging && interacting);
if (reduced == m_viewer.is_reduced_detail()) }
return false;
m_viewer.set_reduced_detail(reduced); // ORCA: libvgcode only builds a reduced set while its mode is not Off, so the preference switch is
return true; // 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);
}
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);
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 == "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) void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)

View File

@@ -230,8 +230,11 @@ private:
bool m_legend_visible{ true }; bool m_legend_visible{ true };
bool m_legend_enabled{ true }; bool m_legend_enabled{ true };
// ORCA: whether the reduced-detail-while-dragging preference is on // ORCA: the reduced-detail-while-dragging preferences, pushed to libvgcode by apply_reduced_detail_settings()
bool m_reduced_detail_while_dragging{ false }; bool m_reduced_detail_while_dragging{ false };
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::NoInternalInfill };
unsigned int m_reduced_detail_layer_stride{ 4 };
void apply_reduced_detail_settings();
float m_legend_height; float m_legend_height;
PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics m_print_statistics;
@@ -342,10 +345,14 @@ public:
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); } 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 // ORCA: while the user drags the camera or a slider, draw the preview from libvgcode's reduced
// toolpath set. Returns true when the detail level actually changed, so the caller can queue the // toolpath set, if the preference asks for one
// frame that puts the full detail back. void set_interacting(bool interacting);
bool set_interacting(bool interacting); bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
void set_reduced_detail_while_dragging(bool value) { m_reduced_detail_while_dragging = value; } 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);
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); void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);

View File

@@ -3240,6 +3240,12 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_dirty |= imgui_requires_extra_frame; m_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh(); 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) if (!m_dirty)
return; return;
@@ -3945,6 +3951,9 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
evt.SetY(evt.GetY() * scale); evt.SetY(evt.GetY() * scale);
#endif #endif
if (m_canvas_type == CanvasPreview)
note_preview_interaction();
if (wxGetApp().imgui()->update_mouse_data(evt)) { if (wxGetApp().imgui()->update_mouse_data(evt)) {
if (m_canvas_type == CanvasPreview) { if (m_canvas_type == CanvasPreview) {
IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider(); IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider();
@@ -4051,6 +4060,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) void GLCanvas3D::schedule_extra_frame(int milliseconds)
{ {
// Schedule idle event right now // Schedule idle event right now
@@ -8513,11 +8527,19 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
IMSlider *layers_slider = m_gcode_viewer.get_layers_slider(); IMSlider *layers_slider = m_gcode_viewer.get_layers_slider();
IMSlider *moves_slider = m_gcode_viewer.get_moves_slider(); IMSlider *moves_slider = m_gcode_viewer.get_moves_slider();
// ORCA: dragging the camera or either slider is the only time the preview has to keep up with // ORCA: dragging the camera, the navigator or either slider is when the preview has to keep up
// continuous input, so that is when the reduced toolpath set earns its visible coarseness. // with continuous input, so that is when the reduced toolpath set earns its visible coarseness.
// A change of detail level needs one more frame to draw the result of the change. // A wheel step has no duration, so it holds the reduced set for a settle time instead, and the
if (m_gcode_viewer.set_interacting(m_mouse.dragging || layers_slider->is_dirty() || moves_slider->is_dirty())) // frame that restores the full detail is scheduled for when that time runs out. The level is
request_extra_frame(); // chosen before the draw, so a change lands in this very frame.
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);
}
m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE); m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE);

View File

@@ -591,6 +591,10 @@ private:
ECursorType m_cursor_type; ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection; GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false }; 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 //BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs; mutable std::vector<int> m_hover_volume_idxs;
@@ -1138,6 +1142,9 @@ public:
void msw_rescale() { m_gcode_viewer.invalidate_legend(); } void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
void request_extra_frame() { m_extra_frame_requested = true; } 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); void schedule_extra_frame(int milliseconds);

View File

@@ -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()); 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) 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(); 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; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); 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; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); const ImRect draw_region(pos, pos + size);

View File

@@ -118,6 +118,9 @@ public:
//BBS update scroll value changed //BBS update scroll value changed
bool is_dirty() { return m_dirty; } 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; } void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; } bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) { void reset_post_tick_event(bool val = false) {
@@ -182,6 +185,8 @@ private:
int m_higher_value; int m_higher_value;
int m_one_layer_value; // ORCA int m_one_layer_value; // ORCA
bool m_dirty = false; 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 }; bool m_render_as_disabled{ false };

View File

@@ -318,7 +318,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
return sizer; 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()); assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0; 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); 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 //// 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()]); app_config->set(param, config_name_index[e.GetSelection()]);
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
e.Skip(); 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))); 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); 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") { if (param == "preview_dim_previous_layers_brightness") {
m_dim_previous_layers_brightness_input = input; m_dim_previous_layers_brightness_input = input;
input->Enable(app_config->get_bool("preview_dim_previous_layers")); 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); m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
@@ -1058,6 +1069,10 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
} }
// ORCA: apply the reduced-detail preference immediately to the currently loaded preview // ORCA: apply the reduced-detail preference immediately to the currently loaded preview
else if (param == "preview_reduced_detail_while_dragging") { 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 (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) { if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_reduced_detail_while_dragging(app_config->get_bool(param)); canvas->get_gcode_viewer().set_reduced_detail_while_dragging(app_config->get_bool(param));
@@ -1950,11 +1965,57 @@ void PreferencesDialog::create_items()
auto item_reduced_detail_while_dragging = create_item_checkbox( auto item_reduced_detail_while_dragging = create_item_checkbox(
_L("Simplify preview while dragging"), _L("Simplify preview while dragging"),
_L("While dragging the camera or a preview slider, draw only part of the toolpaths so that large prints stay responsive. Internal infill is left out and only every fourth layer is drawn, and the full detail is restored as soon as you let go."), _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" "preview_reduced_detail_while_dragging"
); );
g_sizer->Add(item_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, and holes narrower than 5 mm are treated as solid."),
"preview_reduced_detail_mode",
{_L("Skip layers only"), _L("Skip internal infill"), _L("Shell only")},
{"layers", "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_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_dim_previous_layers = create_item_checkbox( auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"), _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."), _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."),

View File

@@ -73,6 +73,8 @@ public:
::CheckBox * m_bambu_cloud_checkbox = {nullptr}; ::CheckBox * m_bambu_cloud_checkbox = {nullptr};
::TextInput *m_backup_interval_textinput = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr};
::SpinInput *m_dim_previous_layers_brightness_input = {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}; ::ComboBox * m_network_version_combo = {nullptr};
std::vector<NetworkLibraryVersionInfo> m_available_versions; std::vector<NetworkLibraryVersionInfo> m_available_versions;
@@ -86,7 +88,7 @@ public:
wxBoxSizer *create_item_title(wxString title); wxBoxSizer *create_item_title(wxString title);
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = ""); 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::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_region_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_language_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); wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);