diff --git a/resources/web/dialog/PresetBundleDialog/index.js b/resources/web/dialog/PresetBundleDialog/index.js index b370fe8018..4ff3167d07 100644 --- a/resources/web/dialog/PresetBundleDialog/index.js +++ b/resources/web/dialog/PresetBundleDialog/index.js @@ -2,7 +2,7 @@ const bundlesById = new Map(); // bundleId -> bundle object const printersByBundle = new Map(); // bundleId -> Map(index -> printerName) const filamentsByBundle = new Map(); // bundleId -> Map(index -> filamentName) -const presetsByBundle = new Map(); // bundleId -> Map(index -> presetName) +const processesByBundle = new Map(); // bundleId -> Map(index -> presetName) const UPDATE_TOOLTIP = "Update available"; const UNAUTHORIZED_TOOLTIP = "Unauthorized bundle"; @@ -193,7 +193,7 @@ function unpackPayload(payload) { bundlesById.clear(); printersByBundle.clear(); filamentsByBundle.clear(); - presetsByBundle.clear(); + processesByBundle.clear(); const list = payload?.data || []; for (const bundle of list) { @@ -212,7 +212,7 @@ function unpackPayload(payload) { printersByBundle.set(id, new Map((bundle.printers || []).map((name, i) => [i, name]))); filamentsByBundle.set(id, new Map((bundle.filaments || []).map((name, i) => [i, name]))); - presetsByBundle.set(id, new Map((bundle.presets || []).map((name, i) => [i, name]))); + processesByBundle.set(id, new Map((bundle.processes || []).map((name, i) => [i, name]))); } } @@ -277,14 +277,14 @@ function renderBottomForBundle(bundleId) { const key = String(bundleId || ""); const printers = printersByBundle.get(key) || new Map(); const filaments = filamentsByBundle.get(key) || new Map(); - const presets = presetsByBundle.get(key) || new Map(); + const processes = processesByBundle.get(key) || new Map(); // Convert to a flat list of rows { typeLabel, name } const rows = []; for (const [, name] of printers) rows.push({ type: "Printer", name }); for (const [, name] of filaments) rows.push({ type: "Filament", name }); - for (const [, name] of presets) rows.push({ type: "Preset", name }); + for (const [, name] of processes) rows.push({ type: "Process", name }); bottomList.innerHTML = rows.map((r, idx) => `
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ba9120d3bd..80b9cb47ee 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -781,6 +781,7 @@ static std::vector get_path_of_change_filament(const Print& print) if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); if (!gcodegen.m_filament_instances_code.empty()) { end_filament_gcode_str += ("M624 " + gcodegen.m_filament_instances_code + "\n"); gcodegen.m_filament_instances_code = ""; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index edcf60cdb6..c0c79be466 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -744,6 +744,13 @@ std::string CoolingBuffer::apply_layer_cooldown( int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); // Is the fan speed ramp enabled? int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + // ORCA: explicit per-filament first-layer override (-1 = disabled, 0-100 = forced PWM percent on layer 0). + // The override is only honoured when the "No cooling for the first" gate is 0; otherwise the gate would + // force layers 1..N-1 to zero while the override sets layer 0 to a non-zero value, which is confusing + // and non-monotonic. The UI greys out and resets the value in that case, but we also guard here so + // legacy profiles loaded with both set are neutralised at the slicer level. + int initial_layer_fan_speed = EXTRUDER_CONFIG(initial_layer_fan_speed); + const bool has_initial_layer_override = initial_layer_fan_speed >= 0 && close_fan_the_first_x_layers <= 0; supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed); // ORCA: previously a silent override forced `close_fan_the_first_x_layers` from 0 up to 1 whenever a ramp @@ -751,7 +758,24 @@ std::string CoolingBuffer::apply_layer_cooldown( // That hid the user's literal "no cooling for the first 0 layers" setting and produced a non-zero starting // factor on the ramp denominator. The override has been removed: with N=0 and M>0 the ramp now genuinely // starts on layer 0 at a factor of 1/M and reaches 100% at layer M-1, matching the intent of the option. - if (int(layer_id) >= close_fan_the_first_x_layers) { + // + // ORCA: First-layer hard override (`initial_layer_fan_speed`). When the user has set this option to a + // value >= 0, layer 0 emits exactly that percentage so the entire first layer + // is at one stable fan speed. The override wins over the `close_fan_the_first_x_layers` gate when + // layer_id == 0. From layer 1 onwards the regular logic resumes. + if (has_initial_layer_override && layer_id == 0) { + fan_speed_new = initial_layer_fan_speed; + overhang_fan_speed = initial_layer_fan_speed; + overhang_fan_control = false; + internal_bridge_fan_speed = initial_layer_fan_speed; + internal_bridge_fan_control = false; + supp_interface_fan_speed = initial_layer_fan_speed; + supp_interface_fan_control = false; + ironing_fan_speed = initial_layer_fan_speed; + ironing_fan_control = false; + // additional_fan_speed_new is left at its configured value (auxiliary fan is independent of the + // part-cooling override). + } else if (int(layer_id) >= close_fan_the_first_x_layers) { float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); @@ -769,10 +793,25 @@ std::string CoolingBuffer::apply_layer_cooldown( //} overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { - // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. - float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); - fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); - overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); + if (has_initial_layer_override && close_fan_the_first_x_layers == 0 && full_fan_speed_layer > 1) { + // ORCA: Option-B anchored ramp. When the first-layer override is configured and there's + // no "no cooling" gate, the ramp interpolates linearly from `initial_layer_fan_speed` on + // layer 0 up to the computed target on layer `full_fan_speed_layer - 1` instead of + // scaling the target by `factor` from zero. Layer 0 itself is handled by the override + // branch above; this branch only runs for layer_id >= 1, but the formula uses t = 0 at + // layer 0 conceptually so the curve is continuous. Guarantees a monotonic transition + // even when the override is larger than the natural 1/M starting value. + const float anchor = float(initial_layer_fan_speed); + const float denom = float(full_fan_speed_layer - 1); + const float t = float(int(layer_id)) / denom; + fan_speed_new = std::clamp(int(anchor + t * (float(fan_speed_new) - anchor) + 0.5f), 0, 255); + overhang_fan_speed = std::clamp(int(anchor + t * (float(overhang_fan_speed) - anchor) + 0.5f), 0, 255); + } else { + // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. + float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); + fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); + overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); + } } supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed); supp_interface_fan_control = supp_interface_fan_speed >= 0; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 38509bfa6a..0ac8562a38 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1333,7 +1333,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", // "bed_type", //BBS:temperature_vitrification "temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed", - "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", + "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "initial_layer_fan_speed", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", "filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode", //exhaust fan control "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 90866bf2b7..a676d24cc0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -134,6 +134,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "dont_slow_down_outer_wall", "fan_cooling_layer_time", "full_fan_speed_layer", + "initial_layer_fan_speed", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_overhangs", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c6758e58f1..0369c70ee3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3348,7 +3348,25 @@ void PrintConfigDef::init_fff_params() def->max = 1000; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInts { 0 }); - + + // ORCA: explicit override for the part cooling fan speed on the first printed layer. + def = this->add("initial_layer_fan_speed", coInts); + def->label = L("First layer fan speed"); + def->tooltip = L("Sets an exact fan speed for the first layer, overriding all other cooling settings. " + "Useful for protecting 3D-printed toolhead parts (e.g. Voron-style ABS/ASA ducts) from " + "a hot bed. A small amount of airflow cools the ducts down, without using full cooling that " + "may in certain conditions hurt first-layer adhesion." + "\nFrom the second layer onwards, normal cooling resumes." + "\nIf \"Full fan speed at layer\" is also set, the fan ramps smoothly from this value " + "on the first layer up to your target by the chosen layer." + "\nOnly available when \"No cooling for the first\" is 0." + "\nSet to -1 to disable it."); + def->sidetext = "%"; + def->min = -1; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInts { -1 }); + def = this->add("support_material_interface_fan_speed", coInts); def->label = L("Support interface fan speed"); def->tooltip = L("This part cooling fan speed is applied when printing support interfaces. Setting this parameter to a higher than regular speed " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index bc3cc4af8c..b51eef8aa3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1555,6 +1555,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloatsNullable, initial_layer_infill_speed)) ((ConfigOptionInts, nozzle_temperature_initial_layer)) ((ConfigOptionInts, full_fan_speed_layer)) + // ORCA: explicit override for the part cooling fan speed on the first printed layer. + ((ConfigOptionInts, initial_layer_fan_speed)) ((ConfigOptionFloats, fan_max_speed)) ((ConfigOptionFloats, max_layer_height)) ((ConfigOptionFloats, fan_min_speed)) diff --git a/src/libslic3r/calib.cpp b/src/libslic3r/calib.cpp index 8ac3240e42..9f33b4459a 100644 --- a/src/libslic3r/calib.cpp +++ b/src/libslic3r/calib.cpp @@ -756,6 +756,16 @@ Vec3d CalibPressureAdvancePattern::get_start_offset() return m_starting_point; } +double CalibPressureAdvancePattern::line_width_first_layer() const +{ + // TODO: FIXME: find out current filament/extruder? + const double nozzle_diameter = m_config.opt_float("nozzle_diameter", m_params.extruder_id); + const double width = m_config.get_abs_value("initial_layer_line_width", nozzle_diameter); + if (width <= 0.) + return Flow::auto_extrusion_width(frExternalPerimeter, nozzle_diameter); + return width; +}; + double CalibPressureAdvancePattern::line_width() const { // TODO: FIXME: find out current filament/extruder? diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp index ea535bcf95..f69ad3e1a3 100644 --- a/src/libslic3r/calib.hpp +++ b/src/libslic3r/calib.hpp @@ -315,12 +315,7 @@ protected: double speed_first_layer() const { return m_config.get_abs_value_at("initial_layer_speed", m_params.extruder_id); }; double speed_perimeter() const { return m_config.get_abs_value_at("outer_wall_speed", m_params.extruder_id); }; double accel_perimeter() const { return m_config.get_abs_value_at("outer_wall_acceleration", m_params.extruder_id); } - double line_width_first_layer() const - { - // TODO: FIXME: find out current filament/extruder? - const double nozzle_diameter = m_config.opt_float("nozzle_diameter", m_params.extruder_id); - return m_config.get_abs_value("initial_layer_line_width", nozzle_diameter); - }; + double line_width_first_layer() const; double line_width() const; int wall_count() const { return m_config.option("wall_loops")->value; }; diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index 48d92a4d58..1fc07bf5a2 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -1393,8 +1393,10 @@ wxString ObjectGridTable::GetValue (int row, int col) else if (grid_col->type == coInt) { ConfigOptionInt& option_value = dynamic_cast(option); return wxString::Format("%d", option_value.value); - } - else if (grid_col->type == coFloat) { + } else if (grid_col->type == coFloat) { + if (auto option_values = dynamic_cast(&option)) { + return wxString::Format("%.2f", option_values->get_at(0)); + } ConfigOptionFloat& option_value = dynamic_cast(option); return wxString::Format("%.2f", option_value.value); } @@ -1592,16 +1594,25 @@ void ObjectGridTable::SetValue( int row, int col, const wxString& value ) else { update_value_to_object(m_panel->m_model, grid_row, col); } - } - else if (grid_col->type == coFloat) { - ConfigOptionFloat &option_value = dynamic_cast((*grid_row)[(GridColType)col]); - ConfigOptionFloat &option_ori_value = dynamic_cast((*grid_row)[(GridColType)(col+1)]); + } else if (grid_col->type == coFloat) { + if (auto option_values = dynamic_cast(&(*grid_row)[(GridColType) col])) { + ConfigOptionFloatsNullable& option_ori_values = dynamic_cast((*grid_row)[(GridColType) (col + 1)]); - double double_value; - value.ToDouble(&double_value); - option_value.value = (float)double_value; + double double_value; + value.ToDouble(&double_value); + option_values->values.at(0) = (float) double_value; - update_value_to_config(grid_row->config, grid_col->key, option_value, option_ori_value); + update_value_to_config(grid_row->config, grid_col->key, *option_values, option_ori_values); + } else { + ConfigOptionFloat& option_value = dynamic_cast((*grid_row)[(GridColType) col]); + ConfigOptionFloat& option_ori_value = dynamic_cast((*grid_row)[(GridColType) (col + 1)]); + + double double_value; + value.ToDouble(&double_value); + option_value.value = (float) double_value; + + update_value_to_config(grid_row->config, grid_col->key, option_value, option_ori_value); + } } else if (grid_col->type == coInt) { ConfigOptionInt &option_value = dynamic_cast((*grid_row)[(GridColType)col]); @@ -1676,8 +1687,11 @@ double ObjectGridTable::GetValueAsDouble( int row, int col ) return 0; ObjectGridRow* grid_row = m_grid_data[row - 1]; - ConfigOptionFloat &option_value = dynamic_cast((*grid_row)[(GridColType)col]); - return (double )option_value.getFloat(); + if (auto option_values = dynamic_cast(&(*grid_row)[(GridColType) col])) { + return (double) option_values->get_at(0); + } + ConfigOptionFloat& option_value = dynamic_cast((*grid_row)[(GridColType) col]); + return (double) option_value.getFloat(); } void ObjectGridTable::SetValueAsLong( int row, int col, long value ) @@ -1722,6 +1736,17 @@ void ObjectGridTable::SetValueAsDouble(int row, int col, double value) if ((value > 100.f) || (value < 0.f)) return; } + + if (auto option_values = dynamic_cast(&(*grid_row)[(GridColType) col])) { + ConfigOptionFloatsNullable &option_ori_values = dynamic_cast((*grid_row)[(GridColType) (col + 1)]); + + option_values->values.at(0) = (float) value; + + update_value_to_config(grid_row->config, grid_col->key, *option_values, option_ori_values); + + return; + } + ConfigOptionFloat &option_value = dynamic_cast((*grid_row)[(GridColType)col]); ConfigOptionFloat &option_ori_value = dynamic_cast((*grid_row)[(GridColType)(col+1)]); @@ -1981,8 +2006,8 @@ void ObjectGridTable::construct_object_configs(ObjectGrid *object_grid) object_grid->ori_enable_support = *(global_config.option(m_col_data[col_enable_support]->key)); object_grid->brim_type = *(get_object_config_value>(global_config, object_grid->config, m_col_data[col_brim_type]->key)); object_grid->ori_brim_type = *(global_config.option>(m_col_data[col_brim_type]->key)); - object_grid->speed_perimeter = *(get_object_config_value(global_config, object_grid->config, m_col_data[col_speed_perimeter]->key)); - object_grid->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); + object_grid->speed_perimeter = *(get_object_config_value(global_config, object_grid->config, m_col_data[col_speed_perimeter]->key)); + object_grid->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); m_grid_data.push_back(object_grid); int volume_count = object->volumes.size(); @@ -2031,7 +2056,7 @@ void ObjectGridTable::construct_object_configs(ObjectGrid *object_grid) volume_grid->ori_enable_support = object_grid->enable_support; volume_grid->brim_type = *(get_volume_config_value>(global_config, object_grid->config, volume_grid->config, m_col_data[col_brim_type]->key)); volume_grid->ori_brim_type = object_grid->brim_type; - volume_grid->speed_perimeter = *(get_volume_config_value(global_config, object_grid->config, volume_grid->config, m_col_data[col_speed_perimeter]->key)); + volume_grid->speed_perimeter = *(get_volume_config_value(global_config, object_grid->config, volume_grid->config, m_col_data[col_speed_perimeter]->key)); volume_grid->ori_speed_perimeter = object_grid->speed_perimeter; m_grid_data.push_back(volume_grid); } @@ -2077,8 +2102,8 @@ void ObjectGridTable::reload_object_data(ObjectGridRow* grid_row, const std::str grid_row->ori_enable_support = *(global_config.option(m_col_data[col_enable_support]->key)); grid_row->brim_type = *(get_object_config_value>(global_config, grid_row->config, m_col_data[col_brim_type]->key)); grid_row->ori_brim_type = *(global_config.option>(m_col_data[col_brim_type]->key)); - grid_row->speed_perimeter = *(get_object_config_value(global_config, grid_row->config, m_col_data[col_speed_perimeter]->key)); - grid_row->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); + grid_row->speed_perimeter = *(get_object_config_value(global_config, grid_row->config, m_col_data[col_speed_perimeter]->key)); + grid_row->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); } else if (category == L("Quality")) { grid_row->layer_height = *(get_object_config_value(global_config, grid_row->config, m_col_data[col_layer_height]->key)); @@ -2099,8 +2124,8 @@ void ObjectGridTable::reload_object_data(ObjectGridRow* grid_row, const std::str grid_row->ori_brim_type = *(global_config.option>(m_col_data[col_brim_type]->key)); } else if (category == L("Speed")) { - grid_row->speed_perimeter = *(get_object_config_value(global_config, grid_row->config, m_col_data[col_speed_perimeter]->key)); - grid_row->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); + grid_row->speed_perimeter = *(get_object_config_value(global_config, grid_row->config, m_col_data[col_speed_perimeter]->key)); + grid_row->ori_speed_perimeter = *(global_config.option(m_col_data[col_speed_perimeter]->key)); } } @@ -2117,7 +2142,7 @@ void ObjectGridTable::reload_part_data(ObjectGridRow* volume_row, ObjectGridRow* volume_row->ori_enable_support = object_row->enable_support; volume_row->brim_type = *(get_volume_config_value>(global_config, object_row->config, volume_row->config, m_col_data[col_brim_type]->key)); volume_row->ori_brim_type = object_row->brim_type; - volume_row->speed_perimeter = *(get_volume_config_value(global_config, object_row->config, volume_row->config, m_col_data[col_speed_perimeter]->key)); + volume_row->speed_perimeter = *(get_volume_config_value(global_config, object_row->config, volume_row->config, m_col_data[col_speed_perimeter]->key)); volume_row->ori_speed_perimeter = object_row->speed_perimeter; } else if (category == L("Quality")) { @@ -2154,7 +2179,7 @@ void ObjectGridTable::reload_part_data(ObjectGridRow* volume_row, ObjectGridRow* volume_row->ori_brim_type = object_row->brim_type; } else if (category == L("Speed")) { - volume_row->speed_perimeter = *(get_volume_config_value(global_config, object_row->config, volume_row->config, m_col_data[col_speed_perimeter]->key)); + volume_row->speed_perimeter = *(get_volume_config_value(global_config, object_row->config, volume_row->config, m_col_data[col_speed_perimeter]->key)); if (volume_row->speed_perimeter == object_row->speed_perimeter) { volume_row->config->erase(m_col_data[col_speed_perimeter]->key); } @@ -2549,6 +2574,8 @@ bool ObjectGridTable::OnCellLeftClick(int row, int col, ConfigOptionType &type) void ObjectGridTable::OnSelectCell(int row, int col) { m_selected_cells.clear(); + if (!m_panel->m_side_window) + return; m_panel->m_side_window->Freeze(); if (row == 0 || col == col_filaments) { m_panel->m_object_settings->UpdateAndShow(row, false, false, false, nullptr, nullptr, std::string()); diff --git a/src/slic3r/GUI/GUI_ObjectTable.hpp b/src/slic3r/GUI/GUI_ObjectTable.hpp index a21436fe0e..a5a0059e1b 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.hpp +++ b/src/slic3r/GUI/GUI_ObjectTable.hpp @@ -344,8 +344,8 @@ public: ConfigOptionBool ori_enable_support; ConfigOptionEnum brim_type; ConfigOptionEnum ori_brim_type; - ConfigOptionFloat speed_perimeter; - ConfigOptionFloat ori_speed_perimeter; + ConfigOptionFloatsNullable speed_perimeter; + ConfigOptionFloatsNullable ori_speed_perimeter; ModelConfig* config; ModelVolumeType model_volume_type; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 7006902a02..f3e54adf45 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -38,6 +38,207 @@ public: bool ShouldScrollToChildOnFocus(wxWindow* child) override { return false; } }; +// TODO before replacing with HyperLink class +// make Wrap(-1) and Wrap(width) functional +// ellipsize_end on wrap(-1) +// add SetUnderlined() for allowing always highlighted while using as HyperLink +class WikiLabel : public wxPanel { +private: + wxString m_label; + wxString m_url; + wxArrayString m_lines; + bool m_hovered = false; + wxFont m_font; + int m_last_wrap_width = -1; + +public: + WikiLabel( + wxWindow* parent, + const wxString& label, + const wxString& url = wxEmptyString, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize + ) + : wxPanel(parent, wxID_ANY, pos, size, wxFULL_REPAINT_ON_RESIZE) + , m_label(label) + , m_url(url) + { +#ifndef __WXOSX__ + SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX +#endif + SetBackgroundColour(parent->GetBackgroundColour()); + + SetFont(Label::Body_14); + + Bind(wxEVT_PAINT, &WikiLabel::OnPaint, this); + Bind(wxEVT_SIZE, &WikiLabel::OnSize, this); + Bind(wxEVT_MOTION, &WikiLabel::OnMotion, this); + Bind(wxEVT_LEAVE_WINDOW, &WikiLabel::OnLeaveWin, this); + Bind(wxEVT_LEFT_DOWN, &WikiLabel::OnLeftDown, this); + } + + void SetLabel(const wxString& label) + { + m_label = label; + m_last_wrap_width = -1; // force re-wrap + ReflowText(); + Refresh(); + } + + bool SetFont(const wxFont& font) override + { + const bool changed = wxPanel::SetFont(font); + m_font = font; + m_last_wrap_width = -1; // force re-wrap + if (IsShownOnScreen()) { + ReflowText(); + Refresh(); + } + return changed; + } + + wxString GetLabel() const override { return m_label; } + void SetURL(const wxString& url) { m_url = url; } + wxString GetURL() const { return m_url; } + + void ReflowText() + { + const int clientW = GetClientSize().GetWidth(); + + if (clientW <= 0 || (clientW == m_last_wrap_width && !m_lines.IsEmpty())) + return; + + m_last_wrap_width = clientW; + + wxArrayString lines; + for (const wxString& para : wxSplit(m_label, '\n')) { + if (para.IsEmpty()) + lines.Add(wxEmptyString); + else { + wxString currentLine; + for (const wxString& word : wxSplit(para, ' ')) { + wxString candidate = currentLine.IsEmpty() ? word : (currentLine + ' ' + word); + + if (GetTextExtent(candidate).GetWidth() <= clientW) + currentLine = candidate; + else { + if (currentLine.IsEmpty()) + lines.Add(word); // single word wider than column + else { + lines.Add(currentLine); + currentLine = word; + } + } + } + if (!currentLine.IsEmpty()) + lines.Add(currentLine); + } + } + m_lines = lines; + + const int lineH = wxMax(1, wxWindow::GetCharHeight()); // GTK can return 0 from GetCharHeight() before the window is realized + const int nLines = m_lines.IsEmpty() ? 1 : static_cast(m_lines.size()); + const int totalH = static_cast(nLines * lineH * 1.3); + + SetMinSize(wxSize(-1, totalH)); + InvalidateBestSize(); + } + + wxSize DoGetBestSize() const override + { + const int lineH = wxMax(1, wxWindow::GetCharHeight()); // GTK can return 0 from GetCharHeight() before the window is realized + const int nLines = m_lines.IsEmpty() ? 1 : static_cast(m_lines.size()); + const int totalH = static_cast(nLines * lineH * 1.3); + + const int clientW = GetClientSize().GetWidth(); + + if (clientW > 0) + return wxSize(clientW, totalH); + + if (m_label.IsEmpty()) + return wxSize(1, lineH); + + int maxW = 0; + for (const wxString& line : wxSplit(m_label, '\n')) + maxW = wxMax(maxW, GetTextExtent(line).GetWidth()); + + return wxSize(wxMax(1, maxW), totalH); + } + + void Rescale() + { + m_last_wrap_width = -1; // force re-wrap + m_lines.Clear(); + InvalidateBestSize(); + } + +private: + void OnPaint(wxPaintEvent& evt) + { + wxPaintDC dc(this); + + dc.SetBackground(wxBrush(GetParent() ? GetParent()->GetBackgroundColour() : *wxWHITE)); + dc.Clear(); + + wxColour textCol = StateColor::darkModeColorFor(m_hovered ? "#26A69A" : "#363636"); + + dc.SetTextForeground(textCol); + dc.SetFont(m_font); + dc.SetBackgroundMode(wxTRANSPARENT); + + int lineH = dc.GetCharHeight(); + int y = lround(lineH * 0.15); + + for (const wxString& line : m_lines) { + if (!line.IsEmpty()) { + dc.DrawText(line, 0, y); + + if (m_hovered) { + int tw, th; + dc.GetTextExtent(line, &tw, &th); + + int underlineY = y + lineH - 1; // 1 px below the baseline + dc.SetPen(wxPen(textCol, 1)); + dc.DrawLine(0, underlineY, tw, underlineY); + } + } + y += lineH; + } + } + + void OnSize(wxSizeEvent& evt) + { + ReflowText(); + Refresh(); + evt.Skip(); + } + + void OnMotion(wxMouseEvent& evt) + { + if(!m_url.IsEmpty() && !m_hovered){ + m_hovered = true; + Refresh(); + } + evt.Skip(); + } + + void OnLeaveWin(wxMouseEvent& evt) + { + if(!m_url.IsEmpty() && m_hovered){ + m_hovered = false; + Refresh(); + } + evt.Skip(); + } + + void OnLeftDown(wxMouseEvent& evt) + { + if (!m_url.IsEmpty()) + wxLaunchDefaultBrowser(m_url); + evt.Skip(); + } +}; + wxBoxSizer *PreferencesDialog::create_item_title(wxString title) { wxBoxSizer *m_sizer_title = new wxBoxSizer(wxHORIZONTAL); @@ -52,24 +253,34 @@ wxBoxSizer *PreferencesDialog::create_item_title(wxString title) return m_sizer_title; } -std::tuple PreferencesDialog::create_item_combobox_base(wxString title, wxString tooltip, std::string param, std::vector vlist, unsigned int current_index) +wxBoxSizer *PreferencesDialog::create_item_label(wxString label, wxString tooltip, wxString wiki_url) { - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + wxString url; + if(!wiki_url.IsEmpty()) + url = "https://www.orcaslicer.com/wiki/" + wiki_url; + + auto label_ctrl = new WikiLabel(m_parent, label, url, wxDefaultPosition, DESIGN_TITLE_SIZE); + + label_ctrl->SetToolTip(tooltip); + + sizer->Add(label_ctrl, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); + sizer->AddSpacer(FromDIP(5)); + + return sizer; +} + +std::tuple PreferencesDialog::create_item_combobox_base(wxString title, wxString tooltip, std::string param, std::vector vlist, unsigned int current_index, const wxString wiki_url) +{ auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - combo_title->SetToolTip(tip); - combo_title->Wrap(DESIGN_TITLE_SIZE.x); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER); + wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url); auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox->SetFont(::Label::Body_14); - combobox->GetDropDown().SetFont(::Label::Body_14); combobox->GetDropDown().SetUseContentWidth(true); + combobox->SetToolTip(tip); std::vector::iterator iter; for (iter = vlist.begin(); iter != vlist.end(); iter++) { @@ -78,12 +289,12 @@ std::tuple PreferencesDialog::create_item_combobox_base( combobox->SetSelection(current_index); - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + m_sizer->Add(combobox, 0, wxALIGN_CENTER); - return {m_sizer_combox, combobox}; + return {m_sizer, combobox}; } -wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::function onchange) +wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::function onchange, const wxString wiki_url) { unsigned int current_index = 0; @@ -92,7 +303,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too current_index = atoi(current_setting.c_str()); } - 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, wiki_url); //// save config combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, onchange](wxCommandEvent& e) { @@ -105,7 +316,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too return sizer; } -wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index) +wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index, const wxString wiki_url) { assert(vlist.size() == config_name_index.size()); unsigned int current_index = 0; @@ -177,23 +388,14 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS auto vlist = language_infos; auto param = "language"; - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - combo_title->SetToolTip(tip); - combo_title->Wrap(DESIGN_TITLE_SIZE.x); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER); - + wxBoxSizer *m_sizer = create_item_label(title, tip); auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox->SetFont(::Label::Body_14); - combobox->GetDropDown().SetFont(::Label::Body_14); combobox->GetDropDown().SetUseContentWidth(true); + combobox->SetToolTip(tip); + auto language = app_config->get(param); m_current_language_selected = -1; std::vector::iterator iter; @@ -280,7 +482,7 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS } combobox->SetSelection(m_current_language_selected); - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + m_sizer->Add(combobox, 0, wxALIGN_CENTER); combobox->Bind(wxEVT_LEFT_DOWN, [this, combobox](wxMouseEvent &e) { m_current_language_selected = combobox->GetSelection(); @@ -335,7 +537,7 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS e.Skip(); }); - return m_sizer_combox; + return m_sizer; } wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxString tooltip) @@ -346,23 +548,15 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxStr auto vlist = Regions; - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - combo_title->SetToolTip(tip); - combo_title->Wrap(DESIGN_TITLE_SIZE.x); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER); + wxBoxSizer *m_sizer = create_item_label(title, tip); auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox->SetFont(::Label::Body_14); - combobox->GetDropDown().SetFont(::Label::Body_14); combobox->GetDropDown().SetUseContentWidth(true); - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + combobox->SetToolTip(tip); + + m_sizer->Add(combobox, 0, wxALIGN_CENTER); std::vector::iterator iter; for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); } @@ -423,25 +617,18 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxStr e.Skip(); }); - return m_sizer_combox; + return m_sizer; } wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector vlist) { - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(DESIGN_TITLE_SIZE.x); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER); + wxBoxSizer *m_sizer = create_item_label(title, tip); auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox->SetFont(::Label::Body_14); - combobox->GetDropDown().SetFont(::Label::Body_14); combobox->GetDropDown().SetUseContentWidth(true); + combobox->SetToolTip(tip); std::vector::iterator iter; for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); } @@ -449,7 +636,7 @@ wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxS auto severity_level = app_config->get("log_severity_level"); if (!severity_level.empty()) { combobox->SetValue(severity_level); } - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + m_sizer->Add(combobox, 0, wxALIGN_CENTER); //// save config combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { @@ -458,93 +645,31 @@ wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxS app_config->set("log_severity_level",level); e.Skip(); }); - return m_sizer_combox; + return m_sizer; } -wxBoxSizer *PreferencesDialog::create_item_multiple_combobox( - wxString title, wxString tooltip, std::string param, std::vector vlista, std::vector vlistb) +wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange, const wxString wiki_url) { - std::vector params; - Split(app_config->get(param), "/", params); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - std::vector::iterator iter; - - wxBoxSizer *m_sizer_tcombox= new wxBoxSizer(wxHORIZONTAL); - m_sizer_tcombox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(DESIGN_TITLE_SIZE.x); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - m_sizer_tcombox->Add(combo_title, 0, wxALIGN_CENTER); - - auto combobox_left = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox_left->SetFont(::Label::Body_14); - combobox_left->GetDropDown().SetFont(::Label::Body_14); - - - for (iter = vlista.begin(); iter != vlista.end(); iter++) { combobox_left->Append(*iter); } - combobox_left->SetValue(std::string(params[0].mb_str())); - m_sizer_tcombox->Add(combobox_left, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); - - auto combo_title_add = new wxStaticText(m_parent, wxID_ANY, wxT("+"), wxDefaultPosition, wxDefaultSize, 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_14); - combo_title_add->Wrap(-1); - m_sizer_tcombox->Add(combo_title_add, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5)); - - auto combobox_right = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - combobox_right->SetFont(::Label::Body_14); - combobox_right->GetDropDown().SetFont(::Label::Body_14); - - for (iter = vlistb.begin(); iter != vlistb.end(); iter++) { combobox_right->Append(*iter); } - combobox_right->SetValue(std::string(params[1].mb_str())); - m_sizer_tcombox->Add(combobox_right, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); - - // save config - combobox_left->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, combobox_right](wxCommandEvent &e) { - auto config = e.GetString() + wxString("/") + combobox_right->GetValue(); - app_config->set(param, std::string(config.mb_str())); - e.Skip(); - }); - - combobox_right->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, combobox_left](wxCommandEvent &e) { - auto config = combobox_left->GetValue() + wxString("/") + e.GetString(); - app_config->set(param, std::string(config.mb_str())); - e.Skip(); - }); - - return m_sizer_tcombox; -} - -wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange) -{ - wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_14); - input_title->SetToolTip(tooltip); - input_title->Wrap(DESIGN_TITLE_SIZE.x); + wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url); auto input = new ::TextInput(m_parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); input->SetBackgroundColor(input_bg); input->GetTextCtrl()->SetValue(app_config->get(param)); wxTextValidator validator(wxFILTER_DIGITS); - input->SetToolTip(tooltip); + input->SetToolTip(tip); input->GetTextCtrl()->SetValidator(validator); auto second_title = new wxStaticText(m_parent, wxID_ANY, title2, wxDefaultPosition, wxDefaultSize, 0); second_title->SetForegroundColour(DESIGN_GRAY900_COLOR); second_title->SetFont(::Label::Body_14); - second_title->SetToolTip(tooltip); + second_title->SetToolTip(tip); second_title->Wrap(-1); - sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - sizer_input->Add(input_title , 0, wxALIGN_CENTER_VERTICAL); - sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); - sizer_input->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2)); + m_sizer->Add(input , 0, wxALIGN_CENTER_VERTICAL); + m_sizer->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2)); input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, param, input, onchange](wxCommandEvent &e) { auto value = input->GetTextCtrl()->GetValue(); @@ -561,33 +686,26 @@ wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2 e.Skip(); }); - return sizer_input; + return m_sizer; } -wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function onchange) +wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function onchange, const wxString wiki_url) { - wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto label = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - label->SetForegroundColour(DESIGN_GRAY900_COLOR); - label->SetFont(::Label::Body_14); - label->SetToolTip(tooltip); - label->Wrap(DESIGN_TITLE_SIZE.x); - label->Wrap(DESIGN_TITLE_SIZE.x); + wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url); auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param))); - input->SetToolTip(tooltip); + input->SetToolTip(tip); - sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL); - sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL); if(!title2.empty()){ auto second_title = new wxStaticText(m_parent, wxID_ANY, title2, wxDefaultPosition, wxDefaultSize, 0); second_title->SetForegroundColour(DESIGN_GRAY900_COLOR); second_title->SetFont(::Label::Body_14); - second_title->SetToolTip(tooltip); - sizer->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + second_title->SetToolTip(tip); + m_sizer->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); } input->Bind(wxEVT_TEXT_ENTER, [this, param, input, onchange](wxCommandEvent& e) { @@ -613,17 +731,15 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit e.Skip(); }); - return sizer; + return m_sizer; } wxBoxSizer *PreferencesDialog::create_camera_orbit_mult_input(wxString title, wxString tooltip) { - wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_14); - input_title->SetToolTip(tooltip); - input_title->Wrap(DESIGN_TITLE_SIZE.x); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty + + wxBoxSizer *m_sizer = create_item_label(title, tip); + auto param = "camera_orbit_mult"; auto input = new ::TextInput(m_parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); @@ -631,12 +747,10 @@ wxBoxSizer *PreferencesDialog::create_camera_orbit_mult_input(wxString title, wx input->SetBackgroundColor(input_bg); input->GetTextCtrl()->SetValue(app_config->get(param)); wxTextValidator validator(wxFILTER_NUMERIC); - input->SetToolTip(tooltip); + input->SetToolTip(tip); input->GetTextCtrl()->SetValidator(validator); - sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL); - sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL); const double min = 0.05; const double max = 2.0; @@ -666,24 +780,18 @@ wxBoxSizer *PreferencesDialog::create_camera_orbit_mult_input(wxString title, wx e.Skip(); }); - return sizer_input; + return m_sizer; } wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString tooltip) { - wxBoxSizer *m_sizer_input = new wxBoxSizer(wxHORIZONTAL); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - m_sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_14); - checkbox_title->Wrap(DESIGN_TITLE_SIZE.x); - checkbox_title->SetToolTip(tooltip); + wxBoxSizer *m_sizer = create_item_label(title, tip); auto checkbox = new ::CheckBox(m_parent); checkbox->SetValue(app_config->get_bool("backup_switch")); - checkbox->SetToolTip(tooltip); + checkbox->SetToolTip(tip); checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox](wxCommandEvent &e) { app_config->set_bool("backup_switch", checkbox->GetValue()); @@ -706,9 +814,8 @@ wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString toolt input->SetToolTip(_L("The period of backup in seconds.")); input->GetTextCtrl()->SetValidator(validator); - m_sizer_input->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); - m_sizer_input->Add(checkbox , 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5)); - m_sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL); + m_sizer->Add(checkbox, 0, wxALIGN_CENTER); + m_sizer->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); input->GetTextCtrl()->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this, input](wxCommandEvent &e) { m_backup_interval_time = input->GetTextCtrl()->GetValue(); @@ -738,20 +845,12 @@ wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString toolt input->Refresh(); m_backup_interval_textinput = input; - return m_sizer_input; + return m_sizer; } wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip) { - wxBoxSizer *sizer_row = new wxBoxSizer(wxHORIZONTAL); - - sizer_row->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_14); - checkbox_title->Wrap(DESIGN_TITLE_SIZE.x); - checkbox_title->SetToolTip(checkbox_tooltip); + wxBoxSizer *m_sizer = create_item_label(title, checkbox_tooltip); auto checkbox = new ::CheckBox(m_parent); checkbox->SetValue(app_config->get_bool("auto_slice_after_change")); @@ -769,9 +868,8 @@ wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString input->SetToolTip(delay_tooltip); input->GetTextCtrl()->SetValidator(validator); - sizer_row->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); - sizer_row->Add(checkbox, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5)); - sizer_row->Add(input, 0, wxALIGN_CENTER_VERTICAL); + m_sizer->Add(checkbox, 0, wxALIGN_CENTER); + m_sizer->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); auto commit_delay = [this, input]() { wxString value = input->GetTextCtrl()->GetValue(); @@ -806,78 +904,21 @@ wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString input->Enable(checkbox->GetValue()); input->Refresh(); - return sizer_row; -} - -wxBoxSizer* PreferencesDialog::create_item_draco(wxString title, wxString side_label, wxString tooltip) -{ - wxBoxSizer* sizer_input = new wxBoxSizer(wxHORIZONTAL); - - auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_14); - input_title->SetToolTip(tooltip); - input_title->Wrap(DESIGN_TITLE_SIZE.x); - input_title->SetToolTip(tooltip); - - auto input = new ::TextInput(m_parent, wxEmptyString, side_label, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), - std::pair(*wxWHITE, StateColor::Enabled)); - input->SetBackgroundColor(input_bg); - input->GetTextCtrl()->SetValue(app_config->get("drc_bits")); - wxTextValidator validator(wxFILTER_DIGITS); - input->SetToolTip(tooltip); - input->GetTextCtrl()->SetValidator(validator); - - sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL); - sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); - - std::function set_draco_bits = [this, input]() { - long drc_bits = DRC_BITS_DEFAULT; - input->GetTextCtrl()->GetValue().ToLong(&drc_bits); - if (drc_bits > DRC_BITS_MAX) { - drc_bits = DRC_BITS_MAX; - input->GetTextCtrl()->SetValue(std::to_string(drc_bits)); - } else if (drc_bits < DRC_BITS_MIN && drc_bits != 0) { - drc_bits = DRC_BITS_MIN; - input->GetTextCtrl()->SetValue(std::to_string(drc_bits)); - } - - app_config->set("drc_bits", std::to_string(drc_bits)); - app_config->save(); - }; - - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [set_draco_bits](wxCommandEvent& e) { - set_draco_bits(); - e.Skip(); - }); - - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [set_draco_bits](wxFocusEvent& e) { - set_draco_bits(); - e.Skip(); - }); - - return sizer_input; + return m_sizer; } wxBoxSizer* PreferencesDialog::create_item_darkmode(wxString title,wxString tooltip, std::string param) { - wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); + auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + wxBoxSizer *m_sizer = create_item_label(title, tip); auto checkbox = new ::CheckBox(m_parent); checkbox->SetValue((app_config->get(param) == "1") ? true : false); + checkbox->SetToolTip(tip); m_dark_mode_ckeckbox = checkbox; - auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_14); - checkbox_title->Wrap(DESIGN_TITLE_SIZE.x); - - m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); - m_sizer_checkbox->Add(checkbox , 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5)); + m_sizer->Add(checkbox, 0, wxALIGN_CENTER); //// save config checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent& e) { @@ -896,10 +937,8 @@ wxBoxSizer* PreferencesDialog::create_item_darkmode(wxString title,wxString tool e.Skip(); }); - auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - checkbox_title->SetToolTip(tip); - checkbox->SetToolTip(tip); - return m_sizer_checkbox; + + return m_sizer; } void PreferencesDialog::set_dark_mode() @@ -915,19 +954,11 @@ void PreferencesDialog::set_dark_mode() #endif } -wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString tooltip, std::string param, const wxString secondary_title) +wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString tooltip, std::string param, const wxString secondary_title, const wxString wiki_url) { - wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - - m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty - auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_14); - checkbox_title->Wrap(DESIGN_TITLE_SIZE.x); - checkbox_title->SetToolTip(tip); + wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url); auto checkbox = new ::CheckBox(m_parent); checkbox->SetValue(app_config->get_bool(param)); @@ -935,8 +966,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too if (param == "sync_user_preset") { m_sync_user_preset_checkbox = checkbox; } - m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); - m_sizer_checkbox->Add(checkbox , 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5)); + m_sizer->Add(checkbox, 0, wxALIGN_CENTER); if(!secondary_title.IsEmpty()){ auto sec_title = new wxStaticText(m_parent, wxID_ANY, secondary_title); @@ -944,7 +974,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too sec_title->SetFont(::Label::Body_14); sec_title->Wrap(-1); sec_title->SetToolTip(tip); - m_sizer_checkbox->Add(sec_title, 0, wxALIGN_CENTER); + m_sizer->Add(sec_title, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); } //// save config @@ -1088,20 +1118,14 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too if (param == "developer_mode") { m_developer_mode_ckeckbox = checkbox; } if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; } - return m_sizer_checkbox; + return m_sizer; } -wxBoxSizer* PreferencesDialog::create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function onclick) +wxBoxSizer* PreferencesDialog::create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function onclick, const wxString wiki_url) { - wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); + auto tip = tooltip.IsEmpty() ? tooltip2 : tooltip; // use button tooltip if label tooltip empty - m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - auto m_staticTextPath = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - m_staticTextPath->SetForegroundColour(DESIGN_GRAY900_COLOR); - m_staticTextPath->SetFont(::Label::Body_14); - m_staticTextPath->Wrap(DESIGN_TITLE_SIZE.x); - - m_staticTextPath->SetToolTip(tooltip.IsEmpty() ? tooltip2 : tooltip); // use button tooltip if label tooltip empty + wxBoxSizer *m_sizer = create_item_label(title, tip, wiki_url); auto m_button_download = new Button(m_parent, title2); m_button_download->SetStyle(title2 == _L("Clear") ? ButtonStyle::Alert : ButtonStyle::Regular, ButtonType::Parameter); @@ -1109,47 +1133,28 @@ wxBoxSizer* PreferencesDialog::create_item_button(wxString title, wxString title m_button_download->Bind(wxEVT_BUTTON, [this, onclick](auto &e) { onclick(); }); - m_sizer_checkbox->Add(m_staticTextPath , 0, wxALIGN_CENTER_VERTICAL); - m_sizer_checkbox->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + m_sizer->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL); - return m_sizer_checkbox; + return m_sizer; } wxBoxSizer* PreferencesDialog::create_item_downloads(wxString title, wxString tooltip) { wxString download_path = wxString::FromUTF8(app_config->get("download_path")); - wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - wxPanel* label_panel = new wxPanel(m_parent); - wxBoxSizer* label_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *m_sizer = create_item_label(title, tooltip); - m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto downloads_folder = new wxStaticText(label_panel, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); - downloads_folder->SetForegroundColour(DESIGN_GRAY900_COLOR); - downloads_folder->SetFont(::Label::Body_14); - downloads_folder->SetToolTip(tooltip); - downloads_folder->Wrap(-1); - - auto m_staticTextPath = new wxStaticText(label_panel, wxID_ANY, download_path, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); + auto m_staticTextPath = new wxStaticText(m_parent, wxID_ANY, download_path, wxDefaultPosition, wxSize(FromDIP(120),-1), wxST_ELLIPSIZE_END); m_staticTextPath->SetForegroundColour(DESIGN_GRAY600_COLOR); m_staticTextPath->SetFont(::Label::Body_14); m_staticTextPath->Wrap(-1); m_staticTextPath->SetToolTip(download_path); - label_sizer->Add(downloads_folder , 0, wxALIGN_CENTER_VERTICAL); - label_sizer->Add(m_staticTextPath , 0, wxALIGN_CENTER_VERTICAL); - label_panel->SetSize( wxSize(DESIGN_TITLE_SIZE.x, -1)); - label_panel->SetMinSize(wxSize(DESIGN_TITLE_SIZE.x, -1)); - label_panel->SetMaxSize(wxSize(DESIGN_TITLE_SIZE.x, -1)); - label_panel->SetSizer(label_sizer); - label_panel->Layout(); - - auto m_button_download = new Button(m_parent, _L("Browse") + " " + dots); + auto m_button_download = new Button(m_parent, _L("Browse") + dots); m_button_download->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); m_button_download->SetToolTip(_L("Choose folder for downloaded items")); - m_button_download->Bind(wxEVT_BUTTON, [this, m_staticTextPath, m_sizer_checkbox](auto& e) { + m_button_download->Bind(wxEVT_BUTTON, [this, m_staticTextPath, m_sizer](auto& e) { wxString defaultPath = wxT("/"); wxDirDialog dialog(this, _L("Choose Download Directory"), defaultPath, wxDD_NEW_DIR_BUTTON); @@ -1159,14 +1164,159 @@ wxBoxSizer* PreferencesDialog::create_item_downloads(wxString title, wxString to app_config->set("download_path", download_path_str); m_staticTextPath->SetLabelText(download_path); m_staticTextPath->SetToolTip(download_path); - m_sizer_checkbox->Layout(); + m_sizer->Layout(); } - }); + }); - m_sizer_checkbox->Add(label_panel , 0, wxALIGN_CENTER_VERTICAL); - m_sizer_checkbox->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); + m_sizer->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL); + m_sizer->Add(m_staticTextPath , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(10)); - return m_sizer_checkbox; + return m_sizer; +} + +wxBoxSizer *PreferencesDialog::create_item_bambu_cloud(wxString title, wxString tooltip) +{ + wxBoxSizer *m_sizer = create_item_label(title, tooltip); + + auto cb = new ::CheckBox(m_parent); + m_bambu_cloud_checkbox = cb; + cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER)); + cb->SetToolTip(tooltip); + + cb->Bind(wxEVT_TOGGLEBUTTON, [this, cb](wxCommandEvent &e) { + e.Skip(); // let CheckBox::update() refresh the bitmap + if (cb->GetValue()) { + app_config->add_cloud_provider(BBL_CLOUD_PROVIDER); + } else { + app_config->remove_cloud_provider(BBL_CLOUD_PROVIDER); + } + app_config->save(); + + // Update homepage visibility immediately + auto *mainframe = wxGetApp().mainframe; + if (mainframe && mainframe->m_webview) + mainframe->m_webview->SendCloudProvidersInfo(); + }); + + m_sizer->Add(cb, 0, wxALIGN_CENTER); + + return m_sizer; +}; + +wxBoxSizer *PreferencesDialog::create_item_network_plugin_version(wxString title, wxString tooltip) +{ + wxBoxSizer *m_sizer = create_item_label(title, tooltip); + + m_network_version_combo = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + m_network_version_combo->GetDropDown().SetUseContentWidth(true); + m_network_version_combo->SetToolTip(tooltip); + + std::string current_version = app_config->get_network_plugin_version(); + if (current_version.empty()) { + current_version = get_latest_network_version(); + } + int current_selection = 0; + + m_available_versions = get_all_available_versions(); + + for (size_t i = 0; i < m_available_versions.size(); i++) { + const auto& ver = m_available_versions[i]; + wxString label; + + if (!ver.suffix.empty()) { + label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name); + } else { + label = wxString::FromUTF8(ver.display_name); + } + + if (ver.is_latest) { + label += " " + _L("(Latest)"); + } + m_network_version_combo->Append(label); + if (current_version == ver.version) { + current_selection = i; + } + } + + m_network_version_combo->SetSelection(current_selection); + m_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER); + + m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) { + int selection = e.GetSelection(); + if (selection >= 0 && selection < (int)m_available_versions.size()) { + const auto& selected_ver = m_available_versions[selection]; + std::string new_version = selected_ver.version; + std::string old_version = app_config->get_network_plugin_version(); + if (old_version.empty()) { + old_version = get_latest_network_version(); + } + + app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version); + app_config->save(); + + if (new_version != old_version) { + BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version; + + // Update the use_legacy_network flag immediately + bool is_legacy = (new_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + bool was_legacy = (old_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + if (is_legacy != was_legacy) { + Slic3r::NetworkAgent::use_legacy_network = is_legacy; + BOOST_LOG_TRIVIAL(info) << "Updated use_legacy_network flag to " << is_legacy; + } + + if (!selected_ver.warning.empty()) { + MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); + if (warn_dlg.ShowModal() != wxID_OK) { + app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version); + app_config->save(); + Slic3r::NetworkAgent::use_legacy_network = was_legacy; + e.Skip(); + return; + } + } + + // Check if the selected version already exists on disk + if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { + BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; + if (wxGetApp().hot_reload_network_plugin()) { + MessageDialog dlg(this, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); + } else { + MessageDialog dlg(this, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING); + dlg.ShowModal(); + } + } else { + wxString msg = wxString::Format( + _L("You've selected network plug-in version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."), + wxString::FromUTF8(new_version)); + + MessageDialog dlg(this, msg, _L("Download Network Plug-in"), wxYES_NO | wxICON_QUESTION); + if (dlg.ShowModal() == wxID_YES) { + DownloadProgressDialog progress_dlg(_L("Downloading Network Plug-in")); + progress_dlg.ShowModal(); + } + } + } + } + e.Skip(); + }); + + auto reload_btn = new Button(m_parent, wxEmptyString, "refresh", 0, 16); + reload_btn->SetStyle(ButtonStyle::Regular, ButtonType::Icon); + reload_btn->SetToolTip(_L("Reload the network plug-in without restarting the application")); + reload_btn->Bind(wxEVT_BUTTON, [this](auto& e) { + if (wxGetApp().hot_reload_network_plugin()) { + MessageDialog dlg(this, _L("Network plug-in reloaded successfully."), _L("Reload"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); + } else { + MessageDialog dlg(this, _L("Failed to reload network plug-in. Please restart the application."), _L("Reload Failed"), wxOK | wxICON_ERROR); + dlg.ShowModal(); + } + }); + m_sizer->Add(reload_btn, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + + return m_sizer; } #ifdef WIN32 @@ -1309,7 +1459,47 @@ PreferencesDialog::~PreferencesDialog() { } -void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) { this->Refresh(); } +void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) { + m_pref_tabs->Rescale(); + + int sel = m_pref_tabs->GetSelection(); + for (size_t i = 0; i < m_pref_tabs->GetCount(); ++i) + f_sizers[i]->Show(true); + + std::function WalkControls; + WalkControls = [&](wxWindow* parent, int depth) -> void { + if (!parent) return; + + for (auto* child : parent->GetChildren()) { + if (!child) + continue; + else if (auto* btn = dynamic_cast(child)) + btn->Rescale(); + else if (auto* chk = dynamic_cast(child)) + chk->msw_rescale(); + else if (auto* txt = dynamic_cast(child)) + txt->Rescale(); + else if (auto* cmb = dynamic_cast(child)) + cmb->Rescale(); + else if (auto* spn = dynamic_cast(child)) + spn->Rescale(); + else if (auto* lbl = dynamic_cast(child)){ + lbl->SetSize(DESIGN_TITLE_SIZE); + lbl->Rescale(); + } + + WalkControls(child, depth + 1); + } + }; + WalkControls(this, 0); + + wxCommandEvent event(wxEVT_TAB_SEL_CHANGED, m_pref_tabs->GetId()); + event.SetInt(sel); + event.SetEventObject(m_pref_tabs); + m_pref_tabs->GetEventHandler()->ProcessEvent(event); + + Refresh(); +} void PreferencesDialog::Split(const std::string &src, const std::string &separator, std::vector &dest) { @@ -1383,9 +1573,6 @@ void PreferencesDialog::create_items() auto item_show_splash_scr = create_item_checkbox(_L("Show splash screen"), _L("Show the splash screen during startup."), "show_splash_screen"); g_sizer->Add(item_show_splash_scr); - auto item_shared_profiles = create_item_checkbox(_L("Show shared profiles notification"), _L("Show a notification with a link to browse shared profiles when the selected printer is changed."), "show_shared_profiles_notification"); - g_sizer->Add(item_shared_profiles); - #ifdef __linux__ auto item_window_button_pos = create_item_checkbox(_L("Use window buttons on left side"), "", "window_buttons_on_left", _L("(Requires restart)")); g_sizer->Add(item_window_button_pos); @@ -1394,7 +1581,7 @@ void PreferencesDialog::create_items() //auto item_hints = create_item_checkbox(_L("Show \"Daily Tips\" after start"), page, _L("If enabled, useful hints are displayed at startup."), "show_daily_tips"); //g_sizer->Add(item_hints); - auto item_downloads = create_item_downloads(_L("Downloads folder") + ": ", _L("Target folder for downloaded items")); + auto item_downloads = create_item_downloads(_L("Downloads folder"), _L("Target folder for downloaded items")); g_sizer->Add(item_downloads); //// GENERAL > Project @@ -1405,6 +1592,9 @@ void PreferencesDialog::create_items() auto item_project_load = create_item_combobox(_L("Load behaviour"), _L("Should printer/filament/process settings be loaded when opening a 3MF file?"), SETTING_PROJECT_LOAD_BEHAVIOUR, projectLoadSettingsBehaviourOptions, projectLoadSettingsConfigOptions); g_sizer->Add(item_project_load); + auto item_backup = create_item_backup(_L("Auto backup"), _L("Backup your project periodically to help with restoring from an occasional crash.")); + g_sizer->Add(item_backup); + auto item_max_recent_count = create_item_input(_L("Maximum recent files"), "", _L("Maximum count of recent files"), "max_recent_count", [](wxString value) { long max = 0; if (value.ToLong(&max)) @@ -1418,11 +1608,20 @@ void PreferencesDialog::create_items() auto item_gcodes_warning = create_item_checkbox(_L("Don't warn when loading 3MF with modified G-code"), "", "no_warn_when_modified_gcodes"); g_sizer->Add(item_gcodes_warning); - auto item_step_dialog = create_item_checkbox(_L("Show options when importing STEP file"), _L("If enabled, a parameter settings dialog will appear during STEP file import."), "enable_step_mesh_setting"); + auto item_step_dialog = create_item_checkbox( + _L("Show options when importing STEP file"), _L("If enabled, a parameter settings dialog will appear during STEP file import."), + "enable_step_mesh_setting", wxEmptyString, "import_export#dont-show-again" + ); g_sizer->Add(item_step_dialog); - auto item_backup = create_item_backup(_L("Auto backup"), _L("Backup your project periodically to help with restoring from an occasional crash.")); - g_sizer->Add(item_backup); + auto item_draco_bits = create_item_spinctrl(_L("Quality level for Draco export"), "", + _L("bits"), + _L("Controls the quantization bit depth used when compressing the mesh to Draco format.\n" + "0 = lossless compression (geometry is preserved at full precision). Valid lossy values range from 8 to 30.\n" + "Lower values produce smaller files but lose more geometric detail; higher values preserve more detail at the cost of larger files."), + "drc_bits", DRC_BITS_MIN, DRC_BITS_MAX, nullptr, "import_export#drc" + ); + g_sizer->Add(item_draco_bits); //// GENERAL > Preset g_sizer->Add(create_item_title(_L("Preset")), 1, wxEXPAND); @@ -1443,6 +1642,9 @@ void PreferencesDialog::create_items() "filaments_area_preferred_count", 8, 99, [this](int value) {m_filament_height_timer.StartOnce(500);}); g_sizer->Add(item_filament_area_height); + auto item_shared_profiles = create_item_checkbox(_L("Show shared profiles notification"), _L("Show a notification with a link to browse shared profiles when the selected printer is changed."), "show_shared_profiles_notification"); + g_sizer->Add(item_shared_profiles); + //// GENERAL > Features g_sizer->Add(create_item_title(_L("Features")), 1, wxEXPAND); @@ -1457,13 +1659,6 @@ void PreferencesDialog::create_items() g_sizer->Add(item_pop_up_filament_map_dialog); #endif - auto item_draco_bits = create_item_draco(_L("Quality level for Draco export"), - _L("bits"), - _L("Controls the quantization bit depth used when compressing the mesh to Draco format.\n" - "0 = lossless compression (geometry is preserved at full precision). Valid lossy values range from 8 to 30.\n" - "Lower values produce smaller files but lose more geometric detail; higher values preserve more detail at the cost of larger files.")); - g_sizer->Add(item_draco_bits); - g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); @@ -1666,42 +1861,8 @@ void PreferencesDialog::create_items() //// ONLINE > Cloud Providers g_sizer->Add(create_item_title(_L("Cloud Providers")), 1, wxEXPAND); - { - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto text = new wxStaticText(m_parent, wxID_ANY, _L("Enable Bambu Cloud"), - wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - text->SetForegroundColour(DESIGN_GRAY900_COLOR); - text->SetFont(::Label::Body_14); - text->SetToolTip(_L("Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu login section appears on the homepage.")); - text->Wrap(DESIGN_TITLE_SIZE.x); - - auto cb = new ::CheckBox(m_parent); - m_bambu_cloud_checkbox = cb; - cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER)); - cb->SetToolTip(text->GetToolTipText()); - - cb->Bind(wxEVT_TOGGLEBUTTON, [this, cb](wxCommandEvent &e) { - e.Skip(); // let CheckBox::update() refresh the bitmap - if (cb->GetValue()) { - app_config->add_cloud_provider(BBL_CLOUD_PROVIDER); - } else { - app_config->remove_cloud_provider(BBL_CLOUD_PROVIDER); - } - app_config->save(); - - // Update homepage visibility immediately - auto *mainframe = wxGetApp().mainframe; - if (mainframe && mainframe->m_webview) - mainframe->m_webview->SendCloudProvidersInfo(); - }); - - sizer->Add(text, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); - sizer->Add(cb, 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5)); - - g_sizer->Add(sizer); - } + auto item_bambu_cloud = create_item_bambu_cloud(_L("Enable Bambu Cloud"), _L("Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu login section appears on the homepage.")); + g_sizer->Add(item_bambu_cloud); //// ONLINE > Update & sync g_sizer->Add(create_item_title(_L("Update & sync")), 1, wxEXPAND); @@ -1717,6 +1878,13 @@ void PreferencesDialog::create_items() if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(false); } + auto item_filament_sync_mode = create_item_combobox( + _L("Filament sync mode"), + _L("Choose whether sync updates both filament preset and color, or only color."), + "sync_ams_filament_mode", + {_L("Filament & Color"), _L("Color only")}); + g_sizer->Add(item_filament_sync_mode); + auto item_system_sync = create_item_checkbox(_L("Update built-in presets automatically."), "", "sync_system_preset"); g_sizer->Add(item_system_sync); @@ -1725,128 +1893,14 @@ void PreferencesDialog::create_items() SETTING_USE_ENCRYPTED_TOKEN_FILE); g_sizer->Add(item_token_storage); - //// ONLINE > Filament Sync Options - g_sizer->Add(create_item_title(_L("Filament Sync Options")), 1, wxEXPAND); - - auto item_filament_sync_mode = create_item_combobox( - _L("Filament sync mode"), - _L("Choose whether sync updates both filament preset and color, or only color."), - "sync_ams_filament_mode", - {_L("Filament & Color"), _L("Color only")}); - g_sizer->Add(item_filament_sync_mode); - //// ONLINE > Network plugin g_sizer->Add(create_item_title(_L("Bambu network plug-in")), 1, wxEXPAND); auto item_enable_plugin = create_item_checkbox(_L("Enable Bambu network plug-in"), "", "installed_networking"); g_sizer->Add(item_enable_plugin); - m_network_version_sizer = new wxBoxSizer(wxHORIZONTAL); - m_network_version_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); - - auto version_title = new wxStaticText(m_parent, wxID_ANY, _L("Network plug-in version"), wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); - version_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - version_title->SetFont(::Label::Body_14); - version_title->SetToolTip(_L("Select the network plug-in version to use")); - version_title->Wrap(DESIGN_TITLE_SIZE.x); - m_network_version_sizer->Add(version_title, 0, wxALIGN_CENTER); - - m_network_version_combo = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(180), -1), 0, nullptr, wxCB_READONLY); - m_network_version_combo->SetFont(::Label::Body_14); - m_network_version_combo->GetDropDown().SetFont(::Label::Body_14); - - std::string current_version = app_config->get_network_plugin_version(); - if (current_version.empty()) { - current_version = get_latest_network_version(); - } - int current_selection = 0; - - m_available_versions = get_all_available_versions(); - - for (size_t i = 0; i < m_available_versions.size(); i++) { - const auto& ver = m_available_versions[i]; - wxString label; - - if (!ver.suffix.empty()) { - label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name); - } else { - label = wxString::FromUTF8(ver.display_name); - } - - if (ver.is_latest) { - label += " " + _L("(Latest)"); - } - m_network_version_combo->Append(label); - if (current_version == ver.version) { - current_selection = i; - } - } - - m_network_version_combo->SetSelection(current_selection); - m_network_version_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); - - m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) { - int selection = e.GetSelection(); - if (selection >= 0 && selection < (int)m_available_versions.size()) { - const auto& selected_ver = m_available_versions[selection]; - std::string new_version = selected_ver.version; - std::string old_version = app_config->get_network_plugin_version(); - if (old_version.empty()) { - old_version = get_latest_network_version(); - } - - app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version); - app_config->save(); - - if (new_version != old_version) { - BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version; - - // Update the use_legacy_network flag immediately - bool is_legacy = (new_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); - bool was_legacy = (old_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); - if (is_legacy != was_legacy) { - Slic3r::NetworkAgent::use_legacy_network = is_legacy; - BOOST_LOG_TRIVIAL(info) << "Updated use_legacy_network flag to " << is_legacy; - } - - if (!selected_ver.warning.empty()) { - MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); - if (warn_dlg.ShowModal() != wxID_OK) { - app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version); - app_config->save(); - Slic3r::NetworkAgent::use_legacy_network = was_legacy; - e.Skip(); - return; - } - } - - // Check if the selected version already exists on disk - if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { - BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; - if (wxGetApp().hot_reload_network_plugin()) { - MessageDialog dlg(this, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION); - dlg.ShowModal(); - } else { - MessageDialog dlg(this, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING); - dlg.ShowModal(); - } - } else { - wxString msg = wxString::Format( - _L("You've selected network plug-in version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."), - wxString::FromUTF8(new_version)); - - MessageDialog dlg(this, msg, _L("Download Network Plug-in"), wxYES_NO | wxICON_QUESTION); - if (dlg.ShowModal() == wxID_YES) { - DownloadProgressDialog progress_dlg(_L("Downloading Network Plug-in")); - progress_dlg.ShowModal(); - } - } - } - } - e.Skip(); - }); - - g_sizer->Add(m_network_version_sizer); + auto item_plugin_version = create_item_network_plugin_version(_L("Network plug-in version"), _L("Select the network plug-in version to use")); + g_sizer->Add(item_plugin_version); g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); @@ -1923,39 +1977,33 @@ void PreferencesDialog::create_items() //// DEVELOPER > Settings g_sizer->Add(create_item_title(_L("Settings")), 1, wxEXPAND); - auto item_develop_mode = create_item_checkbox(_L("Developer mode"), "", "developer_mode"); + auto item_develop_mode = create_item_checkbox(_L("Developer mode"), "", "developer_mode", wxEmptyString, "option_mode#developer+mode"); g_sizer->Add(item_develop_mode); auto item_ams_blacklist = create_item_checkbox(_L("Skip AMS blacklist check"), "", "skip_ams_blacklist_check"); g_sizer->Add(item_ams_blacklist); - - auto item_keep_painting = create_item_checkbox(_L("(Experimental) Keep painted feature after mesh change"), _L("Attempt to keep painted features (color/seam/support/fuzzy etc.) after changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\nHighly experimental! Slow and may create artifact."), "keep_painting"); - g_sizer->Add(item_keep_painting); - + auto item_show_unsupported = create_item_checkbox(_L("Show unsupported presets"), _L("Show incompatible/unsupported presets in the printer and filament dropdown lists. These presets cannot be selected."), "show_unsupported_presets"); g_sizer->Add(item_show_unsupported); + //// DEVELOPER > Experimental Features + g_sizer->Add(create_item_title(_L("Experimental Features")), 1, wxEXPAND); + + auto item_keep_painting = create_item_checkbox(_L("Keep painted feature after mesh change"), _L("Attempt to keep painted features (color/seam/support/fuzzy etc.) after changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\nHighly experimental! Slow and may create artifact."), "keep_painting"); + g_sizer->Add(item_keep_painting); + + //// DEVELOPER > Storage + g_sizer->Add(create_item_title(_L("Storage")), 1, wxEXPAND); auto item_allow_abnormal_storage = create_item_checkbox(_L("Allow Abnormal Storage"), _L("This allows the use of Storage that is marked as abnormal by the Printer.\nUse at your own risk, can cause issues!"), "allow_abnormal_storage"); g_sizer->Add(item_allow_abnormal_storage); + //// DEVELOPER > Log Level g_sizer->Add(create_item_title(_L("Log Level")), 1, wxEXPAND); auto log_level_list = std::vector{_L("fatal"), _L("error"), _L("warning"), _L("info"), _L("debug"), _L("trace")}; auto loglevel_combox = create_item_loglevel_combobox(_L("Log Level"), _L("Log Level"), log_level_list); g_sizer->Add(loglevel_combox); - g_sizer->Add(create_item_title(_L("Network plug-in")), 1, wxEXPAND); - auto item_reload_plugin = create_item_button(_L("Network plug-in"), _L("Reload"), _L("Reload the network plug-in without restarting the application"), "", [this]() { - if (wxGetApp().hot_reload_network_plugin()) { - MessageDialog dlg(this, _L("Network plug-in reloaded successfully."), _L("Reload"), wxOK | wxICON_INFORMATION); - dlg.ShowModal(); - } else { - MessageDialog dlg(this, _L("Failed to reload network plug-in. Please restart the application."), _L("Reload Failed"), wxOK | wxICON_ERROR); - dlg.ShowModal(); - } - }); - g_sizer->Add(item_reload_plugin); - //// DEVELOPER > Debug #if !BBL_RELEASE_TO_PUBLIC g_sizer->Add(create_item_title(_L("Debug")), 1, wxEXPAND); @@ -2003,39 +2051,6 @@ void PreferencesDialog::create_sync_page() sizer_page->Fit(page); } -void PreferencesDialog::create_shortcuts_page() -{ - auto page = new wxWindow(this, wxID_ANY); - wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL); - - auto title_view_control = create_item_title(_L("View control settings")); - std::vector keyboard_supported; - Split(app_config->get("keyboard_supported"), "/", keyboard_supported); - - std::vector mouse_supported; - Split(app_config->get("mouse_supported"), "/", mouse_supported); - - auto item_rotate_view = create_item_multiple_combobox(_L("Rotate view"), _L("Rotate view"), "rotate_view", keyboard_supported, - mouse_supported); - auto item_move_view = create_item_multiple_combobox(_L("Pan view"), _L("Pan view"), "move_view", keyboard_supported, mouse_supported); - auto item_zoom_view = create_item_multiple_combobox(_L("Zoom view"), _L("Zoom view"), "rotate_view", keyboard_supported, mouse_supported); - - auto title_other = create_item_title(_L("Other")); - auto item_other = create_item_checkbox(_L("Reverse scroll direction while zooming"), _L("Reverse scroll direction while zooming"), "mouse_wheel"); - - sizer_page->Add(title_view_control, 0, wxTOP, 26); - sizer_page->Add(item_rotate_view, 0, wxTOP, 8); - sizer_page->Add(item_move_view, 0, wxTOP, 8); - sizer_page->Add(item_zoom_view, 0, wxTOP, 8); - // sizer_page->Add(item_precise_control, 0, wxTOP, 0); - sizer_page->Add(title_other, 0, wxTOP, 20); - sizer_page->Add(item_other, 0, wxTOP, 5); - - page->SetSizer(sizer_page); - page->Layout(); - sizer_page->Fit(page); -} - wxBoxSizer* PreferencesDialog::create_debug_page() { m_internal_developer_mode_def = app_config->get("internal_developer_mode"); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index d7daca5ff8..86d1e06358 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -28,9 +28,6 @@ namespace Slic3r { namespace GUI { #define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1) #define DESIGN_LEFT_MARGIN 25 -class CheckBox; -class TextInput; - class PreferencesDialog : public DPIDialog { private: @@ -75,7 +72,6 @@ public: ::CheckBox * m_bambu_cloud_checkbox = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr}; ::ComboBox * m_network_version_combo = {nullptr}; - wxBoxSizer * m_network_version_sizer = {nullptr}; std::vector m_available_versions; wxString m_developer_mode_def; @@ -86,30 +82,30 @@ public: std::vector f_sizers; wxBoxSizer *create_item_title(wxString title); - wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::function onchange = {}); - wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index); + 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 vlist, std::function onchange = {}, const wxString wiki_url = ""); + wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector vlist, std::vector config_name_index, const wxString wiki_url = ""); wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector vlist); - wxBoxSizer *create_item_checkbox(wxString title, wxString tooltip, std::string param, const wxString secondary_title = ""); + wxBoxSizer *create_item_checkbox(wxString title, wxString tooltip, std::string param, const wxString secondary_title = "", const wxString wiki_url = ""); wxBoxSizer *create_item_darkmode(wxString title,wxString tooltip, std::string param); void set_dark_mode(); - wxBoxSizer *create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function onclick); + wxBoxSizer *create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function onclick, const wxString wiki_url = ""); wxBoxSizer *create_item_downloads(wxString title, wxString tooltip); - wxBoxSizer *create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange = {}); - wxBoxSizer *create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function onchange = nullptr); + wxBoxSizer *create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange = {}, const wxString wiki_url = ""); + wxBoxSizer *create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function onchange = nullptr, const wxString wiki_url = ""); wxBoxSizer *create_camera_orbit_mult_input(wxString title, wxString tooltip); wxBoxSizer *create_item_backup(wxString title, wxString tooltip); wxBoxSizer *create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip); - wxBoxSizer *create_item_draco(wxString title, wxString side_label, wxString tooltip); - wxBoxSizer *create_item_multiple_combobox(wxString title, wxString tooltip, std::string parama, std::vector vlista, std::vector vlistb); + wxBoxSizer *create_item_bambu_cloud(wxString title, wxString tooltip); + wxBoxSizer *create_item_network_plugin_version(wxString title, wxString tooltip); #ifdef WIN32 wxBoxSizer *create_item_link_association(wxString url_prefix, wxString website_name); #endif // WIN32 void create_items(); void create_sync_page(); - void create_shortcuts_page(); wxBoxSizer* create_debug_page(); void UpdateSidebarLayout(); @@ -121,7 +117,7 @@ public: int m_current_language_selected = {0}; private: - std::tuple create_item_combobox_base(wxString title, wxString tooltip, std::string param, std::vector vlist, unsigned int current_index); + std::tuple create_item_combobox_base(wxString title, wxString tooltip, std::string param, std::vector vlist, unsigned int current_index, const wxString wiki_url = ""); }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetBundleDialog.cpp b/src/slic3r/GUI/PresetBundleDialog.cpp index c94695c175..ac1b33dd1e 100644 --- a/src/slic3r/GUI/PresetBundleDialog.cpp +++ b/src/slic3r/GUI/PresetBundleDialog.cpp @@ -387,7 +387,7 @@ void PresetBundleDialog::ListBundles() temp["printers"] = strip_prefix(metadata.printer_presets); temp["filaments"] = strip_prefix(metadata.filament_presets); - temp["presets"] = strip_prefix(metadata.print_presets); + temp["processes"] = strip_prefix(metadata.print_presets); temp["update_available"] = metadata.update_available; temp["unauthorized"] = metadata.unauthorized; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2f2940d60a..39512eb396 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4368,6 +4368,8 @@ void TabFilament::build() //optgroup->append_line(line); optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling_specific_layer"); optgroup->append_single_option_line("close_fan_the_first_x_layers", "material_cooling#no-cooling-for-the-first"); + // ORCA: explicit override for the part cooling fan on layer 0; also anchors the ramp when "Full fan speed at layer" is set. + optgroup->append_single_option_line("initial_layer_fan_speed", "material_cooling#first-layer-fan-speed"); optgroup->append_single_option_line("full_fan_speed_layer", "material_cooling#full-fan-speed-at-layer"); optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_part_fan"); @@ -4589,6 +4591,29 @@ void TabFilament::toggle_options() bool has_slow_down_for_layer_cooling = m_config->opt_bool("slow_down_for_layer_cooling", 0); toggle_option("dont_slow_down_outer_wall", has_slow_down_for_layer_cooling); + // ORCA: First layer fan speed override only makes sense when no layers are gated off ("No cooling for + // the first" == 0). Otherwise the override would set layer 0 to a non-zero value while the gate forces + // layers 1..N-1 to zero, producing a confusing non-monotonic profile. When the gate is active we both + // grey out the UI line and force the underlying value to -1 so the cooling buffer never enters the + // override branch. + const int close_fan_first_n = m_config->opt_int("close_fan_the_first_x_layers", 0); + const bool initial_layer_fan_speed_enabled = close_fan_first_n <= 0; + toggle_line("initial_layer_fan_speed", initial_layer_fan_speed_enabled); + if (!initial_layer_fan_speed_enabled) { + if (auto* opt = dynamic_cast(m_config->option("initial_layer_fan_speed"))) { + bool needs_reset = false; + for (int v : opt->values) { + if (v != -1) { needs_reset = true; break; } + } + if (needs_reset) { + std::vector reset_values(opt->values.size(), -1); + m_config->set_key_value("initial_layer_fan_speed", new ConfigOptionInts(reset_values)); + update_dirty(); + reload_config(); + } + } + } + toggle_line("additional_cooling_fan_speed", printer_cfg.opt_bool("auxiliary_fan")); bool support_air_filtration = printer_cfg.opt_bool("support_air_filtration"); diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 4400b13fa6..e236c84e67 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -203,6 +203,12 @@ void Button::SetStyle(const ButtonStyle style, const ButtonType type) this->SetCornerRadius(this->FromDIP(4)); this->SetFont(Label::Body_14); } + else if (type == ButtonType::Icon) { + this->SetPaddingSize(FromDIP(wxSize(5,5))); + this->SetMinSize(FromDIP(wxSize(26,26))); + this->SetSize(FromDIP(wxSize(26,26))); + this->SetCornerRadius(this->FromDIP(4)); + } else if (type == ButtonType::Expanded) { this->SetMinSize(FromDIP(wxSize(-1,32))); this->SetPaddingSize(FromDIP(wxSize(12,8))); diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp index a5ba989db3..bc093b512a 100644 --- a/src/slic3r/GUI/Widgets/Button.hpp +++ b/src/slic3r/GUI/Widgets/Button.hpp @@ -23,6 +23,7 @@ enum class ButtonType{ Window , // Font12 FullyRounded For regular buttons in windows and not related with parameter boxes Choice , // Font14 Semi-Rounded For dialog/window choice buttons Parameter, // Font14 Semi-Rounded For buttons that near parameter boxes + Icon , // ------ Semi-Rounded For buttons that only has icons. icons should be 16x16 and iconSize has to be defined as 16 while creation of button Expanded , // Font14 Semi-Rounded For full length buttons. ex. buttons in static box }; diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index 4e09072fff..8a65679104 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -31,10 +31,7 @@ boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter"); boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill"); boost::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt"); -// [NotWorking]: slice() intermittently throws clipper's "Coordinate outside allowed -// range" in CI (Linux) while passing locally. Disabled pending a root-cause fix in a -// follow-up PR. -SCENARIO( "PrintGCode basic functionality", "[PrintGCode][NotWorking]") { +SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { GIVEN("A default configuration and a print test object") { WHEN("the output is executed with no support material") { Slic3r::Print print; diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 7bd3259925..5529abe615 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -32,10 +32,7 @@ using namespace Slic3r; return brim_tool; } -// [NotWorking]: slice() intermittently throws clipper's "Coordinate outside allowed -// range" in CI (Linux) while passing locally. Disabled pending a root-cause fix in a -// follow-up PR. -TEST_CASE("Skirt height is honored", "[SkirtBrim][NotWorking]") { +TEST_CASE("Skirt height is honored", "[SkirtBrim]") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_deserialize_strict({ { "skirt_loops", 1 }, @@ -55,8 +52,7 @@ TEST_CASE("Skirt height is honored", "[SkirtBrim][NotWorking]") { REQUIRE(layers_with_role(gcode, "skirt").size() == (size_t)config.opt_int("skirt_height")); } -// [NotWorking]: see "Skirt height is honored" above; same CI-only clipper range throw. -SCENARIO("Skirt and brim generation", "[SkirtBrim][NotWorking]") { +SCENARIO("Skirt and brim generation", "[SkirtBrim]") { GIVEN("A default configuration") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_num_extruders(4); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 66f7dc9ae1..d5e6eb958e 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(${_TEST_NAME}_tests test_arachne_walls.cpp test_arrange.cpp test_bambu_networking.cpp + test_calib.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp diff --git a/tests/libslic3r/test_calib.cpp b/tests/libslic3r/test_calib.cpp new file mode 100644 index 0000000000..e72eb73e0d --- /dev/null +++ b/tests/libslic3r/test_calib.cpp @@ -0,0 +1,40 @@ +#include + +#include "libslic3r/calib.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/PrintConfig.hpp" + +using namespace Slic3r; + +namespace { + +// The width-resolution getters are protected; expose them so the resolution can be asserted directly. +struct PaPatternProbe : public CalibPressureAdvancePattern +{ + using CalibPressureAdvancePattern::CalibPressureAdvancePattern; + using CalibPressureAdvancePattern::line_width; + using CalibPressureAdvancePattern::line_width_first_layer; +}; + +} // namespace + +TEST_CASE("Zero calibration line width resolves to a positive default", "[Calib][Regression]") +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + {"line_width", "0"}, + {"initial_layer_line_width", "0"}, + }); + + Model model; + model.add_object("cube", "", make_cube(20, 20, 20))->add_instance(); + + Calib_Params params; + params.mode = CalibMode::Calib_PA_Pattern; + + PaPatternProbe pattern(params, config, /* is_bbl_machine */ true, *model.objects.front(), Vec3d(0, 0, 0)); + + REQUIRE(pattern.line_width() > 0.); + REQUIRE(pattern.line_width_first_layer() > 0.); +}