mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 11:21:30 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d89d6978 | ||
|
|
abecef53b0 | ||
|
|
44d5a22a32 | ||
|
|
dec146f1e6 | ||
|
|
a9fec0ffc4 | ||
|
|
9ab6bbebf0 | ||
|
|
cda1588578 | ||
|
|
ec0d8c225f | ||
|
|
3040ebaac1 | ||
|
|
5298e49dd2 | ||
|
|
4964f49765 | ||
|
|
ea471dc82d | ||
|
|
bd65cce3f4 | ||
|
|
d347a80ef8 | ||
|
|
e340a13c18 | ||
|
|
9e16cdb23b | ||
|
|
58842bab05 | ||
|
|
04204ec0d3 | ||
|
|
9ed459e132 |
@@ -0,0 +1,80 @@
|
||||
# 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. The `preview_reduced_detail_mode` preference (*Graphics > G-code Preview*, off by
|
||||
default) lets the preview draw less while the user drags and put the full toolpaths back when they
|
||||
let go.
|
||||
|
||||
| Preference | Values | Effect |
|
||||
|---|---|---|
|
||||
| `preview_reduced_detail_mode` | `off`, `solid`, `shell` | what is drawn while dragging |
|
||||
|
||||
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
|
||||
preferences onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
|
||||
The OpenGL ES path keeps a single set and ignores the preference.
|
||||
|
||||
## Two sets, one walk
|
||||
|
||||
`ViewerImpl::update_enabled_entities()` walks the visible vertex range once and fills two segment
|
||||
index buffers side by side: the **full** set and the **reduced** set (segments and options).
|
||||
Building them together is what makes switching free: starting or ending a drag is a buffer
|
||||
binding, never a rebuild. A change of mode does rebuild. Nothing is built while the mode
|
||||
is off, and the reduced buffers are then uploaded empty so that the last set does not stay
|
||||
allocated.
|
||||
|
||||
Whatever the mode leaves out, the bottom and top layers of the visible range are kept whole: they
|
||||
are the faces the range cuts open, and the top is what the user is looking at.
|
||||
|
||||
### Modes
|
||||
|
||||
- `EndLayersOnly` (`solid` in the preference) keeps only the two end layers. `GCodeViewer` then
|
||||
draws the sliced objects and the prime tower as opaque solids, see below.
|
||||
- `ShellOnly` (`shell`) keeps every role but the sparse infill, internal solid infill, internal
|
||||
bridge infill and gap fill, which lie under the skins or between the walls. Walls, top and
|
||||
bottom surfaces, bridges, supports and the prime tower are drawn whole, so from outside the
|
||||
print looks as it does at rest. The roles come from
|
||||
the slicer, so the classification is exact wherever the slicer's is; what it cannot express is
|
||||
the inside of the prime tower and of a support, which share one role with their outside and are
|
||||
kept whole. A profile with no top or bottom shell layers, or no walls, leaves the infill on the
|
||||
surface, so `GCodeViewer` turns the hiding off for such a print when it loads it, from the
|
||||
print's default region settings; per-object overrides are not consulted.
|
||||
|
||||
## 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 colors, with the
|
||||
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
|
||||
the reduced set are drawn afterwards and cap the cut with what was really printed there. The
|
||||
shells hold only the objects, so while this mode 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 mode changes,
|
||||
without reloading the objects, keeps its opaque color so that it never appears among the
|
||||
translucent shells, and stays out of their bounding box. Supports have no mesh and are not shown,
|
||||
and `load_shells()` drops every non-model-part volume, so a negative volume is not cut out.
|
||||
|
||||
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
|
||||
only the end layers.
|
||||
|
||||
## 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 `GLCanvas3D::is_user_interacting()`, the same answer the scene cache reads: the camera, the
|
||||
navigator, a gizmo, the rectangle selection 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 reduced set 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 set 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 reduced frame outlives a drag, and the frame that ends a wheel's settle time is
|
||||
requested as a full frame.
|
||||
@@ -205,6 +205,13 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// what the preview draws while the user drags it
|
||||
{
|
||||
const std::string mode = get("preview_reduced_detail_mode");
|
||||
if (mode != "off" && mode != "solid" && mode != "shell")
|
||||
set("preview_reduced_detail_mode", "off");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
+10
-5
@@ -2504,7 +2504,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2556,7 +2557,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
}
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + err_msg;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2580,7 +2582,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + ex.what();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2693,7 +2696,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
if (ret) {
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2712,7 +2716,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
|
||||
+10
-5
@@ -2717,7 +2717,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
|
||||
@@ -3344,7 +3345,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
|
||||
@@ -4950,7 +4952,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
{
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4980,7 +4983,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = file + "\n" + ex.what();
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4994,7 +4998,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
|
||||
@@ -158,6 +158,21 @@ enum class EGCodeExtrusionRole : uint8_t
|
||||
|
||||
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
|
||||
|
||||
//
|
||||
// What the reduced set, drawn while the user is dragging, holds in place of the full toolpaths
|
||||
//
|
||||
enum class EReducedDetailMode : uint8_t
|
||||
{
|
||||
// nothing: no reduced set is built
|
||||
Off,
|
||||
// only the bottom and top layers of the visible range, for a caller that draws the print
|
||||
// itself some other way
|
||||
EndLayersOnly,
|
||||
// every role but the infill under the skins and the gap fill: what can be seen from outside
|
||||
ShellOnly,
|
||||
COUNT
|
||||
};
|
||||
|
||||
//
|
||||
// Option types
|
||||
//
|
||||
|
||||
@@ -114,6 +114,19 @@ public:
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// The reduced set drawn while the user drags: what the mode keeps, and always the bottom and
|
||||
// top layers of the visible range. While a mode is set it is built alongside the full set, so
|
||||
// set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
|
||||
//
|
||||
EReducedDetailMode get_reduced_detail_mode() const;
|
||||
void set_reduced_detail_mode(EReducedDetailMode mode);
|
||||
// Whether the shell mode hides the infill roles; off for a profile that leaves them on the
|
||||
// surface, with no top or bottom shell or no walls.
|
||||
bool get_reduced_detail_hide_infill() const;
|
||||
void set_reduced_detail_hide_infill(bool value);
|
||||
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,11 @@ 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 };
|
||||
// what the reduced set holds and whether it is drawn. Ignored on the OpenGL ES path.
|
||||
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
|
||||
// whether the shell mode may hide the infill roles: false when the profile leaves them on the surface
|
||||
bool reduced_detail_hide_infill{ true };
|
||||
bool reduced_detail{ false };
|
||||
//
|
||||
// Required update flags
|
||||
//
|
||||
|
||||
@@ -34,6 +34,10 @@ static const char* Segments_Vertex_Shader =
|
||||
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
|
||||
// world_position, so the caster would write a depth the receiver never looks up.
|
||||
"uniform float bias_scale;\n"
|
||||
// draw the instances last to first, top layers before the ones they hide, so that early depth
|
||||
// rejection discards most of the hidden fragments; set when the camera looks down on the print
|
||||
"uniform int reverse_order;\n"
|
||||
"uniform int instance_count;\n"
|
||||
"in int vertex_id;\n"
|
||||
"out vec3 color;\n"
|
||||
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
|
||||
@@ -59,7 +63,8 @@ static const char* Segments_Vertex_Shader =
|
||||
" return top_diffuse + front_diffuse + top_specular;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
|
||||
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
|
||||
" int id_b = id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, id_b).xyz;\n"
|
||||
|
||||
@@ -92,6 +92,36 @@ bool Viewer::is_dim_previous_layers() const
|
||||
return m_impl->is_dim_previous_layers();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail(bool value)
|
||||
{
|
||||
m_impl->set_reduced_detail(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_reduced_detail() const
|
||||
{
|
||||
return m_impl->is_reduced_detail();
|
||||
}
|
||||
|
||||
EReducedDetailMode Viewer::get_reduced_detail_mode() const
|
||||
{
|
||||
return m_impl->get_reduced_detail_mode();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail_mode(EReducedDetailMode mode)
|
||||
{
|
||||
m_impl->set_reduced_detail_mode(mode);
|
||||
}
|
||||
|
||||
bool Viewer::get_reduced_detail_hide_infill() const
|
||||
{
|
||||
return m_impl->get_reduced_detail_hide_infill();
|
||||
}
|
||||
|
||||
void Viewer::set_reduced_detail_hide_infill(bool value)
|
||||
{
|
||||
m_impl->set_reduced_detail_hide_infill(value);
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers(bool value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <cfloat>
|
||||
|
||||
namespace libvgcode {
|
||||
|
||||
@@ -763,6 +764,8 @@ void ViewerImpl::init(const std::string& opengl_context_version)
|
||||
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
|
||||
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
|
||||
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
|
||||
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
|
||||
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
|
||||
// ORCA: realistic view
|
||||
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
|
||||
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
|
||||
@@ -899,9 +902,17 @@ void ViewerImpl::reset()
|
||||
#else
|
||||
m_enabled_segments_count = 0;
|
||||
m_enabled_options_count = 0;
|
||||
m_enabled_segments_reduced_count = 0;
|
||||
m_enabled_options_reduced_count = 0;
|
||||
m_enabled_segments_reduced_tex_size = 0;
|
||||
m_enabled_options_reduced_tex_size = 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);
|
||||
@@ -1161,6 +1172,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
|
||||
@@ -1172,6 +1194,18 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
|
||||
update_colors();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
// the roles that lie under a skin or between walls, never seen from outside the print
|
||||
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
|
||||
{
|
||||
return role == EGCodeExtrusionRole::InternalInfill ||
|
||||
role == EGCodeExtrusionRole::SolidInfill ||
|
||||
role == EGCodeExtrusionRole::InternalBridgeInfill ||
|
||||
role == EGCodeExtrusionRole::GapFill;
|
||||
}
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
|
||||
void ViewerImpl::update_enabled_entities()
|
||||
{
|
||||
if (m_vertices.empty())
|
||||
@@ -1179,6 +1213,17 @@ 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. Whatever the
|
||||
// mode leaves out, the bottom and top layers of the visible range are kept whole: they are the
|
||||
// surfaces the range cuts open
|
||||
const EReducedDetailMode reduced_mode = m_settings.reduced_detail_mode;
|
||||
const bool build_reduced = reduced_mode != EReducedDetailMode::Off;
|
||||
const bool hide_infill = m_settings.reduced_detail_hide_infill;
|
||||
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
|
||||
@@ -1226,6 +1271,20 @@ 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) {
|
||||
const bool end_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
|
||||
if (end_layer)
|
||||
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
|
||||
else if (reduced_mode == EReducedDetailMode::ShellOnly) {
|
||||
if (v.is_option())
|
||||
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
|
||||
else if (!v.is_extrusion() || !hide_infill || !is_hidden_in_shell(v.role))
|
||||
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
@@ -1254,6 +1313,23 @@ void ViewerImpl::update_enabled_entities()
|
||||
else
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
|
||||
|
||||
m_enabled_segments_reduced_count = enabled_segments_reduced.size();
|
||||
m_enabled_options_reduced_count = enabled_options_reduced.size();
|
||||
m_enabled_segments_reduced_tex_size = enabled_segments_reduced.size() * sizeof(uint32_t);
|
||||
m_enabled_options_reduced_tex_size = enabled_options_reduced.size() * sizeof(uint32_t);
|
||||
|
||||
// uploaded even when nothing was built, so that the last reduced set is released as soon as
|
||||
// the preference is switched off
|
||||
assert(m_enabled_segments_reduced_buf_id > 0);
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
|
||||
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_size,
|
||||
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, m_enabled_options_reduced_tex_size,
|
||||
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
|
||||
|
||||
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
@@ -1461,6 +1537,23 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// Both decide which vertices land in the reduced set, so the sets are 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_hide_infill(bool value)
|
||||
{
|
||||
if (m_settings.reduced_detail_hide_infill == value)
|
||||
return;
|
||||
m_settings.reduced_detail_hide_infill = 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)
|
||||
{
|
||||
@@ -1854,6 +1947,8 @@ size_t ViewerImpl::get_used_gpu_memory() const
|
||||
ret += m_colors_tex_size;
|
||||
ret += m_enabled_segments_tex_size;
|
||||
ret += m_enabled_options_tex_size;
|
||||
ret += m_enabled_segments_reduced_tex_size;
|
||||
ret += m_enabled_options_reduced_tex_size;
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
return ret;
|
||||
}
|
||||
@@ -2070,7 +2165,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;
|
||||
|
||||
@@ -2090,6 +2186,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
|
||||
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
|
||||
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
|
||||
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
|
||||
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
|
||||
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
|
||||
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
|
||||
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(segments.count)));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
|
||||
// with the lookup off.
|
||||
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
|
||||
@@ -2138,10 +2243,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)
|
||||
@@ -2167,7 +2272,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;
|
||||
|
||||
@@ -2225,10 +2331,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)
|
||||
|
||||
@@ -109,6 +109,21 @@ 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; }
|
||||
EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; }
|
||||
void set_reduced_detail_mode(EReducedDetailMode mode);
|
||||
bool get_reduced_detail_hide_infill() const { return m_settings.reduced_detail_hide_infill; }
|
||||
void set_reduced_detail_hide_infill(bool value);
|
||||
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
|
||||
@@ -362,6 +377,8 @@ private:
|
||||
int m_uni_segments_height_width_angle_tex_id{ -1 };
|
||||
int m_uni_segments_colors_tex_id{ -1 };
|
||||
int m_uni_segments_segment_index_tex_id{ -1 };
|
||||
int m_uni_segments_reverse_order_id{ -1 };
|
||||
int m_uni_segments_instance_count_id{ -1 };
|
||||
int m_uni_segments_shadow_map_id{ -1 };
|
||||
int m_uni_segments_shadow_light_vp_id{ -1 };
|
||||
int m_uni_segments_shadow_intensity_id{ -1 };
|
||||
@@ -499,6 +516,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 };
|
||||
@@ -506,6 +532,27 @@ private:
|
||||
size_t m_colors_tex_size{ 0 };
|
||||
size_t m_enabled_segments_tex_size{ 0 };
|
||||
size_t m_enabled_options_tex_size{ 0 };
|
||||
size_t m_enabled_segments_reduced_tex_size{ 0 };
|
||||
size_t m_enabled_options_reduced_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_mode != EReducedDetailMode::Off; }
|
||||
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
|
||||
|
||||
//
|
||||
|
||||
@@ -819,7 +819,7 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
|
||||
m_scrolled_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL);
|
||||
m_scrolled_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
|
||||
wxBoxSizer *title_sizer_h= new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *title_sizer_v = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -1000,13 +1000,13 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
show_pos.x = screen_size.GetLeft();
|
||||
m_scrolled_window->SetMaxSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_scrolled_window->SetMaxSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
}
|
||||
|
||||
//Vertical Direction Processing
|
||||
@@ -2117,7 +2117,7 @@ void AmsReplaceMaterialDialog::create()
|
||||
identical_filament->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#009688"))); // Orca: accent teal (not brand green)
|
||||
|
||||
m_scrollview_groups = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrollview_groups->SetScrollRate(5, 5);
|
||||
m_scrollview_groups->SetScrollRate(5, FromDIP(20));
|
||||
//m_scrollview_groups->SetMinSize(wxSize(400, 400));
|
||||
//m_scrollview_groups->SetMaxSize(wxSize(400, 400));
|
||||
m_scrollview_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -606,7 +606,7 @@ AuFolderPanel::AuFolderPanel(wxWindow *parent, AuxiliaryFolderType type, wxWindo
|
||||
wxBoxSizer *sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
wxBoxSizer *sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *sizer_top = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
//show bind failed info
|
||||
m_sw_bind_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(450), FromDIP(300)), wxVSCROLL);
|
||||
m_sw_bind_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, 5);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, FromDIP(20));
|
||||
m_sw_bind_failed_info->SetMinSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
m_sw_bind_failed_info->SetMaxSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
|
||||
|
||||
@@ -3521,7 +3521,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
cards->Show(m_box_mate, false, true);
|
||||
|
||||
m_form->FitInside();
|
||||
m_form->SetScrollRate(0, 10); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetScrollRate(0, FromDIP(20)); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetMinSize(wxSize(264, -1));
|
||||
|
||||
// Right column: a small view toolbar over the live 3D viewport that mirrors
|
||||
|
||||
@@ -115,7 +115,7 @@ HistoryWindow::HistoryWindow(wxWindow* parent, const std::vector<PACalibResult>&
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto scroll_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
scroll_window->SetScrollRate(5, 5);
|
||||
scroll_window->SetScrollRate(5, FromDIP(20));
|
||||
scroll_window->SetBackgroundColour(*wxWHITE);
|
||||
scroll_window->SetMinSize(HISTORY_WINDOW_SIZE);
|
||||
scroll_window->SetSize(HISTORY_WINDOW_SIZE);
|
||||
|
||||
@@ -207,7 +207,7 @@ SelectMObjectPopup::SelectMObjectPopup(wxWindow* parent)
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_LIST_SIZE, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetMinSize(SELECT_MACHINE_LIST_SIZE);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, SELECT_MACHINE_ITEM_SIZE.y);
|
||||
auto m_sizxer_scrolledWindow = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow->SetSizer(m_sizxer_scrolledWindow);
|
||||
m_scrolledWindow->Layout();
|
||||
|
||||
@@ -81,7 +81,7 @@ CalibrationWizard::CalibrationWizard(wxWindow* parent, CalibMode mode, wxWindowI
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* padding_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
@@ -834,7 +834,7 @@ void CaliPageSendingPanel::create(wxWindow* parent)
|
||||
|
||||
m_sw_print_failed_info = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(380), FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
|
||||
|
||||
@@ -2710,7 +2710,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
// We can now enable scrolling on hscroll
|
||||
p->hscroll->SetScrollRate(30, 30);
|
||||
p->hscroll->SetScrollRate(30, FromDIP(20));
|
||||
|
||||
on_window_geometry(this, [this]() {
|
||||
p->init_dialog_size();
|
||||
|
||||
@@ -699,7 +699,7 @@ CreateFilamentPresetDialog::CreateFilamentPresetDialog(wxWindow *parent)
|
||||
m_scrolled_preset_panel = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrolled_preset_panel->SetMaxSize(wxSize(-1, FromDIP(350)));
|
||||
m_scrolled_preset_panel->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolled_sizer->Add(create_item(FilamentOptionType::PRESET_FOR_PRINTER), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
|
||||
m_scrolled_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
@@ -1593,7 +1593,7 @@ CreatePrinterPresetDialog::CreatePrinterPresetDialog(wxWindow *parent)
|
||||
|
||||
m_page1 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_page1->SetBackgroundColour(*wxWHITE);
|
||||
m_page1->SetScrollRate(5, 5);
|
||||
m_page1->SetScrollRate(5, FromDIP(20));
|
||||
m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_page2->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
create_printer_page1(m_page1);
|
||||
@@ -2652,7 +2652,7 @@ wxBoxSizer *CreatePrinterPresetDialog::create_presets_template_item(wxWindow *pa
|
||||
wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
//m_scrolled_preset_window->SetMinSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
@@ -4297,7 +4297,7 @@ wxBoxSizer *ExportConfigsDialog::create_select_printer(wxWindow *parent)
|
||||
optionSizer->SetMinSize(OPTION_SIZE);
|
||||
horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL, FromDIP(10));
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
m_scrolled_preset_window->SetSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
@@ -4736,7 +4736,7 @@ wxBoxSizer *EditFilamentPresetDialog::create_preset_tree_sizer()
|
||||
{
|
||||
wxBoxSizer *filament_preset_tree_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_preset_tree_window = new wxScrolledWindow(this);
|
||||
m_preset_tree_window->SetScrollRate(5, 5);
|
||||
m_preset_tree_window->SetScrollRate(5, FromDIP(20));
|
||||
m_preset_tree_window->SetBackgroundColour(PRINTER_LIST_COLOUR);
|
||||
m_preset_tree_window->SetMinSize(wxSize(-1, FromDIP(400)));
|
||||
m_preset_tree_window->SetMaxSize(wxSize(-1, FromDIP(300)));
|
||||
|
||||
@@ -39,7 +39,7 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
|
||||
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scroll_area->SetScrollRate(0, 5);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetBackgroundColour(*wxWHITE);
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(320), FromDIP(250)));
|
||||
|
||||
|
||||
@@ -1267,6 +1267,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_reduced_detail_preferences();
|
||||
|
||||
// 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")));
|
||||
@@ -1490,6 +1492,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
|
||||
// BBS: data for rendering color arrangement recommendation
|
||||
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
// the shell drag mode hides the infill only where the profile covers it: with no top or bottom
|
||||
// shell, or no walls, the infill is the surface. Per-object settings are not consulted.
|
||||
const PrintRegionConfig& region = print.default_region_config();
|
||||
m_viewer.set_reduced_detail_hide_infill(region.top_shell_layers.value > 0 && region.bottom_shell_layers.value > 0 && region.wall_loops.value > 0);
|
||||
// Orca hack: Hide filament group for non-bbl printers
|
||||
if (!print.is_BBL_printer()) m_nozzle_nums = 1;
|
||||
std::vector<int> filament_maps = print.get_filament_maps();
|
||||
@@ -1660,6 +1666,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();
|
||||
}
|
||||
|
||||
@@ -1696,7 +1703,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 in the solid model mode, the objects stand in for their toolpaths, cut to the
|
||||
// visible layer range; the toolpath set then holds only the range's bottom and top layers
|
||||
if (m_viewer.is_reduced_detail() && solid_model_enabled())
|
||||
render_solid_model(canvas_width, canvas_height);
|
||||
else
|
||||
render_shells(canvas_width, canvas_height);
|
||||
|
||||
if (m_viewer.get_extrusion_roles_count() == 0)
|
||||
return;
|
||||
@@ -2019,6 +2031,56 @@ 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
|
||||
const bool usable = !solid_model_enabled() || !m_shells.volumes.empty();
|
||||
m_viewer.set_reduced_detail(interacting && usable);
|
||||
}
|
||||
|
||||
void GCodeViewer::read_reduced_detail_preferences()
|
||||
{
|
||||
m_reduced_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_reduced_detail_mode"));
|
||||
apply_reduced_detail_settings();
|
||||
}
|
||||
|
||||
void GCodeViewer::apply_reduced_detail_settings()
|
||||
{
|
||||
m_viewer.set_reduced_detail_mode(m_reduced_detail_mode);
|
||||
}
|
||||
|
||||
void GCodeViewer::set_reduced_detail_mode(const std::string& mode)
|
||||
{
|
||||
const bool was_solid = solid_model_enabled();
|
||||
m_reduced_detail_mode = reduced_detail_mode_from_string(mode);
|
||||
apply_reduced_detail_settings();
|
||||
reload_shells_if_solid_model_changed(was_solid);
|
||||
}
|
||||
|
||||
libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const std::string& mode)
|
||||
{
|
||||
if (mode == "solid")
|
||||
return libvgcode::EReducedDetailMode::EndLayersOnly;
|
||||
if (mode == "shell")
|
||||
return libvgcode::EReducedDetailMode::ShellOnly;
|
||||
return libvgcode::EReducedDetailMode::Off;
|
||||
}
|
||||
|
||||
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
|
||||
{
|
||||
if (was_enabled == solid_model_enabled() || 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]));
|
||||
@@ -2343,7 +2405,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_reduced_detail_preferences();
|
||||
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;
|
||||
@@ -2452,10 +2518,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 = solid_model_enabled() && 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();
|
||||
@@ -2656,6 +2757,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;
|
||||
@@ -234,6 +236,17 @@ private:
|
||||
|
||||
bool m_legend_visible{ true };
|
||||
bool m_legend_enabled{ true };
|
||||
// the reduced-detail preferences, pushed to libvgcode by apply_reduced_detail_settings()
|
||||
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::Off };
|
||||
void read_reduced_detail_preferences();
|
||||
void apply_reduced_detail_settings();
|
||||
static libvgcode::EReducedDetailMode reduced_detail_mode_from_string(const std::string& mode);
|
||||
// in the solid model mode, the sliced objects are drawn as solid shapes instead of toolpaths
|
||||
bool solid_model_enabled() const { return m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly; }
|
||||
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;
|
||||
@@ -291,7 +304,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();
|
||||
@@ -363,6 +376,14 @@ 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(); }
|
||||
|
||||
// whether the mouse is holding either slider's handle
|
||||
bool is_slider_dragging() const { return m_layers_slider->is_dragging() || m_moves_slider->is_dragging(); }
|
||||
// while the user drags the camera or a slider, draw the reduced set, if the preference asks for one
|
||||
void set_interacting(bool interacting);
|
||||
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
|
||||
// the preference's string value: "off", "solid" or "shell"
|
||||
void set_reduced_detail_mode(const std::string& mode);
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
|
||||
|
||||
@@ -2084,6 +2084,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 its reduced set 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);
|
||||
@@ -3277,9 +3282,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 reduced set 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);
|
||||
@@ -3355,6 +3367,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;
|
||||
@@ -3885,6 +3908,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.
|
||||
@@ -3985,6 +4012,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
|
||||
@@ -5631,6 +5663,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();
|
||||
@@ -7779,13 +7814,20 @@ bool GLCanvas3D::_is_scene_cacheable() const
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while
|
||||
// the cursor is on the layer height bar, where the object shader draws a band at its height.
|
||||
// The scene follows the cursor while the user drags, under a gizmo that draws at the cursor, and
|
||||
// while the cursor is on the layer height bar, where the object shader draws a band at its height.
|
||||
const GLGizmoBase* gizmo = m_gizmos.get_current();
|
||||
const bool cursor_on_layers_bar = is_layers_editing_enabled() &&
|
||||
m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
|
||||
return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() &&
|
||||
(gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
|
||||
return !is_user_interacting() && (gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
|
||||
}
|
||||
|
||||
// Whether the user is holding something that moves the scene: the camera, the navigator, a gizmo,
|
||||
// the rectangle selection or a preview slider.
|
||||
bool GLCanvas3D::is_user_interacting() const
|
||||
{
|
||||
return m_mouse.dragging || m_navigator_dragging || m_gizmos.is_dragging() || m_rectangle_selection.is_dragging() ||
|
||||
m_gcode_viewer.is_slider_dragging();
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_frame_skipping_enabled() const
|
||||
@@ -8776,6 +8818,24 @@ void GLCanvas3D::_render_wireframe_overlay()
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// The reduced set is drawn while the camera, the navigator or either slider is dragged. A wheel
|
||||
// step has no duration, so it holds the reduced set for a settle time instead, and the frame that
|
||||
// restores the full 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()
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool settling = now < m_preview_interaction_until;
|
||||
const bool dragging = is_user_interacting();
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -656,6 +656,10 @@ private:
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
// until when a wheel step keeps the preview's reduced set 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;
|
||||
@@ -1222,6 +1226,10 @@ public:
|
||||
void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
|
||||
|
||||
void request_extra_frame() { m_extra_frame_requested = true; }
|
||||
// whether the user is holding the camera, the navigator, a gizmo, the rectangle selection or a preview slider
|
||||
bool is_user_interacting() const;
|
||||
// a wheel step is over before the next frame, so it holds the preview's reduced set for a settle time
|
||||
void note_preview_interaction();
|
||||
|
||||
void schedule_extra_frame(int milliseconds);
|
||||
|
||||
@@ -1372,6 +1380,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 reduced set 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();
|
||||
|
||||
@@ -456,8 +456,9 @@ void GLModel::init_from(const indexed_triangle_set& its)
|
||||
data.reserve_vertices(3 * its.indices.size());
|
||||
data.reserve_indices(3 * its.indices.size());
|
||||
|
||||
// Read user preference: smooth normals enabled
|
||||
const bool smooth_normals_enabled = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
// CLI thumbnail generation can initialize models without a wx application.
|
||||
const bool smooth_normals_enabled = wxApp::GetInstance() != nullptr && wxGetApp().app_config != nullptr &&
|
||||
wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
|
||||
if (smooth_normals_enabled) {
|
||||
// Use per-corner smooth normals (via IGL)
|
||||
|
||||
@@ -2764,12 +2764,12 @@ ObjectTablePanel::ObjectTablePanel( wxWindow* parent, wxWindowID id, const wxPoi
|
||||
//m_object_grid->AssignTable(m_object_grid_table);
|
||||
|
||||
m_side_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(440),FromDIP(480)), wxVSCROLL);
|
||||
m_side_window->SetScrollRate( 0, 5 );
|
||||
m_side_window->SetScrollRate(0, FromDIP(20));
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
//m_page_top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_side_window->SetBackgroundColour(wxColour(0xff, 0xff, 0xff));
|
||||
m_side_window->SetSizer(m_page_sizer);
|
||||
m_side_window->SetScrollbars(1, 20, 1, 2);
|
||||
m_side_window->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_side_window->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
|
||||
|
||||
//m_side_window->EnableScrolling(false, true);
|
||||
|
||||
@@ -171,7 +171,7 @@ HMSPanel::HMSPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wx
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const Page& page)
|
||||
wxGetApp().UpdateDarkUI(scrollable_panel);
|
||||
const wxColour page_colour = StateColor::darkModeColorFor(*wxWHITE);
|
||||
scrollable_panel->SetBackgroundColour(page_colour);
|
||||
scrollable_panel->SetScrollRate(0, 20);
|
||||
scrollable_panel->SetScrollRate(0, FromDIP(20));
|
||||
const int page_width = FromDIP(PAGE_WIDTH);
|
||||
scrollable_panel->SetInitialSize(wxSize(page_width, FromDIP(450)));
|
||||
|
||||
|
||||
@@ -1125,7 +1125,7 @@ wxBoxSizer* MixedFilamentDialog::create_recommendation_grid()
|
||||
outer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, FromDIP(4));
|
||||
|
||||
m_recommendation_scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(116)));
|
||||
m_recommendation_scroll->SetScrollRate(0, 5);
|
||||
m_recommendation_scroll->SetScrollRate(0, FromDIP(20));
|
||||
m_recommendation_scroll->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F8F8F8")));
|
||||
|
||||
m_recommendation_grid = new wxWrapSizer(wxHORIZONTAL, wxREMOVE_LEADING_SPACES);
|
||||
|
||||
@@ -194,9 +194,10 @@ Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, con
|
||||
bd->button = btn;
|
||||
//bd->type = type;
|
||||
|
||||
mb->id = wxString::Format("%d", m_buttons.size());
|
||||
// m_buttons.size() is a size_t, which does not match the %d conversion
|
||||
mb->id = wxString(std::to_string(m_buttons.size()));
|
||||
mb->buttondata = bd;
|
||||
m_buttons[ wxString::Format("%d", m_buttons.size())] = mb;
|
||||
m_buttons[ wxString(std::to_string(m_buttons.size()))] = mb;
|
||||
return btn;
|
||||
};
|
||||
|
||||
@@ -398,7 +399,7 @@ static void add_msg_content(wxWindow *parent,
|
||||
}
|
||||
wxScrolledWindow *scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
scrolledWindow->SetScrollRate(0, 20);
|
||||
scrolledWindow->SetScrollRate(0, scrolledWindow->FromDIP(20));
|
||||
scrolledWindow->EnableScrolling(false, true);
|
||||
wxBoxSizer *sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
|
||||
Label *wrapped_text = new Label(scrolledWindow, font, msg, LB_AUTO_WRAP, wxSize(info_width, -1));
|
||||
|
||||
@@ -398,7 +398,7 @@ MultiMachineManagerPage::MultiMachineManagerPage(wxWindow* parent)
|
||||
|
||||
m_machine_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_machine_list->SetBackgroundColour(*wxWHITE);
|
||||
m_machine_list->SetScrollRate(0, 5);
|
||||
m_machine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_machine_list->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_machine_list->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ MultiMachinePickPage::MultiMachinePickPage(Plater* plater /*= nullptr*/)
|
||||
scroll_macine_list->SetMinSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetMaxSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetBackgroundColour(*wxWHITE);
|
||||
scroll_macine_list->SetScrollRate(0, 5);
|
||||
scroll_macine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
|
||||
sizer_machine_list = new wxBoxSizer(wxVERTICAL);
|
||||
scroll_macine_list->SetSizer(sizer_machine_list);
|
||||
|
||||
@@ -675,7 +675,7 @@ LocalTaskManagerPage::LocalTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
@@ -1056,7 +1056,7 @@ CloudTaskManagerPage::CloudTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ ObjColorPanel::ObjColorPanel(wxWindow *parent, Slic3r::ObjDialogInOut &in_out, c
|
||||
//new color table
|
||||
m_scrolledWindow = new wxScrolledWindow(m_page_simple, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(0, 20);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->EnableScrolling(false, true);
|
||||
m_scrolledWindow->ShowScrollbars(wxScrollbarVisibility::wxSHOW_SB_NEVER, wxScrollbarVisibility::wxSHOW_SB_DEFAULT);
|
||||
draw_new_table();
|
||||
|
||||
@@ -391,7 +391,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_page_view->SetSizer(m_page_sizer);
|
||||
m_page_view->SetScrollbars(1, 20, 1, 2);
|
||||
m_page_view->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_page_view->SetScrollRate( 5, 5 );
|
||||
|
||||
if (m_mode_region)
|
||||
|
||||
@@ -148,7 +148,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_line->SetBackgroundColour(wxColor(238, 238, 238));
|
||||
|
||||
m_list_view = new wxScrolledWindow(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(267, -1), wxHSCROLL | wxVSCROLL);
|
||||
m_list_view->SetScrollRate(5, 5);
|
||||
m_list_view->SetScrollRate(5, FromDIP(30));
|
||||
m_list_view->SetMinSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
@@ -3049,8 +3049,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
|
||||
// add filament content
|
||||
p->m_panel_filament_content = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
p->m_panel_filament_content->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_panel_filament_content->SetScrollRate(0, 5);
|
||||
p->m_panel_filament_content->SetScrollRate(0, FromDIP(20));
|
||||
//p->m_panel_filament_content->SetMaxSize(wxSize{-1, FromDIP(174)});
|
||||
p->m_panel_filament_content->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
|
||||
@@ -3142,8 +3141,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
// 3) Mixed filament rows, in their own scroll area so a long mixed list does not
|
||||
// push the physical filament list off screen.
|
||||
p->m_mixed_scroll_area = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
p->m_mixed_scroll_area->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, 5);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
p->m_mixed_scroll_area->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
{
|
||||
auto* mix_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -12802,8 +12800,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
notification_manager->set_slicing_progress_export_possible();
|
||||
|
||||
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
|
||||
const std::string lifecycle_job_name = this->background_process.fff_print() ?
|
||||
this->background_process.fff_print()->output_filename() : std::string();
|
||||
this->background_process.reset_export();
|
||||
// This bool stops showing export finished notification even when process_completed_with_error is false
|
||||
bool has_error = false;
|
||||
@@ -12850,8 +12846,18 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
|
||||
{
|
||||
Slic3r::LifecycleEventContext ctx;
|
||||
ctx.name = lifecycle_job_name;
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
if (const PrintBase* print = this->background_process.current_print()) {
|
||||
const Model& model = print->model();
|
||||
ctx.id = std::to_string(model.id().id);
|
||||
if (model.model_info)
|
||||
ctx.name = model.model_info->model_name;
|
||||
} else {
|
||||
// Realistically Printbase* print will never be null because select_technology already asserts an active print
|
||||
// and the worker thread asserts it before processing.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": slicing completed without an active print; lifecycle event has no model ID";
|
||||
}
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn :
|
||||
(has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
|
||||
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
|
||||
return sizer;
|
||||
}
|
||||
|
||||
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url)
|
||||
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange, const wxString wiki_url)
|
||||
{
|
||||
assert(vlist.size() == config_name_index.size());
|
||||
unsigned int current_index = 0;
|
||||
@@ -338,8 +338,9 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
|
||||
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
|
||||
|
||||
//// save config
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
|
||||
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index, onchange](wxCommandEvent& e) {
|
||||
app_config->set(param, config_name_index[e.GetSelection()]);
|
||||
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
@@ -1475,7 +1476,7 @@ void PreferencesDialog::create()
|
||||
app_config = get_app_config();
|
||||
|
||||
m_parent = new MyscrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_parent->SetScrollRate(5, 5);
|
||||
m_parent->SetScrollRate(0, FromDIP(20));
|
||||
m_parent->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -2044,6 +2045,32 @@ void PreferencesDialog::create_items()
|
||||
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
|
||||
g_sizer->Add(item_preview_view_type);
|
||||
|
||||
auto item_reduced_detail_mode = create_item_combobox(
|
||||
_L("Simplify preview while dragging"),
|
||||
_L("What the sliced preview draws while you drag the camera or a preview slider, or zoom with the mouse wheel, so that large prints stay responsive. "
|
||||
"The full toolpaths are restored as soon as you let go.\n"
|
||||
"Off: the full toolpaths.\n"
|
||||
"Solid model: the sliced objects and the prime tower as solid shapes in their filament colors, cut to the visible layer range, "
|
||||
"with its bottom and top layers drawn as toolpaths. Supports are not shown, and negative volumes are not cut out.\n"
|
||||
"Shell only: every layer without its sparse infill, internal solid infill and gap fill, which lie under the walls and skins. "
|
||||
"Walls, top and bottom surfaces, bridges, supports and the prime tower are drawn whole, so the print looks the same from outside.\n"
|
||||
"The bottom and top of the visible layer range are always drawn whole."),
|
||||
"preview_reduced_detail_mode",
|
||||
{_L("Off"), _L("Solid model"), _L("Shell only")},
|
||||
{"off", "solid", "shell"},
|
||||
// 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_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."),
|
||||
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
wxBoxSizer *create_item_title(wxString title);
|
||||
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange = {}, const wxString wiki_url = "");
|
||||
wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
|
||||
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
|
||||
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);
|
||||
|
||||
@@ -32,7 +32,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
||||
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(10));
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrollwindow->SetMinSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
m_scrollwindow->SetMaxSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
|
||||
@@ -1225,7 +1225,7 @@ size_t PublishSettingsDialog::category_index_for(
|
||||
}
|
||||
|
||||
category.scroll = new wxScrolledWindow(category.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
category.scroll->SetScrollRate(0, 10);
|
||||
category.scroll->SetScrollRate(0, FromDIP(20));
|
||||
category.scroll->SetBackgroundColour(GetBackgroundColour());
|
||||
category.list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
category.scroll->SetSizer(category.list_sizer);
|
||||
|
||||
@@ -76,7 +76,7 @@ ReleaseNoteDialog::ReleaseNoteDialog(Plater *plater /*= nullptr*/)
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)), wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(5, 5);
|
||||
m_vebview_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_vebview_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
|
||||
@@ -142,7 +142,7 @@ UpdatePluginDialog::UpdatePluginDialog(wxWindow* parent /*= nullptr*/)
|
||||
operation_tips->SetMaxSize(wxSize(FromDIP(260), -1));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(5, 5);
|
||||
m_vebview_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(260), FromDIP(150)));
|
||||
m_vebview_release_note->SetMaxSize(wxSize(FromDIP(260), FromDIP(150)));
|
||||
@@ -277,7 +277,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
m_simplebook_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
|
||||
m_scrollwindows_release_note = new wxScrolledWindow(m_simplebook_release_note, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)), wxVSCROLL);
|
||||
m_scrollwindows_release_note->SetScrollRate(5, 5);
|
||||
m_scrollwindows_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_scrollwindows_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
|
||||
//webview
|
||||
@@ -538,7 +538,7 @@ SecondaryCheckDialog::SecondaryCheckDialog(wxWindow* parent, wxWindowID id, cons
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(400), FromDIP(380)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
@@ -823,7 +823,7 @@ PrintErrorDialog::PrintErrorDialog(wxWindow* parent, wxWindowID id, const wxStri
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(5));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), FromDIP(250)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
@@ -1122,7 +1122,7 @@ ConfirmBeforeSendDialog::ConfirmBeforeSendDialog(wxWindow* parent, wxWindowID id
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(400), FromDIP(380)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
|
||||
@@ -31,7 +31,7 @@ SafetyOptionsDialog::SafetyOptionsDialog(wxWindow* parent)
|
||||
SetSize(FromDIP(480),FromDIP(320));
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(10));
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrollwindow->SetMinSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
m_scrollwindow->SetMaxSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
|
||||
@@ -148,7 +148,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
|
||||
wxBoxSizer* m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scroll_area = new wxScrolledWindow(this);
|
||||
m_scroll_area->SetScrollRate(0, 20);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetBackgroundColour(m_colour_def_color);
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(700), FromDIP(600)));
|
||||
m_scroll_area->SetMaxSize(wxSize(FromDIP(700), FromDIP(600)));
|
||||
@@ -707,7 +707,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
//show bind failed info
|
||||
m_sw_print_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)));
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ SelectMachinePopup::SelectMachinePopup(wxWindow *parent)
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_LIST_SIZE, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetMinSize(SELECT_MACHINE_LIST_SIZE);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, SELECT_MACHINE_ITEM_SIZE.y);
|
||||
auto m_sizxer_scrolledWindow = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow->SetSizer(m_sizxer_scrolledWindow);
|
||||
m_scrolledWindow->Layout();
|
||||
|
||||
@@ -278,7 +278,7 @@ SendMultiMachinePage::SendMultiMachinePage(Plater* plater)
|
||||
|
||||
m_main_scroll = new ScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_main_scroll->SetBackgroundColour(*wxWHITE);
|
||||
m_main_scroll->SetScrollRate(5, 5);
|
||||
m_main_scroll->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
m_main_page = create_page();
|
||||
@@ -1353,7 +1353,7 @@ wxPanel* SendMultiMachinePage::create_page()
|
||||
|
||||
scroll_macine_list = new wxScrolledWindow(main_page, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(800), FromDIP(300)), wxHSCROLL | wxVSCROLL);
|
||||
scroll_macine_list->SetBackgroundColour(*wxWHITE);
|
||||
scroll_macine_list->SetScrollRate(5, 5);
|
||||
scroll_macine_list->SetScrollRate(5, FromDIP(SEND_ITEM_MAX_HEIGHT));
|
||||
scroll_macine_list->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(SEND_ITEM_MAX_HEIGHT)));
|
||||
scroll_macine_list->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(SEND_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
|
||||
//show bind failed info
|
||||
m_sw_print_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(380), FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
|
||||
|
||||
@@ -1302,7 +1302,7 @@ void PrintingTaskPanel::set_star_count(int star_count)
|
||||
StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
|
||||
: wxScrolledWindow(parent, id, pos, size, wxHSCROLL | wxVSCROLL)
|
||||
{
|
||||
this->SetScrollRate(25, 25);
|
||||
SetScrollRate(25, FromDIP(20));
|
||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return;
|
||||
obj = dev->get_selected_machine();
|
||||
|
||||
@@ -700,7 +700,7 @@ SyncAmsInfoDialog::SyncAmsInfoDialog(wxWindow *parent, SyncInfo &info) :
|
||||
//wxBoxSizer *m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow = new wxScrolledWindow(m_show_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(0, 20);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->SetMinSize(wxSize(-1, SyncAmsInfoDialogHeightMAX));
|
||||
m_scrolledWindow->SetMaxSize(wxSize(-1, SyncAmsInfoDialogHeightMAX));
|
||||
m_scrolledWindow->EnableScrolling(false,true);
|
||||
|
||||
@@ -534,7 +534,7 @@ public:
|
||||
|
||||
m_content = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_content->SetBackgroundColour(pop_bg);
|
||||
m_content->SetScrollRate(0, FromDIP(5));
|
||||
m_content->SetScrollRate(0, FromDIP(20));
|
||||
auto* outer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
const int pop_w = std::max(FromDIP(213), popup_width);
|
||||
@@ -2274,7 +2274,7 @@ void TextureImportDialog::build_mapping_panel(wxWindow* parent, wxSizer* sizer)
|
||||
|
||||
m_mapping_scroll = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition,
|
||||
wxSize(-1, FromDIP(300)));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(10));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(20));
|
||||
m_mapping_scroll->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
m_mapping_scroll->Bind(wxEVT_MOUSEWHEEL, &TextureImportDialog::dismiss_filament_popup_on_wheel, this);
|
||||
|
||||
@@ -4335,7 +4335,7 @@ void TextureImportDialog::on_dpi_changed(const wxRect&)
|
||||
|
||||
if (m_mapping_scroll) {
|
||||
m_mapping_scroll->SetMinSize(wxSize(-1, FromDIP(300)));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(10));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(20));
|
||||
}
|
||||
|
||||
if (m_btn_skip) {
|
||||
|
||||
@@ -942,7 +942,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
||||
m_sizer_tab->Add(m_table_top, 1, 0, 0);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(m_panel_tab, wxID_ANY, wxDefaultPosition, UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE, wxNO_BORDER|wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->SetBackgroundColour(GREY200);
|
||||
m_sizer_bottom = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer_bottom->Add(m_scrolledWindow, 1, wxEXPAND, 0);
|
||||
|
||||
@@ -130,7 +130,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
auto m_scrollwindw_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)),wxVSCROLL);
|
||||
m_scrollwindw_release_note->SetScrollRate(0, 5);
|
||||
m_scrollwindw_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindw_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_scrollwindw_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_scrollwindw_release_note->SetWindowStyle(wxVSCROLL);
|
||||
|
||||
@@ -1629,7 +1629,7 @@ UpgradePanel::UpgradePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
||||
auto m_main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 25);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_machine_list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ CheckList::CheckList(
|
||||
|
||||
s_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style);
|
||||
m_scroll_area->SetScrollRate(0, 10);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetSizer(s_sizer);
|
||||
m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour());
|
||||
m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this);
|
||||
|
||||
@@ -199,7 +199,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int
|
||||
m_sizer_main->Add(m_simplebook, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28));
|
||||
} else {
|
||||
m_msg_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
|
||||
m_msg_scrolledWindow->SetScrollRate(0,5);
|
||||
m_msg_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
wxBoxSizer* m_msg_sizer= new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_msg = new wxStaticText(m_msg_scrolledWindow, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x, -1), 0);
|
||||
|
||||
@@ -1222,7 +1222,7 @@ ImageTransientPopup::ImageTransientPopup( wxWindow *parent, bool scrolled, wxBit
|
||||
m_panel->SetSize(300, 300);
|
||||
|
||||
// And also actually enable them.
|
||||
m_panel->SetScrollRate(10, 10);
|
||||
m_panel->SetScrollRate(10, FromDIP(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/app.h>
|
||||
@@ -115,10 +116,67 @@ std::string PrintHost::get_print_host_webui(DynamicPrintConfig* config)
|
||||
return webui_url;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Moonraker (Klipper's API server) reports a raised exception as { "error": { "code", "message", "traceback" } }
|
||||
// under every host type that connects to it, often with the cause only in the traceback. Returns the reason to show,
|
||||
// or empty for any other body.
|
||||
std::string moonraker_error_reason(const std::string &body)
|
||||
{
|
||||
const auto root = nlohmann::json::parse(body, nullptr, false);
|
||||
const auto err = root.find("error");
|
||||
if (err == root.end())
|
||||
return {};
|
||||
const auto message = err->find("message");
|
||||
const auto traceback = err->find("traceback");
|
||||
if (message == err->end() || traceback == err->end() || !message->is_string() || !traceback->is_string())
|
||||
return {};
|
||||
|
||||
const auto &msg = message->get_ref<const std::string &>();
|
||||
const auto &tb = traceback->get_ref<const std::string &>();
|
||||
if (msg.empty())
|
||||
return {};
|
||||
const auto end = tb.find_last_not_of(" \t\r\n");
|
||||
if (end == std::string::npos)
|
||||
return msg;
|
||||
|
||||
// Chained exceptions each start a new traceback; the one that failed the request is the last.
|
||||
const auto header = tb.rfind("Traceback (most recent call last):", end);
|
||||
|
||||
// Tornado renders a raised HTTPError as "HTTP <code>: <reason>[ (<detail>)]", and the detail may span lines.
|
||||
const auto code = err->find("code");
|
||||
if (code != err->end() && code->is_number_integer()) {
|
||||
const std::string marker = "HTTP " + std::to_string(code->get<int>()) + ": ";
|
||||
const auto pos = tb.rfind(marker, end);
|
||||
if (pos != std::string::npos && (header == std::string::npos || pos > header) && pos + marker.size() <= end) {
|
||||
const std::string reason = tb.substr(pos + marker.size(), end + 1 - pos - marker.size());
|
||||
// An HTTPError whose detail equals its reason, like HTTPError(401, "Unauthorized"), renders the phrase twice.
|
||||
return reason == msg + " (" + msg + ")" ? msg : reason;
|
||||
}
|
||||
}
|
||||
|
||||
// Any other exception's type and message are everything from the first unindented line after its frames.
|
||||
auto begin = (header == std::string::npos) ? std::string::npos : tb.find('\n', header);
|
||||
while (begin != std::string::npos && begin < end) {
|
||||
++begin;
|
||||
if (tb[begin] != ' ' && tb[begin] != '\r' && tb[begin] != '\n')
|
||||
break;
|
||||
begin = tb.find('\n', begin);
|
||||
}
|
||||
if (begin == std::string::npos || begin > end) {
|
||||
const auto nl = tb.rfind('\n', end);
|
||||
begin = (nl == std::string::npos) ? 0 : nl + 1;
|
||||
}
|
||||
return msg + " (" + tb.substr(begin, end + 1 - begin) + ")";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||
{
|
||||
if (status != 0) {
|
||||
auto wxbody = wxString::FromUTF8(body.data());
|
||||
const std::string reason = moonraker_error_reason(body);
|
||||
auto wxbody = wxString::FromUTF8(reason.empty() ? body : reason);
|
||||
return wxString::Format("HTTP %u: %s", status, wxbody);
|
||||
} else {
|
||||
if (error.find("curl:Timeout was reached") != std::string::npos) {
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/LifecycleEvents.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
#include "test_utils.hpp"
|
||||
@@ -22,7 +25,10 @@
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
@@ -224,8 +230,88 @@ std::string resolved_output_name(Model& model, const std::string& format, const
|
||||
return print.output_filename(filename_base);
|
||||
}
|
||||
|
||||
struct ScopedLifecycleHook
|
||||
{
|
||||
explicit ScopedLifecycleHook(LifecycleHookFn hook) { set_lifecycle_hook_fn(std::move(hook)); }
|
||||
~ScopedLifecycleHook() { set_lifecycle_hook_fn(nullptr); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Slicing lifecycle events identify the model", "[Print][LifecycleEvents]")
|
||||
{
|
||||
struct ObservedEvent {
|
||||
LifecycleEvent event;
|
||||
std::string id;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<ObservedEvent> events;
|
||||
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||
events.push_back({ event, ctx.id, ctx.name });
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
ModelInfo info;
|
||||
info.model_name = "Lifecycle test model";
|
||||
model.model_info = std::make_shared<ModelInfo>(std::move(info));
|
||||
init_print({cube(20)}, print, model);
|
||||
|
||||
print.process();
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||
GCodeProcessorResult result;
|
||||
print.export_gcode_from_previous_file(temp.string(), &result);
|
||||
|
||||
const std::string expected_id = std::to_string(print.model().id().id);
|
||||
const std::vector<LifecycleEvent> expected_events = {
|
||||
LifecycleEvent::SliceStarted,
|
||||
LifecycleEvent::SliceGeometryFinished,
|
||||
LifecycleEvent::GCodeExportStarted,
|
||||
LifecycleEvent::GCodeExportFinished,
|
||||
LifecycleEvent::GCodeExportStarted,
|
||||
LifecycleEvent::GCodeExportFinished,
|
||||
};
|
||||
REQUIRE(events.size() == expected_events.size());
|
||||
for (size_t i = 0; i < expected_events.size(); ++i) {
|
||||
CHECK(events[i].event == expected_events[i]);
|
||||
CHECK(events[i].id == expected_id);
|
||||
CHECK(events[i].name == "Lifecycle test model");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Slicing lifecycle event name is empty without model metadata", "[Print][LifecycleEvents]")
|
||||
{
|
||||
std::string event_id;
|
||||
std::string event_name = "unset";
|
||||
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||
if (event == LifecycleEvent::SliceStarted) {
|
||||
event_id = ctx.id;
|
||||
event_name = ctx.name;
|
||||
}
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model);
|
||||
print.process();
|
||||
|
||||
CHECK(event_id == std::to_string(print.model().id().id));
|
||||
CHECK(event_name.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Output filenames with numeric statistics fail before slicing finishes", "[Print][Regression]")
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_key_value("filename_format", new ConfigOptionString("{int(total_weight*10) / 10.0}"));
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, config);
|
||||
|
||||
CHECK_THROWS_AS(print.output_filename(), PlaceholderParserError);
|
||||
}
|
||||
|
||||
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
|
||||
{
|
||||
Model model;
|
||||
|
||||
@@ -18,6 +18,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_plugin_install.cpp
|
||||
test_plugin_lifecycle.cpp
|
||||
test_plugin_printer_agent.cpp
|
||||
test_printhost.cpp
|
||||
test_slicing_pipeline_bindings.cpp
|
||||
test_slicing_pipeline_config.cpp
|
||||
test_plugin_sort.cpp
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
class TestPrintHost : public PrintHost
|
||||
{
|
||||
public:
|
||||
using PrintHost::format_error;
|
||||
|
||||
const char* get_name() const override { return "Test"; }
|
||||
bool test(wxString&) const override { return true; }
|
||||
wxString get_test_ok_msg() const override { return {}; }
|
||||
wxString get_test_failed_msg(wxString&) const override { return {}; }
|
||||
bool upload(PrintHostUpload, ProgressFn, ErrorFn, InfoFn) const override { return true; }
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return false; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||
std::string get_host() const override { return {}; }
|
||||
};
|
||||
|
||||
std::string format_error(const std::string& body, const std::string& error, unsigned status)
|
||||
{
|
||||
return TestPrintHost().format_error(body, error, status).ToStdString();
|
||||
}
|
||||
|
||||
std::string envelope(int code, const std::string& message, const std::string& traceback)
|
||||
{
|
||||
return nlohmann::json{{"error", {{"code", code}, {"message", message}, {"traceback", traceback}}}}.dump();
|
||||
}
|
||||
|
||||
std::string moonraker_error(int code, const std::string& message, const std::string& detail = {})
|
||||
{
|
||||
std::string line = "tornado.web.HTTPError: HTTP " + std::to_string(code) + ": " + message;
|
||||
if (!detail.empty())
|
||||
line += " (" + detail + ")";
|
||||
return envelope(code, message, "Traceback (most recent call last):\n ...\n" + line + "\n");
|
||||
}
|
||||
|
||||
// A real Moonraker body for uploading a file that is being printed.
|
||||
constexpr const char* k_busy_file_403 =
|
||||
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
|
||||
{
|
||||
const std::string msg = format_error(k_busy_file_403, "", 403);
|
||||
INFO("actual: " << msg);
|
||||
|
||||
CHECK(msg == "HTTP 403: Forbidden (File is loaded, upload not permitted)");
|
||||
CHECK_THAT(msg, !Catch::Matchers::ContainsSubstring("Traceback"));
|
||||
CHECK_THAT(msg, !Catch::Matchers::ContainsSubstring("file_manager.py"));
|
||||
}
|
||||
|
||||
TEST_CASE("The specific cause is recovered from a file endpoint's traceback", "[PrintHost]")
|
||||
{
|
||||
SECTION("a plain detail")
|
||||
{
|
||||
const std::string body = moonraker_error(403, "Forbidden", "File is loaded, upload not permitted");
|
||||
CHECK(format_error(body, "", 403) == "HTTP 403: Forbidden (File is loaded, upload not permitted)");
|
||||
}
|
||||
|
||||
SECTION("a detail whose own parentheses nest (a filename)")
|
||||
{
|
||||
const std::string detail = "Directory does not exist (/home/pi/gcodes/plate (1).gcode)";
|
||||
const std::string body = moonraker_error(400, "Bad Request", detail);
|
||||
CHECK(format_error(body, "", 400) == "HTTP 400: Bad Request (" + detail + ")");
|
||||
}
|
||||
|
||||
SECTION("a detail that contains the reason phrase")
|
||||
{
|
||||
const std::string body = moonraker_error(403, "Forbidden", "Forbidden zone: access denied");
|
||||
CHECK(format_error(body, "", 403) == "HTTP 403: Forbidden (Forbidden zone: access denied)");
|
||||
}
|
||||
|
||||
SECTION("a detail that spans lines")
|
||||
{
|
||||
const std::string detail = "Move out of range\nX=250.000 Y=10.000";
|
||||
const std::string body = moonraker_error(400, "Bad Request", detail);
|
||||
CHECK(format_error(body, "", 400) == "HTTP 400: Bad Request (" + detail + ")");
|
||||
}
|
||||
|
||||
SECTION("a detail that only repeats the reason phrase is dropped")
|
||||
{
|
||||
const std::string body = moonraker_error(401, "Unauthorized", "Unauthorized");
|
||||
CHECK(format_error(body, "", 401) == "HTTP 401: Unauthorized");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("An unhandled exception shows its type and message", "[PrintHost]")
|
||||
{
|
||||
const std::string frame = "Traceback (most recent call last):\n"
|
||||
" File \"/home/pi/moonraker/moonraker/components/file_manager/file_manager.py\", line 1, in write\n"
|
||||
" self._write(data)\n";
|
||||
|
||||
SECTION("a one-line message")
|
||||
{
|
||||
const std::string body = envelope(500, "Internal Server Error", frame + "OSError: [Errno 28] No space left on device\n");
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: Internal Server Error (OSError: [Errno 28] No space left on device)");
|
||||
}
|
||||
|
||||
SECTION("a message that spans lines")
|
||||
{
|
||||
const std::string body = envelope(500, "Internal Server Error", frame + "ServerError: Klippy request failed\n see klippy.log\n");
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: Internal Server Error (ServerError: Klippy request failed\n see klippy.log)");
|
||||
}
|
||||
|
||||
SECTION("raised while handling an HTTPError with the same code")
|
||||
{
|
||||
const std::string traceback = frame + "tornado.web.HTTPError: HTTP 500: Internal Server Error (Database locked)\n\n"
|
||||
"During handling of the above exception, another exception occurred:\n\n" +
|
||||
frame + "OSError: [Errno 5] Input/output error\n";
|
||||
const std::string body = envelope(500, "Internal Server Error", traceback);
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: Internal Server Error (OSError: [Errno 5] Input/output error)");
|
||||
}
|
||||
|
||||
SECTION("a traceback with no header")
|
||||
{
|
||||
const std::string body = envelope(500, "Internal Server Error", "OSError: [Errno 5] Input/output error");
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: Internal Server Error (OSError: [Errno 5] Input/output error)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("A reason already complete in message is shown unchanged", "[PrintHost]")
|
||||
{
|
||||
SECTION("message is the whole reason, no trailing detail")
|
||||
{
|
||||
const std::string body = moonraker_error(503, "Klippy is not ready");
|
||||
CHECK(format_error(body, "", 503) == "HTTP 503: Klippy is not ready");
|
||||
}
|
||||
|
||||
SECTION("a message that itself contains parentheses is not duplicated")
|
||||
{
|
||||
const std::string reason = "Requested blocks (0-5) are unavailable";
|
||||
const std::string body = moonraker_error(400, reason);
|
||||
CHECK(format_error(body, "", 400) == "HTTP 400: " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("A Moonraker error with no usable detail shows just the reason phrase", "[PrintHost]")
|
||||
{
|
||||
struct Case
|
||||
{
|
||||
const char* name;
|
||||
const char* body;
|
||||
unsigned status;
|
||||
const char* expected;
|
||||
};
|
||||
|
||||
const auto c = GENERATE(
|
||||
Case{"an empty traceback", R"JSON({"error": {"code": 500, "message": "Internal Server Error", "traceback": ""}})JSON", 500,
|
||||
"HTTP 500: Internal Server Error"},
|
||||
Case{"a traceback of only whitespace", R"JSON({"error": {"code": 500, "message": "Internal Server Error", "traceback": "\n \n"}})JSON",
|
||||
500, "HTTP 500: Internal Server Error"});
|
||||
|
||||
DYNAMIC_SECTION(c.name) { CHECK(format_error(c.body, "", c.status) == c.expected); }
|
||||
}
|
||||
|
||||
TEST_CASE("A percent sign in the reason is not a format specifier", "[PrintHost]")
|
||||
{
|
||||
const std::string body = moonraker_error(507, "Insufficient Storage", "disk 100% full");
|
||||
CHECK(format_error(body, "", 507) == "HTTP 507: Insufficient Storage (disk 100% full)");
|
||||
}
|
||||
|
||||
TEST_CASE("Error bodies that are not a Moonraker envelope are left unchanged", "[PrintHost]")
|
||||
{
|
||||
SECTION("OctoPrint's string-valued error member")
|
||||
{
|
||||
const std::string body = R"JSON({"error": "File not found"})JSON";
|
||||
CHECK(format_error(body, "", 404) == "HTTP 404: " + body);
|
||||
}
|
||||
|
||||
SECTION("PrusaLink's top-level message, not under error")
|
||||
{
|
||||
const std::string body = R"JSON({"title": "Conflict", "message": "Printer is printing"})JSON";
|
||||
CHECK(format_error(body, "", 409) == "HTTP 409: " + body);
|
||||
}
|
||||
|
||||
SECTION("a body that is not JSON")
|
||||
{
|
||||
const std::string html = "<html><head><title>502 Bad Gateway</title></head></html>";
|
||||
CHECK(format_error(html, "", 502) == "HTTP 502: " + html);
|
||||
}
|
||||
|
||||
SECTION("an error object with no traceback")
|
||||
{
|
||||
const std::string body = R"JSON({"error": {"code": 500, "message": "Internal Server Error"}})JSON";
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: " + body);
|
||||
}
|
||||
|
||||
SECTION("an error object whose traceback is null")
|
||||
{
|
||||
const std::string body = R"JSON({"error": {"code": 500, "message": "Internal Server Error", "traceback": null}})JSON";
|
||||
CHECK(format_error(body, "", 500) == "HTTP 500: " + body);
|
||||
}
|
||||
|
||||
SECTION("an envelope whose reason phrase is empty")
|
||||
{
|
||||
const std::string body = envelope(403, "", "Traceback (most recent call last):\nOSError: denied\n");
|
||||
CHECK(format_error(body, "", 403) == "HTTP 403: " + body);
|
||||
}
|
||||
|
||||
SECTION("a transport error with no HTTP status")
|
||||
{
|
||||
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user