Compare commits

...

12 Commits

Author SHA1 Message Date
Ian Chua
b121051220 Merge branch 'main' into fix/plugin-bugs 2026-07-21 12:56:52 +08:00
Ian Chua
8b93cc5df3 prune stale plugin capability overrides (#14862)
* prune stale plugin capability overrides

* fix: configure button still shows (n) modified configs after removing

* fix: add slicing_pipeline_plugin to deep_diff
2026-07-21 12:55:44 +08:00
yw4z
a24115d996 Network plugin dialog fixes / improvements (#14720)
init
2026-07-21 01:21:40 +03:00
Wegerich
cb35e89f82 PA_Line Calibration QOL improvements and tweaks (#14440)
* fix: constrain PA_Line calibration bounding box to model geometry

- PA_Line bounding box now uses actual model convex hull for X constraint instead of full printable area
- Skip EXCLUDE_OBJECT_DEFINE for PA_Line mode (single object, no exclusion needed)
- Add tool config files to .gitignore

* modified:   .gitignore

* fix: add gcode type annotations and fix box height in PA line calibration

- Label calibration segments with appropriate TYPE comments (Outer wall, Bottom surface, Top surface, Custom) for proper gcode processing
- Fix bounding box height calculation to account for z_offset
- Add LAYER_CHANGE and HEIGHT comments for multi-layer numbering display

* fix: lock PA_Line bounding box X to bed centre instead of model hull

* Added extra TYPE to ensure text/numbers/glyphs are labelled correctly

`  gcode << ";TYPE:Outer wall\n"; `

* Remove hardcoded Perl path in OpenSSL.cmake

Remove hardcoded Perl path for Windows configuration.

* Add cross compilation support for Windows in OpenSSL.cmake

* Remove unnecessary blank line in OpenSSL.cmake

* Set perl config command back to variable 

Accidentally uploaded version with hardcoded path for my local environment

* whitespace adjustment in previous

* removed personal .gitignore config

Remove specific files and directories from .gitignore.

* Fix box height parameter in DrawBoxOptArgs

Update DrawBoxOptArgs to use `m_height_layer` instead of `m_height_layer*2+z_offset`. This isn't a Z coordinate, it's a layer height

* Replace hardcoded extrusion type with call to `GCodeProcessor::reserved_tag` Etags

* Get pa_line bounding box from actual calibration variables

Updated calibration line bounding box calculation to use actual geometry for X bounds, using a fake call to generate the calibration pattern. 

This will accurately get the size of the calibration pattern, massively reducing wasted time from bed mesh probing.

* Implement print_extents method in CalibPressureAdvanceLine

Add print_extents method to calculate bounding box extents based on bed dimensions.

* Declare print_extents method in CalibPressureAdvanceLine

Added print_extents method to return X-bounds of the pattern.

* Fixed whitespace issues

* Adjust print_extents to account for delta printers

Check if the printer is delta layout and adjust bed dimensions if so.
Used code from `CalibPressureAdvanceLine::generate_test`

* Added semicolons to reserved tags

Didn't realise etags wouldn't add semicolon - added these

* only include number list in bounding box if number list is used

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* avoid bounding box overflowing bed

* Tabs to spaces

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-07-20 18:25:07 -03:00
Noisyfox
60d1f9d502 Show OpenGL profile in troubleshoot dialog (#14464) 2026-07-20 18:17:06 -03:00
Kris Austin
0d6022477b fix: calibration crash when cancelling the model-load dialog (#14546) 2026-07-20 18:16:42 -03:00
Ian Bassi
4e4e867502 Dim lower layers (#14705) 2026-07-20 18:14:01 -03:00
Valerii Bokhan
e28b5dae94 Fix: Default values in tooltips for coFloats, coInts, coPercents, coFloatsOrPercents, coBools, coStrings config options (#14796) 2026-07-20 17:09:37 -03:00
Ian Chua
242e100feb fix: stale .whl cache when .whl changes, added option to delete cache and reload 2026-07-20 14:47:26 +08:00
Ian Chua
2a46bd1384 fix: crash transferring Slicing Pipeline Plugin changes across a printer switch 2026-07-20 13:58:57 +08:00
Ian Chua
ddbbc7f7f2 fix: only the top level frame should have the orca syles and bridges injected 2026-07-20 12:48:17 +08:00
Ian Chua
3a48086f5d fix: plugin descriptor should not reply on changelog for latest version 2026-07-20 12:47:18 +08:00
28 changed files with 571 additions and 217 deletions

View File

@@ -202,6 +202,10 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false);
if (get("filaments_area_preferred_count").empty())
set("filaments_area_preferred_count", "10");

View File

@@ -3246,7 +3246,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
BoundingBoxf bbox;
auto pts = std::make_unique<ConfigOptionPoints>();
if (print.calib_mode() == CalibMode::Calib_PA_Line || print.calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print.calib_mode() == CalibMode::Calib_PA_Pattern) {
//PA_Pattern can have any size or arrangement - not dependent on 3mf model size
bbox = bbox_bed;
bbox.offset(-25.0);
// add 4 corner points of bbox into pts
@@ -3256,6 +3257,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else if (print.calib_mode() == CalibMode::Calib_PA_Line) {
// Derive X bounds from the actual calibration geometry.
CalibPressureAdvanceLine temp_pa_line_forsize(this);
BoundingBoxf pattern_extents = temp_pa_line_forsize.print_extents(bbox_bed);
bbox = bbox_bed;
bbox.offset(-25.0);
bbox.min.x() = std::max(pattern_extents.min.x(), bbox.min.x());
bbox.max.x() = std::min(pattern_extents.max.x(), bbox.max.x());
pts->values.reserve(4);
pts->values.emplace_back(bbox.min.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else {
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
@@ -7532,7 +7549,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
@@ -9087,7 +9104,7 @@ std::string GCode::set_object_info(Print *print) {
std::ostringstream gcode;
size_t object_id = 0;
// Orca: check if we are in pa calib mode
if (print->calib_mode() == CalibMode::Calib_PA_Line || print->calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print->calib_mode() == CalibMode::Calib_PA_Pattern) {
BoundingBoxf bbox_bed(print->config().printable_area.values);
bbox_bed.offset(-25.0);
Polygon polygon_bed;
@@ -9098,6 +9115,8 @@ std::string GCode::set_object_info(Print *print) {
gcode << "EXCLUDE_OBJECT_DEFINE NAME="
<< "Orca-PA-Calibration-Test"
<< " CENTER=" << 0 << "," << 0 << " POLYGON=" << polygon_to_string(polygon_bed, print, true) << "\n";
} else if (print->calib_mode() == CalibMode::Calib_PA_Line) {
// PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed
} else {
size_t unique_id = 0;
for (PrintObject* object : print->objects()) {

View File

@@ -3504,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
{
//BBS: add bed_exclude_area
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
// Scalar variable, or a vector variable, which is independent from number of extruders,
// thus the vector is presented to the user as a single input.
diff.emplace_back(opt_key);

View File

@@ -469,6 +469,31 @@ std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, dou
return print_pa_lines(startx, starty, start_pa, step_pa, count);
}
BoundingBoxf CalibPressureAdvanceLine::print_extents(const BoundingBoxf &bed_ext) const
{
BoundingBoxf adjusted_bed = bed_ext;
if (is_delta()) {
CalibPressureAdvanceLine::delta_scale_bed_ext(adjusted_bed);
}
double bed_width = adjusted_bed.size().x();
// m_length_long adjusts for narrow beds exactly as in generate_test()
double line_long = 40.0 + std::min(bed_width - 120.0, 0.0);
double total_line_len = m_length_short * 2 + line_long;
double start_x = adjusted_bed.min.x() + (bed_width - 2 * m_length_short - line_long - 20.0) / 2.0;
double box_width = m_draw_numbers ? (number_spacing() * 8) : 0.0; // 3.0 * 8 = 24 mm
BoundingBoxf extent;
extent.min.x() = start_x;
extent.max.x() = start_x + total_line_len + m_line_width + box_width;
// Y bounds are the full bed (the caller will inset them by -25)
extent.min.y() = adjusted_bed.min.y();
extent.max.y() = adjusted_bed.max.y();
return extent;
}
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
@@ -491,9 +516,10 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
std::stringstream gcode;
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer + z_offset);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer + z_offset) << "\n";
double y_pos = start_y;
// prime line
// Purge/first perimeter - acts as an anchor to the rest of the model
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_pressure_advance(0.0);
auto prime_x = start_x;
gcode << move_to(Vec2d(prime_x, y_pos + (num) * m_space_y), writer);
@@ -503,10 +529,13 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
for (int i = 0; i < num; ++i) {
gcode << writer.set_pressure_advance(start_pa + i * step_pa);
gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << writer.set_speed(fast);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y),
e_per_mm * m_length_short);
@@ -530,9 +559,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short + m_line_width;
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
//Draw box
default_box_opt_args.is_filled = true;
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
//Ensure numbers are shown on the next layer in gcode processor, as in reality
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer*2 + z_offset) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Top surface\n";
gcode << writer.travel_to_z(m_height_layer*2 + z_offset);
for (int i = 0; i < num; i += 2) {
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
@@ -595,12 +630,16 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
speed_adjust(speed_first_layer()));
// create anchoring frame
//pattern uses outer wall speed/width
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
// create tab for numbers
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
draw_box_opt_args.is_filled = true;
draw_box_opt_args.num_perimeters = wall_count();
//draw box as bottom surface, so numbers are clearly visible on top
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
print_size_x(),
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
@@ -611,7 +650,9 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
const double zhop_config_value = m_config.option<ConfigOptionFloats>("z_hop")->get_at(0);
const auto accel = accel_perimeter();
// draw pressure advance pattern
// Draw pressure advance pattern
// pattern uses outer wall speed, label it as such
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int i = 0; i < m_num_layers; ++i) {
const double layer_height = height_first_layer() + height_z_offset() + (i * height_layer());
const double zhop_height = layer_height + zhop_config_value;
@@ -643,6 +684,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
// glyph on every other line
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int j = 0; j < num_patterns; j += 2) {
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
m_params.start + (j * m_params.step), m_draw_digit_mode, line_width(), number_e_per_mm,
@@ -692,7 +734,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
for (int j = 0; j < num_patterns; ++j) {
// increment pressure advance
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int k = 0; k < wall_count(); ++k) {
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;

View File

@@ -254,6 +254,8 @@ class CalibPressureAdvanceLine : public CalibPressureAdvance
public:
CalibPressureAdvanceLine(GCode* gcodegen);
~CalibPressureAdvanceLine(){};
// Return the Xbounds of the pattern on the given bed.
BoundingBoxf print_extents(const BoundingBoxf &bed_ext) const;
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
@@ -379,4 +381,4 @@ private:
const double m_glyph_padding_vertical{1};
};
} // namespace Slic3r
} // namespace Slic3r

View File

@@ -91,6 +91,13 @@ public:
//
void toggle_top_layer_only_view_range();
//
// Dim previous layers (ORCA, ported from preFlight)
// Whether the layers below the current top layer are rendered darkened while
// scrubbing below the full print, so only the current layer is shown at full brightness.
//
bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value);
//
// Returns true if the given option is visible.
//
bool is_option_visible(EOptionType type) const;

View File

@@ -19,6 +19,9 @@ struct Settings
EViewType view_type{ EViewType::FeatureType };
ETimeMode time_mode{ ETimeMode::Normal };
bool top_layer_only_view_range{ false };
// ORCA: when enabled, all layers below the current top layer are rendered
// darkened (keeping their color) while scrubbing below the full print (ported from preFlight)
bool dim_previous_layers{ false };
bool spiral_vase_mode{ false };
//
// Required update flags

View File

@@ -72,6 +72,16 @@ void Viewer::toggle_top_layer_only_view_range()
m_impl->toggle_top_layer_only_view_range();
}
bool Viewer::is_dim_previous_layers() const
{
return m_impl->is_dim_previous_layers();
}
void Viewer::set_dim_previous_layers(bool value)
{
m_impl->set_dim_previous_layers(value);
}
bool Viewer::is_option_visible(EOptionType type) const
{
return m_impl->is_option_visible(type);

View File

@@ -1223,6 +1223,20 @@ static float encode_color(const Color& color) {
return static_cast<float>(i_color);
}
// ORCA: how much the layers below the current top layer are darkened when
// Settings::dim_previous_layers is enabled (ported from preFlight). 0.0 = no change, 1.0 = black.
static constexpr float PREVIOUS_LAYER_DARKEN_FACTOR = 0.60f;
// ORCA: returns the encoded color scaled towards black by 'factor', preserving its hue
static float encode_color_darkened(const Color& color, float factor) {
const float keep = 1.0f - factor;
const int r = static_cast<int>(color[0] * keep);
const int g = static_cast<int>(color[1] * keep);
const int b = static_cast<int>(color[2] * keep);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(i_color);
}
void ViewerImpl::update_colors_texture()
{
@@ -1234,14 +1248,30 @@ void ViewerImpl::update_colors_texture()
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
// ORCA: when dim_previous_layers is enabled, darken every layer below the current top layer
// (keeping its color) whenever we are not rendering the whole print, so that only the layer
// being scrubbed to is shown at full brightness (ported from preFlight). This shares
// top_layer_id with the greying path, so it only applies while in top-layer-only mode - that
// way the moves slider still animates normally across all layers when that mode is disabled.
const bool dim_previous_layers = m_settings.dim_previous_layers && !m_layers.empty();
const bool full_render = (m_layers.get_view_range()[0] == 0) &&
(m_layers.get_view_range()[1] >= static_cast<uint32_t>(m_layers.count()) - 1) &&
(m_view_range.get_visible()[1] == m_view_range.get_full()[1]);
// Based on current settings and slider position, we might want to render some
// vertices as dark grey. Use either that or the normal color (from the cache).
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
std::vector<float> colors(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i)
colors[i] = (color_top_layer_only && m_vertices[i].layer_id < top_layer_id &&
(!m_settings.spiral_vase_mode || i != m_view_range.get_enabled()[0])) ?
encode_color(DUMMY_COLOR) : m_vertices_colors[i];
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
if (dim_previous_layers && !full_render && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color_darkened(get_vertex_color(v), PREVIOUS_LAYER_DARKEN_FACTOR);
else if (color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color(DUMMY_COLOR);
else
colors[i] = m_vertices_colors[i];
}
#ifdef ENABLE_OPENGL_ES
if (!colors.empty())
@@ -1349,6 +1379,17 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture();
}
// ORCA: enable/disable darkening of the layers below the current top layer (ported from preFlight)
void ViewerImpl::set_dim_previous_layers(bool value)
{
if (m_settings.dim_previous_layers == value)
return;
m_settings.dim_previous_layers = value;
// defer the actual color/texture rebuild to the next render(), when the GL context is current
// (this may be toggled from the Preferences dialog, outside the canvas context)
m_settings.update_colors = true;
}
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
{
std::vector<ETimeMode> ret;

View File

@@ -85,6 +85,10 @@ public:
bool is_top_layer_only_view_range() const { return m_settings.top_layer_only_view_range; }
void toggle_top_layer_only_view_range();
// ORCA: darken layers below the current top layer while scrubbing (ported from preFlight)
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value);
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
std::vector<ETimeMode> get_time_modes() const;

View File

@@ -111,14 +111,25 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
{
wxString tooltip = _(opt.tooltip);
std::string opt_id = id;
auto hash_pos = opt_id.find("#");
std::string parameter_name = id;
std::string opt_key = id;
int opt_idx = 0;
const size_t hash_pos = static_cast<std::string>(id).find("#");
if (hash_pos != std::string::npos) {
opt_id.replace(hash_pos, 1,"[");
opt_id += "]";
parameter_name.replace(hash_pos, 1, "[");
parameter_name += "]";
std::string temp_str = id;
boost::erase_head(temp_str, hash_pos + 1);
size_t orig_opt_idx = atoi(temp_str.c_str());
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
boost::erase_tail(opt_key, opt_key.size() - hash_pos);
}
tooltip += (tooltip.empty() ? "" : "\n\n") + _(L("parameter name")) + ": " + opt_id;
tooltip += (tooltip.empty() ? "" : "\n\n") + _(L("parameter name")) + ": " + parameter_name;
// Orca:
// We can't use Orca's default values as-is because they sometimes depend on other values.
@@ -126,7 +137,7 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
if (const Preset* print_parent_preset = wxGetApp().preset_bundle->prints.get_selected_preset_parent()) {
const DynamicPrintConfig& parent_config = print_parent_preset->config;
if (!parent_config.has(opt_id))
if (!parent_config.has(opt_key))
return tooltip;
wxString side_text = from_u8(opt.sidetext);
@@ -135,18 +146,71 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
if (opt.sidetext == L("layers"))
side_text = " " + _(side_text);
if (opt.type == coFloat || opt.type == coInt || opt.type == coPercent || opt.type == coFloatOrPercent) {
double default_value = 0.;
if (opt.type == coFloat || opt.type == coInt || opt.type == coPercent || opt.type == coFloatOrPercent ||
opt.type == coFloats || opt.type == coInts || opt.type == coPercents || opt.type == coFloatsOrPercents) {
double default_value = std::numeric_limits<double>::quiet_NaN();
if (opt.type == coFloat)
default_value = parent_config.option<ConfigOptionFloat>(opt_id)->value;
default_value = parent_config.option<ConfigOptionFloat>(opt_key)->value;
else if (opt.type == coFloats) {
if (opt.nullable) {
auto opt_floats_nullable = parent_config.option<ConfigOptionFloatsNullable>(opt_key);
if (!opt_floats_nullable->values.empty())
default_value = opt_floats_nullable->get_at(opt_idx);
} else {
auto opt_floats = parent_config.option<ConfigOptionFloats>(opt_key);
if (!opt_floats->values.empty())
default_value = opt_floats->get_at(opt_idx);
}
}
else if (opt.type == coInt)
default_value = parent_config.option<ConfigOptionInt>(opt_id)->value;
default_value = parent_config.option<ConfigOptionInt>(opt_key)->value;
else if(opt.type == coInts) {
if (opt.nullable) {
auto opt_ints_nullable = parent_config.option<ConfigOptionIntsNullable>(opt_key);
if (!opt_ints_nullable->values.empty())
default_value = opt_ints_nullable->get_at(opt_idx);
} else {
auto opt_ints = parent_config.option<ConfigOptionInts>(opt_key);
if (!opt_ints->values.empty())
default_value = opt_ints->get_at(opt_idx);
}
}
else if (opt.type == coPercent)
default_value = parent_config.option<ConfigOptionPercent>(opt_id)->value;
else if (opt.type == coFloatOrPercent) {
default_value = parent_config.option<ConfigOptionFloatOrPercent>(opt_id)->value;
if (parent_config.option<ConfigOptionFloatOrPercent>(opt_id)->percent)
default_value = parent_config.option<ConfigOptionPercent>(opt_key)->value;
else if (opt.type == coPercents) {
if (opt.nullable) {
auto opt_percents_nullable = parent_config.option<ConfigOptionPercentsNullable>(opt_key);
if (!opt_percents_nullable->values.empty())
default_value = opt_percents_nullable->get_at(opt_idx);
} else {
auto opt_percents = parent_config.option<ConfigOptionPercents>(opt_key);
if (!opt_percents->values.empty())
default_value = opt_percents->get_at(opt_idx);
}
}
else if (opt.type == coFloatOrPercent || opt.type == coFloatsOrPercents) {
bool is_percent = false;
if (opt.type == coFloatOrPercent) {
default_value = parent_config.option<ConfigOptionFloatOrPercent>(opt_key)->value;
is_percent = parent_config.option<ConfigOptionFloatOrPercent>(opt_key)->percent;
} else if (opt.type == coFloatsOrPercents) {
if (opt.nullable) {
auto opt_floats_or_percents_nullable = parent_config.option<ConfigOptionFloatsOrPercentsNullable>(opt_key);
if (!opt_floats_or_percents_nullable->values.empty()) {
default_value = opt_floats_or_percents_nullable->get_at(opt_idx).value;
is_percent = opt_floats_or_percents_nullable->get_at(opt_idx).percent;
}
} else {
auto opt_floats_or_percents = parent_config.option<ConfigOptionFloatsOrPercents>(opt_key);
if (!opt_floats_or_percents->values.empty()) {
default_value = opt_floats_or_percents->get_at(opt_idx).value;
is_percent = opt_floats_or_percents->get_at(opt_idx).percent;
}
}
}
if (is_percent)
side_text = "%";
else if (!side_text.empty()) {
static std::string postfix = " or %";
@@ -156,20 +220,38 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
}
}
tooltip += "\n\n" + _(L("Default")) + ": " + _(double_to_string(default_value)) + _(side_text);
if (!std::isnan(default_value))
tooltip += "\n\n" + _(L("Default")) + ": " + _(double_to_string(default_value)) + _(side_text);
if (opt.min > -FLT_MAX && opt.max < FLT_MAX) {
tooltip += "\n" + _(L("Range")) + ": [" +
_(double_to_string(opt.min)) + _(side_text) + ", " +
_(double_to_string(opt.max)) + _(side_text) + "]";
}
} else if (opt.type == coBool || opt.type == coString) {
} else if (opt.type == coBool || opt.type == coBools || opt.type == coString || opt.type == coStrings) {
std::string default_value = "";
if (opt.type == coString)
default_value = parent_config.option<ConfigOptionString>(opt_id)->value;
default_value = parent_config.option<ConfigOptionString>(opt_key)->value;
else if (opt.type == coStrings) {
auto opt_strings = parent_config.option<ConfigOptionStrings>(opt_key);
if (!opt_strings->values.empty())
default_value = opt_strings->get_at(opt_idx);
}
else if (opt.type == coBool)
default_value = parent_config.option<ConfigOptionBool>(opt_id)->value ? "true" : "false";
default_value = parent_config.option<ConfigOptionBool>(opt_key)->value != 0 ? "true" : "false";
else if (opt.type == coBools) {
if (opt.nullable) {
auto opt_bools_nullable = parent_config.option<ConfigOptionBoolsNullable>(opt_key);
if (!opt_bools_nullable->values.empty())
default_value = opt_bools_nullable->get_at(opt_idx) != 0 ? "true" : "false";
} else {
auto opt_bools = parent_config.option<ConfigOptionBools>(opt_key);
if (!opt_bools->values.empty())
default_value = opt_bools->get_at(opt_idx) != 0 ? "true" : "false";
}
}
tooltip += "\n\n" + _(L("Default")) + ": " +
(default_value.empty() ? _(L("Empty string")) : _(default_value) + _(side_text));

View File

@@ -1134,6 +1134,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range();
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
// avoid processing if called with the same gcode_result
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
//BBS: add logs

View File

@@ -333,6 +333,10 @@ public:
libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); }
// ORCA: darken layers below the current top layer while scrubbing the preview (ported from preFlight)
void set_dim_previous_layers(bool value) { m_viewer.set_dim_previous_layers(value); }
bool is_dim_previous_layers() const { return m_viewer.is_dim_previous_layers(); }
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; }

View File

@@ -4,6 +4,7 @@
#include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/DialogButtons.hpp"
#include "BitmapCache.hpp"
#include "wxExtensions.hpp"
#include "slic3r/Utils/bambu_networking.hpp"
@@ -12,6 +13,10 @@
#include <wx/stattext.h>
#include <wx/collpane.h>
#define BORDER_W FromDIP(20)
#define TEXT_WRAP FromDIP(400)
#define DIALOG_WIDTH FromDIP(440)
namespace Slic3r {
namespace GUI {
@@ -30,10 +35,10 @@ NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(DIALOG_WIDTH, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
main_sizer->Add(m_line_top, 0, wxEXPAND, 0);
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
main_sizer->AddSpacer(BORDER_W);
SetSizer(main_sizer);
@@ -52,157 +57,136 @@ void NetworkPluginDownloadDialog::create_missing_plugin_ui()
{
wxBoxSizer* main_sizer = static_cast<wxBoxSizer*>(GetSizer());
auto* desc = new wxStaticText(this, wxID_ANY,
auto* desc = new Label(this,
m_mode == Mode::CorruptedPlugin ?
_L("The Bambu Network Plug-in is corrupted or incompatible. Please reinstall it.") :
_L("The Bambu Network Plug-in is required for cloud features, printer discovery, and remote printing."));
desc->SetFont(::Label::Body_13);
desc->Wrap(FromDIP(400));
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
desc->Wrap(TEXT_WRAP);
desc->SetMaxSize(wxSize(TEXT_WRAP, -1));
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(15));
if (!m_error_message.empty()) {
auto* error_label = new wxStaticText(this, wxID_ANY,
wxString::Format(_L("Error: %s"), wxString::FromUTF8(m_error_message)));
error_label->SetFont(::Label::Body_13);
error_label->SetForegroundColour(wxColour(208, 93, 93));
error_label->Wrap(FromDIP(400));
main_sizer->Add(error_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
error_label->Wrap(TEXT_WRAP);
error_label->SetMaxSize(wxSize(TEXT_WRAP, -1));
main_sizer->Add(error_label, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(5));
if (!m_error_details.empty()) {
m_details_pane = new wxCollapsiblePane(this, wxID_ANY, _L("Show details"));
auto* pane = m_details_pane->GetPane();
auto* pane_sizer = new wxBoxSizer(wxVERTICAL);
auto expand_btn = new Button(this, _L("Show details"));
expand_btn->SetStyle(ButtonStyle::Regular, ButtonType::Compact);
main_sizer->Add(expand_btn, 0, wxLEFT, BORDER_W);
main_sizer->AddSpacer(FromDIP(5));
auto details_text = new wxTextCtrl(this, wxID_ANY, wxString::FromUTF8(m_error_details),
wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxNO_BORDER);
auto* details_text = new wxStaticText(pane, wxID_ANY, wxString::FromUTF8(m_error_details));
details_text->SetFont(wxGetApp().code_font());
details_text->Wrap(FromDIP(380));
pane_sizer->Add(details_text, 0, wxALL, FromDIP(10));
details_text->SetBackgroundColour(wxColour("#F1F1F1"));
details_text->SetMaxSize(wxSize(TEXT_WRAP, -1));
main_sizer->Add(details_text, 0, wxLEFT | wxRIGHT | wxEXPAND, BORDER_W);
pane->SetSizer(pane_sizer);
main_sizer->Add(m_details_pane, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
details_text->Hide();
expand_btn->Bind(wxEVT_BUTTON, [this, details_text, expand_btn](wxCommandEvent&){
Freeze();
details_text->Show(!details_text->IsShown());
expand_btn->SetLabel(details_text->IsShown() ? _L("Hide details") : _L("Show details"));
Layout();
Fit();
Refresh();
Thaw();
});
main_sizer->AddSpacer(FromDIP(15));
}
}
auto* version_label = new wxStaticText(this, wxID_ANY, _L("Version to install:"));
version_label->SetFont(::Label::Body_13);
main_sizer->Add(version_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
auto* version_label = new Label(this, _L("Version to install:"));
main_sizer->Add(version_label, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(3));
setup_version_selector();
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, BORDER_W);
main_sizer->AddSpacer(15);
StateColor btn_bg_green(
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
auto dlg_btns = new DialogButtons(this,
{"Download and Install", "Skip for Now"},
_L("Download and Install") // Primary button
);
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
dlg_btns->GetButtonFromIndex(0)->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
dlg_btns->GetButtonFromIndex(1)->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip, this);
auto* btn_download = new Button(this, _L("Download and Install"));
btn_download->SetBackgroundColor(btn_bg_green);
btn_download->SetBorderColor(*wxWHITE);
btn_download->SetTextColor(*wxWHITE);
btn_download->SetFont(::Label::Body_12);
btn_download->SetMinSize(wxSize(FromDIP(120), FromDIP(24)));
btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10));
auto* btn_skip = new Button(this, _L("Skip for Now"));
btn_skip->SetBackgroundColor(btn_bg_white);
btn_skip->SetBorderColor(wxColour(38, 46, 48));
btn_skip->SetFont(::Label::Body_12);
btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip, this);
btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10));
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
main_sizer->Add(dlg_btns, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, FromDIP(10));
}
void NetworkPluginDownloadDialog::create_update_available_ui(const std::string& current_version)
{
wxBoxSizer* main_sizer = static_cast<wxBoxSizer*>(GetSizer());
auto* desc = new wxStaticText(this, wxID_ANY,
auto* desc = new Label(this,
_L("A new version of the Bambu Network Plug-in is available."));
desc->SetFont(::Label::Body_13);
desc->Wrap(FromDIP(400));
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15));
desc->Wrap(TEXT_WRAP);
desc->SetMaxSize(wxSize(TEXT_WRAP, -1));
main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(15));
auto* version_text = new wxStaticText(this, wxID_ANY,
auto* version_text = new Label(this,
wxString::Format(_L("Current version: %s"), wxString::FromUTF8(current_version)));
version_text->SetFont(::Label::Body_13);
main_sizer->Add(version_text, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
main_sizer->Add(version_text, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(15));
auto* update_label = new wxStaticText(this, wxID_ANY, _L("Update to version:"));
update_label->SetFont(::Label::Body_13);
main_sizer->Add(update_label, 0, wxLEFT | wxRIGHT, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
auto* update_label = new Label(this, _L("Update to version:"));
main_sizer->Add(update_label, 0, wxLEFT | wxRIGHT, BORDER_W);
main_sizer->AddSpacer(FromDIP(3));
setup_version_selector();
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, BORDER_W);
main_sizer->AddSpacer(20);
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
auto daa_sizer = new wxBoxSizer(wxHORIZONTAL);
auto cfg = wxGetApp().app_config;
StateColor btn_bg_green(
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
auto daa_chk = new CheckBox(this);
daa_chk->SetValue(cfg->is_network_update_prompt_disabled());
daa_chk->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e){
auto cfg = wxGetApp().app_config;
cfg->set_network_update_prompt_disabled(e.IsChecked());
cfg->save();
});
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
auto daa_str = new Label(this, _L("Don't Ask Again"));
auto on_toggle = [this, daa_chk]() {
daa_chk->SetValue(!daa_chk->GetValue());
wxCommandEvent evt(wxEVT_TOGGLEBUTTON, daa_chk->GetId());
evt.SetEventObject(daa_chk);
daa_chk->GetEventHandler()->ProcessEvent(evt);
};
daa_str->Bind(wxEVT_LEFT_DOWN, [on_toggle](wxMouseEvent& e) {if(!e.LeftDClick()) on_toggle();});
daa_str->Bind(wxEVT_LEFT_DCLICK, [on_toggle](wxMouseEvent& e) {on_toggle();});
auto* btn_download = new Button(this, _L("Update Now"));
btn_download->SetBackgroundColor(btn_bg_green);
btn_download->SetBorderColor(*wxWHITE);
btn_download->SetTextColor(*wxWHITE);
btn_download->SetFont(::Label::Body_12);
btn_download->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10));
daa_sizer->Add(daa_chk, 0, wxALIGN_CENTER_VERTICAL);
daa_sizer->Add(daa_str, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
auto* btn_remind = new Button(this, _L("Remind Later"));
btn_remind->SetBackgroundColor(btn_bg_white);
btn_remind->SetBorderColor(wxColour(38, 46, 48));
btn_remind->SetFont(::Label::Body_12);
btn_remind->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_remind->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_remind_later, this);
btn_sizer->Add(btn_remind, 0, wxRIGHT, FromDIP(10));
main_sizer->Add(daa_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, BORDER_W);
main_sizer->AddSpacer(10);
auto* btn_skip = new Button(this, _L("Skip Version"));
btn_skip->SetBackgroundColor(btn_bg_white);
btn_skip->SetBorderColor(wxColour(38, 46, 48));
btn_skip->SetFont(::Label::Body_12);
btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip_version, this);
btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10));
auto dlg_btns = new DialogButtons(this,
{"Update Now", "Remind Later", "Skip Version"},
_L("Update Now")
);
auto* btn_dont_ask = new Button(this, _L("Don't Ask Again"));
btn_dont_ask->SetBackgroundColor(btn_bg_white);
btn_dont_ask->SetBorderColor(wxColour(38, 46, 48));
btn_dont_ask->SetFont(::Label::Body_12);
btn_dont_ask->SetMinSize(wxSize(FromDIP(110), FromDIP(24)));
btn_dont_ask->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_dont_ask, this);
btn_sizer->Add(btn_dont_ask, 0, wxRIGHT, FromDIP(10));
dlg_btns->GetButtonFromIndex(0)->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this);
dlg_btns->GetButtonFromIndex(1)->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_remind_later, this);
dlg_btns->GetButtonFromIndex(2)->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip_version, this);
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
main_sizer->Add(dlg_btns, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, FromDIP(10));
}
wxString network_version_label(const NetworkLibraryVersionInfo& ver)
@@ -222,7 +206,6 @@ void NetworkPluginDownloadDialog::setup_version_selector()
{
m_version_combo = new ComboBox(this, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxSize(FromDIP(380), FromDIP(28)), 0, nullptr, wxCB_READONLY);
m_version_combo->SetFont(::Label::Body_13);
m_available_versions = get_all_available_versions();
for (const auto& ver : m_available_versions)
@@ -294,10 +277,10 @@ NetworkPluginRestartDialog::NetworkPluginRestartDialog(wxWindow* parent)
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(DIALOG_WIDTH, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
main_sizer->Add(m_line_top, 0, wxEXPAND, 0);
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
main_sizer->AddSpacer(BORDER_W);
auto* icon_sizer = new wxBoxSizer(wxHORIZONTAL);
auto* icon_bitmap = new wxStaticBitmap(this, wxID_ANY,
@@ -306,61 +289,39 @@ NetworkPluginRestartDialog::NetworkPluginRestartDialog(wxWindow* parent)
auto* text_sizer = new wxBoxSizer(wxVERTICAL);
auto* desc = new wxStaticText(this, wxID_ANY,
auto* desc = new Label(this,
_L("The Bambu Network Plug-in has been installed successfully."));
desc->SetFont(::Label::Body_14);
desc->Wrap(FromDIP(350));
desc->Wrap(TEXT_WRAP);
desc->SetMaxSize(wxSize(TEXT_WRAP, -1));
text_sizer->Add(desc, 0, wxTOP, FromDIP(10));
text_sizer->Add(0, 0, 0, wxTOP, FromDIP(10));
text_sizer->AddSpacer(FromDIP(10));
auto* restart_msg = new wxStaticText(this, wxID_ANY,
auto* restart_msg = new Label(this,
_L("A restart is required to load the new plug-in. Would you like to restart now?"));
restart_msg->SetFont(::Label::Body_13);
restart_msg->Wrap(FromDIP(350));
restart_msg->Wrap(TEXT_WRAP);
restart_msg->SetMaxSize(wxSize(TEXT_WRAP, -1));
text_sizer->Add(restart_msg, 0, wxBOTTOM, FromDIP(10));
icon_sizer->Add(text_sizer, 1, wxEXPAND | wxRIGHT, FromDIP(20));
main_sizer->Add(icon_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(15));
main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20));
icon_sizer->Add(text_sizer, 1, wxEXPAND | wxRIGHT, BORDER_W);
main_sizer->Add(icon_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, BORDER_W);
main_sizer->AddSpacer(15);
auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->Add(0, 0, 1, wxEXPAND, 0);
auto dlg_btns = new DialogButtons(this,
{"Restart Now", "Restart Later"},
_L("Restart Now") // Primary button
);
StateColor btn_bg_green(
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
auto* btn_restart = new Button(this, _L("Restart Now"));
btn_restart->SetBackgroundColor(btn_bg_green);
btn_restart->SetBorderColor(*wxWHITE);
btn_restart->SetTextColor(*wxWHITE);
btn_restart->SetFont(::Label::Body_12);
btn_restart->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_restart->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
dlg_btns->GetButtonFromIndex(0)->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
m_restart_now = true;
EndModal(wxID_OK);
});
btn_sizer->Add(btn_restart, 0, wxRIGHT, FromDIP(10));
auto* btn_later = new Button(this, _L("Restart Later"));
btn_later->SetBackgroundColor(btn_bg_white);
btn_later->SetBorderColor(wxColour(38, 46, 48));
btn_later->SetFont(::Label::Body_12);
btn_later->SetMinSize(wxSize(FromDIP(100), FromDIP(24)));
btn_later->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
dlg_btns->GetButtonFromIndex(1)->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
m_restart_now = false;
EndModal(wxID_CANCEL);
});
btn_sizer->Add(btn_later, 0, wxRIGHT, FromDIP(10));
main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
main_sizer->Add(dlg_btns, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, FromDIP(10));
SetSizer(main_sizer);
Layout();

View File

@@ -56,7 +56,6 @@ private:
Mode m_mode;
ComboBox* m_version_combo{nullptr};
wxCollapsiblePane* m_details_pane{nullptr};
std::string m_error_message;
std::string m_error_details;
std::vector<NetworkLibraryVersionInfo> m_available_versions;

View File

@@ -13513,7 +13513,7 @@ bool Plater::up_to_date(bool saved, bool backup)
!Slic3r::has_other_changes(backup));
}
void Plater::add_model(bool imperial_units, std::string fname)
bool Plater::add_model(bool imperial_units, std::string fname)
{
wxArrayString input_files;
@@ -13521,7 +13521,7 @@ void Plater::add_model(bool imperial_units, std::string fname)
if (fname.empty()) {
wxGetApp().import_model(this, input_files);
if (input_files.empty())
return;
return false;
for (const auto& file : input_files)
paths.emplace_back(into_path(file));
@@ -13565,7 +13565,8 @@ void Plater::add_model(bool imperial_units, std::string fname)
auto strategy = LoadStrategy::LoadModel;
if (imperial_units) strategy = strategy | LoadStrategy::ImperialUnits;
if (!load_files(paths, strategy, ask_multi).empty()) {
const bool loaded = !load_files(paths, strategy, ask_multi).empty();
if (loaded) {
if (get_project_name() == _L("Untitled") && paths.size() > 0) {
boost::filesystem::path full_path(paths[0].string());
@@ -13574,6 +13575,7 @@ void Plater::add_model(bool imperial_units, std::string fname)
wxGetApp().mainframe->update_title();
}
return loaded;
}
void Plater::calib_pa(const Calib_Params& params)
@@ -13860,7 +13862,8 @@ void Plater::cut_horizontal(size_t obj_idx, size_t instance_idx, double z, Model
}
void Plater::_calib_pa_tower(const Calib_Params& params) {
add_model(false, Slic3r::resources_dir() + "/calib/pressure_advance/tower_with_seam.drc");
if (!add_model(false, Slic3r::resources_dir() + "/calib/pressure_advance/tower_with_seam.drc"))
return;
auto& print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -14100,7 +14103,8 @@ void Plater::calib_temp(const Calib_Params& params) {
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
if (params.mode != CalibMode::Calib_Temp_Tower) return;
add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc");
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
return;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto start_temp = lround(params.start);
@@ -14176,7 +14180,8 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
return;
add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc");
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
@@ -14255,7 +14260,8 @@ void Plater::calib_retraction(const Calib_Params& params)
if (params.mode != CalibMode::Calib_Retraction_tower)
return;
add_model(false, Slic3r::resources_dir() + "/calib/retraction/retraction_tower.drc");
if (!add_model(false, Slic3r::resources_dir() + "/calib/retraction/retraction_tower.drc"))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
@@ -14314,7 +14320,8 @@ void Plater::calib_VFA(const Calib_Params& params)
if (params.mode != CalibMode::Calib_VFA_Tower)
return;
add_model(false, Slic3r::resources_dir() + "/calib/vfa/vfa.drc");
if (!add_model(false, Slic3r::resources_dir() + "/calib/vfa/vfa.drc"))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -14359,7 +14366,8 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
if (params.mode != CalibMode::Calib_Input_shaping_freq)
return;
add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc"));
if (!add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc")))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -14424,7 +14432,8 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
if (params.mode != CalibMode::Calib_Input_shaping_damp)
return;
add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc"));
if (!add_model(false, Slic3r::resources_dir() + (params.test_model < 1 ? "/calib/input_shaping/ringing_tower.drc" : "/calib/input_shaping/fast_tower_test.drc")))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
@@ -14491,7 +14500,8 @@ void Plater::Calib_Cornering(const Calib_Params& params)
const std::string cornering_model_path = params.test_model == 0
? "/calib/input_shaping/ringing_tower.drc"
: (params.test_model == 1 ? "/calib/input_shaping/fast_tower_test.drc" : "/calib/cornering/SCV-V2.drc");
add_model(false, Slic3r::resources_dir() + cornering_model_path);
if (!add_model(false, Slic3r::resources_dir() + cornering_model_path))
return;
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;

View File

@@ -328,7 +328,8 @@ public:
bool open_3mf_file(const fs::path &file_path);
int get_3mf_file_count(std::vector<fs::path> paths);
void add_file();
void add_model(bool imperial_units = false, std::string fname = "");
// Returns false when no object was added (e.g. the user cancelled the load dialog).
bool add_model(bool imperial_units = false, std::string fname = "");
void import_zip_archive();
void import_sl1_archive();
void extract_config_from_project();

View File

@@ -48,13 +48,14 @@ std::string plugin_defaults_user_script()
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
}
// Injected into every page at document start (before the plugin's own scripts).
// Defines window.orca as the only host surface the page may use. It references
// window.wx lazily (at call time) so it never races the backend's deferred
// registration of the "wx" message handler. Guarded against double-injection so
// it is harmless if also prepended.
// Injected into the top-level page at document start (before the plugin's own
// scripts). Defines window.orca as the only host surface the page may use. It
// references window.wx lazily (at call time) so it never races the backend's
// deferred registration of the "wx" message handler. Guarded against
// double-injection so it is harmless if also prepended.
constexpr char ORCA_BRIDGE_JS[] = R"JS(
(function () {
if (window.top !== window.self) return;
if (window.orca) return;
var handlers = [];
function send(kind, data) {

View File

@@ -300,7 +300,12 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
add_action("open_folder", "Show in folder", has_local);
add_action("reinstall_plugin", "Reinstall");
if (is_cloud) {
add_action("reinstall_plugin", "Reinstall");
} else {
add_action("reload_plugin", "Reload");
add_action("clear_cache_reload_plugin", "Delete cache and reload");
}
return available_actions;
}
@@ -739,11 +744,13 @@ void PluginsDialog::handle_plugin_menu_action(const std::string& plugin_key, con
unsubscribe_cloud_plugin(row_data);
} else if (action == "delete_mine_plugin") {
delete_mine_local_and_cloud_plugin(plugin_key);
} else if (action == "reload_plugin") {
reload_local_plugin(plugin_key, /*clear_cache=*/false);
} else if (action == "clear_cache_reload_plugin") {
reload_local_plugin(plugin_key, /*clear_cache=*/true);
} else if (action == "reinstall_plugin") {
if (row_data.is_cloud_plugin())
reinstall_cloud_plugin(row_data);
else
reinstall_local_plugin(plugin_key);
}
}
@@ -1123,7 +1130,7 @@ void PluginsDialog::unsubscribe_cloud_plugin(const PluginDescriptor& plugin)
_L("Unsubscribing plugin"), _L("Deleting local files and unsubscribing plugin..."));
}
void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
void PluginsDialog::reload_local_plugin(const std::string& plugin_key, bool clear_cache)
{
if (plugin_key.empty())
return;
@@ -1132,11 +1139,34 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
std::pair<bool, std::string> reload_result{false, ""};
try {
reload_result = run_with_dialog_wait(
[plugin_key, was_loaded]() -> std::pair<bool, std::string> {
[plugin_key, was_loaded, clear_cache]() -> std::pair<bool, std::string> {
PluginManager& manager = PluginManager::instance();
boost::filesystem::path cache_dir;
if (clear_cache) {
PluginDescriptor descriptor;
if (!manager.try_get_plugin_descriptor(plugin_key, descriptor))
return {false, "Plugin not found."};
boost::filesystem::path resolved_root;
std::string resolve_error;
if (!resolve_allowed_plugin_root(descriptor, {get_orca_plugins_dir()},
"Refusing to clear a plugin cache outside the local plugin directory.",
resolved_root, resolve_error))
return {false, resolve_error};
cache_dir = resolved_root / "__whl_extracted__";
}
if (!manager.unload_plugin(plugin_key))
return {false, "Failed to unload plugin."};
if (clear_cache) {
boost::system::error_code ec;
boost::filesystem::remove_all(cache_dir, ec);
if (ec)
return {false, "Failed to clear plugin cache: " + ec.message()};
}
manager.load_plugin(plugin_key, false);
std::string error;
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) || !manager.is_plugin_loaded(plugin_key))

View File

@@ -96,7 +96,7 @@ private:
void open_plugin_folder(const Slic3r::PluginDescriptor& plugin);
void delete_local_plugin(const Slic3r::PluginDescriptor& plugin);
void unsubscribe_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
void reinstall_local_plugin(const std::string& plugin_key);
void reload_local_plugin(const std::string& plugin_key, bool clear_cache);
void reinstall_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
void delete_mine_local_and_cloud_plugin(const std::string& plugin_key);

View File

@@ -3,6 +3,7 @@
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
#include "GLCanvas3D.hpp" // ORCA: for live preview refresh when toggling "Dim lower layers"
#include "MsgDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
@@ -1049,6 +1050,16 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
}
}
// ORCA: apply the preview dimming change immediately to the currently loaded preview (ported from preFlight)
else if (param == "preview_dim_previous_layers") {
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_dim_previous_layers(app_config->get_bool(param));
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
#ifdef __WXMSW__
if (param == "associate_3mf") {
@@ -1893,11 +1904,21 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_fps_overlay);
//// GRAPHICS > G-code Preview
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
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."),
"preview_dim_previous_layers"
);
g_sizer->Add(item_dim_previous_layers);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// ONLINE TAB
//// ONLINE TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Online"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));

View File

@@ -34,6 +34,7 @@
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
@@ -1795,9 +1796,19 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
// Keep this preset's "plugins" manifest in sync when a plugin picker changes, so full_config() and
// save_to_json() always find resolved "name;uuid;capability" references and rebuild it nowhere else.
// Also drop any plugin_config_overrides entries for a capability the change just stopped
// referencing (e.g. a plugin removed from slicing_pipeline_plugin), so a saved preset never
// carries configuration for a capability it no longer names. The Configure button is a separate
// field holding its own cached copy of that value, so it needs to be told explicitly, or it
// keeps showing the stale count until something else happens to refresh it.
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
opt_def && opt_def->is_plugin_backed())
opt_def && opt_def->is_plugin_backed()) {
m_config->update_plugin_manifest();
if (prune_stale_plugin_overrides(*m_config)) {
if (Field* overrides_field = get_field(PLUGIN_OVERRIDES_OPTION_KEY))
overrides_field->set_value(boost::any(m_config->opt_string(PLUGIN_OVERRIDES_OPTION_KEY)), false);
}
}
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
if (auto printer_tab = dynamic_cast<TabPrinter*>(this))

View File

@@ -807,7 +807,11 @@ wxString TroubleshootDialog::GetRAMinfo()
wxString TroubleshootDialog::GetGPUinfo()
{
auto gl_info = OpenGLManager::get_gl_info();
return gl_info.get_renderer()+ " GLSL:" + gl_info.get_glsl_version();
#if !SLIC3R_OPENGL_ES
return gl_info.get_renderer() + " GLSL:" + gl_info.get_glsl_version() + (gl_info.is_core_profile() ? " Core" : " Compatibility");
#else
return gl_info.get_renderer() + " GLSL:" + gl_info.get_glsl_version() + " ES";
#endif
}
wxString TroubleshootDialog::GetMONinfo()

View File

@@ -96,6 +96,9 @@ std::string WebViewHostDialog::document_start_injector(const std::string& markup
const std::string literal = nlohmann::json(markup).dump();
std::string s;
s += "(function(){";
// wxWebView's AddUserScript runs in child frames too (including cross-origin
// frames on WebView2). Host theme state belongs only to the top-level page.
s += "if(window.top!==window.self)return;";
s += prelude;
s += "var css=" + literal + ";";
s += "function inject(){";

View File

@@ -5,6 +5,7 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include <libslic3r/Config.hpp>
#include <libslic3r/PresetBundle.hpp>
#include <libslic3r/PrintConfig.hpp>
#include <slic3r/GUI/GUI.hpp>
@@ -164,6 +165,20 @@ bool CapabilityConfigDocument::erase(const PluginCapabilityId& id)
return erased;
}
bool CapabilityConfigDocument::prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced)
{
bool changed = false;
for (auto it = m_entries.begin(); it != m_entries.end();) {
if (referenced.count({it->first.type, it->first.name}) != 0) {
++it;
} else {
it = m_entries.erase(it);
changed = true;
}
}
return changed;
}
bool CapabilityConfigDocument::empty() const
{
return m_entries.empty() && m_opaque_entries.empty();
@@ -342,6 +357,50 @@ std::string serialize_plugin_overrides(const CapabilityConfigDocument& document)
return document.empty() ? std::string() : document.serialize_entries().dump();
}
bool prune_stale_plugin_overrides(DynamicConfig& config)
{
const auto* overrides_opt = dynamic_cast<const ConfigOptionString*>(config.option(PLUGIN_OVERRIDES_OPTION_KEY));
if (overrides_opt == nullptr || overrides_opt->value.empty())
return false;
CapabilityConfigDocument overrides;
std::string error;
if (!parse_plugin_overrides(overrides_opt->value, overrides, error)) {
// Malformed text is not ours to fix up here: leave it untouched rather than risk
// discarding data the user might still be able to recover.
BOOST_LOG_TRIVIAL(error) << "prune_stale_plugin_overrides: " << error;
return false;
}
// Capability names currently referenced by a plugin-backed option's value(s) — e.g.
// slicing_pipeline_plugin's ConfigOptionStrings entries name SlicingPipeline capabilities
// directly, the same raw values save_plugin_collection() resolves into the "plugins" manifest.
std::set<std::pair<PluginCapabilityType, std::string>> referenced;
const ConfigDef* def = config.def();
for (const std::string& opt_key : config.keys()) {
const ConfigOptionDef* opt_def = def != nullptr ? def->get(opt_key) : nullptr;
if (opt_def == nullptr || !opt_def->is_plugin_backed())
continue;
const ConfigOption* opt = config.option(opt_key);
const PluginCapabilityType type = plugin_capability_type_from_string(opt_def->plugin_type);
if (const auto* string_opt = dynamic_cast<const ConfigOptionString*>(opt)) {
if (!string_opt->value.empty())
referenced.emplace(type, string_opt->value);
} else if (const auto* vector_opt = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
for (const std::string& value : vector_opt->vserialize())
if (!value.empty())
referenced.emplace(type, value);
}
}
if (!overrides.prune_unreferenced(referenced))
return false;
config.set_key_value(PLUGIN_OVERRIDES_OPTION_KEY, new ConfigOptionString(serialize_plugin_overrides(overrides)));
return true;
}
EffectiveCapabilityConfig PresetPluginConfigService::get_effective_config(const CapabilityConfigDocument& overrides,
const PluginCapabilityId& id) const
{

View File

@@ -8,7 +8,9 @@
#include <map>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#define PLUGIN_CONFIG_DIR "config.json"
@@ -16,6 +18,7 @@
namespace Slic3r {
class Preset;
class DynamicConfig;
struct CapabilityConfigEntry
{
PluginCapabilityId id;
@@ -35,6 +38,9 @@ public:
bool contains(const PluginCapabilityId& id) const;
bool upsert(CapabilityConfigEntry entry);
bool erase(const PluginCapabilityId& id);
// Drops every entry whose (type, name) is not in `referenced`, e.g. capabilities a preset's
// plugin-backed options no longer name. Returns true if anything was removed.
bool prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced);
bool empty() const;
nlohmann::json serialize_entries() const;
nlohmann::json root_json() const;
@@ -50,6 +56,14 @@ std::string plugin_overrides_of(const Preset& preset);
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
// Drops plugin_config_overrides entries for capabilities no longer named by any plugin-backed
// option's current value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a
// different capability), and writes the result back if anything changed. Called wherever a
// plugin-backed option's value changes, so a saved preset never carries configuration for a
// capability it no longer references. Returns true if `config` was modified, so a caller holding a
// GUI field over PLUGIN_OVERRIDES_OPTION_KEY knows it must refresh that field's displayed value.
bool prune_stale_plugin_overrides(DynamicConfig& config);
struct EffectiveCapabilityConfig
{
PluginCapabilityId id;

View File

@@ -54,7 +54,7 @@ struct PluginDescriptor
std::string description; // Plugin description
std::string author; // Plugin author from manifest, if available
std::string version; // Selected plugin version
std::string latest_version; // Latest available cloud version fallback when changelog is unavailable.
std::string latest_version; // Authoritative latest available cloud version.
std::string installed_version; // Locally installed package version. Preserved across cloud merges, which overwrite `version` with the latest cloud version. Empty when not installed.
std::vector<std::string> display_types; // Display-only "compatibility" labels (cloud: raw service labels; local: from real capabilities). Never used for dispatch.
std::string plugin_root; // Installed plugin directory, even when entry_path is invalid or ambiguous.
@@ -113,10 +113,6 @@ struct PluginDescriptor
bool is_unauthorized() const { return get_update_status() == PluginUpdateStatus::Unauthorized; }
std::string latest_available_version() const
{
for (const PluginChangelog& entry : changelog) {
if (!entry.version.empty())
return entry.version;
}
if (!latest_version.empty())
return latest_version;
return version;

View File

@@ -52,6 +52,29 @@ print('ok')
} // namespace
TEST_CASE("plugin latest version uses the authoritative catalog field", "[PluginDescriptor]")
{
PluginDescriptor descriptor;
descriptor.version = "1.3.0";
descriptor.latest_version = "1.3.0";
PluginChangelog changelog;
changelog.version = "1.2.0";
descriptor.changelog.push_back(changelog);
CHECK(descriptor.latest_available_version() == "1.3.0");
}
TEST_CASE("plugin latest version falls back to the descriptor version", "[PluginDescriptor]")
{
PluginDescriptor descriptor;
descriptor.version = "1.1.0";
PluginChangelog changelog;
changelog.version = "1.0.0";
descriptor.changelog.push_back(changelog);
CHECK(descriptor.latest_available_version() == "1.1.0");
}
// Regression: update_cloud_metadata() replaces a matched entry's descriptor wholesale with the
// cloud catalog record (`entry = cloud_entry`). Configuration used to ride on the descriptor, so
// that overwrite silently wiped it and plugins fell back to their built-in defaults (found via