Compare commits

..
Author SHA1 Message Date
Hanif Koh 536ef75894 Fill Settings Missing From a CLI Project From Its System Presets
A project saved before a printer or process option existed has no value
for it. The GUI takes such keys from the project's system preset; the
CLI left them at the option default, so e.g. extruder_clearance_dist_to_rod
sliced as 40 instead of the P1S's 33.

The CLI now resolves the project's system printer and process presets by
name and copies the keys the project lacks, skipping preset bookkeeping,
print-host keys, the extruder variant layout and keys the legacy handler
drops. PresetBundle::resolve_system_preset finds the vendor through its
manifest or preset cache, so it also works in release builds, which ship
vendors as caches only.
2026-09-28 18:01:00 +08:00
22 changed files with 320 additions and 598 deletions
-80
View File
@@ -1,80 +0,0 @@
# 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.
+52 -6
View File
@@ -2116,9 +2116,14 @@ int CLI::run(int argc, char **argv)
// One resolver for the whole run, so presets from the same vendor tree share its load. // One resolver for the whole run, so presets from the same vendor tree share its load.
std::unique_ptr<PresetBundle> system_preset_resolver; std::unique_ptr<PresetBundle> system_preset_resolver;
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config, auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
std::string &config_type, const std::string &config_from, if (!system_preset_resolver)
bool probe_type, std::string &error) { system_preset_resolver = std::make_unique<PresetBundle>();
return *system_preset_resolver;
};
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
std::string &config_type, const std::string &config_from,
bool probe_type, std::string &error) {
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS); const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
if (!probe_type && (inherits == nullptr || inherits->value.empty())) if (!probe_type && (inherits == nullptr || inherits->value.empty()))
return true; return true;
@@ -2126,9 +2131,7 @@ int CLI::run(int argc, char **argv)
PresetBundle *bundle = nullptr; PresetBundle *bundle = nullptr;
bool allow_source_manifest = false; bool allow_source_manifest = false;
if (config_from == "system") { if (config_from == "system") {
if (!system_preset_resolver) bundle = &ensure_system_preset_resolver();
system_preset_resolver = std::make_unique<PresetBundle>();
bundle = system_preset_resolver.get();
allow_source_manifest = true; allow_source_manifest = true;
} else { } else {
bundle = ensure_cli_preset_bundle(error); bundle = ensure_cli_preset_bundle(error);
@@ -3120,6 +3123,49 @@ int CLI::run(int argc, char **argv)
return 0; return 0;
}; };
// A project saved before a printer or process option existed has no value for it. The GUI takes such
// keys from the project's system preset (load_external_preset refreshes every key the project did not
// override), so fill them from there too instead of leaving them to the option default.
// The extruder variant keys describe the project's variant layout and are kept as they are, so an
// older project is not left with a variant list from one layout and ids from another.
auto fill_missing_project_keys = [this, &ensure_system_preset_resolver](const std::string &system_name, Preset::Type type) {
if (system_name.empty())
return;
static const std::set<std::string> skip_keys = {
"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition", "compatible_prints_condition",
"print_settings_id", "filament_settings_id", "printer_settings_id",
"print_host", "print_host_webui", "printhost_apikey", "printhost_cafile", "printhost_user", "printhost_password", "printhost_port",
"printer_extruder_id", "printer_extruder_variant", "print_extruder_id", "print_extruder_variant", "extruder_variant_list"};
const std::vector<std::string> &options = type == Preset::TYPE_PRINTER ? Preset::printer_options() : Preset::print_options();
// Keys the legacy handler drops on load can never be in a project, so they do not count as missing.
auto dropped_on_load = [](std::string key) {
std::string value;
PrintConfigDef::handle_legacy(key, value);
return key.empty();
};
std::vector<std::string> missing;
for (const std::string &key : options)
if (m_print_config.option(key) == nullptr && skip_keys.count(key) == 0 && !dropped_on_load(key))
missing.push_back(key);
if (missing.empty())
return;
DynamicPrintConfig system_config;
std::string error;
if (!ensure_system_preset_resolver().resolve_system_preset(system_config, type, system_name, config_substitution_rule, error)) {
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); keys missing from the project keep their defaults") % system_name % error;
return;
}
for (const std::string &key : missing)
if (const ConfigOption *opt = system_config.option(key)) {
m_print_config.set_key_value(key, opt->clone());
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% missing from the project, taken from '%2%': %3%") % key % system_name % opt->serialize();
}
};
if (new_printer_name.empty())
fill_missing_project_keys(current_printer_system_name, Preset::TYPE_PRINTER);
if (new_process_name.empty())
fill_missing_project_keys(current_process_system_name, Preset::TYPE_PRINT);
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values; std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values; std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
inherits_group.resize(filament_count + 2, std::string()); inherits_group.resize(filament_count + 2, std::string());
-7
View File
@@ -205,13 +205,6 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty()) if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1"); set("seq_top_layer_only", "1");
// 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 // ORCA: darken the layers the preview layer slider is not scrubbed to
if (get("preview_dim_previous_layers").empty()) if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false); set_bool("preview_dim_previous_layers", false);
+45 -6
View File
@@ -575,18 +575,20 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir, const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir,
const std::string &vendor_id, const std::string &vendor_id,
ForwardCompatibilitySubstitutionRule compatibility_rule, ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error) std::string &error, bool allow_cache)
{ {
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule); auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule, allow_cache);
if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end()) if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end())
return it->second.get(); return it->second.get();
// The library loads with no base of its own, so the tree a vendor inherits from // The library loads with no base of its own, so the tree a vendor inherits from
// is the same one that resolves the library's own presets. // is the same one that resolves the library's own presets.
const PresetBundle *library = nullptr; const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
const PresetBundle *library = nullptr;
if (vendor_id != ORCA_FILAMENT_LIBRARY && if (vendor_id != ORCA_FILAMENT_LIBRARY &&
boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { (boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) ||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error); (allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) {
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache);
if (library == nullptr) { if (library == nullptr) {
error = "OrcaFilamentLibrary contains invalid presets"; error = "OrcaFilamentLibrary contains invalid presets";
return nullptr; return nullptr;
@@ -595,7 +597,7 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
auto bundle = std::make_unique<PresetBundle>(); auto bundle = std::make_unique<PresetBundle>();
bundle->m_preserve_vendor_source_paths = true; bundle->m_preserve_vendor_source_paths = true;
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false); bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, allow_cache);
if (bundle->error_count() != 0) { if (bundle->error_count() != 0) {
error = "Vendor bundle contains invalid presets"; error = "Vendor bundle contains invalid presets";
return nullptr; return nullptr;
@@ -638,6 +640,43 @@ bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset
return true; return true;
} }
bool PresetBundle::resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error)
{
const std::string vendor_id = find_preset_vendor(name, type);
if (vendor_id.empty()) {
error = "No vendor lists the preset";
return false;
}
// Release builds ship a vendor as its preset cache alone, without the profile JSONs.
auto installed = [&vendor_id](const fs::path &root) {
return fs::is_regular_file(root / (vendor_id + ".json")) || fs::is_regular_file(root / (vendor_id + ".opc"));
};
fs::path root_dir = fs::path(data_dir()) / PRESET_SYSTEM_DIR;
if (!installed(root_dir))
root_dir = fs::path(resources_dir()) / PRESET_PROFILES_DIR;
const bool cache_only = !fs::is_regular_file(root_dir / (vendor_id + ".json"));
try {
const PresetBundle *vendor = load_source_vendor(root_dir, vendor_id, compatibility_rule, error, cache_only);
if (vendor == nullptr)
return false;
const PresetCollection &collection = type == Preset::TYPE_PRINTER ? vendor->printers :
type == Preset::TYPE_PRINT ? vendor->prints : vendor->filaments;
const Preset *preset = collection.find_preset(name, false);
if (preset == nullptr) {
error = "Preset was not found in its vendor bundle";
return false;
}
config = preset->config;
} catch (const std::exception &ex) {
error = ex.what();
return false;
}
error.clear();
return true;
}
PresetBundle::PresetBundle(const PresetBundle &rhs) PresetBundle::PresetBundle(const PresetBundle &rhs)
{ {
*this = rhs; *this = rhs;
+7 -2
View File
@@ -273,6 +273,10 @@ public:
const std::string &source_file, const std::string &source_file,
ForwardCompatibilitySubstitutionRule compatibility_rule, ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error, bool allow_source_manifest = true); std::string &error, bool allow_source_manifest = true);
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
// there, as the GUI reads it, and from the bundled profiles otherwise.
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
// Load selections (current print, current filaments, current printer) from config.ini // Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up. // This is done just once on application start up.
@@ -663,13 +667,14 @@ private:
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset // Vendor trees loaded by resolve_preset_config's manifest path, so every preset
// resolved through this bundle shares one load per source root and vendor. The // resolved through this bundle shares one load per source root and vendor. The
// filament library is one such tree, shared by every vendor under its root. // filament library is one such tree, shared by every vendor under its root.
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>> // A tree read from its preset cache is kept apart: its presets carry no source file.
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
m_source_vendor_bundles; m_source_vendor_bundles;
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir, const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
const std::string &vendor_id, const std::string &vendor_id,
ForwardCompatibilitySubstitutionRule compatibility_rule, ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error); std::string &error, bool allow_cache = false);
// Orca: validation only - flag any printer with two or more compatible // Orca: validation only - flag any printer with two or more compatible
// filament presets sharing one filament_id (ambiguous AMS subtype match). // filament presets sharing one filament_id (ambiguous AMS subtype match).
-15
View File
@@ -158,21 +158,6 @@ enum class EGCodeExtrusionRole : uint8_t
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT); static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
//
// 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 // Option types
// //
-13
View File
@@ -114,19 +114,6 @@ public:
// //
bool is_dim_previous_layers() const; bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value); void set_dim_previous_layers(bool value);
//
// 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; float get_dim_previous_layers_brightness() const;
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);
// //
-5
View File
@@ -25,11 +25,6 @@ struct Settings
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black // ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
float dim_previous_layers_brightness{ 0.4f }; float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false }; 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 // Required update flags
// //
-30
View File
@@ -92,36 +92,6 @@ bool Viewer::is_dim_previous_layers() const
return m_impl->is_dim_previous_layers(); 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) void Viewer::set_dim_previous_layers(bool value)
{ {
m_impl->set_dim_previous_layers(value); m_impl->set_dim_previous_layers(value);
+9 -104
View File
@@ -17,7 +17,6 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <cfloat>
namespace libvgcode { namespace libvgcode {
@@ -902,17 +901,9 @@ void ViewerImpl::reset()
#else #else
m_enabled_segments_count = 0; m_enabled_segments_count = 0;
m_enabled_options_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; 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_textures(m_enabled_options_tex_id);
delete_buffers(m_enabled_options_buf_id); delete_buffers(m_enabled_options_buf_id);
delete_textures(m_enabled_segments_tex_id); delete_textures(m_enabled_segments_tex_id);
@@ -1172,17 +1163,6 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
glsafe(glGenTextures(1, &m_enabled_options_tex_id)); glsafe(glGenTextures(1, &m_enabled_options_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, 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(glBindBuffer(GL_TEXTURE_BUFFER, 0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
@@ -1194,18 +1174,6 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
update_colors(); 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() void ViewerImpl::update_enabled_entities()
{ {
if (m_vertices.empty()) if (m_vertices.empty())
@@ -1213,17 +1181,6 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments; std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options; 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(); Interval range = m_view_range.get_visible();
// when top layer only visualization is enabled, we need to render // when top layer only visualization is enabled, we need to render
@@ -1271,20 +1228,6 @@ void ViewerImpl::update_enabled_entities()
enabled_options.push_back(static_cast<uint32_t>(i)); enabled_options.push_back(static_cast<uint32_t>(i));
else else
enabled_segments.push_back(static_cast<uint32_t>(i)); 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 #ifdef ENABLE_OPENGL_ES
@@ -1313,23 +1256,6 @@ void ViewerImpl::update_enabled_entities()
else else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW)); 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)); glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
@@ -1537,23 +1463,6 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture(); 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 // ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
void ViewerImpl::set_dim_previous_layers(bool value) void ViewerImpl::set_dim_previous_layers(bool value)
{ {
@@ -1947,8 +1856,6 @@ size_t ViewerImpl::get_used_gpu_memory() const
ret += m_colors_tex_size; ret += m_colors_tex_size;
ret += m_enabled_segments_tex_size; ret += m_enabled_segments_tex_size;
ret += m_enabled_options_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 #endif // ENABLE_OPENGL_ES
return ret; return ret;
} }
@@ -2165,8 +2072,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
#ifdef ENABLE_OPENGL_ES #ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_segments_count() == 0) if (m_texture_data.get_enabled_segments_count() == 0)
#else #else
const ActiveSet segments = active_segments(); if (m_enabled_segments_count == 0)
if (segments.count == 0)
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
return; return;
@@ -2193,7 +2099,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f; 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)); glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES #ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(segments.count))); glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades // ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off. // with the lookup off.
@@ -2243,10 +2149,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3)); glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, segments.tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, segments.buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
m_segment_template.render(segments.count); m_segment_template.render(m_enabled_segments_count);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
if (curr_cull_face) if (curr_cull_face)
@@ -2272,8 +2178,7 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
#ifdef ENABLE_OPENGL_ES #ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_options_count() == 0) if (m_texture_data.get_enabled_options_count() == 0)
#else #else
const ActiveSet options = active_options(); if (m_enabled_options_count == 0)
if (options.count == 0)
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
return; return;
@@ -2331,10 +2236,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3)); glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, options.tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, options.buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id));
m_option_template.render(options.count); m_option_template.render(m_enabled_options_count);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
if (!curr_cull_face) if (!curr_cull_face)
-45
View File
@@ -109,21 +109,6 @@ public:
// 0.0 = black // 0.0 = black
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; } bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value); 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; } float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);
@@ -516,15 +501,6 @@ private:
unsigned int m_enabled_options_tex_id{ 0 }; unsigned int m_enabled_options_tex_id{ 0 };
size_t m_enabled_options_count{ 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 // Caches for size of data sent to gpu, in bytes
// //
size_t m_positions_tex_size{ 0 }; size_t m_positions_tex_size{ 0 };
@@ -532,27 +508,6 @@ private:
size_t m_colors_tex_size{ 0 }; size_t m_colors_tex_size{ 0 };
size_t m_enabled_segments_tex_size{ 0 }; size_t m_enabled_segments_tex_size{ 0 };
size_t m_enabled_options_tex_size{ 0 }; size_t m_enabled_options_tex_size{ 0 };
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 #endif // ENABLE_OPENGL_ES
// //
+1 -146
View File
@@ -1267,8 +1267,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only) if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range(); m_viewer.toggle_top_layer_only_view_range();
read_reduced_detail_preferences();
// ORCA: darken the layers the preview layer slider is not scrubbed to // ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers")); m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness"))); m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
@@ -1492,10 +1490,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
// BBS: data for rendering color arrangement recommendation // BBS: data for rendering color arrangement recommendation
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size(); 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 // Orca hack: Hide filament group for non-bbl printers
if (!print.is_BBL_printer()) m_nozzle_nums = 1; if (!print.is_BBL_printer()) m_nozzle_nums = 1;
std::vector<int> filament_maps = print.get_filament_maps(); std::vector<int> filament_maps = print.get_filament_maps();
@@ -1666,7 +1660,6 @@ void GCodeViewer::reset_shell()
{ {
m_shells.volumes.clear(); m_shells.volumes.clear();
m_shells.print_id = -1; m_shells.print_id = -1;
m_shells.with_wipe_tower = false;
m_shell_bounding_box = BoundingBoxf3(); m_shell_bounding_box = BoundingBoxf3();
} }
@@ -1703,12 +1696,7 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height) void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{ {
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
// while dragging in the solid model mode, the objects stand in for their toolpaths, cut to the render_shells(canvas_width, canvas_height);
// 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) if (m_viewer.get_extrusion_roles_count() == 0)
return; return;
@@ -2031,56 +2019,6 @@ void GCodeViewer::update_layers_slider_mode()
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); // TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
} }
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) 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])); m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1]));
@@ -2405,11 +2343,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing) 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; 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)) { 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 //BBS: update force previewing logic
if (force_previewing) if (force_previewing)
m_shells.previewing = force_previewing; m_shells.previewing = force_previewing;
@@ -2518,45 +2452,10 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
m_shells.print_id = print.id().id; m_shells.print_id = print.id().id;
m_shells.print_modify_count = print.get_modified_count(); m_shells.print_modify_count = print.get_modified_count();
m_shells.previewing = true; 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%") 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(); % 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() void GCodeViewer::render_toolpaths()
{ {
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
@@ -2757,50 +2656,6 @@ void GCodeViewer::render_shells(int canvas_width, int canvas_height)
glsafe(::glDepthMask(GL_TRUE)); 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 //BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const { void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
if (!show) if (!show)
+1 -22
View File
@@ -174,8 +174,6 @@ public:
int print_id{-1}; int print_id{-1};
int print_modify_count{-1}; int print_modify_count{-1};
bool previewing{false}; bool previewing{false};
// the prime tower was loaded with the objects, for the solid model
bool with_wipe_tower{false};
}; };
//BBS //BBS
ConflictResultOpt m_conflict_result; ConflictResultOpt m_conflict_result;
@@ -236,17 +234,6 @@ private:
bool m_legend_visible{ true }; bool m_legend_visible{ true };
bool m_legend_enabled{ 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; float m_legend_height;
PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics m_print_statistics;
@@ -304,7 +291,7 @@ public:
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // 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_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); // 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_count() != 0; } bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); }
bool can_export_toolpaths() const; bool can_export_toolpaths() const;
std::vector<int> get_plater_extruder(); std::vector<int> get_plater_extruder();
@@ -376,14 +363,6 @@ public:
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); } 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(); } 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); 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; } bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
+4 -64
View File
@@ -2084,11 +2084,6 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
const bool overlay_tick = m_fps_overlay_tick; const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false; 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 // 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. // 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); const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
@@ -3282,16 +3277,9 @@ void GLCanvas3D::bind_event_handlers()
if (m_selection_edit.kind != SelectionEdit::None) if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit(); finish_selection_edit();
ImGui::SetWindowFocus(nullptr); 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(); render();
evt.Skip(); 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_event_handlers_bound = true;
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this); m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -3367,17 +3355,6 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_overlay_dirty |= imgui_requires_extra_frame; m_overlay_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh(); m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
// 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) if (!m_dirty && !m_overlay_dirty)
return; return;
@@ -3908,10 +3885,6 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return; return;
} }
// only a wheel the panels did not take moves the camera
if (m_canvas_type == CanvasPreview)
note_preview_interaction();
#ifdef __WXMSW__ #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, // 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. // if the event is not allowed to be passed further.
@@ -4012,11 +3985,6 @@ void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
wxWakeUpIdle(); 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) void GLCanvas3D::schedule_extra_frame(int milliseconds)
{ {
// Schedule idle event right now // Schedule idle event right now
@@ -5663,9 +5631,6 @@ void GLCanvas3D::mouse_up_cleanup()
m_mouse.ignore_left_up = false; m_mouse.ignore_left_up = false;
m_mouse.ignore_right_up = false; m_mouse.ignore_right_up = false;
m_dirty = true; 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()) if (m_canvas->HasCapture())
m_canvas->ReleaseMouse(); m_canvas->ReleaseMouse();
@@ -7814,20 +7779,13 @@ bool GLCanvas3D::_is_scene_cacheable() const
return false; return false;
#endif #endif
// The scene follows the cursor while the user drags, under a gizmo that draws at the cursor, and // The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while
// while the cursor is on the layer height bar, where the object shader draws a band at its height. // 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 GLGizmoBase* gizmo = m_gizmos.get_current();
const bool cursor_on_layers_bar = is_layers_editing_enabled() && 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()); m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
return !is_user_interacting() && (gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar; return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() &&
} (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 bool GLCanvas3D::_is_frame_skipping_enabled() const
@@ -8818,24 +8776,6 @@ void GLCanvas3D::_render_wireframe_overlay()
shader->stop_using(); 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 //BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{ {
-11
View File
@@ -656,10 +656,6 @@ private:
ECursorType m_cursor_type; ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection; GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false }; bool m_navigator_dragging{ false };
// 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 //BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs; mutable std::vector<int> m_hover_volume_idxs;
@@ -1226,10 +1222,6 @@ public:
void msw_rescale() { m_gcode_viewer.invalidate_legend(); } void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
void request_extra_frame() { m_extra_frame_requested = true; } void request_extra_frame() { m_extra_frame_requested = true; }
// 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); void schedule_extra_frame(int milliseconds);
@@ -1380,9 +1372,6 @@ private:
//BBS: GUI refactor: add canvas size as parameters //BBS: GUI refactor: add canvas size as parameters
void _render_gcode(int canvas_width, int canvas_height); void _render_gcode(int canvas_width, int canvas_height);
void _render_gcode_overlay(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 //BBS: render a plane for assemble
void _render_plane() const; void _render_plane() const;
void _render_selection(); void _render_selection();
-7
View File
@@ -483,11 +483,6 @@ void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& g
ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth()); ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth());
} }
bool IMSlider::is_dragging() const
{
return GImGui != nullptr && m_imgui_id != 0 && GImGui->ActiveId == m_imgui_id && GImGui->IO.MouseDown[0];
}
bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale) bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale)
{ {
ImGuiWindow* window = ImGui::GetCurrentWindow(); ImGuiWindow* window = ImGui::GetCurrentWindow();
@@ -496,7 +491,6 @@ bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int
ImGuiContext& context = *GImGui; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); const ImRect draw_region(pos, pos + size);
@@ -889,7 +883,6 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
ImGuiContext& context = *GImGui; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); const ImRect draw_region(pos, pos + size);
-5
View File
@@ -118,9 +118,6 @@ public:
//BBS update scroll value changed //BBS update scroll value changed
bool is_dirty() { return m_dirty; } 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; } void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; } bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) { void reset_post_tick_event(bool val = false) {
@@ -185,8 +182,6 @@ private:
int m_higher_value; int m_higher_value;
int m_one_layer_value; // ORCA int m_one_layer_value; // ORCA
bool m_dirty = false; bool m_dirty = false;
// the ImGui id of the slider widget, as of its last render
unsigned int m_imgui_id = 0;
bool m_render_as_disabled{ false }; bool m_render_as_disabled{ false };
+2 -29
View File
@@ -322,7 +322,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
return sizer; return sizer;
} }
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange, 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, const wxString wiki_url)
{ {
assert(vlist.size() == config_name_index.size()); assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0; unsigned int current_index = 0;
@@ -338,9 +338,8 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index); auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
//// save config //// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index, onchange](wxCommandEvent& e) { combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
app_config->set(param, config_name_index[e.GetSelection()]); app_config->set(param, config_name_index[e.GetSelection()]);
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
e.Skip(); e.Skip();
}); });
@@ -2045,32 +2044,6 @@ void PreferencesDialog::create_items()
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues); "preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
g_sizer->Add(item_preview_view_type); 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( auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"), _L("Dim lower layers"),
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."), _L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
+1 -1
View File
@@ -93,7 +93,7 @@ public:
wxBoxSizer *create_item_title(wxString title); wxBoxSizer *create_item_title(wxString title);
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = ""); wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = ""); wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> 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_region_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist); wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);
+8
View File
@@ -15,3 +15,11 @@ set_tests_properties(cli_strict_mode PROPERTIES
LABELS "CLI;RequiresApp" LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77 SKIP_RETURN_CODE 77
TIMEOUT 900) TIMEOUT 900)
add_test(NAME cli_project_missing_keys
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_project_missing_keys.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON}
${CMAKE_SOURCE_DIR}/resources/profiles/BBL)
set_tests_properties(cli_project_missing_keys PROPERTIES
LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77
TIMEOUT 900)
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# End-to-end check that the CLI fills settings missing from a project from the project's system presets.
#
# A project saved before an option existed has no value for it. The GUI takes such keys from the
# project's system printer and process presets, not from the option defaults, and the CLI must slice
# the project with the same values. A project is exported from the shipped Bambu Lab P1S presets, one
# printer key and one process key are removed from it, one kept key is changed, and it is sliced again.
#
# usage: test_cli_project_missing_keys.sh <orca-slicer binary> <python3> <resources/profiles/BBL>
set -u
BIN="${1:-}"
PY="${2:-python3}"
PROFILES="${3:-}"
# 77 is the test's SKIP_RETURN_CODE.
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
[ -d "$PROFILES" ] || { echo "FAIL: profiles directory not found: $PROFILES"; exit 1; }
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-missing-keys.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
"$PY" - "$WORK/cube.stl" <<'EOF'
import sys
v = [(x, y, z) for z in (0, 10) for y in (0, 10) for x in (0, 10)]
with open(sys.argv[1], "w") as f:
f.write("solid cube\n")
for a, b, c, d in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)):
for tri in ((v[a], v[b], v[c]), (v[a], v[c], v[d])):
f.write("facet normal 0 0 0\nouter loop\n")
for p in tri:
f.write("vertex %g %g %g\n" % p)
f.write("endloop\nendfacet\n")
f.write("endsolid cube\n")
EOF
# slice <tag> <input> [option...]: slice into $WORK/<tag>/out.3mf with a fresh data directory.
slice() {
local out="$WORK/$1" input="$2"; shift 2
mkdir -p "$out"
timeout 300 "$BIN" --datadir "$out/datadir" "$@" --slice 0 --outputdir "$out" --export-3mf out.3mf "$input" \
> "$out/log" 2>&1 || { echo "FAIL: $1: orca-slicer exited $?"; tail -n 40 "$out/log"; exit 1; }
}
slice base "$WORK/cube.stl" \
--load-settings "$PROFILES/machine/Bambu Lab P1S 0.4 nozzle.json;$PROFILES/process/0.20mm Standard @BBL X1C.json" \
--load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
# The removed keys, with their option defaults from PrintConfig.cpp, and a kept key with a new value.
"$PY" - "$WORK/base/out.3mf" "$WORK/old.3mf" <<'EOF' || exit $?
import json, sys, zipfile
src, dst = sys.argv[1], sys.argv[2]
missing = {"extruder_clearance_dist_to_rod": "40", "sparse_infill_density": "20%"}
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == "Metadata/project_settings.config":
config = json.loads(data)
for key, default in missing.items():
if config[key] == default:
print("SKIP: %s is %s in the system preset, the option default, so the test cannot tell them apart" % (key, default))
sys.exit(77)
expected = {key: config.pop(key) for key in missing}
expected["wall_loops"] = str(int(config["wall_loops"]) + 1)
config["wall_loops"] = expected["wall_loops"]
data = json.dumps(config, indent=4)
zout.writestr(item, data)
with open(dst + ".expected.json", "w") as f:
json.dump(expected, f)
EOF
slice project "$WORK/old.3mf"
"$PY" - "$WORK/project/out.3mf" "$WORK/old.3mf.expected.json" <<'EOF'
import json, sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z:
config = json.loads(z.read("Metadata/project_settings.config"))
with open(sys.argv[2]) as f:
expected = json.load(f)
errors = ["%s is %r, want %r" % (key, config.get(key), want) for key, want in expected.items() if config.get(key) != want]
for e in errors:
print("FAIL: " + e)
sys.exit(1 if errors else 0)
EOF
status=$?
[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; }
echo "PASS"
@@ -5405,6 +5405,35 @@ struct ScopedDataDir
~ScopedDataDir() { set_data_dir(previous); } ~ScopedDataDir() { set_data_dir(previous); }
}; };
// resources_dir() is process-wide too; system preset lookups scan its profiles directory.
struct ScopedResourcesDir
{
std::string previous = resources_dir();
explicit ScopedResourcesDir(const fs::path &dir) { set_resources_dir(dir.string()); }
~ScopedResourcesDir() { set_resources_dir(previous); }
};
// An "Acme" vendor under root whose "Acme Printer" inherits extruder_clearance_dist_to_rod from an
// abstract base, with the printer in a nested sub_path so the name cannot be derived from the file.
void write_acme_printer_vendor(const fs::path &root, double dist_to_rod)
{
const fs::path machine_dir = root / "Acme" / "machine";
fs::create_directories(machine_dir / "nested");
std::ofstream((root / "Acme.json").string())
<< R"({"version":"1.0.0","name":"Acme",)"
<< R"("machine_model_list":[{"name":"Acme One","sub_path":"machine/model.json"}],"machine_list":[)"
<< R"({"name":"fdm_acme_common","sub_path":"machine/base.json"},)"
<< R"({"name":"Acme Printer","sub_path":"machine/nested/printer.json"}]})";
std::ofstream((machine_dir / "model.json").string())
<< R"({"type":"machine_model","name":"Acme One","nozzle_diameter":"0.4"})";
std::ofstream((machine_dir / "base.json").string())
<< R"({"type":"machine","name":"fdm_acme_common","from":"system","instantiation":"false",)"
<< R"("extruder_clearance_dist_to_rod":")" << dist_to_rod << R"("})";
std::ofstream((machine_dir / "nested" / "printer.json").string())
<< R"({"type":"machine","name":"Acme Printer","from":"system","instantiation":"true","inherits":"fdm_acme_common",)"
<< R"("printer_model":"Acme One","printer_variant":"0.4"})";
}
std::string read_file(const fs::path &file) std::string read_file(const fs::path &file)
{ {
std::ifstream in(file.string(), std::ios::binary); std::ifstream in(file.string(), std::ios::binary);
@@ -5482,3 +5511,75 @@ TEST_CASE("Config import confines zip entries, preset names and bundle ids to th
CHECK_FALSE(any_filename_contains(temp_dir.path(), "bundle-escape")); CHECK_FALSE(any_filename_contains(temp_dir.path(), "bundle-escape"));
} }
} }
TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
ScopedDataDir data(temp_dir.path() / "data");
ScopedResourcesDir resources(temp_dir.path() / "resources");
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK(error.empty());
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
}
TEST_CASE("A system preset resolves from the data directory copy of its vendor", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
ScopedDataDir data(temp_dir.path() / "data");
ScopedResourcesDir resources(temp_dir.path() / "resources");
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
write_acme_printer_vendor(temp_dir.path() / "data" / PRESET_SYSTEM_DIR, 35.);
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(35., 1e-6));
}
TEST_CASE("A system preset resolves from a vendor shipped as its preset cache alone", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
ScopedDataDir data(temp_dir.path() / "data");
ScopedResourcesDir resources(temp_dir.path() / "resources");
const fs::path profiles = temp_dir.path() / "resources" / "profiles";
write_acme_printer_vendor(profiles, 33.);
PresetBundle writer;
writer.set_generate_vendor_caches(true);
writer.load_vendor_configs_from_json(profiles.string(), "Acme", PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent);
REQUIRE(fs::exists(profiles / "Acme.opc"));
// Release builds ship the cache and drop the profile JSONs, manifest included.
fs::remove(profiles / "Acme.json");
fs::remove_all(profiles / "Acme");
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
}
TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
ScopedDataDir data(temp_dir.path() / "data");
ScopedResourcesDir resources(temp_dir.path() / "resources");
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
CHECK_FALSE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Unknown Printer",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_FALSE(error.empty());
}