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