From 2e61d9eab2dcf2fef3511bb5606641cb9509eee1 Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:08:55 +0300 Subject: [PATCH 01/42] Correct gap fill width bounds in outer-only and mixed-wall cases (#12057) --- src/libslic3r/PerimeterGenerator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 1fa4d58bb0..f8827923f4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1562,9 +1562,9 @@ void PerimeterGenerator::process_classic() } // for each loop of an island // fill gaps - if (! gaps.empty()) { - // collapse - double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); + if (! gaps.empty()) { // collapse + // ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap + double min = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE); double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. From a2cff48a469a7b50dca5c0095b942cfdef54faea Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:40:17 +0300 Subject: [PATCH 02/42] Extend short-travel smoothing to external overhang walls (#11975) --- src/libslic3r/GCode.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 04a2df48db..b90d70fecd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7091,9 +7091,16 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { jerk_to_set = m_config.initial_layer_jerk.value; } - } else { + } else { // ORCA: Handle short-travel acceleration and jerk for outer perimeters (if applicable) + const bool is_short_travel = travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel)); + if (m_config.default_acceleration.value > 0) { - if (role == erExternalPerimeter && travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) { + if (role == erOverhangPerimeter && is_short_travel) { + const double bridge_acceleration = m_config.get_abs_value("bridge_acceleration"); + + if (bridge_acceleration > 0) + acceleration_to_set = (unsigned int) floor(bridge_acceleration + 0.5); + } else if (role == erExternalPerimeter && is_short_travel) { if (m_config.outer_wall_acceleration.value > 0) acceleration_to_set = (unsigned int) floor(m_config.outer_wall_acceleration.value + 0.5); } else { @@ -7103,7 +7110,7 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string } if (m_config.default_jerk.value > 0) { - if (role == erExternalPerimeter && travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) { + if ((role == erExternalPerimeter || role == erOverhangPerimeter) && is_short_travel) { if (m_config.outer_wall_jerk.value > 0) jerk_to_set = m_config.outer_wall_jerk.value; } else { From 00704c368fedeac85f620af6f8ce29fe599525aa Mon Sep 17 00:00:00 2001 From: Richard Copra <137560494+rcopra@users.noreply.github.com> Date: Wed, 22 Apr 2026 03:55:24 +0200 Subject: [PATCH 03/42] make initial layer travel move acceleration and jerk configurable (#11674) * make initial layer travel move acceleration and jerk configurable * Update spelling to match UI pattern * Update min integer and re-order to match patterns --------- Co-authored-by: Thomas Scheiblauer Co-authored-by: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> --- localization/i18n/OrcaSlicer.pot | 12 ++++++++++++ localization/i18n/de/OrcaSlicer_de.po | 12 ++++++++++++ localization/i18n/en/OrcaSlicer_en.po | 12 ++++++++++++ src/libslic3r/GCode.cpp | 12 +++++++----- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 2 ++ src/libslic3r/PrintConfig.cpp | 18 ++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Tab.cpp | 2 ++ 9 files changed, 68 insertions(+), 6 deletions(-) diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index bd4afb8adc..86c732f29b 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -12999,6 +12999,18 @@ msgstr "" msgid "Travel speed of the first layer." msgstr "" +msgid "First layer travel acceleration" +msgstr "" + +msgid "Travel acceleration of first layer." +msgstr "" + +msgid "First layer travel jerk" +msgstr "" + +msgid "Travel jerk of first layer." +msgstr "" + msgid "Number of slow layers" msgstr "" diff --git a/localization/i18n/de/OrcaSlicer_de.po b/localization/i18n/de/OrcaSlicer_de.po index 2a338e7afe..f507299180 100644 --- a/localization/i18n/de/OrcaSlicer_de.po +++ b/localization/i18n/de/OrcaSlicer_de.po @@ -15193,6 +15193,18 @@ msgstr "Bewegung" msgid "Travel speed of the first layer." msgstr "Bewegungsgeschwindigkeit der ersten Schicht" +msgid "First layer travel acceleration" +msgstr "Eilgang Beschleunigung" + +msgid "Travel acceleration of first layer." +msgstr "Eilgang Beschleunigung der ersten Schicht." + +msgid "First layer travel jerk" +msgstr "Eilgang Ruck" + +msgid "Travel jerk of first layer." +msgstr "Eilgang Ruck der ersten Schicht." + msgid "Number of slow layers" msgstr "Anzahl der langsamen Schichten" diff --git a/localization/i18n/en/OrcaSlicer_en.po b/localization/i18n/en/OrcaSlicer_en.po index 3c6b7b9040..b1b8aad458 100644 --- a/localization/i18n/en/OrcaSlicer_en.po +++ b/localization/i18n/en/OrcaSlicer_en.po @@ -13280,6 +13280,18 @@ msgstr "" msgid "Travel speed of the first layer." msgstr "" +msgid "First layer travel acceleration" +msgstr "" + +msgid "Travel acceleration of first layer." +msgstr "" + +msgid "First layer travel jerk" +msgstr "" + +msgid "Travel jerk of first layer." +msgstr "" + #, fuzzy msgid "Number of slow layers" msgstr "This is the number of top interface layers." diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b90d70fecd..3658070fdb 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7084,12 +7084,14 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string unsigned int acceleration_to_set = 0; if (this->on_first_layer()) { - if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { - acceleration_to_set = (unsigned int) floor(m_config.initial_layer_acceleration.value + 0.5); + unsigned int initial_layer_travel_acceleration = m_config.get_abs_value("initial_layer_travel_acceleration"); + double initial_layer_travel_jerk = m_config.get_abs_value("initial_layer_travel_jerk"); + + if (m_config.default_acceleration.value > 0 && initial_layer_travel_acceleration > 0) { + acceleration_to_set = (unsigned int) floor(initial_layer_travel_acceleration + 0.5); } - - if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0) { - jerk_to_set = m_config.initial_layer_jerk.value; + if (m_config.default_jerk.value > 0 && initial_layer_travel_jerk > 0) { + jerk_to_set = initial_layer_travel_jerk; } } else { // ORCA: Handle short-travel acceleration and jerk for outer perimeters (if applicable) const bool is_short_travel = travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel)); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f8001d2806..65397d11e1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -944,7 +944,7 @@ static std::vector s_Preset_print_options { "bridge_density","internal_bridge_density", "precise_outer_wall", "bridge_acceleration", "sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_auto_brim", "tree_support_brim_width", "gcode_comments", "gcode_label_objects", - "initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width", + "initial_layer_travel_speed", "initial_layer_travel_acceleration", "initial_layer_travel_jerk", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width", "make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes", "wipe_tower_cone_angle", "wipe_tower_extra_spacing","wipe_tower_max_purge_speed", "wipe_tower_wall_type", "wipe_tower_extra_rib_length", "wipe_tower_rib_width", "wipe_tower_fillet_wall", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a7f0f0a2b5..b9e8842b0b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -343,6 +343,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "travel_speed_z" || opt_key == "initial_layer_speed" || opt_key == "initial_layer_travel_speed" + || opt_key == "initial_layer_travel_acceleration" + || opt_key == "initial_layer_travel_jerk" || opt_key == "slow_down_layers" || opt_key == "idle_temperature" || opt_key == "wipe_tower_cone_angle" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9f8fa618f7..f8918cb7b8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3031,6 +3031,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(300)); + def = this->add("initial_layer_travel_acceleration", coFloatOrPercent); + def->label = L("First layer travel"); + def->tooltip = L("Travel acceleration of first layer."); + def->sidetext = L("mm/s² or %"); + def->min = 0; + def->mode = comAdvanced; + def->ratio_over = "travel_acceleration"; + def->set_default_value(new ConfigOptionFloatOrPercent(100, true)); + def = this->add("accel_to_decel_enable", coBool); def->label = L("Enable accel_to_decel"); def->category = L("Speed"); @@ -3121,6 +3130,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(12)); + def = this->add("initial_layer_travel_jerk", coFloatOrPercent); + def->label = L("First layer travel"); + def->tooltip = L("Travel jerk of first layer."); + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comAdvanced; + def->ratio_over = "travel_jerk"; + def->set_default_value(new ConfigOptionFloatOrPercent(100, true)); + def = this->add("initial_layer_line_width", coFloatOrPercent); def->label = L("First layer"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7d40ef0706..0acd1747a6 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1361,6 +1361,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, accel_to_decel_enable)) ((ConfigOptionPercent, accel_to_decel_factor)) ((ConfigOptionFloatOrPercent, initial_layer_travel_speed)) + ((ConfigOptionFloatOrPercent, initial_layer_travel_acceleration)) + ((ConfigOptionFloatOrPercent, initial_layer_travel_jerk)) ((ConfigOptionBool, bbl_calib_mark_logo)) ((ConfigOptionBool, disable_m73)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6141d96885..a38893d7bd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2503,6 +2503,7 @@ void TabPrint::build() optgroup->append_single_option_line("sparse_infill_acceleration", "speed_settings_acceleration#sparse-infill"); optgroup->append_single_option_line("internal_solid_infill_acceleration", "speed_settings_acceleration#internal-solid-infill"); optgroup->append_single_option_line("initial_layer_acceleration", "speed_settings_acceleration#initial-layer"); + optgroup->append_single_option_line("initial_layer_travel_acceleration"); optgroup->append_single_option_line("top_surface_acceleration", "speed_settings_acceleration#top-surface"); optgroup->append_single_option_line("travel_acceleration", "speed_settings_acceleration#travel"); optgroup->append_single_option_line("accel_to_decel_enable", "speed_settings_acceleration"); @@ -2516,6 +2517,7 @@ void TabPrint::build() optgroup->append_single_option_line("infill_jerk", "speed_settings_jerk_xy#infill"); optgroup->append_single_option_line("top_surface_jerk", "speed_settings_jerk_xy#top-surface"); optgroup->append_single_option_line("initial_layer_jerk", "speed_settings_jerk_xy#initial-layer"); + optgroup->append_single_option_line("initial_layer_travel_jerk"); optgroup->append_single_option_line("travel_jerk", "speed_settings_jerk_xy#travel"); optgroup = page->new_optgroup(L("Advanced"), L"param_advanced", 15); From 1267801de96440662d6ef381e572382c4c807d1a Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:04:13 +0300 Subject: [PATCH 04/42] PR #11674 follow-up - toggle first layer accel&jerk (#13320) Toggle initial layer accel&jerk --- src/slic3r/GUI/ConfigManipulation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index f531aebafb..98baf6d641 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -667,7 +667,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_default_acceleration = config->opt_float("default_acceleration") > 0; - for (auto el : {"outer_wall_acceleration", "inner_wall_acceleration", "initial_layer_acceleration", + for (auto el : {"outer_wall_acceleration", "inner_wall_acceleration", "initial_layer_acceleration", "initial_layer_travel_acceleration", "top_surface_acceleration", "travel_acceleration", "bridge_acceleration", "sparse_infill_acceleration", "internal_solid_infill_acceleration"}) toggle_field(el, have_default_acceleration); @@ -681,13 +681,13 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co if (machine_supports_junction_deviation) { toggle_field("default_junction_deviation", true); toggle_field("default_jerk", false); - for (auto el : { "outer_wall_jerk", "inner_wall_jerk", "initial_layer_jerk", "top_surface_jerk", "travel_jerk", "infill_jerk"}) - toggle_line(el, false); + for (auto el : { "outer_wall_jerk", "inner_wall_jerk", "initial_layer_jerk", "initial_layer_travel_jerk", "top_surface_jerk", "travel_jerk", "infill_jerk"}) + toggle_line(el, false); } else { toggle_field("default_junction_deviation", false); toggle_field("default_jerk", true); bool have_default_jerk = config->has("default_jerk") && config->opt_float("default_jerk") > 0; - for (auto el : { "outer_wall_jerk", "inner_wall_jerk", "initial_layer_jerk", "top_surface_jerk", "travel_jerk", "infill_jerk"}) { + for (auto el : { "outer_wall_jerk", "inner_wall_jerk", "initial_layer_jerk", "initial_layer_travel_jerk", "top_surface_jerk", "travel_jerk", "infill_jerk"}) { toggle_line(el, true); toggle_field(el, have_default_jerk); } From d76da458bc66dcce3b961c4c6eb6120fd398d448 Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:51:54 +0300 Subject: [PATCH 05/42] Update HU language (#13321) Update the HU language file with the newly message IDs introduced by PR #11674. --- localization/i18n/hu/OrcaSlicer_hu.po | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/localization/i18n/hu/OrcaSlicer_hu.po b/localization/i18n/hu/OrcaSlicer_hu.po index dd71a8e1db..7253ad7b9b 100644 --- a/localization/i18n/hu/OrcaSlicer_hu.po +++ b/localization/i18n/hu/OrcaSlicer_hu.po @@ -14963,7 +14963,19 @@ msgid "First layer travel speed" msgstr "Első réteg mozgási sebessége" msgid "Travel speed of the first layer." -msgstr "Az első réteg utazási sebessége." +msgstr "Az első réteg mozgási sebessége." + +msgid "First layer travel acceleration" +msgstr "Első réteg mozgási gyorsulása" + +msgid "Travel acceleration of first layer." +msgstr "Az első réteg mozgási gyorsulása." + +msgid "First layer travel jerk" +msgstr "Első réteg mozgási jerkje" + +msgid "Travel jerk of first layer." +msgstr "Az első réteg mozgási jerkje." msgid "Number of slow layers" msgstr "Lassú rétegek száma" @@ -22662,7 +22674,7 @@ msgstr "" "Mikor nyomtass nyitott ajtóval\n" "Tudtad, hogy a nyomtató ajtajának kinyitásával csökkentheted az extruder/" "fejegység eltömődésének valószínűségét, ha alacsonyabb hőmérsékletű " -"filamentet nyomtatsz? További információ a Wikiben olvashatsz erről." +"filamentet nyomtatsz? További információ erről a Wikiben található." #: resources/data/hints.ini: [hint:Avoid warping] msgid "" @@ -22738,5 +22750,3 @@ msgstr "" #~ "maximális áramlás közül a kisebbik korlátozza. Kikapcsolva csak a " #~ "felhasználó által megadott maximális áramlás érvényesül." -#~ msgid "Travel speed of First layer." -#~ msgstr "Az első réteg mozgási sebessége." From eba1769eddccfc23cac1dbc801444bb69687433d Mon Sep 17 00:00:00 2001 From: "Neko.vecter" <36069884+Neko-vecter@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:05:23 -0400 Subject: [PATCH 06/42] i18n: fix local in zh_CN (#13235) --- localization/i18n/zh_CN/OrcaSlicer_zh_CN.po | 76 ++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po index fdf0705303..2c68d0b76f 100644 --- a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po +++ b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po @@ -1764,7 +1764,7 @@ msgid "Choose one file (GCODE/3MF):" msgstr "选择一个文件(GCODE/3MF):" msgid "Ext" -msgstr "分机" +msgstr "Ext" msgid "Some presets are modified." msgstr "预设已被修改。" @@ -5726,13 +5726,13 @@ msgid "Pass 1" msgstr "粗调" msgid "Flow ratio test - Pass 1" -msgstr "流量比例测试 - 通过 1" +msgstr "流量比例测试 - 粗调" msgid "Pass 2" -msgstr "细调" +msgstr "微调" msgid "Flow ratio test - Pass 2" -msgstr "流量比例测试 - 通过 2" +msgstr "流量比例测试 - 微调" msgid "YOLO (Recommended)" msgstr "YOLO(推荐)" @@ -5871,7 +5871,7 @@ msgid "The project is no longer available." msgstr "此项目不可用。" msgid "Filament Settings" -msgstr "打印丝设置" +msgstr "材料设置" msgid "" "Do you want to synchronize your personal data from Bambu Cloud?\n" @@ -5880,7 +5880,7 @@ msgid "" "2. The Filament presets\n" "3. The Printer presets" msgstr "" -"想从Bambu 云同步你的个人数据吗?\n" +"想从 Bambu 云同步你的个人数据吗?\n" "包含如下信息:\n" "1. 工艺预设\n" "2. 打印丝预设\n" @@ -5923,7 +5923,7 @@ msgid "Initializing..." msgstr "正在初始化……" msgid "Connection Failed. Please check the network and try again" -msgstr "链接失败。请检查网络后重试" +msgstr "连接失败。请检查网络后重试" msgid "" "Please check the network and try again. You can restart or update the " @@ -6349,7 +6349,7 @@ msgid "Debug Info" msgstr "调试信息" msgid "Filament loading..." -msgstr "耗材丝进料..." +msgstr "耗材丝进料中..." msgid "No Storage" msgstr "无存储" @@ -7304,8 +7304,8 @@ msgid "" "The object from file %s is too small, and maybe in meters or inches.\n" " Do you want to scale to millimeters?" msgstr "" -"文件 %s 中对象的尺寸似乎是以米或者英寸为单位定义的。\n" -"逆戟鲸切片器的内部单位为毫米。是否要转换成毫米?" +"文件 %s 中对象的尺寸过小,似乎是以米(m)或者英寸(inch)为单位定义的。\n" +"OrcaSlicer的内部单位为毫米(mm)。是否要转换成毫米(mm)?" msgid "Object too small" msgstr "对象尺寸过小" @@ -7547,7 +7547,7 @@ msgid "" msgstr "未提供校准加速度。使用默认加速度值" msgid "mm/s²" -msgstr "毫米/秒²" +msgstr "mm/s²" msgid "No speeds provided for calibration. Use default optimal speed " msgstr "未提供校准速度。使用默认最佳速度" @@ -7609,10 +7609,10 @@ msgid "The current project has unsaved changes, save it before continue?" msgstr "当前项目包含未保存的修改,是否先保存?" msgid "Number of copies:" -msgstr "克隆数量:" +msgstr "复制数量:" msgid "Copies of the selected object" -msgstr "所选对象的克隆数量" +msgstr "所选对象的复制数量" msgid "Save G-code file as:" msgstr "G-code文件另存为:" @@ -7752,19 +7752,19 @@ msgstr "对象名字:%1%\n" #, boost-format msgid "Size: %1% x %2% x %3% in\n" -msgstr "大小:%1% x %2% x %3% 英寸\n" +msgstr "大小:%1% x %2% x %3% in\n" #, boost-format msgid "Size: %1% x %2% x %3% mm\n" -msgstr "大小: %1% x %2% x %3% 毫米\n" +msgstr "大小: %1% x %2% x %3% mm\n" #, boost-format msgid "Volume: %1% in³\n" -msgstr "体积: %1% 英寸³\n" +msgstr "体积: %1% in³\n" #, boost-format msgid "Volume: %1% mm³\n" -msgstr "体积: %1% 毫米³\n" +msgstr "体积: %1% mm³\n" #, boost-format msgid "Triangles: %1%\n" @@ -7813,7 +7813,7 @@ msgid "rear" msgstr "后部" msgid "Switching the language requires application restart.\n" -msgstr "切换语言要求重启应用程序。\n" +msgstr "切换语言需要重启应用程序。\n" msgid "Do you want to continue?" msgstr "是否继续?" @@ -7852,7 +7852,7 @@ msgid "The period of backup in seconds." msgstr "备份的周期" msgid "Bed Temperature Difference Warning" -msgstr "床温差警告" +msgstr "热床温度不同警告" msgid "" "Using filaments with significantly different temperatures may cause:\n" @@ -7895,10 +7895,10 @@ msgid "General" msgstr "常规" msgid "Metric" -msgstr "公制" +msgstr "公制(Metric)" msgid "Imperial" -msgstr "英制" +msgstr "英制(Imperial)" msgid "Units" msgstr "单位" @@ -8027,7 +8027,7 @@ msgid "Optimizes filament area maximum height by chosen filament count." msgstr "根据选定的耗材丝数量优化耗材区域最大高度" msgid "Features" -msgstr "特征" +msgstr "特性" msgid "Multi device management" msgstr "多设备管理" @@ -10226,7 +10226,7 @@ msgid "" msgstr "仅同步耗材丝类型和颜色,不包括插槽信息。" msgid "Ext spool" -msgstr "外线轴" +msgstr "外置耗材盘" msgid "" "Please check whether the nozzle type of the device is the same as the preset " @@ -10300,16 +10300,16 @@ msgid "For constant flow rate, hold %1% while dragging." msgstr "为保持恒定流量,拖动时按住%1%" msgid "ms" -msgstr "多发性硬化症" +msgstr "ms" msgid "Total ramming" -msgstr "总顶压" +msgstr "总冲刷量" msgid "Volume" msgstr "体积" msgid "Ramming line" -msgstr "顶压线" +msgstr "预冲刷线" msgid "" "Orca would re-calculate your flushing volumes everytime the filaments color " @@ -11968,7 +11968,7 @@ msgstr "" "以及对象的流量比(如果已设置)来计算的。" msgid "Support flow ratio" -msgstr "支持流量比" +msgstr "支撑流量比" msgid "" "This factor affects the amount of material for support.\n" @@ -11980,7 +11980,7 @@ msgstr "" "象的流量比(如果已设置)来计算的。" msgid "Support interface flow ratio" -msgstr "支持接口流量比例" +msgstr "支撑面流量比例" msgid "" "This factor affects the amount of material for the support interface.\n" @@ -13049,10 +13049,10 @@ msgid "" msgstr "打印此材料的所需的最小喷嘴硬度。零值表示不检查喷嘴硬度。" msgid "Filament map to extruder" -msgstr "耗材图至挤出机" +msgstr "耗材映射到挤出机" msgid "Filament map to extruder." -msgstr "耗材图到挤出机。" +msgstr "耗材映射到挤出机。" msgid "Auto For Flush" msgstr "自动冲洗" @@ -15768,7 +15768,7 @@ msgstr "" "不使用该值。" msgid "∆℃" -msgstr "℃" +msgstr "∆℃" msgid "Preheat time" msgstr "预热时间" @@ -15860,7 +15860,7 @@ msgid "Enable filament ramming" msgstr "启用耗材尖端成型" msgid "No sparse layers (beta)" -msgstr "无稀疏层 (实验)" +msgstr "无稀疏层 (实验功能)" msgid "" "If enabled, the wipe tower will not be printed on layers with no tool " @@ -19170,7 +19170,7 @@ msgid "" "Note: If the only preset under this filament is deleted, the filament will " "be deleted after exiting the dialog." msgstr "" -"注意:如果在该耗材下仅有的预设被删除,那么在退出对话框后,该耗材将被删除。" +"注意:如果在该耗材下仅有的预设被删除,在关闭对话框后,该耗材将被删除。" msgid "Presets inherited by other presets cannot be deleted" msgstr "附属于其他预设的预设不能被删除。" @@ -19341,7 +19341,7 @@ msgid "Success!" msgstr "成功!" msgid "Are you sure to log out?" -msgstr "您确定要注销吗?" +msgstr "您确定要登出吗?" msgid "View print host webui in Device tab" msgstr "在 设备 标签页中查看打印机主机的网页界面" @@ -19760,7 +19760,7 @@ msgid "" "set the outer wall speed to be 40 to 60 mm/s when slicing." msgstr "" "为了使打印件获得更高的光泽度,请在使用前将耗材干燥,并在切片时将外壁速度设置" -"为 40 至 60 毫米/秒。" +"为 40 至 60 mm/s。" msgid "" "This filament is only used to print models with a low density usually, and " @@ -19793,7 +19793,7 @@ msgid "" "the AMS. Printing it is of many requirements, and to get better printing " "quality, please refer to this wiki: TPU printing guide." msgstr "" -"该耗材具有足够高的硬度(约 67 D)并且与 AMS 兼容。打印此类耗材需要满足较多条" +"该耗材具有足够高的硬度(约 67D)并且与 AMS 兼容。打印此类耗材需要满足较多条" "件,为了获得更好的打印质量,请参考这个英文wiki:TPU printing guide(“TPU打印" "指南”)" @@ -19884,7 +19884,7 @@ msgid "The number of printers in use simultaneously cannot be equal to 0." msgstr "同时使用的打印机数量不能等于0。" msgid "Use External Spool" -msgstr "使用外置线卷" +msgstr "使用外置耗材盘" msgid "Select Printers" msgstr "选择打印机" @@ -20642,7 +20642,7 @@ msgid "" "Did you know that you can save wasted filament by flushing it into support/" "objects/infill during filament change?" msgstr "" -"冲刷到支持/对象/填充中\n" +"冲刷到支撑/对象/填充中\n" "你知道吗?你可以在换料时将它们冲入支撑/对象/填充,以节省浪费的料丝。" #: resources/data/hints.ini: [hint:Improve strength] From ec20a02fa58568d6e36ea89d0259842f248a6265 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Wed, 22 Apr 2026 12:09:41 -0300 Subject: [PATCH 07/42] Initial travel jerk/accell wiki + desc (#13324) --- src/libslic3r/PrintConfig.cpp | 4 ++-- src/slic3r/GUI/Tab.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f8918cb7b8..72fb71ceab 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3033,7 +3033,7 @@ void PrintConfigDef::init_fff_params() def = this->add("initial_layer_travel_acceleration", coFloatOrPercent); def->label = L("First layer travel"); - def->tooltip = L("Travel acceleration of first layer."); + def->tooltip = L("Travel acceleration of first layer.\nThe percentage value is relative to Travel Acceleration."); def->sidetext = L("mm/s² or %"); def->min = 0; def->mode = comAdvanced; @@ -3132,7 +3132,7 @@ void PrintConfigDef::init_fff_params() def = this->add("initial_layer_travel_jerk", coFloatOrPercent); def->label = L("First layer travel"); - def->tooltip = L("Travel jerk of first layer."); + def->tooltip = L("Travel jerk of first layer.\nThe percentage value is relative to Travel Jerk."); def->sidetext = L("mm/s or %"); def->min = 0; def->mode = comAdvanced; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a38893d7bd..9ccf458323 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2503,7 +2503,7 @@ void TabPrint::build() optgroup->append_single_option_line("sparse_infill_acceleration", "speed_settings_acceleration#sparse-infill"); optgroup->append_single_option_line("internal_solid_infill_acceleration", "speed_settings_acceleration#internal-solid-infill"); optgroup->append_single_option_line("initial_layer_acceleration", "speed_settings_acceleration#initial-layer"); - optgroup->append_single_option_line("initial_layer_travel_acceleration"); + optgroup->append_single_option_line("initial_layer_travel_acceleration", "speed_settings_acceleration#initial-layer-travel"); optgroup->append_single_option_line("top_surface_acceleration", "speed_settings_acceleration#top-surface"); optgroup->append_single_option_line("travel_acceleration", "speed_settings_acceleration#travel"); optgroup->append_single_option_line("accel_to_decel_enable", "speed_settings_acceleration"); @@ -2517,7 +2517,7 @@ void TabPrint::build() optgroup->append_single_option_line("infill_jerk", "speed_settings_jerk_xy#infill"); optgroup->append_single_option_line("top_surface_jerk", "speed_settings_jerk_xy#top-surface"); optgroup->append_single_option_line("initial_layer_jerk", "speed_settings_jerk_xy#initial-layer"); - optgroup->append_single_option_line("initial_layer_travel_jerk"); + optgroup->append_single_option_line("initial_layer_travel_jerk", "speed_settings_jerk_xy#initial-layer-travel"); optgroup->append_single_option_line("travel_jerk", "speed_settings_jerk_xy#travel"); optgroup = page->new_optgroup(L("Advanced"), L"param_advanced", 15); From 81d81da08c935544d07764aec02a86d67ca74a5d Mon Sep 17 00:00:00 2001 From: Misterff1 Date: Wed, 22 Apr 2026 17:28:18 +0200 Subject: [PATCH 08/42] Add more granular control over exhaust fan speed during print and on completion (#13228) # Description This commit adds the enhancement of https://github.com/OrcaSlicer/OrcaSlicer/issues/13116 - Adds checkboxes to enable or disable the fan speed override during print or after printing is completed. This allows users to, for example, only override the fan speed during printing whilst leaving the fan speed as is set in the machine gcode at the end. - Two new flags are added for this: `activate_air_filtration_during_print[extruder]` and `activate_air_filtration_on_completion[extruder]`. These can be used to more finely control machine gcode - Current filament settings remain as they are: `activate_air_filtration[extruder]` settings are unaffected and by default both new flags are set to true, ensuring the same set behavior as before. # Screenshots/Recordings/Graphs Screenshot From 2026-04-09 18-57-14 As you can see there are now checkboxes left of the fan speeds. They have their own tooltip too, which also helps identify the correct flags for users who want to adjust their machine gcode. ## Tests I have thoroughly tested this on my own computer (CachyOS) and on my printer. I have also carefully checked the gcode in every possible state this is in and ensured that the unsaved changes dialog properly displays the labels for these settings. Flags are set properly, sliced files properly use the flags if you check for them in machine gcode and default behavior is unaffected for those who have already changed settings for air filtration. From what I can see, this does exactly what it should be doing without any issues. --- src/libslic3r/GCode.cpp | 24 ++++++++++++++---------- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 2 ++ src/libslic3r/PrintConfig.cpp | 20 +++++++++++++++++++- src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Tab.cpp | 14 +++++++++++--- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3658070fdb..c67d8b6695 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3099,15 +3099,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true); } // Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed - bool activate_air_filtration = false; + bool activate_air_filtration_during_print = false; int during_print_exhaust_fan_speed = 0; for (const auto &extruder : m_writer.extruders()) { - activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id()); - if (m_config.activate_air_filtration.get_at(extruder.id())) + if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) { + activate_air_filtration_during_print = true; during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed, m_config.during_print_exhaust_fan_speed.get_at(extruder.id())); + } } - if (activate_air_filtration) + if (activate_air_filtration_during_print) file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed, true)); print.throw_if_canceled(); @@ -3407,13 +3408,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato if (activate_chamber_temp_control && max_chamber_temp > 0) file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature - if (activate_air_filtration) { - int complete_print_exhaust_fan_speed = 0; - for (const auto& extruder : m_writer.extruders()) - if (m_config.activate_air_filtration.get_at(extruder.id())) - complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); - file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); + bool activate_air_filtration_on_completion = false; + int complete_print_exhaust_fan_speed = 0; + for (const auto& extruder : m_writer.extruders()) { + if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) { + activate_air_filtration_on_completion = true; + complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); + } } + if (activate_air_filtration_on_completion) + file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); // adds tags for time estimators file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); file.write_format("; EXECUTABLE_BLOCK_END\n\n"); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 65397d11e1..9e2437ae0a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -972,7 +972,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", "filament_start_gcode", "filament_end_gcode", //exhaust fan control - "activate_air_filtration","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", + "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", // Retract overrides "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b9e8842b0b..ad6e279cd4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -219,6 +219,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "ironing_fan_speed", "single_extruder_multi_material_priming", "activate_air_filtration", + "activate_air_filtration_during_print", + "activate_air_filtration_on_completion", "during_print_exhaust_fan_speed", "complete_print_exhaust_fan_speed", "activate_chamber_temp_control", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 72fb71ceab..7139341e47 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1740,6 +1740,19 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Activate for better air filtration. G-code command: M106 P3 S(0-255)"); def->mode = comSimple; def->set_default_value(new ConfigOptionBools{false}); + + + def = this->add("activate_air_filtration_during_print", coBools); + def->full_label = L("During print"); + def->tooltip=L("Enable this to override the fan speed set in custom G-code during print."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBools{true}); + + def = this->add("activate_air_filtration_on_completion", coBools); + def->full_label = L("On completion"); + def->tooltip=L("Enable this to override the fan speed set in custom G-code after print completion."); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBools{true}); def = this->add("during_print_exhaust_fan_speed", coInts); def->label = L("Fan speed"); @@ -7862,7 +7875,12 @@ std::set filament_options_with_variant = { "filament_ironing_flow", "filament_ironing_spacing", "filament_ironing_inset", - "filament_ironing_speed" + "filament_ironing_speed", + "activate_air_filtration", + "activate_air_filtration_during_print", + "activate_air_filtration_on_completion", + "during_print_exhaust_fan_speed", + "complete_print_exhaust_fan_speed" }; // Parameters that are the same as the number of extruders diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0acd1747a6..60aff4f0e9 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1457,6 +1457,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBools, dont_slow_down_outer_wall)) ((ConfigOptionFloats, fan_cooling_layer_time)) ((ConfigOptionBools, activate_air_filtration)) + ((ConfigOptionBools, activate_air_filtration_during_print)) + ((ConfigOptionBools, activate_air_filtration_on_completion)) ((ConfigOptionInts, during_print_exhaust_fan_speed)) ((ConfigOptionInts, complete_print_exhaust_fan_speed)) ((ConfigOptionFloatOrPercent, initial_layer_line_width)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9ccf458323..4c2a908cf6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4018,16 +4018,18 @@ void TabFilament::build() optgroup = page->new_optgroup(L("Exhaust fan"),L"param_cooling_exhaust"); - optgroup->append_single_option_line("activate_air_filtration", "material_cooling#activate-air-filtration"); + optgroup->append_single_option_line("activate_air_filtration", "material_cooling#activate-air-filtration", 0); line = {L("During print"), ""}; - line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); + line.append_option(optgroup->get_option("activate_air_filtration_during_print", 0)); + line.append_option(optgroup->get_option("during_print_exhaust_fan_speed", 0)); line.label_path = "material_cooling#during-print"; optgroup->append_line(line); line = {L("Complete print"), ""}; - line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); + line.append_option(optgroup->get_option("activate_air_filtration_on_completion", 0)); + line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed", 0)); line.label_path = "material_cooling#complete-print"; optgroup->append_line(line); //BBS @@ -4195,6 +4197,12 @@ void TabFilament::toggle_options() toggle_option("additional_cooling_fan_speed", cfg.opt_bool("auxiliary_fan")); + bool activate_air_filtration = m_config->opt_bool("activate_air_filtration", 0); + toggle_option("activate_air_filtration_during_print", activate_air_filtration, 0); + toggle_option("activate_air_filtration_on_completion", activate_air_filtration, 0); + toggle_option("during_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_during_print", 0), 0); + toggle_option("complete_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_on_completion", 0), 0); + // Orca: toggle dont slow down for external perimeters if 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); From 1cc38cda06964ca502f9a66b753bfd6c97b982c9 Mon Sep 17 00:00:00 2001 From: Alexandre Folle de Menezes Date: Wed, 22 Apr 2026 12:29:09 -0300 Subject: [PATCH 09/42] Remove the wrong "c-format, boost-format" marker from non-format string (#13251) # Description Ditto. --- localization/i18n/pt_BR/OrcaSlicer_pt_BR.po | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po index 9e136b4e09..5f9ba1a7d6 100644 --- a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po +++ b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po @@ -4331,7 +4331,7 @@ msgstr "" "seam_slope_start_height precisa ser menor que layer_height.\n" "Redefinir para 0." -#, fuzzy, c-format, boost-format +#, no-c-format, no-boost-format msgid "" "Lock depth should smaller than skin depth.\n" "Reset to 50% of skin depth." diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 98baf6d641..de318bb96f 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -513,6 +513,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con // layer_height shouldn't be equal to zero float skin_depth = config->opt_float("skin_infill_depth"); if (config->opt_float("infill_lock_depth") > skin_depth) { + // xgettext:no-c-format, no-boost-format const wxString msg_text = _(L("Lock depth should smaller than skin depth.\nReset to 50% of skin depth.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; From 3611903b8d953b06d7b77a2a9504872463bda68c Mon Sep 17 00:00:00 2001 From: HYzd766 <108379794+HYzd766@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:00:18 +0800 Subject: [PATCH 10/42] QIDI: Disable Reduce infill retraction (#13267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 关掉减小填充回抽功能 * 关掉减小填充回抽功能 * Update Qidi X-Max 4 0.4 nozzle.json Optimize the starting code of MAX4 --- resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json | 2 +- resources/profiles/Qidi/process/0.12mm Fine @Qidi X3.json | 2 +- resources/profiles/Qidi/process/0.12mm Fine @Qidi XCFPro.json | 2 +- resources/profiles/Qidi/process/0.12mm Fine @Qidi XMax.json | 2 +- resources/profiles/Qidi/process/0.12mm Fine @Qidi XPlus.json | 2 +- resources/profiles/Qidi/process/0.16mm Optimal @Qidi X3.json | 2 +- .../profiles/Qidi/process/0.16mm Optimal @Qidi XCFPro.json | 2 +- resources/profiles/Qidi/process/0.16mm Optimal @Qidi XMax.json | 2 +- resources/profiles/Qidi/process/0.16mm Optimal @Qidi XPlus.json | 2 +- .../profiles/Qidi/process/0.20mm Standard @Qidi XCFPro.json | 2 +- resources/profiles/Qidi/process/0.20mm Standard @Qidi XMax.json | 2 +- .../profiles/Qidi/process/0.20mm Standard @Qidi XPlus.json | 2 +- resources/profiles/Qidi/process/0.24mm Draft @Qidi X3.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi Q1 Pro.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2C.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XCFPro.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax3.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus3.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus4.json | 2 +- resources/profiles/Qidi/process/0.25mm Draft @Qidi XSmart3.json | 2 +- .../profiles/Qidi/process/0.28mm Extra Draft @Qidi X3.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi Q1 Pro.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2C.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XCFPro.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax3.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus3.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus4.json | 2 +- .../profiles/Qidi/process/0.30mm Extra Draft @Qidi XSmart3.json | 2 +- resources/profiles/Qidi/process/fdm_process_n_common.json | 2 +- resources/profiles/Qidi/process/fdm_process_qidi_common.json | 2 +- resources/profiles/Qidi/process/fdm_process_qidi_x3_common.json | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json b/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json index d5c672cee3..d70e0e96e7 100644 --- a/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json +++ b/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json @@ -57,7 +57,7 @@ "machine_max_speed_y": [ "800" ], - "machine_start_gcode": "INIT_MAPPING_VALUE\n;===== PRINT_PHASE_INIT =====\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nSET_PRINT_MAIN_STATUS MAIN_STATUS=print_start\nM220 S100\nM221 S100\nSET_INPUT_SHAPER SHAPER_TYPE_X=mzv\nSET_INPUT_SHAPER SHAPER_TYPE_Y=mzv\nDISABLE_ALL_SENSOR\nM1002 R1\nM107\nCLEAR_PAUSE\nM140 S[bed_temperature_initial_layer_single]\nM141 S[chamber_temperature]\nG29.0\nG28\n\n;===== BOX_PREPAR =====\nBOX_PRINT_START EXTRUDER=[initial_no_support_extruder] HOTENDTEMP={nozzle_temperature_range_high[initial_tool]}\nM400\nEXTRUSION_AND_FLUSH HOTEND=[nozzle_temperature_initial_layer]\n\n;===== CLEAR_NOZZLE =====\nG1 Z20 F480\nMOVE_TO_TRASH\nG1 Y403.5 F2000\n{if chamber_temperature[0] == 0}\nM106 P3 S[during_print_exhaust_fan_speed]\n{else}\nM106 P3 S0\n{endif}\nM1004\nM106 S0\nM109 S[nozzle_temperature_initial_layer]\nG92 E0\nM83\nG1 E5 F80\nG1 E250 F300\nM400\nM106 S255\nG1 E-3 F1000\nM104 S140\nM109.1 S{nozzle_temperature_initial_layer[0]-30}\nM204 S10000\nG1 Y403 F2000\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F8000\nG1 X145 F5000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F10000\nG1 Y395 F6000\nG1 X188\nG1 Z-0.2 F480\nM106 S255\nM109.1 S150\nG91\nG1 X15 F200\nG1 Y2\nG1 X-15\nG1 Y-2\nG1 X15\nG90\nG2 I0.5 J0.5 F480\nG2 I0.5 J0.5\nG2 I0.5 J0.5\nG1 Z10\nG1 Y383 F12000\nG1 X116\nG1 Y403\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F8000\nG1 X145 F5000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F10000\nG1 X195 Y195\nM106 S0\nM190 S[bed_temperature_initial_layer_single]\nM191 S[chamber_temperature]\nM400\nSET_OPERATING_CURRENT STEPPER=x VALUE=1500\nG4 P400\nSET_OPERATING_CURRENT STEPPER=y VALUE=1500\nG4 P400\nG1 Y0 F15000\nG1 X15\nG1 X3 F5000\nG4 P1000\nG1 X4 F1000\nG1 X3 F5000\nG4 P1000\nG1 E-4 F1800\nG1 X15 F3000\n\nM400\nSET_OPERATING_CURRENT STEPPER=x VALUE=1200\nG4 P400\nSET_OPERATING_CURRENT STEPPER=y VALUE=1200\nG4 P2000\nG1 X20 Y20 F15000\nZ_TILT_ADJUST\nG29\nM1002 A1\nG1 X195 Y195 Z10 F20000\nG92_ Z{10 - ((nozzle_temperature_initial_layer[initial_tool] - 130) / 14 - 5.0) / 100}\nG0 Y1\nM109 S[nozzle_temperature_initial_layer]\nENABLE_ALL_SENSOR\n\n;===== PRINT_START =====\n; LAYER_HEIGHT: 0.2\nT[initial_tool]\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nprobe samples=1\nG91\nG0 Z0.6 F480\nG90\nG1 X175 Y1 F20000\nG1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053*60}\nG1 X215 E20 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053*60}\nG1 Z1 F480\nSET_PRINT_MAIN_STATUS MAIN_STATUS=printing", + "machine_start_gcode": ";===== PRINT_PHASE_INIT =====\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nSET_PRINT_MAIN_STATUS MAIN_STATUS=print_start\nM220 S100\nM221 S100\nSET_INPUT_SHAPER SHAPER_TYPE_X=mzv\nSET_INPUT_SHAPER SHAPER_TYPE_Y=mzv\nDISABLE_ALL_SENSOR\nM1002 R1\nM107\nCLEAR_PAUSE\nM140 S[bed_temperature_initial_layer_single]\nM141 S[chamber_temperature]\nG29.0\nSET_PRINT_SUB_STATUS SUB_STATUS=tool_head_reset\nG28\n\n;===== BOX_PREPAR =====\nSET_PRINT_SUB_STATUS SUB_STATUS=change_filament\nBOX_PRINT_START EXTRUDER=[initial_no_support_extruder] HOTENDTEMP={nozzle_temperature_range_high[initial_tool]}\nM400\nEXTRUSION_AND_FLUSH HOTEND=[nozzle_temperature_initial_layer]\n\n;===== CLEAR_NOZZLE =====\nSET_PRINT_SUB_STATUS SUB_STATUS=flush_filament\nG1 Z20 F480\nMOVE_TO_TRASH\nG1 Y403.5 F2000\n{if chamber_temperature[0] == 0}\nM106 P3 S[during_print_exhaust_fan_speed]\n{else}\nM106 P3 S0\n{endif}\nM1004\nM106 S0\nM109 S[nozzle_temperature_initial_layer]\nG92 E0\nM83\nG1 E5 F80\nG1 E250 F300\nM400\nM106 S255\nG1 E-3 F1000\nM104 S140\nSET_PRINT_SUB_STATUS SUB_STATUS=clear_nozzle\nM109.1 S{nozzle_temperature_initial_layer[0]-30}\nM204 S10000\nG1 Y403 F2000\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F8000\nG1 X145 F5000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F10000\nG1 Y395 F6000\nG1 X188\nG1 Z-0.2 F480\nM106 S255\nM109.1 S150\nG91\nG1 X15 F200\nG1 Y2\nG1 X-15\nG1 Y-2\nG1 X15\nG90\nG2 I0.5 J0.5 F480\nG2 I0.5 J0.5\nG2 I0.5 J0.5\nG1 Z10\nG1 Y383 F12000\nG1 X116\nG1 Y403\nG1 X163 F8000\nG1 X145 F5000\nG1 X163 F8000\nG1 X145 F5000\nG1 X175 F6000\nG1 X163\nG1 X175\nG1 X163\nG1 X175\nG1 X163\nG1 X180 F10000\nG1 X195 Y195\nM106 S0\nSET_PRINT_SUB_STATUS SUB_STATUS=wait_bed_temp\nM190 S[bed_temperature_initial_layer_single]\nSET_PRINT_SUB_STATUS SUB_STATUS=wait_chamber_temp\nM191 S[chamber_temperature]\nG1 Y0 F15000\nG1 X15\nG1 X3 F5000\nG4 P1000\nG1 X4 F1000\nG1 X3 F5000\nG4 P1000\nG1 E-4 F1800\nG1 X15 F3000\nG1 X20 Y20 F15000\nSET_PRINT_SUB_STATUS SUB_STATUS=z_tilt_adjust\nZ_TILT_ADJUST\nSET_PRINT_SUB_STATUS SUB_STATUS=auto_bed_adjust\nG29\nM1002 A1\nG1 X195 Y195 Z10 F20000\nG92_ Z{10 - ((nozzle_temperature_initial_layer[initial_tool] - 130) / 14 - 5.0) / 100}\nG0 Y1\nM109 S[nozzle_temperature_initial_layer]\nENABLE_ALL_SENSOR\n\n;===== PRINT_START =====\n; LAYER_HEIGHT: 0.2\nT[initial_tool]\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nprobe samples=1\nG91\nG0 Z0.6 F480\nG90\nG1 X175 Y1 F20000\nG1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053*60}\nG1 X215 E20 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053*60}\nG1 Z1 F480\nSET_PRINT_MAIN_STATUS MAIN_STATUS=printing", "nozzle_diameter": [ "0.4" ], diff --git a/resources/profiles/Qidi/process/0.12mm Fine @Qidi X3.json b/resources/profiles/Qidi/process/0.12mm Fine @Qidi X3.json index 04e89f160a..856c04af32 100644 --- a/resources/profiles/Qidi/process/0.12mm Fine @Qidi X3.json +++ b/resources/profiles/Qidi/process/0.12mm Fine @Qidi X3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XCFPro.json b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XCFPro.json index 978fef59cb..aab6b90d00 100644 --- a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XCFPro.json +++ b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XCFPro.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XMax.json b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XMax.json index d761beae95..30f8b1e2d7 100644 --- a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XMax.json +++ b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XMax.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XPlus.json b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XPlus.json index 370f398d6c..2438a54632 100644 --- a/resources/profiles/Qidi/process/0.12mm Fine @Qidi XPlus.json +++ b/resources/profiles/Qidi/process/0.12mm Fine @Qidi XPlus.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi X3.json b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi X3.json index 629aa553f8..1de2e53487 100644 --- a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi X3.json +++ b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi X3.json @@ -32,7 +32,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XCFPro.json b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XCFPro.json index 3d08653107..f48d5ed26f 100644 --- a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XCFPro.json +++ b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XCFPro.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XMax.json b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XMax.json index f5ba3f97d0..61070ce4e1 100644 --- a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XMax.json +++ b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XMax.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XPlus.json b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XPlus.json index e39ea686f1..ee81050de7 100644 --- a/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XPlus.json +++ b/resources/profiles/Qidi/process/0.16mm Optimal @Qidi XPlus.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XCFPro.json b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XCFPro.json index a9a0d0a5c8..cae0bca0c0 100644 --- a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XCFPro.json +++ b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XCFPro.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XMax.json b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XMax.json index 677d8d85aa..73d92a8aad 100644 --- a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XMax.json +++ b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XMax.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XPlus.json b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XPlus.json index 6e301151d4..25d0f427ad 100644 --- a/resources/profiles/Qidi/process/0.20mm Standard @Qidi XPlus.json +++ b/resources/profiles/Qidi/process/0.20mm Standard @Qidi XPlus.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.24mm Draft @Qidi X3.json b/resources/profiles/Qidi/process/0.24mm Draft @Qidi X3.json index de045aea8f..c4319985ce 100644 --- a/resources/profiles/Qidi/process/0.24mm Draft @Qidi X3.json +++ b/resources/profiles/Qidi/process/0.24mm Draft @Qidi X3.json @@ -30,7 +30,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q1 Pro.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q1 Pro.json index d4cf9c71c0..752802d615 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q1 Pro.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q1 Pro.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2.json index bb60a6d69f..1cf2127af9 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2C.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2C.json index a3da500452..0c12c542e1 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2C.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi Q2C.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XCFPro.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XCFPro.json index a561b8cabe..2a4a1939d7 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XCFPro.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XCFPro.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax.json index 6668c5db58..779e51ce5c 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax3.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax3.json index f1efd7e0ce..16f0898e67 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax3.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XMax3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus.json index 77f07c77da..35163be785 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus3.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus3.json index 07228915c7..22314df1d6 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus3.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus4.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus4.json index 5663b4d87a..eea82f1af6 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus4.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XPlus4.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XSmart3.json b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XSmart3.json index 946b22b2c8..e1cd57fe1e 100644 --- a/resources/profiles/Qidi/process/0.25mm Draft @Qidi XSmart3.json +++ b/resources/profiles/Qidi/process/0.25mm Draft @Qidi XSmart3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.28mm Extra Draft @Qidi X3.json b/resources/profiles/Qidi/process/0.28mm Extra Draft @Qidi X3.json index bf46754a16..e455467d5d 100644 --- a/resources/profiles/Qidi/process/0.28mm Extra Draft @Qidi X3.json +++ b/resources/profiles/Qidi/process/0.28mm Extra Draft @Qidi X3.json @@ -30,7 +30,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q1 Pro.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q1 Pro.json index bc77a45787..abebe5a095 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q1 Pro.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q1 Pro.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2.json index 7989b011c2..f9371fd9bc 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2C.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2C.json index 554459814d..b8dd180907 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2C.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi Q2C.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XCFPro.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XCFPro.json index 9a7a3b452f..1e9e0b2206 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XCFPro.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XCFPro.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax.json index a02ce0928e..186f136f67 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax3.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax3.json index f5eb1a43fc..70acdbf026 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax3.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XMax3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus.json index 4dea613c5b..c7ce549965 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus.json @@ -43,7 +43,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus3.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus3.json index e56ea3e702..fa9c82940b 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus3.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus4.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus4.json index c53d5ce575..ef03cf5818 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus4.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XPlus4.json @@ -36,7 +36,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XSmart3.json b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XSmart3.json index 3f660d8315..cc645f7de0 100644 --- a/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XSmart3.json +++ b/resources/profiles/Qidi/process/0.30mm Extra Draft @Qidi XSmart3.json @@ -35,7 +35,7 @@ "ironing_flow": "15%", "ironing_spacing": "0.1", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "inner_wall_line_width": "0.45", diff --git a/resources/profiles/Qidi/process/fdm_process_n_common.json b/resources/profiles/Qidi/process/fdm_process_n_common.json index 45c1fdbabd..28fcbe32a9 100644 --- a/resources/profiles/Qidi/process/fdm_process_n_common.json +++ b/resources/profiles/Qidi/process/fdm_process_n_common.json @@ -116,7 +116,7 @@ "print_settings_id": "", "raft_layers": "0", "reduce_crossing_wall": "0", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "resolution": "0.012", "seam_position": "aligned", "seam_placement_away_from_overhangs": "0", diff --git a/resources/profiles/Qidi/process/fdm_process_qidi_common.json b/resources/profiles/Qidi/process/fdm_process_qidi_common.json index 8172f9f3e0..3a7676e3fe 100644 --- a/resources/profiles/Qidi/process/fdm_process_qidi_common.json +++ b/resources/profiles/Qidi/process/fdm_process_qidi_common.json @@ -41,7 +41,7 @@ "ironing_spacing": "0.1", "ironing_speed": "15", "ironing_type": "no ironing", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", diff --git a/resources/profiles/Qidi/process/fdm_process_qidi_x3_common.json b/resources/profiles/Qidi/process/fdm_process_qidi_x3_common.json index 7be30d7acf..176add07f7 100644 --- a/resources/profiles/Qidi/process/fdm_process_qidi_x3_common.json +++ b/resources/profiles/Qidi/process/fdm_process_qidi_x3_common.json @@ -62,7 +62,7 @@ "ironing_speed": "30", "ironing_type": "no ironing", "layer_height": "0.2", - "reduce_infill_retraction": "1", + "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}_{filament_type[0]}_{print_time}.gcode", "detect_overhang_wall": "1", "overhang_1_4_speed": "0", From 5b05b6104a2f03070b756ea56f92a05d7383028a Mon Sep 17 00:00:00 2001 From: yogihybo Date: Thu, 23 Apr 2026 02:02:12 +1000 Subject: [PATCH 11/42] Fix first layer speed and layer slow down handling with rafts (#13224) Adjust initial- and slowdown-layer speed logic to correctly account for raft layers. Replace the previous layer checks with object_layer_over_raft(), simplify initial-layer speed selection, and split the slow_down_layers interpolation into separate branches for configurations with and without rafts so the lerp uses the correct layer offset. Also avoid applying overhang speed adjustments to object layers that are over rafts. --- src/libslic3r/GCode.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c67d8b6695..e23543869b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6365,14 +6365,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (speed == 0) speed = filament_max_volumetric_speed / _mm3_per_mm; - if (this->on_first_layer()) { + + const auto _layer = layer_id(); + if (this->on_first_layer() || object_layer_over_raft()) { //BBS: for solid infill of first layer, speed can be higher as long as //wall lines have be attached - if (path.role() != erBottomSurface) - speed = m_config.get_abs_value("initial_layer_speed"); - } - else if(m_config.slow_down_layers > 1){ - const auto _layer = layer_id(); + if (path.role() != erBottomSurface) { + speed = is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") : + m_config.get_abs_value("initial_layer_infill_speed"); + } + } else if (m_config.slow_down_layers > 1 && !m_config.raft_layers > 0) { + if (_layer > 0 && _layer < m_config.slow_down_layers) { const auto first_layer_speed = is_perimeter(path.role()) @@ -6382,7 +6385,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = std::min( speed, Slic3r::lerp(first_layer_speed, speed, - (double)_layer / m_config.slow_down_layers)); + (double) (_layer) / m_config.slow_down_layers)); + } + } + } else if (m_config.slow_down_layers > 1 && m_config.raft_layers > 0 ) { + + if (_layer > m_config.raft_layers && (_layer - m_config.raft_layers) < m_config.slow_down_layers) { + const auto first_layer_speed + = is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") : + m_config.get_abs_value("initial_layer_infill_speed"); + if (first_layer_speed < speed) { + speed = std::min(speed, Slic3r::lerp(first_layer_speed, speed, + (double) (_layer - m_config.raft_layers) / m_config.slow_down_layers)); } } } @@ -6445,7 +6459,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, bool variable_speed = false; std::vector new_points {}; - if (m_config.enable_overhang_speed && !this->on_first_layer() && + if (m_config.enable_overhang_speed && !this->on_first_layer() && !object_layer_over_raft() && (is_bridge(path.role()) || is_perimeter(path.role()))) { bool is_external = is_external_perimeter(path.role()); double ref_speed = is_external ? m_config.get_abs_value("outer_wall_speed") : m_config.get_abs_value("inner_wall_speed"); From e99382da680da615b7fd1886ed8e1252d1a4be9c Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:21:19 -0300 Subject: [PATCH 12/42] =?UTF-8?q?=F0=9F=92=A5Fix=20crash=20svg=20use=20sur?= =?UTF-8?q?face=20(#13262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix crash svg use surface * unnecesarry header --- src/libslic3r/CutSurface.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index 01a5a8383a..606a5b8aaa 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -2559,6 +2559,21 @@ void priv::create_face_types(FaceTypeMap &map, bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper) { CutMesh& tm = cut.mesh; + auto is_mesh_usable_for_clip = [](const CutMesh &mesh) { + return !mesh.is_empty() && + mesh.number_of_vertices() >= 3 && + mesh.number_of_faces() > 0 && + mesh.is_valid(false); + }; + + if (!is_mesh_usable_for_clip(tm) || !is_mesh_usable_for_clip(clipper)) + return false; + + // Hard-stop pathological inputs before entering corefinement internals. + if (CGAL::Polygon_mesh_processing::does_self_intersect(tm) || + CGAL::Polygon_mesh_processing::does_self_intersect(clipper)) + return false; + // create backup for case that there is no intersection CutMesh backup_copy = tm; From 709ec5171253d1e5b265680c2dd4f9596f8d097b Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Wed, 22 Apr 2026 13:22:34 -0300 Subject: [PATCH 13/42] Remove validation for Disabled Small area compensation (#12528) Do not check if disabled --- src/libslic3r/GCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e23543869b..333b9b842a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2476,7 +2476,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } else m_enable_extrusion_role_markers = false; - if (!print.config().small_area_infill_flow_compensation_model.empty()) + if (m_config.small_area_infill_flow_compensation.value && !m_config.small_area_infill_flow_compensation_model.empty()) m_small_area_infill_flow_compensator = make_unique(print.config()); // Process file_start_gcode - written at the very top of the file, before any header From b2ce9f649f9f2b63d6f33158998452127bcf9d9e Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:58:49 +0300 Subject: [PATCH 14/42] Fix air filtration/exhaust and chamber heater UI control (#12525) --- src/slic3r/GUI/Tab.cpp | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4c2a908cf6..cbd4690515 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3883,8 +3883,8 @@ void TabFilament::build() // optgroup = page->new_optgroup(L("Print chamber temperature"), L"param_chamber_temp"); - optgroup->append_single_option_line("chamber_temperature", "material_temperatures#print-chamber-temperature"); optgroup->append_single_option_line("activate_chamber_temp_control", "material_temperatures#print-chamber-temperature"); + optgroup->append_single_option_line("chamber_temperature", "material_temperatures#print-chamber-temperature"); optgroup = page->new_optgroup(L("Print temperature"), L"param_extruder_temp"); line = { L("Nozzle"), L("Nozzle temperature when printing") }; @@ -4018,18 +4018,18 @@ void TabFilament::build() optgroup = page->new_optgroup(L("Exhaust fan"),L"param_cooling_exhaust"); - optgroup->append_single_option_line("activate_air_filtration", "material_cooling#activate-air-filtration", 0); + optgroup->append_single_option_line("activate_air_filtration", "material_cooling#activate-air-filtration"); line = {L("During print"), ""}; - line.append_option(optgroup->get_option("activate_air_filtration_during_print", 0)); - line.append_option(optgroup->get_option("during_print_exhaust_fan_speed", 0)); + line.append_option(optgroup->get_option("activate_air_filtration_during_print")); + line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); line.label_path = "material_cooling#during-print"; optgroup->append_line(line); line = {L("Complete print"), ""}; - line.append_option(optgroup->get_option("activate_air_filtration_on_completion", 0)); - line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed", 0)); + line.append_option(optgroup->get_option("activate_air_filtration_on_completion")); + line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); line.label_path = "material_cooling#complete-print"; optgroup->append_line(line); //BBS @@ -4184,28 +4184,33 @@ void TabFilament::toggle_options() return; bool is_BBL_printer = false; if (m_preset_bundle) { - is_BBL_printer = - wxGetApp().preset_bundle->is_bbl_vendor(); + is_BBL_printer = wxGetApp().preset_bundle->is_bbl_vendor(); } - bool is_multi_extruder = m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->size() > 1; - auto cfg = m_preset_bundle->printers.get_edited_preset().config; + auto printer_cfg = m_preset_bundle->printers.get_edited_preset().config; + if (m_active_page->title() == L("Cooling")) { - bool has_enable_overhang_bridge_fan = m_config->opt_bool("enable_overhang_bridge_fan", 0); - for (auto el : {"overhang_fan_speed", "overhang_fan_threshold", "internal_bridge_fan_speed"}) // ORCA: Add support for separate internal bridge fan speed control + bool has_enable_overhang_bridge_fan = m_config->opt_bool("enable_overhang_bridge_fan", 0); + for (auto el : {"overhang_fan_speed", "overhang_fan_threshold", "internal_bridge_fan_speed"}) // ORCA: Add support for separate internal bridge fan speed control toggle_option(el, has_enable_overhang_bridge_fan); - toggle_option("additional_cooling_fan_speed", cfg.opt_bool("auxiliary_fan")); + // Orca: toggle dont slow down for external perimeters if + 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); - bool activate_air_filtration = m_config->opt_bool("activate_air_filtration", 0); - toggle_option("activate_air_filtration_during_print", activate_air_filtration, 0); - toggle_option("activate_air_filtration_on_completion", activate_air_filtration, 0); - toggle_option("during_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_during_print", 0), 0); - toggle_option("complete_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_on_completion", 0), 0); + toggle_line("additional_cooling_fan_speed", printer_cfg.opt_bool("auxiliary_fan")); - // Orca: toggle dont slow down for external perimeters if - 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); + bool support_air_filtration = printer_cfg.opt_bool("support_air_filtration"); + for (auto el : {"activate_air_filtration", "during_print_exhaust_fan_speed", "complete_print_exhaust_fan_speed"}) + toggle_line(el, support_air_filtration); + + if (support_air_filtration) { + bool activate_air_filtration = m_config->opt_bool("activate_air_filtration", 0); + toggle_option("activate_air_filtration_during_print", activate_air_filtration); + toggle_option("during_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_during_print", 0)); + toggle_option("activate_air_filtration_on_completion", activate_air_filtration); + toggle_option("complete_print_exhaust_fan_speed", activate_air_filtration && m_config->opt_bool("activate_air_filtration_on_completion", 0)); + } } if (m_active_page->title() == L("Filament")) { @@ -4226,7 +4231,7 @@ void TabFilament::toggle_options() bool support_multi_bed_types = std::find(bed_temp_keys.begin(), bed_temp_keys.end(), bed_temp_1st_layer_key) == bed_temp_keys.end() || - is_BBL_printer || cfg.opt_bool("support_multi_bed_types"); + is_BBL_printer || printer_cfg.opt_bool("support_multi_bed_types"); for (const auto& key : bed_temp_keys) { @@ -4245,20 +4250,26 @@ void TabFilament::toggle_options() toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa); toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa); - bool is_pellet_printer = cfg.opt_bool("pellet_modded_printer"); + bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer"); toggle_line("pellet_flow_coefficient", is_pellet_printer); toggle_line("filament_diameter", !is_pellet_printer); - bool support_chamber_temp_control = this->m_preset_bundle->printers.get_edited_preset().config.opt_bool("support_chamber_temp_control"); + bool support_chamber_temp_control = printer_cfg.opt_bool("support_chamber_temp_control"); + toggle_line("activate_chamber_temp_control", support_chamber_temp_control); toggle_line("chamber_temperature", support_chamber_temp_control); + if (support_chamber_temp_control) { + bool activate_chamber_temp_control = m_config->opt_bool("activate_chamber_temp_control", 0); + toggle_option("chamber_temperature", activate_chamber_temp_control); + } + std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", 0u); bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0"; toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + 0u); } if (m_active_page->title() == L("Setting Overrides")) - update_filament_overrides_page(&cfg); + update_filament_overrides_page(&printer_cfg); if (m_active_page->title() == L("Multimaterial")) { // Orca: hide specific settings for BBL printers @@ -4271,9 +4282,10 @@ void TabFilament::toggle_options() toggle_option("filament_multitool_ramming_volume", multitool_ramming); toggle_option("filament_multitool_ramming_flow", multitool_ramming); + bool is_BBL_multi_extruder = is_BBL_printer && printer_cfg.option("nozzle_diameter")->size() > 1; const int extruder_idx = 0; // m_variant_combo->GetSelection(); // TODO: Orca hack - toggle_line("long_retractions_when_ec", is_multi_extruder && is_BBL_printer, 256 + extruder_idx); - toggle_line("retraction_distances_when_ec", is_multi_extruder && is_BBL_printer && m_config->opt_bool("long_retractions_when_ec", extruder_idx), 256 + extruder_idx); + toggle_line("long_retractions_when_ec", is_BBL_multi_extruder, 256 + extruder_idx); + toggle_line("retraction_distances_when_ec", is_BBL_multi_extruder && m_config->opt_bool("long_retractions_when_ec", extruder_idx), 256 + extruder_idx); } } From ff3ad5ed98e99f1c4b9cba0917f625b3728fd915 Mon Sep 17 00:00:00 2001 From: Damir Galeev <49319928+DImagine@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:12:18 +0300 Subject: [PATCH 15/42] Harden #5070 fix + disable dead change_part_type() via #if 0 (#13281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Restore SVG/text→Support crash guard (#5070), disable change_part_type() The crash fix from #5070 prevented converting SVG/text modifiers to Support Blocker / Support Enforcer by hiding those entries in the old SingleChoiceDialog opened from ObjectList::change_part_type(). When the "Change type" UI was reworked into a submenu that calls ObjectList::set_volume_type() directly, the guard was left behind in the unused change_part_type() path -- the submenu path had no protection, silently reintroducing the #5070 crash on the submenu route (ModelVolume::set_type() does not clear text_configuration / emboss_shape, so the stale emboss state stays attached to a support volume). - Guard set_volume_type(): reject SUPPORT_BLOCKER / SUPPORT_ENFORCER when any selected volume is SVG or text, showing the same error as before. - Wrap ObjectList::change_part_type() in #if 0 instead of deleting it: the function is unused since the submenu rework, but keeping it in commented-out form preserves traceability with upstream Bambu Studio (avoids merge conflicts on each BBL sync). Suggested by RF47, confirmed by ianalexis. * Log instead of showing user error; action guard is defense-in-depth only Per review feedback from RF47: with the UI-side guard in #13120, the set_volume_type() action-time check is a pure safety net -- a healthy UI should never let the user reach it. Showing a modal error dialog would therefore either confuse a user who did nothing wrong (if the UI guard is bypassed by a future refactor/plugin/shortcut) or be dead code at runtime. Log the event for developers instead, keep the early return so the crash is still prevented. --- src/slic3r/GUI/GUI_ObjectList.cpp | 27 +++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ 2 files changed, 29 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c0a790961b..da0326704a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -5377,6 +5378,11 @@ ModelVolume* ObjectList::get_selected_model_volume() return (*m_objects)[obj_idx]->volumes[vol_idx]; } +// ORCA: kept as dead code (not called by any active path). Preserved in #if 0 +// form for traceability with upstream Bambu Studio -- removing outright would +// produce merge conflicts on every BBL sync. The active "Change Type" UI goes +// through the submenu in GUI_Factories.cpp -> ObjectList::set_volume_type(). +#if 0 void ObjectList::change_part_type() { wxDataViewItemArray selections; @@ -5573,6 +5579,7 @@ void ObjectList::change_part_type() return; } +#endif ModelVolumeType ObjectList::get_selected_volume_type() { @@ -5646,6 +5653,26 @@ void ObjectList::set_volume_type(ModelVolumeType new_type) return; } + // Defense-in-depth safety net against issue #5070: SVG/text volumes carry emboss metadata + // (text_configuration, emboss_shape) that only makes sense for Part / Negative Part / Modifier. + // ModelVolume::set_type() does not clear that metadata, so converting such volumes to + // Support Blocker / Support Enforcer leaves stale emboss state attached to a support volume + // and historically crashed (originally fixed in the now-disabled change_part_type() by hiding + // Support entries in the old choice dialog; the UI-side guard for the current submenu lives + // in MenuFactory::append_menu_item_change_type, see #13120). + // This block must never be reachable under a healthy UI; if it ever logs, the UI guard has + // been bypassed (new entry point, refactor, plugin, etc.) and should be investigated. + if (new_type == ModelVolumeType::SUPPORT_BLOCKER || new_type == ModelVolumeType::SUPPORT_ENFORCER) { + const bool has_text_or_svg = std::any_of(volumes.begin(), volumes.end(), + [](const VolumeSelection& sel) { return sel.volume->is_svg() || sel.volume->is_text(); }); + if (has_text_or_svg) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << ": blocked attempt to set SUPPORT_BLOCKER/ENFORCER on SVG/text volume; " + << "UI guard should have prevented this -- possible regression in the Change Type menu"; + return; + } + } + const bool any_diff = std::any_of(volumes.begin(), volumes.end(), [new_type](const VolumeSelection& sel) { return sel.volume->type() != new_type; }); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 8fe85dc7f6..ed0dfabd11 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -413,7 +413,9 @@ public: bool fix_cut_selection(wxDataViewItemArray &sels); ModelVolume* get_selected_model_volume(); +#if 0 // ORCA: disabled alongside definition in GUI_ObjectList.cpp (see #if 0 block there) void change_part_type(); +#endif void set_volume_type(ModelVolumeType new_type); ModelVolumeType get_selected_volume_type(); From 0c7e16aa3678aca7cc724f5da2259329dd6d595c Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:20:06 +0300 Subject: [PATCH 16/42] Fix support preview artifacts (missing layers) (#13310) Fix support preview artifacts caused by incorrect gap subdivision Support generation could sometimes split a gap into too many steps, even when it should fit exactly into a single layer. This was most noticeable when max_suport_layer_height was equal to the print layer height (e.g. 0.2 mm). This resulted in incorrect support layering and visible artifacts in preview. The issue was caused by floating-point precision, where values that should be equal to the configured limit were treated as slightly larger. Fix by biasing the subdivision calculation with EPSILON so near-equal values are not split into extra steps. Applied consistently to: - SupportMaterial (normal supports) - TreeSupportCommon (tree stepping) - TreeSupport (layer creation) --- src/libslic3r/Support/SupportMaterial.cpp | 9 ++++++--- src/libslic3r/Support/TreeSupport.cpp | 17 +++++++++++------ src/libslic3r/Support/TreeSupportCommon.hpp | 5 +++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index d1fa5d8627..48e3f4caf2 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -2882,8 +2882,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp intermediate_layers.push_back(&layer_new); } } else { - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); + // ORCA: Bias by EPSILON so a gap effectively equal to + // max_suport_layer_height is not split by floating-point noise. + size_t n_layers_extra = size_t(ceil((dist - EPSILON) / m_slicing_params.max_suport_layer_height)); assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); if (extr1 != nullptr && extr1->layer_type == SupporLayerType::TopContact && @@ -2898,7 +2899,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp layer_new.height = extr1->height; intermediate_layers.push_back(&layer_new); dist = extr2z - extr1z; - n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); + // ORCA: Recalculate with the same EPSILON bias after re-anchoring at the top + // contact layer so near-equal gaps do not gain an extra split here either. + n_layers_extra = size_t(ceil((dist - EPSILON) / m_slicing_params.max_suport_layer_height)); if (n_layers_extra == 0) continue; // Continue printing the other layers up to extr2z. diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index fcd29e4dcd..e6ed4acc96 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1157,12 +1157,17 @@ void TreeSupport::create_tree_support_layers() // Layers between the raft contacts and bottom of the object. double dist_to_go = m_slicing_params.object_print_z_min - raft_print_z; - auto nsteps = int(ceil(dist_to_go / m_slicing_params.max_suport_layer_height)); - double height = dist_to_go / nsteps; - for (int i = 0; i < nsteps; ++i) { - raft_print_z += height; - raft_slice_z = raft_print_z - height / 2; - m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + // ORCA: Guard tiny residual raft-to-object gaps so the EPSILON-biased ceil() + // below cannot turn them into zero steps after floating-point accumulation. + if (dist_to_go > EPSILON) { + // ORCA: Bias by EPSILON so near-equal gaps do not get an extra split from FP noise. + auto nsteps = int(ceil((dist_to_go - EPSILON) / m_slicing_params.max_suport_layer_height)); + double height = dist_to_go / nsteps; + for (int i = 0; i < nsteps; ++i) { + raft_print_z += height; + raft_slice_z = raft_print_z - height / 2; + m_object->add_tree_support_layer(layer_id++, height, raft_print_z, raft_slice_z); + } } m_raft_layers = layer_id; } diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 31e410838d..39e07cfaf4 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -343,7 +343,8 @@ public: } if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) { // Layers between the raft contacts and bottom of the object. - auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height)); + // ORCA: Bias by EPSILON so near-equal gaps do not get an extra split from FP noise. + auto nsteps = int(ceil((dist_to_go - EPSILON) / slicing_params.max_suport_layer_height)); double step = dist_to_go / nsteps; for (int i = 0; i < nsteps; ++ i) { z += step; @@ -754,4 +755,4 @@ enum class LineStatus } // namespace Slic3r -#endif // slic3r_TreeSupportCommon_hpp \ No newline at end of file +#endif // slic3r_TreeSupportCommon_hpp From 35b2b6212d27540cfbfc20211860ba4c364af860 Mon Sep 17 00:00:00 2001 From: Alexandre Folle de Menezes Date: Thu, 23 Apr 2026 10:34:36 -0300 Subject: [PATCH 17/42] Fix: Globally fixing the "milisecond" typo in the code (#13329) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/libslic3r/Timer.hpp | 4 ++-- src/slic3r/GUI/GLCanvas3D.cpp | 16 ++++++++-------- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 2 +- src/slic3r/Utils/EmbossStyleManager.cpp | 2 +- src/slic3r/Utils/EmbossStyleManager.hpp | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Timer.hpp b/src/libslic3r/Timer.hpp index f2e5dde1a8..01f73a9b3e 100644 --- a/src/libslic3r/Timer.hpp +++ b/src/libslic3r/Timer.hpp @@ -71,8 +71,8 @@ namespace Timing { static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) { return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message); } - static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) { - return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message); + static TimeLimitAlarm new_millis(uint64_t time_limit_millis, std::string_view limit_exceeded_message) { + return TimeLimitAlarm(uint64_t(time_limit_millis) * 1000000l, limit_exceeded_message); } static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) { return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bdbc73430b..3be30551c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3993,14 +3993,14 @@ void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt) } -void GLCanvas3D::schedule_extra_frame(int miliseconds) +void GLCanvas3D::schedule_extra_frame(int milliseconds) { // Schedule idle event right now - if (miliseconds == 0) + if (milliseconds == 0) { - // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait + // We want to wakeup idle event but most likely this is call inside render cycle so we need to wait if (m_in_render) - miliseconds = 33; + milliseconds = 33; else { m_dirty = true; wxWakeUpIdle(); @@ -4010,12 +4010,12 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds) int remaining_time = m_render_timer.GetInterval(); // Timer is not running if (!m_render_timer.IsRunning()) { - m_render_timer.StartOnce(miliseconds); - // Timer is running - restart only if new period is shorter than remaning period + m_render_timer.StartOnce(milliseconds); + // Timer is running - restart only if new period is shorter than remaining period } else { - if (miliseconds + 20 < remaining_time) { + if (milliseconds + 20 < remaining_time) { m_render_timer.Stop(); - m_render_timer.StartOnce(miliseconds); + m_render_timer.StartOnce(milliseconds); } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5bde8e37a7..8a3156db15 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1114,7 +1114,7 @@ public: void request_extra_frame() { m_extra_frame_requested = true; } - void schedule_extra_frame(int miliseconds); + void schedule_extra_frame(int milliseconds); int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index ec592685a1..620e5fa8ef 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -32,7 +32,7 @@ static constexpr float GAP_WIDTH = 10.0f; static constexpr float SPACE_RIGHT_PANEL = 10.0f; static constexpr float FADING_OUT_DURATION = 2.0f; -// Time in Miliseconds after next render when fading out is requested +// Time in Milliseconds after next render when fading out is requested static constexpr int FADING_OUT_TIMEOUT = 100; namespace Slic3r { diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 0cfb400476..5e777e2f29 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -401,7 +401,7 @@ void StyleManager::init_style_images(const Vec2i32 &max_size, auto mf = wxGetApp().mainframe; // dot per inch for monitor int dpi = get_dpi_for_window(mf); - // pixel per milimeter + // pixel per millimeter double ppm = dpi / GizmoObjectManipulation::in_to_mm; auto &worker = wxGetApp().plater()->get_ui_job_worker(); diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index fc493fb2ff..171f6fb1aa 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -309,7 +309,7 @@ private: // place to store result in main thread in Finalize std::shared_ptr result; - // pixel per milimeter (scaled DPI) + // pixel per millimeter (scaled DPI) double ppm; }; std::shared_ptr m_temp_style_images = nullptr; From 95ce474c8d846d6c80cf6f98078a5b91b6c8db9b Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:50:28 +0300 Subject: [PATCH 18/42] Fix extrusion of some support layers at wrong Z height (#13327) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some rare support edge cases, Orca could start extruding a new layer before moving to the correct Z height. This happened when two support layers were generated back-to-back and the next layer started exactly where the previous one ended. In that situation, there was no movement that naturally updated the Z position first. The code was clearing the “pending layer change” flag too early, so it lost track of the fact that a Z move was still required. This change ensures that if a layer change is still pending, Orca will always move to the correct Z height before the first extrusion of that layer. This guarantees that every layer starts at the correct height and fixes the missing / incorrect support layers seen in those edge cases. --- src/libslic3r/GCode.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 333b9b842a..392f58fc48 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6199,11 +6199,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, "move to first " + description + " point", sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) ); - m_need_change_layer_lift_z = false; // Orca: ensure Z matches planned layer height - if (_last_pos_undefined && !slope_need_z_travel) { - gcode += this->writer().travel_to_z(m_nominal_z, "ensure Z matches planned layer height", true); + if (!slope_need_z_travel && (_last_pos_undefined || m_need_change_layer_lift_z)) { + const std::string z_sync_comment = _last_pos_undefined ? + "ensure Z matches planned layer height" : ""; // no comment for normal layer-Z lift + gcode += this->writer().travel_to_z(m_nominal_z, z_sync_comment, true); } + m_need_change_layer_lift_z = false; } From aa4dd7dfd5bb01639b7648c4738b754eabe6f2ee Mon Sep 17 00:00:00 2001 From: Kiss Lorand <50251547+kisslorand@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:10:50 +0300 Subject: [PATCH 19/42] Fix: Dirty-state marker ("*") not shown for edited system printer presets (#11900) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix missing dirty marker for edited system printer presets System printer presets did not display the modified (“*”) marker in the sidebar when edited. Unlike other preset types, printer presets use `printer_model` as their display label, bypassing the normal dirty-state formatting. This patch prepends the modified suffix to the displayed name when the selected system printer preset is dirty, restoring consistent UI behavior across all preset types. --- src/slic3r/GUI/PresetComboBoxes.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 9296d0100a..fa427cc493 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1210,14 +1210,24 @@ void PlaterPresetComboBox::update() //BBS: move system to the end if (m_type == Preset::TYPE_PRINTER) { auto printer_model = preset.config.opt_string("printer_model"); - name = from_u8(printer_model); + + // ORCA: Make system printer presets display the dirty "*" prefix when edited. + name = from_u8(is_selected && preset.is_dirty ? Preset::suffix_modified() + printer_model : printer_model); + if (system_printer_models.count(printer_model) == 0) { system_presets.emplace(name, bmp); system_printer_models.insert(printer_model); } + else if (is_selected) { + const wxString alternate_name = from_u8(preset.is_dirty ? printer_model : Preset::suffix_modified() + printer_model); + // Remove the old preset name if exists, and add the new one with the same name but with modified suffix if needed. + if (system_presets.erase(alternate_name)) + system_presets.emplace(name, bmp); + } } else { system_presets.emplace(name, bmp); } + if (is_selected) { tooltip = get_tooltip(preset); selected_system_preset = name; From 9c046c7c2433bf8c30c0a1da4cf509810e151b36 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Fri, 24 Apr 2026 13:54:09 +0800 Subject: [PATCH 20/42] Dedupe json traversal (#13334) * WIP: benchmarking caching vs multi-thread parsing json * stick to just caching, other functions does not seem to have any duplicate traversals * Remove benchmarking code * cleanup leftover code and clear cache when loading is done --------- Co-authored-by: SoftFever --- src/slic3r/GUI/WebGuideDialog.cpp | 32 +++++++++++++++++++++++++++---- src/slic3r/GUI/WebGuideDialog.hpp | 10 ++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 2933dd7bc9..05bc2ab287 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "MainFrame.hpp" #include @@ -985,6 +986,17 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList, //GetStardardFilePath(filepath); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " GetFilamentInfo:VendorDirectory - " << VendorDirectory << ", Filepath - "<second.vendor; + } + if (sType.empty()) { + sType = cache_it->second.type; + } + return cache_it->second.status; + } + try { std::string contents; LoadFile(filepath, contents); @@ -1014,6 +1026,7 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList, if (!pFilaList.contains(FName)) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "pFilaList - Not Contains inherits filaments: " << FName; + filament_info_cache[filepath] = CachedFilamentInfo{-1, sVendor, sType}; return -1; } @@ -1025,28 +1038,36 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList, inherits_path = (boost::filesystem::path(m_OrcaFilaLibPath) / boost::filesystem::path(FPath)).make_preferred(); //boost::filesystem::path nf(strNewFile.c_str()); - if (boost::filesystem::exists(inherits_path)) - return GetFilamentInfo(VendorDirectory,pFilaList, inherits_path.string(), sVendor, sType); - else { + if (boost::filesystem::exists(inherits_path)) { + const int ret = GetFilamentInfo(VendorDirectory,pFilaList, inherits_path.string(), sVendor, sType); + filament_info_cache[filepath] = CachedFilamentInfo{ret, sVendor, sType}; + return ret; + } else { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits File Not Exist: " << inherits_path; + filament_info_cache[filepath] = CachedFilamentInfo{-1, sVendor, sType}; return -1; } } else { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains inherits"; if (sType == "") { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "sType is Empty"; + filament_info_cache[filepath] = CachedFilamentInfo{-1, sVendor, sType}; return -1; } else sVendor = "Generic"; + filament_info_cache[filepath] = CachedFilamentInfo{0, sVendor, sType}; return 0; } } - else + else { + filament_info_cache[filepath] = CachedFilamentInfo{0, sVendor, sType}; return 0; + } } catch(nlohmann::detail::parse_error &err) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "< + #include namespace Slic3r { namespace GUI { @@ -135,6 +137,14 @@ private: wxString m_bbl_user_agent; std::string m_editing_filament_id; + + struct CachedFilamentInfo + { + int status{-1}; + std::string vendor; + std::string type; + }; + std::unordered_map filament_info_cache; }; }} // namespace Slic3r::GUI From f1cacd89c071e4c16865fb028290d85e7b8f8274 Mon Sep 17 00:00:00 2001 From: Kegan Holtzhausen Date: Fri, 24 Apr 2026 08:04:09 +0200 Subject: [PATCH 21/42] Display number of tool changes after slicing #12464 (#12474) --- src/slic3r/GUI/GCodeViewer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2608262944..25d8c584b0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3875,6 +3875,14 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ::sprintf(buf, "%d", m_print_statistics.total_filament_changes); imgui.text(buf); + //display tool change times + ImGui::Dummy({window_padding, window_padding}); + ImGui::SameLine(); + imgui.text(_u8L("Tool changes") + ":"); + ImGui::SameLine(); + ::sprintf(buf, "%d", m_print_statistics.total_extruder_changes); + imgui.text(buf); + //BBS display cost ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); From f9f5425a33c84cac6ac45d68bdee31c2d610c978 Mon Sep 17 00:00:00 2001 From: yw4z Date: Fri, 24 Apr 2026 09:06:16 +0300 Subject: [PATCH 22/42] Fix: Could not load bitmap: printer_preview_ error causing crash on Linux (#12425) Update Plater.cpp --- src/slic3r/GUI/Plater.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 40b984d34c..56f703e817 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4030,6 +4030,8 @@ void Sidebar::update_printer_thumbnail() if (printer_thumbnails.find(printer_type) != printer_thumbnails.end()) // Use known cache first p->image_printer->SetBitmap(create_scaled_bitmap(printer_thumbnails[printer_type], this, PRINTER_THUMBNAIL_SIZE.GetHeight())); else { + /* ORCA this part check images folder for BBL covers but not checks file existence and causes crash on Linux + * BBL covers already exist on profile folder so no need to use this section try { // No cache, try dedicated printer preview p->image_printer->SetBitmap(create_scaled_bitmap("printer_preview_" + printer_type, this, 48)); @@ -4037,6 +4039,7 @@ void Sidebar::update_printer_thumbnail() printer_thumbnails[printer_type] = "printer_preview_" + printer_type; return; } catch (...) {} + */ // Orca: try to use the printer model cover as the thumbnail const auto model_name = selected_preset.config.opt_string("printer_model"); From 1eb9a8efbd3bd66cde3a25ed016b5227edc16f5c Mon Sep 17 00:00:00 2001 From: Wachhund Skytower Date: Fri, 24 Apr 2026 08:07:24 +0200 Subject: [PATCH 23/42] Fix Windows test linking: use main() instead of wmain() (#12939) wxWidgets defines _UNICODE globally, which causes Catch2's catch_main.cpp to provide wmain() instead of main(). This leads to LNK2001 "unresolved external symbol main" for all test executables when building with BUILD_TESTS=ON on MSVC. Add DO_NOT_USE_WMAIN compile definition to Catch2WithMain target so that Catch2 provides the standard main() entry point. Co-authored-by: Claude Opus 4.6 (1M context) --- tests/catch2/src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/catch2/src/CMakeLists.txt b/tests/catch2/src/CMakeLists.txt index 554346f49a..a0acf2272a 100644 --- a/tests/catch2/src/CMakeLists.txt +++ b/tests/catch2/src/CMakeLists.txt @@ -385,6 +385,10 @@ target_include_directories(Catch2 add_library(Catch2WithMain ${SOURCES_DIR}/internal/catch_main.cpp ) +# Force standard main() entry point instead of wmain() on Windows. +# wxWidgets defines _UNICODE globally, which causes Catch2 to use wmain, +# but test executables expect a standard main() entry point. +target_compile_definitions(Catch2WithMain PRIVATE DO_NOT_USE_WMAIN) if(CATCH_ENABLE_REPRODUCIBLE_BUILD) add_build_reproducibility_settings(Catch2WithMain) endif() From c30276ef247ca8e2ae610576898f1194b344fd66 Mon Sep 17 00:00:00 2001 From: Valerii Bokhan <80919135+valerii-bokhan@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:19:14 +0200 Subject: [PATCH 24/42] Feature: Ability to set flow ratio for brims (#11876) Fixes #11853 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/libslic3r/GCode.cpp | 2 ++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 11 +++++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 20 ++++++++++++++++++-- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/GUI_Factories.cpp | 23 +++++++++++------------ src/slic3r/GUI/Plater.cpp | 4 ++-- src/slic3r/GUI/Tab.cpp | 1 + 9 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 392f58fc48..159fe73f4d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6285,6 +6285,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, _mm3_per_mm *= m_config.bottom_solid_infill_flow_ratio; } else if (path.role() == erInternalBridgeInfill) { _mm3_per_mm *= m_config.internal_bridge_flow; + } else if (path.role() == erBrim) { + _mm3_per_mm *= m_config.brim_flow_ratio; } else if (sloped) { _mm3_per_mm *= m_config.scarf_joint_flow_ratio; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9e2437ae0a..65f094af38 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -904,7 +904,7 @@ static std::vector s_Preset_print_options { "top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed", "bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed", "outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield", - "brim_width", "brim_object_gap", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", + "brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style", // BBS diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7139341e47..ed6315c447 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1583,6 +1583,17 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("brim_flow_ratio", coFloat); + def->label = L("Brim flow ratio"); + def->category = L("Support"); + def->tooltip = L("This factor affects the amount of material for brims.\n\n" + "The actual brim flow used is calculated by multiplying this value by the filament flow ratio, and if set, the object's flow ratio.\n\n" + "Note: The resulting value will not be affected by the first-layer flow ratio."); + def->min = 0; + def->max = 2; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("brim_use_efc_outline", coBool); def->label = L("Brim follows compensated outline"); def->category = L("Support"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 60aff4f0e9..4b9ebe5901 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -888,6 +888,7 @@ PRINT_CONFIG_CLASS_DEFINE( PrintObjectConfig, ((ConfigOptionFloat, brim_object_gap)) + ((ConfigOptionFloat, brim_flow_ratio)) ((ConfigOptionBool, brim_use_efc_outline)) ((ConfigOptionEnum, brim_type)) ((ConfigOptionFloat, brim_width)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index fc68fce76b..6a10d432af 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1300,7 +1300,6 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "scarf_angle_threshold" || opt_key == "scarf_overhang_threshold" || opt_key == "scarf_joint_speed" - || opt_key == "scarf_joint_flow_ratio" || opt_key == "seam_slope_start_height" || opt_key == "seam_slope_entire_loop" || opt_key == "seam_slope_min_length" @@ -1324,7 +1323,24 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "bed_mesh_min" || opt_key == "bed_mesh_max" || opt_key == "adaptive_bed_mesh_margin" - || opt_key == "bed_mesh_probe_distance") { + || opt_key == "bed_mesh_probe_distance" + || opt_key == "print_flow_ratio" + || opt_key == "first_layer_flow_ratio" + || opt_key == "top_solid_infill_flow_ratio" + || opt_key == "bottom_solid_infill_flow_ratio" + || opt_key == "outer_wall_flow_ratio" + || opt_key == "inner_wall_flow_ratio" + || opt_key == "overhang_flow_ratio" + || opt_key == "sparse_infill_flow_ratio" + || opt_key == "internal_solid_infill_flow_ratio" + || opt_key == "gap_fill_flow_ratio" + || opt_key == "support_flow_ratio" + || opt_key == "support_interface_flow_ratio" + || opt_key == "brim_flow_ratio" + || opt_key == "filament_flow_ratio" + || opt_key == "scarf_joint_flow_ratio" + || opt_key == "spiral_starting_flow_ratio" + || opt_key == "spiral_finishing_flow_ratio") { invalidated |= m_print->invalidate_step(psGCodeExport); } else if ( opt_key == "flush_into_infill" diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index de318bb96f..4936c65aca 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -707,6 +707,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && config->opt_enum("brim_type") != btPainted; toggle_field("brim_width", have_brim_width); + toggle_field("brim_flow_ratio", have_brim); // wall_filament uses the same logic as in Print::extruders() toggle_field("wall_filament", have_perimeters || have_brim); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 831b4c5f34..401b2e0e7f 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -85,19 +85,18 @@ std::map> SettingsFactory::OBJECT_C {"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7}, {"make_overhang_printable_angle","", 8},{"make_overhang_printable_hole_size","",9}, {"wall_sequence","",10}, {"precise_z_height", "",10} - }}, - { L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},{"brim_use_efc_outline", "",4}, - {"enable_support", "",5},{"support_type", "",6},{"support_threshold_angle", "",7}, {"support_threshold_overlap", "",8}, {"support_on_build_plate_only", "",9}, - {"support_filament", "",10},{"support_interface_filament", "",11},{"support_expansion", "",12},{"support_style", "",13}, - {"tree_support_brim_width", "",14}, {"tree_support_branch_angle", "",15},{"tree_support_branch_angle_organic","",16}, {"tree_support_wall_count", "",17},{"tree_support_branch_diameter_angle", "",18},//tree support - {"support_bottom_z_distance", "",19},{"support_top_z_distance", "",20},{"support_base_pattern", "",21},{"support_base_pattern_spacing", "",22}, - {"support_interface_top_layers", "",23},{"support_interface_bottom_layers", "",24},{"support_interface_spacing", "",25},{"support_bottom_interface_spacing", "",26}, - {"support_object_xy_distance", "",27}, {"bridge_no_support", "",28},{"max_bridge_length", "",29},{"support_critical_regions_only", "",30},{"support_remove_small_overhang","",31}, - {"support_object_first_layer_gap","",32} - }}, + { L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},{"brim_flow_ratio", "",4},{"brim_use_efc_outline", "",5}, + {"enable_support", "",6},{"support_type", "",7},{"support_threshold_angle", "",8}, {"support_threshold_overlap", "",9}, {"support_on_build_plate_only", "",10}, + {"support_filament", "",11},{"support_interface_filament", "",12},{"support_expansion", "",13},{"support_style", "",14}, + {"tree_support_brim_width", "",15}, {"tree_support_branch_angle", "",16},{"tree_support_branch_angle_organic","",17}, {"tree_support_wall_count", "",18},{"tree_support_branch_diameter_angle", "",19},//tree support + {"support_bottom_z_distance", "",20},{"support_top_z_distance", "",21},{"support_base_pattern", "",22},{"support_base_pattern_spacing", "",23}, + {"support_interface_top_layers", "",24},{"support_interface_bottom_layers", "",25},{"support_interface_spacing", "",26},{"support_bottom_interface_spacing", "",27}, + {"support_object_xy_distance", "",28}, {"bridge_no_support", "",29},{"max_bridge_length", "",30},{"support_critical_regions_only", "",31},{"support_remove_small_overhang","",32}, + {"support_object_first_layer_gap","",33} + }}, { L("Speed"), {{"support_speed", "",12}, {"support_interface_speed", "",13} - }} + }} }; std::map> SettingsFactory::PART_CATEGORY_SETTINGS= @@ -109,7 +108,7 @@ std::map> SettingsFactory::PART_CAT {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, {"align_infill_direction_to_model", "", 1}, {"extra_solid_infills", "", 1}, - {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} + {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} }}, { L("Speed"), {{"outer_wall_speed", "",1},{"inner_wall_speed", "",2},{"sparse_infill_speed", "",3},{"top_surface_speed", "",4}, {"internal_solid_infill_speed", "",5}, {"enable_overhang_speed", "",6}, {"overhang_1_4_speed", "",7}, {"overhang_2_4_speed", "",8}, {"overhang_3_4_speed", "",9}, {"overhang_4_4_speed", "",10}, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 56f703e817..72e17448d8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4879,13 +4879,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "nozzle_height", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", - "brim_width", "brim_object_gap", "brim_use_efc_outline", "combine_brims", "brim_type", "nozzle_diameter", "single_extruder_multi_material", "preferred_orientation", + "brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "nozzle_diameter", "single_extruder_multi_material", "preferred_orientation", "enable_prime_tower", "wipe_tower_x", "wipe_tower_y", "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_tower_enable_framework", "prime_tower_infill_gap", "prime_volume", "extruder_colour", "filament_colour", "filament_type", "material_colour", "printable_height", "extruder_printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", - "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "top_shell_layers", + "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", "support_top_z_distance", "support_bottom_z_distance", "raft_layers", "wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_max_purge_speed", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cbd4690515..4bd29b9fbf 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2659,6 +2659,7 @@ void TabPrint::build() optgroup->append_single_option_line("brim_type", "others_settings_brim#type"); optgroup->append_single_option_line("brim_width", "others_settings_brim#width"); optgroup->append_single_option_line("brim_object_gap", "others_settings_brim#brim-object-gap"); + optgroup->append_single_option_line("brim_flow_ratio", "others_settings_brim#brim-flow-ratio"); optgroup->append_single_option_line("brim_use_efc_outline", "others_settings_brim#brim-use-efc-outline"); optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims"); optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle"); From f8b0bcc725d06b29d165e7e9e47af07b95fda8f9 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 03:20:47 -0300 Subject: [PATCH 25/42] Add Kinematics (Jerk & Accel) visualization support (#13169) * Acceleration preview Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Jerk visualization * JD --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- src/libslic3r/GCode/GCodeProcessor.cpp | 51 +++++++++++++- src/libslic3r/GCode/GCodeProcessor.hpp | 5 ++ src/libvgcode/include/PathVertex.hpp | 10 +++ src/libvgcode/include/Types.hpp | 4 ++ src/libvgcode/include/Viewer.hpp | 8 +++ src/libvgcode/src/ViewerImpl.cpp | 32 ++++++++- src/libvgcode/src/ViewerImpl.hpp | 4 ++ src/slic3r/GUI/GCodeViewer.cpp | 70 +++++++++++++++++++ src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp | 16 +++-- 9 files changed, 192 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 8876cbeede..13eb840463 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5480,6 +5480,37 @@ void GCodeProcessor::process_filament_change(int id) void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, bool internal_only) { int filament_id = get_filament_id(); + const auto normal_mode = PrintEstimatedStatistics::ETimeMode::Normal; + const size_t normal_mode_id = static_cast(normal_mode); + const float delta_x = std::abs(m_end_position[X] - m_start_position[X]); + const float delta_y = std::abs(m_end_position[Y] - m_start_position[Y]); + const float delta_z = std::abs(m_end_position[Z] - m_start_position[Z]); + const float delta_e = std::abs(m_end_position[E] - m_start_position[E]); + const bool has_x = delta_x > 0.0f; + const bool has_y = delta_y > 0.0f; + const bool has_z = delta_z > 0.0f; + const bool has_e = delta_e > 0.0f; + const float move_acceleration = + (type == EMoveType::Travel) ? get_travel_acceleration(normal_mode) : + ((type == EMoveType::Retract || type == EMoveType::Unretract) ? get_retract_acceleration(normal_mode) : + get_acceleration(normal_mode)); + const float junction_deviation = get_option_value(m_time_processor.machine_limits.machine_max_junction_deviation, normal_mode_id); + const bool use_jd_jerk = (m_flavor == gcfMarlinFirmware && junction_deviation > 0.0f); + const auto axis_jerk_for_preview = [this, normal_mode, use_jd_jerk, move_acceleration](Axis axis) { + return use_jd_jerk ? get_axis_max_jerk_with_jd(normal_mode, axis, move_acceleration) : get_axis_max_jerk(normal_mode, axis); + }; + const float jerk_x = axis_jerk_for_preview(X); + const float jerk_y = axis_jerk_for_preview(Y); + const float jerk_z = axis_jerk_for_preview(Z); + const float jerk_e = axis_jerk_for_preview(E); + const float move_jerk = + (has_e && !has_x && !has_y && !has_z) ? jerk_e : + (has_z && !has_x && !has_y) ? jerk_z : + (has_x && has_y) ? std::min(jerk_x, jerk_y) : + has_x ? jerk_x : + has_y ? jerk_y : + has_z ? jerk_z : + std::min(jerk_x, jerk_y); m_last_line_id = (type == EMoveType::Color_change || type == EMoveType::Pause_Print || type == EMoveType::Custom_GCode) ? m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); @@ -5503,6 +5534,10 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, m_extruder_temps[filament_id], // ORCA: Add Pressure Advance visualization support m_pressure_advance, + // ORCA: Add Acceleration visualization support + move_acceleration, + // ORCA: Add Jerk visualization support + move_jerk, { 0.0f, 0.0f }, // time static_cast(m_layer_id), //layer_duration: set later std::max(1, m_layer_id) - 1, @@ -5578,7 +5613,7 @@ float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeM } } -float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const { if (axis != X && axis != Y && axis != Z && axis != E) return 0.0f; @@ -5589,12 +5624,22 @@ float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeM return 0.0f; const float axis_max_acc = get_axis_max_acceleration(mode, axis); - const float generic_acc = get_acceleration(mode); - const float effective_acc = axis_max_acc > 0.0f ? axis_max_acc : generic_acc; + float effective_acc = acceleration; + if (effective_acc <= 0.0f) + effective_acc = get_acceleration(mode); + if (axis_max_acc > 0.0f) + effective_acc = effective_acc > 0.0f ? std::min(effective_acc, axis_max_acc) : axis_max_acc; + if (effective_acc <= 0.0f) + return 0.0f; return std::sqrt(jd * effective_acc * 2.5f); } +float GCodeProcessor::get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const +{ + return get_axis_max_jerk_with_jd(mode, axis, get_acceleration(mode)); +} + float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { const size_t id = static_cast(mode); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 1d72b1c614..f4347dfe86 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -186,6 +186,10 @@ class Print; float temperature{ 0.0f }; // Celsius degrees // ORCA: Add Pressure Advance visualization support float pressure_advance{ 0.0f }; + // ORCA: Add Acceleration visualization support + float acceleration{ 0.0f }; // mm/s^2 + // ORCA: Add Jerk visualization support + float jerk{ 0.0f }; // mm/s std::array(PrintEstimatedStatistics::ETimeMode::Count)> time{ 0.0f, 0.0f }; // s float layer_duration{ 0.0f }; // s unsigned int layer_id{ 0 }; @@ -1074,6 +1078,7 @@ class Print; // per-nozzle machine limits (filament_map_2 / get_config_idx_for_filament). float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const; float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const; diff --git a/src/libvgcode/include/PathVertex.hpp b/src/libvgcode/include/PathVertex.hpp index 54d0a800f4..f5704d5376 100644 --- a/src/libvgcode/include/PathVertex.hpp +++ b/src/libvgcode/include/PathVertex.hpp @@ -91,6 +91,16 @@ struct PathVertex // Pressure advance value // float pressure_advance{ 0.0f }; + // + // ORCA: Add Acceleration visualization support + // Acceleration value + // + float acceleration{ 0.0f }; + // + // ORCA: Add Jerk visualization support + // Jerk value + // + float jerk{ 0.0f }; // // Return true if the segment is an extrusion move diff --git a/src/libvgcode/include/Types.hpp b/src/libvgcode/include/Types.hpp index 7503ebc18b..2187238ef0 100644 --- a/src/libvgcode/include/Types.hpp +++ b/src/libvgcode/include/Types.hpp @@ -94,6 +94,10 @@ enum class EViewType : uint8_t Temperature, // ORCA: Add Pressure Advance visualization support PressureAdvance, + // ORCA: Add Acceleration visualization support + Acceleration, + // ORCA: Add Jerk visualization support + Jerk, Tool, COUNT }; diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 817b6ef5c2..fa3236fd5c 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -171,6 +171,10 @@ public: // EViewType::Temperature // ORCA: Add Pressure Advance visualization support // EViewType::PressureAdvance + // ORCA: Add Acceleration visualization support + // EViewType::Acceleration + // ORCA: Add Jerk visualization support + // EViewType::Jerk // EViewType::VolumetricFlowRate // EViewType::ActualVolumetricFlowRate // EViewType::LayerTimeLinear @@ -189,6 +193,10 @@ public: // EViewType::Temperature // ORCA: Add Pressure Advance visualization support // EViewType::PressureAdvance + // ORCA: Add Acceleration visualization support + // EViewType::Acceleration + // ORCA: Add Jerk visualization support + // EViewType::Jerk // EViewType::VolumetricFlowRate // EViewType::ActualVolumetricFlowRate // EViewType::LayerTimeLinear diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 4732c042cf..1c52ffe40f 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -1469,7 +1469,7 @@ Color ViewerImpl::get_vertex_color(const PathVertex& v) const if (v.type == EMoveType::Noop) return DUMMY_COLOR; - if ((v.is_wipe() && (m_settings.view_type != EViewType::Speed && m_settings.view_type != EViewType::ActualSpeed)) || v.is_option()) + if ((v.is_wipe() && (m_settings.view_type != EViewType::Speed && m_settings.view_type != EViewType::ActualSpeed && m_settings.view_type != EViewType::Acceleration && m_settings.view_type != EViewType::Jerk)) || v.is_option()) return get_option_color(move_type_to_option(v.type)); switch (m_settings.view_type) @@ -1507,6 +1507,16 @@ Color ViewerImpl::get_vertex_color(const PathVertex& v) const { return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_pressure_advance_range.get_color_at(v.pressure_advance); } + // ORCA: Add Acceleration visualization support + case EViewType::Acceleration: + { + return m_acceleration_range.get_color_at(v.acceleration); + } + // ORCA: Add Jerk visualization support + case EViewType::Jerk: + { + return m_jerk_range.get_color_at(v.jerk); + } case EViewType::VolumetricFlowRate: { return v.is_travel() ? get_option_color(move_type_to_option(v.type)) : m_volumetric_rate_range.get_color_at(v.volumetric_rate()); @@ -1598,6 +1608,10 @@ const ColorRange& ViewerImpl::get_color_range(EViewType type) const case EViewType::Temperature: { return m_temperature_range; } // ORCA: Add Pressure Advance visualization support case EViewType::PressureAdvance: { return m_pressure_advance_range; } + // ORCA: Add Acceleration visualization support + case EViewType::Acceleration: { return m_acceleration_range; } + // ORCA: Add Jerk visualization support + case EViewType::Jerk: { return m_jerk_range; } case EViewType::VolumetricFlowRate: { return m_volumetric_rate_range; } case EViewType::ActualVolumetricFlowRate: { return m_actual_volumetric_rate_range; } case EViewType::LayerTimeLinear: { return m_layer_time_range[0]; } @@ -1618,6 +1632,10 @@ void ViewerImpl::set_color_range_palette(EViewType type, const Palette& palette) case EViewType::Temperature: { m_temperature_range.set_palette(palette); break; } // ORCA: Add Pressure Advance visualization support case EViewType::PressureAdvance: { m_pressure_advance_range.set_palette(palette); break; } + // ORCA: Add Acceleration visualization support + case EViewType::Acceleration: { m_acceleration_range.set_palette(palette); break; } + // ORCA: Add Jerk visualization support + case EViewType::Jerk: { m_jerk_range.set_palette(palette); break; } case EViewType::VolumetricFlowRate: { m_volumetric_rate_range.set_palette(palette); break; } case EViewType::ActualVolumetricFlowRate: { m_actual_volumetric_rate_range.set_palette(palette); break; } case EViewType::LayerTimeLinear: { m_layer_time_range[0].set_palette(palette); break; } @@ -1657,6 +1675,10 @@ size_t ViewerImpl::get_used_cpu_memory() const ret += m_temperature_range.size_in_bytes_cpu(); // ORCA: Add Pressure Advance visualization support ret += m_pressure_advance_range.size_in_bytes_cpu(); + // ORCA: Add Acceleration visualization support + ret += m_acceleration_range.size_in_bytes_cpu(); + // ORCA: Add Jerk visualization support + ret += m_jerk_range.size_in_bytes_cpu(); ret += m_volumetric_rate_range.size_in_bytes_cpu(); ret += m_actual_volumetric_rate_range.size_in_bytes_cpu(); for (size_t i = 0; i < COLOR_RANGE_TYPES_COUNT; ++i) { @@ -1809,6 +1831,10 @@ void ViewerImpl::update_color_ranges() m_temperature_range.reset(); // ORCA: Add Pressure Advance visualization support m_pressure_advance_range.reset(); + // ORCA: Add Acceleration visualization support + m_acceleration_range.reset(); + // ORCA: Add Jerk visualization support + m_jerk_range.reset(); m_volumetric_rate_range.reset(); m_actual_volumetric_rate_range.reset(); m_layer_time_range[0].reset(); // ColorRange::EType::Linear @@ -1834,6 +1860,10 @@ void ViewerImpl::update_color_ranges() v.is_extrusion()) { m_speed_range.update(v.feedrate); m_actual_speed_range.update(v.actual_feedrate); + // ORCA: Add Acceleration visualization support + m_acceleration_range.update(v.acceleration); + // ORCA: Add Jerk visualization support + m_jerk_range.update(v.jerk); } } diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index 428815d277..a5740033a5 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -291,6 +291,10 @@ private: ColorRange m_temperature_range; // ORCA: Add Pressure Advance visualization support ColorRange m_pressure_advance_range; + // ORCA: Add Acceleration visualization support + ColorRange m_acceleration_range; + // ORCA: Add Jerk visualization support + ColorRange m_jerk_range; ColorRange m_volumetric_rate_range; ColorRange m_actual_volumetric_rate_range; std::array m_layer_time_range{ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 25d8c584b0..678a99deb5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -77,6 +77,10 @@ static std::string get_view_type_string(libvgcode::EViewType view_type) return _u8L("Speed"); else if (view_type == libvgcode::EViewType::ActualSpeed) return _u8L("Actual Speed"); + else if (view_type == libvgcode::EViewType::Acceleration) + return _u8L("Acceleration"); + else if (view_type == libvgcode::EViewType::Jerk) + return _u8L("Jerk"); else if (view_type == libvgcode::EViewType::FanSpeed) return _u8L("Fan Speed"); else if (view_type == libvgcode::EViewType::Temperature) @@ -382,6 +386,16 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.feedrate); const std::string text = std::string(buff); ImGuiWrapper::text(text); + }); + append_table_row(_u8L("Acceleration"), [&vertex, &buff]() { + sprintf(buff, ("%.0f " + _u8L("mm/s²")).c_str(), vertex.acceleration); + const std::string text = std::string(buff); + ImGuiWrapper::text(text); + }); + append_table_row(_u8L("Jerk"), [&vertex, &buff]() { + sprintf(buff, ("%.1f " + _u8L("mm/s")).c_str(), vertex.jerk); + const std::string text = std::string(buff); + ImGuiWrapper::text(text); }); append_table_row(_u8L("Flow rate"), [&vertex, &buff, NA_TXT]() { // ORCA use "Flow rate" instead "Volumetric flow Rate" to make window more compact std::string text; @@ -617,6 +631,14 @@ void GCodeViewer::SequentialView::Marker::render_position_window(const libvgcode sprintf(buf, "%s %s%.1f", buf, _u8L("Actual Speed: ").c_str(), vertex.actual_feedrate); break; } + case libvgcode::EViewType::Acceleration: { + sprintf(buf, "%s %s%.0f", buf, _u8L("Acceleration: ").c_str(), vertex.acceleration); + break; + } + case libvgcode::EViewType::Jerk: { + sprintf(buf, "%s %s%.1f", buf, _u8L("Jerk: ").c_str(), vertex.jerk); + break; + } // ORCA: Add Pressure Advance visualization support case libvgcode::EViewType::PressureAdvance: { sprintf(buf, "%s %s%.4f", buf, _u8L("PA: ").c_str(), vertex.pressure_advance); @@ -1031,6 +1053,8 @@ void GCodeViewer::update_by_mode(ConfigOptionMode mode) view_type_items.push_back(libvgcode::EViewType::ColorPrint); view_type_items.push_back(libvgcode::EViewType::Speed); view_type_items.push_back(libvgcode::EViewType::ActualSpeed); + view_type_items.push_back(libvgcode::EViewType::Acceleration); + view_type_items.push_back(libvgcode::EViewType::Jerk); view_type_items.push_back(libvgcode::EViewType::Height); view_type_items.push_back(libvgcode::EViewType::Width); view_type_items.push_back(libvgcode::EViewType::VolumetricFlowRate); @@ -2287,6 +2311,8 @@ void GCodeViewer::render_toolpaths() add_range_property_row("height range", m_viewer.get_color_range(libvgcode::EViewType::Height).get_range()); add_range_property_row("width range", m_viewer.get_color_range(libvgcode::EViewType::Width).get_range()); add_range_property_row("speed range", m_viewer.get_color_range(libvgcode::EViewType::Speed).get_range()); + add_range_property_row("acceleration range", m_viewer.get_color_range(libvgcode::EViewType::Acceleration).get_range()); + add_range_property_row("jerk range", m_viewer.get_color_range(libvgcode::EViewType::Jerk).get_range()); add_range_property_row("fan speed range", m_viewer.get_color_range(libvgcode::EViewType::FanSpeed).get_range()); add_range_property_row("temperature range", m_viewer.get_color_range(libvgcode::EViewType::Temperature).get_range()); // ORCA: Add Pressure Advance visualization support @@ -3536,6 +3562,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.title(_u8L("Actual Speed (mm/s)")); break; } + case libvgcode::EViewType::Acceleration: + { + imgui.title(_u8L("Acceleration (mm/s²)")); + break; + } + case libvgcode::EViewType::Jerk: + { + imgui.title(_u8L("Jerk (mm/s)")); + break; + } case libvgcode::EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case libvgcode::EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } // ORCA: Add Pressure Advance visualization support @@ -3714,6 +3750,38 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::PopStyleVar(1); break; } + case libvgcode::EViewType::Acceleration: { + append_range(m_viewer.get_color_range(libvgcode::EViewType::Acceleration), 0); + ImGui::Spacing(); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); + offsets = calculate_offsets({ { _u8L("Options"), { _u8L("Travel")}}, { _u8L("Display"), {""}} }, icon_size); + append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} }); + const bool travel_visible = m_viewer.is_option_visible(libvgcode::EOptionType::Travels); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); + append_item(EItemType::None, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), { {_u8L("Travel"), offsets[0] }}, true, predictable_icon_pos/*ORCA checkbox_pos*/, travel_visible, [this, travel_visible]() { + m_viewer.toggle_option_visibility(libvgcode::EOptionType::Travels); + update_moves_slider(); + }); + ImGui::PopStyleVar(1); + break; + } + case libvgcode::EViewType::Jerk: { + append_range(m_viewer.get_color_range(libvgcode::EViewType::Jerk), 1); + ImGui::Spacing(); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); + offsets = calculate_offsets({ { _u8L("Options"), { _u8L("Travel")}}, { _u8L("Display"), {""}} }, icon_size); + append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} }); + const bool travel_visible = m_viewer.is_option_visible(libvgcode::EOptionType::Travels); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); + append_item(EItemType::None, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), { {_u8L("Travel"), offsets[0] }}, true, predictable_icon_pos/*ORCA checkbox_pos*/, travel_visible, [this, travel_visible]() { + m_viewer.toggle_option_visibility(libvgcode::EOptionType::Travels); + update_moves_slider(); + }); + ImGui::PopStyleVar(1); + break; + } case libvgcode::EViewType::FanSpeed: { append_range(m_viewer.get_color_range(libvgcode::EViewType::FanSpeed), 0); break; } case libvgcode::EViewType::Temperature: { append_range(m_viewer.get_color_range(libvgcode::EViewType::Temperature), 0); break; } // ORCA: Add Pressure Advance visualization support @@ -4078,6 +4146,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv { case libvgcode::EViewType::Speed: case libvgcode::EViewType::ActualSpeed: + case libvgcode::EViewType::Acceleration: + case libvgcode::EViewType::Jerk: case libvgcode::EViewType::Tool: case libvgcode::EViewType::ColorPrint: { break; diff --git a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp index 4f33c18b80..08e336c119 100644 --- a/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp +++ b/src/slic3r/GUI/LibVGCode/LibVGCodeWrapper.cpp @@ -225,13 +225,17 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve curr.mm3_per_mm, curr.fan_speed, curr.temperature, 0.0f, convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f }, - /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance }; + /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, + /* ORCA: Add Acceleration visualization support */ curr.acceleration, + /* ORCA: Add Jerk visualization support */ curr.jerk }; #else const libvgcode::PathVertex vertex = { convert(prev.position), curr.height, curr.width, curr.feedrate, prev.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), static_cast(curr.extruder_id), static_cast(curr.cp_color_id), { 0.0f, 0.0f }, - /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance }; + /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, + /* ORCA: Add Acceleration visualization support */ curr.acceleration, + /* ORCA: Add Jerk visualization support */ curr.jerk }; #endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS ret.vertices.emplace_back(vertex); } @@ -243,13 +247,17 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve result.filament_densities[curr.extruder_id] * curr.mm3_per_mm * (curr.position - prev.position).norm(), convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time, - /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance }; + /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, + /* ORCA: Add Acceleration visualization support */ curr.acceleration, + /* ORCA: Add Jerk visualization support */ curr.jerk }; #else const libvgcode::PathVertex vertex = { convert(curr.position), curr.height, curr.width, curr.feedrate, curr.actual_feedrate, curr.mm3_per_mm, curr.fan_speed, curr.temperature, convert(curr.extrusion_role), curr_type, static_cast(curr.gcode_id), static_cast(curr.layer_id), static_cast(curr.extruder_id), static_cast(curr.cp_color_id), curr.time, - /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance }; + /* ORCA: Add Pressure Advance visualization support */ 0.0f, curr.pressure_advance, + /* ORCA: Add Acceleration visualization support */ curr.acceleration, + /* ORCA: Add Jerk visualization support */ curr.jerk }; #endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS ret.vertices.emplace_back(vertex); } From 2b0a0023e04530ef6b91074f90f6c6a221509348 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 03:21:44 -0300 Subject: [PATCH 26/42] 3MF Export/Import Orca export tags (#12509) * Using bbs versioning for bbs atributes * Orca tags * Bump SLIC3R version to match current BBS * New import logic * to_string_sf * 2.3.2 No tagged * Update comment --- src/libslic3r/Format/bbs_3mf.cpp | 46 ++++++-- src/libslic3r/Format/bbs_3mf.hpp | 2 +- src/libslic3r/Model.cpp | 12 +- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Plater.cpp | 197 ++++++++++++++++++------------- version.inc | 2 +- 6 files changed, 163 insertions(+), 97 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 42f55aae8a..e91d92309c 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -172,7 +172,10 @@ const std::string BBL_LICENSE_TAG = "License"; const std::string BBL_REGION_TAG = "Region"; const std::string BBL_MODIFICATION_TAG = "ModificationDate"; const std::string BBL_CREATION_DATE_TAG = "CreationDate"; +// Orca: BBL current version const std::string BBL_APPLICATION_TAG = "Application"; +// OrcaSlicer version tag +const std::string ORCASLICER_TAG = "OrcaSlicer"; const std::string BBL_MAKERLAB_TAG = "MakerLab"; const std::string BBL_MAKERLAB_VERSION_TAG = "MakerLabVersion"; @@ -1031,6 +1034,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string m_origin_file; // Semantic version of Orca Slicer, that generated this 3MF. boost::optional m_bambuslicer_generator_version; + // Semantic version from the OrcaSlicer metadata tag (if present). + boost::optional m_orca_slicer_version; unsigned int m_fdm_supports_painting_version = 0; unsigned int m_seam_painting_version = 0; unsigned int m_mm_painting_version = 0; @@ -1102,7 +1107,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //BBS: add plate data related logic // add backup & restore logic bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, - ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0); + ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, bool* is_orca_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0); bool get_thumbnail(const std::string &filename, std::string &data); bool load_gcode_3mf_from_stream(std::istream & data, Model& model, PlateDataPtrs& plate_data_list, DynamicPrintConfig& config, Semver& file_version); unsigned int version() const { return m_version; } @@ -1310,7 +1315,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //BBS: add plate data related logic // add backup & restore logic bool _BBS_3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, - ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project, int plate_id) + ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool* is_bbl_3mf, bool* is_orca_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project, int plate_id) { m_version = 0; m_fdm_supports_painting_version = 0; @@ -1366,8 +1371,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (is_bbl_3mf) { *is_bbl_3mf = m_is_bbl_3mf; } - if (m_bambuslicer_generator_version) - file_version = *m_bambuslicer_generator_version; + // If the OrcaSlicer tag is present, use it as file_version (ignoring the Bambu Application version). + // Otherwise fall back to the version parsed from the Application tag. + if (m_orca_slicer_version) { + file_version = *m_orca_slicer_version; + if (is_orca_3mf) + *is_orca_3mf = true; + } else { + if (m_bambuslicer_generator_version) + file_version = *m_bambuslicer_generator_version; + if (is_orca_3mf) + *is_orca_3mf = false; + } // save for restore if (result && m_load_aux && !m_load_restore) { save_string_file(model.get_backup_path() + "/origin.txt", filename); @@ -3818,6 +3833,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) m_is_bbl_3mf = true; m_bambuslicer_generator_version = Semver::parse(m_curr_characters.substr(11)); } + } else if (m_curr_metadata_name == ORCASLICER_TAG) { + // OrcaSlicer version tag (written from OrcaSlicer 2.3.2 onwards) + m_orca_slicer_version = Semver::parse(m_curr_characters); + if (m_orca_slicer_version) { + m_is_bbl_3mf = true; + } //TODO: currently use version 0, no need to load&&save this string /*} else if (m_curr_metadata_name == BBS_FDM_SUPPORTS_PAINTING_VERSION) { m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); @@ -6658,8 +6679,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // Orca: PRIVACY: do not store creation & modification date in 3mf metadata_item_map[BBL_CREATION_DATE_TAG] = ""; metadata_item_map[BBL_MODIFICATION_TAG] = ""; - //SoftFever: write BambuStudio tag to keep it compatible - metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SoftFever_VERSION).str(); + // Orca: Write the BambuStudio compatibility version string using SLIC3R_VERSION + metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SLIC3R_VERSION).str(); } metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF); @@ -6684,6 +6705,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) BOOST_LOG_TRIVIAL(info) << "bbs_3mf: save key= " << item.first << ", value = " << item.second; stream << " <" << METADATA_TAG << " name=\"" << item.first << "\">" << xml_escape(item.second) << "\n"; + if (item.first == BBL_APPLICATION_TAG) { + stream << " <" << METADATA_TAG << " name=\"" << ORCASLICER_TAG << "\">" + << xml_escape(SoftFever_VERSION) << "\n"; + } } stream << " <" << RESOURCES_TAG << ">\n"; @@ -7543,7 +7568,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) { const std::string& temp_path = model.get_backup_path(); std::string temp_file = temp_path + std::string("/") + "_temp_1.config"; - config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION)); + config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION)); return _add_file_to_archive(archive, BBS_PROJECT_CONFIG_FILE, temp_file); } @@ -7938,7 +7963,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // save slice header for debug stream << " <" << SLICE_HEADER_TAG << ">\n"; stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Type" << "\" " << VALUE_ATTR << "=\"" << "slicer" << "\"/>\n"; - stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SoftFever_VERSION) << "\"/>\n"; + stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SLIC3R_VERSION) << "\"/>\n"; + stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "OrcaSlicer-Version" << "\" " << VALUE_ATTR << "=\"" << SoftFever_VERSION << "\"/>\n"; stream << " \n"; for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i) @@ -8634,7 +8660,7 @@ private: //BBS: add plate data list related logic bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector* project_presets, - bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id) + bool* is_bbl_3mf, bool* is_orca_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id) { if (path == nullptr || config == nullptr || model == nullptr) return false; @@ -8642,7 +8668,7 @@ bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstituti // All import should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; _BBS_3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, is_bbl_3mf, *file_version, proFn, project, plate_id); + bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, is_bbl_3mf, is_orca_3mf, *file_version, proFn, project, plate_id); importer.log_errors(); //BBS: remove legacy project logic currently //handle_legacy_project_loaded(importer.version(), *config); diff --git a/src/libslic3r/Format/bbs_3mf.hpp b/src/libslic3r/Format/bbs_3mf.hpp index 5fd57c4d61..080655a427 100644 --- a/src/libslic3r/Format/bbs_3mf.hpp +++ b/src/libslic3r/Format/bbs_3mf.hpp @@ -248,7 +248,7 @@ struct StoreParams // add restore logic // Load the content of a 3mf file into the given model and preset bundle. extern bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector* project_presets, - bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, BBLProject *project = nullptr, int plate_id = 0); + bool* is_bbl_3mf, bool* is_orca_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, BBLProject *project = nullptr, int plate_id = 0); extern std::string bbs_3mf_get_thumbnail(const char * path); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 05a56c3a47..e3b2ada837 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -323,7 +323,7 @@ Model Model::read_from_file(const std::string& // BBS: backup & restore //FIXME options & LoadStrategy::CheckVersion ? //BBS: is_xxx is used for is_bbs_3mf when load 3mf - result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, is_xxx, file_version, proFn, options, project, plate_id); + result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, is_xxx, nullptr, file_version, proFn, options, project, plate_id); #ifdef __APPLE__ else if (boost::algorithm::iends_with(input_file, ".usd") || boost::algorithm::iends_with(input_file, ".usda") || boost::algorithm::iends_with(input_file, ".usdc") || boost::algorithm::iends_with(input_file, ".usdz") || @@ -382,7 +382,8 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig Model model; bool result = false; - bool is_bbl_3mf; + bool is_bbl_3mf = false; + bool is_orca_3mf = false; if (boost::algorithm::iends_with(input_file, ".3mf")) { PrusaFileParser prusa_file_parser; if (prusa_file_parser.check_3mf_from_prusa(input_file)) { @@ -392,7 +393,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig } else { // BBS: add part plate related logic // BBS: backup & restore - result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, &is_bbl_3mf, file_version, proFn, options, project); + result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, &is_bbl_3mf, &is_orca_3mf, file_version, proFn, options, project); } } else if (boost::algorithm::iends_with(input_file, ".zip.amf")) @@ -401,7 +402,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .3mf or .zip.amf extension.")); if (out_file_type != En3mfType::From_Prusa) { - out_file_type = is_bbl_3mf ? En3mfType::From_BBS : En3mfType::From_Other; + if (is_orca_3mf) + out_file_type = En3mfType::From_Orca; + else + out_file_type = is_bbl_3mf ? En3mfType::From_BBS : En3mfType::From_Other; } if (!result) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7d483e61c5..5974ca4cd6 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -719,6 +719,7 @@ enum class ConversionType : int { }; enum class En3mfType : int { + From_Orca, From_BBS, From_Prusa, From_Other diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 72e17448d8..22d590a969 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5954,9 +5954,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (cancel) is_user_cancel = cancel; }); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ - << boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_3mf %3%, file_version %4% \n") % plate_data.size() % - project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS) % file_version.to_string(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ + << boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_or_orca_3mf %3%, file_version %4% \n") % plate_data.size() % + project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) % file_version.to_string(); // 1. add extruder for prusa model if the number of existing extruders is not enough // 2. add extruder for BBS or Other model if only import geometry @@ -5998,88 +5998,123 @@ std::vector Plater::priv::load_files(const std::vector& input_ if(load_type != LoadType::LoadGeometry) show_info(q, _L("The 3MF is not supported by OrcaSlicer, loading geometry data only."), _L("Load 3MF")); } - // else if (load_config && (file_version.maj() != app_version.maj())) { - // // version mismatch, only load geometries - // load_config = false; - // if (!load_model) { - // // only load config case, return directly - // show_info(q, _L("The Config cannot be loaded."), _L("Load 3MF")); - // q->skip_thumbnail_invalid = false; - // return empty_result; - // } - // load_old_project = true; - // // select view to 3D - // q->select_view_3D("3D"); - // // select plate 0 as default - // q->select_plate(0); - // if (load_type != LoadType::LoadGeometry) { - // if (en_3mf_file_type == En3mfType::From_BBS) - // show_info(q, _L("The 3MF was generated by an old OrcaSlicer, loading geometry data only."), _L("Load 3MF")); - // else - // show_info(q, _L("The 3MF is not supported by OrcaSlicer, loading geometry data only."), _L("Load 3MF")); - // } - // for (ModelObject *model_object : model.objects) { - // model_object->config.reset(); - // // Is there any modifier or advanced config data? - // for (ModelVolume *model_volume : model_object->volumes) model_volume->config.reset(); - // } - // } - // Orca: check if the project is created with OrcaSlicer 2.3.1-alpha and use the sparse infill rotation template for non-safe infill patterns - else if (load_config && (file_version < app_version) && file_version == Semver("2.3.1-alpha")) { - if (!config_loaded.opt_string("sparse_infill_rotate_template").empty()) { - const auto _sparse_infill_pattern = - config_loaded.option>("sparse_infill_pattern")->value; - bool is_safe_to_rotate = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine || - _sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag || - _sparse_infill_pattern == ipLockedZag; - if (!is_safe_to_rotate) { - wxString msg_text = _( - L("This project was created with an OrcaSlicer 2.3.1-alpha and uses " - "infill rotation template settings that may not work properly with your current infill pattern. " - "This could result in weak support or print quality issues.")); - msg_text += "\n\n" + - _(L("Would you like OrcaSlicer to automatically fix this by clearing the rotation template settings?")); - MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); - dialog.SetButtonLabel(wxID_YES, _L("Yes")); - dialog.SetButtonLabel(wxID_NO, _L("No")); - if (dialog.ShowModal() == wxID_YES) { - config_loaded.opt_string("sparse_infill_rotate_template") = ""; + else if (en_3mf_file_type == En3mfType::From_Orca) { + // OrcaSlicer file (has OrcaSlicer tag) - compare file_version with SoftFever_VERSION + // Migration fix for OrcaSlicer 2.3.1-alpha sparse infill rotation template + if (load_config && (file_version < app_version) && file_version == Semver("2.3.1-alpha")) { + if (!config_loaded.opt_string("sparse_infill_rotate_template").empty()) { + const auto _sparse_infill_pattern = + config_loaded.option>("sparse_infill_pattern")->value; + bool is_safe_to_rotate = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine || + _sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag || + _sparse_infill_pattern == ipLockedZag; + if (!is_safe_to_rotate) { + wxString msg_text = _( + L("This project was created with an OrcaSlicer 2.3.1-alpha and uses " + "infill rotation template settings that may not work properly with your current infill pattern. " + "This could result in weak support or print quality issues.")); + msg_text += "\n\n" + + _(L("Would you like OrcaSlicer to automatically fix this by clearing the rotation template settings?")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + dialog.SetButtonLabel(wxID_YES, _L("Yes")); + dialog.SetButtonLabel(wxID_NO, _L("No")); + if (dialog.ShowModal() == wxID_YES) { + config_loaded.opt_string("sparse_infill_rotate_template") = ""; + } + } + } + } else if (load_config && (file_version > app_version)) { + if (config_substitutions.unrecogized_keys.size() > 0) { + wxString text = wxString::Format(_L("The 3MF file version %s is newer than %s's version %s, found the following unrecognized keys:"), + file_version.to_string_sf(), std::string(SLIC3R_APP_FULL_NAME), app_version.to_string_sf()); + text += "\n"; + wxString context = text; + wxString append = _L("You'd better upgrade your software.\n"); + context += "\n\n"; + context += append; + show_info(q, context, _L("Newer 3MF version")); + } + else { + //if the minor version is not matched + if (file_version.min() != app_version.min()) { + wxString text = wxString::Format(_L("The 3MF file version %s is newer than %s's version %s, we suggest to upgrade your software."), + file_version.to_string_sf(), std::string(SLIC3R_APP_FULL_NAME), app_version.to_string_sf()); + text += "\n"; + show_info(q, text, _L("Newer 3MF version")); } } } - - } else if (load_config && (file_version > app_version)) { - if (config_substitutions.unrecogized_keys.size() > 0) { - wxString text = wxString::Format(_L("The 3MF file version %s is newer than %s's version %s, found the following unrecognized keys:"), - file_version.to_string(), std::string(SLIC3R_APP_FULL_NAME), app_version.to_string()); - text += "\n"; - bool first = true; - // std::string context = into_u8(text); - wxString context = text; - // if (wxGetApp().app_config->get("user_mode") == "develop") { - // for (auto &key : config_substitutions.unrecogized_keys) { - // context += " -"; - // context += key; - // context += ";\n"; - // first = false; - // } - // } - wxString append = _L("You'd better upgrade your software.\n"); - context += "\n\n"; - // context += into_u8(append); - context += append; - show_info(q, context, _L("Newer 3MF version")); + else if (load_config && config_loaded.empty()) { + load_config = false; + show_info(q, _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only."), _L("Load 3MF")); } - else { - //if the minor version is not matched - if (file_version.min() != app_version.min()) { - wxString text = wxString::Format(_L("The 3MF file version %s is newer than %s's version %s, we suggest to upgrade your software."), - file_version.to_string(), std::string(SLIC3R_APP_FULL_NAME), app_version.to_string()); - text += "\n"; - show_info(q, text, _L("Newer 3MF version")); + } + else if (en_3mf_file_type == En3mfType::From_BBS) { + // No OrcaSlicer tag - check Bambu/Application version + Semver orca_tag_start_version(2, 3, 2); + if (file_version <= orca_tag_start_version) { + // Compatible old version (before OrcaSlicer tagging was introduced after 2.3.2). + // Any version prior or equal to 2.3.2 is older than the current one, no version warnings needed. + // Still apply migration fixes for known old versions. + if (load_config && (file_version == Semver("2.3.1-alpha"))) { + if (!config_loaded.opt_string("sparse_infill_rotate_template").empty()) { + const auto _sparse_infill_pattern = + config_loaded.option>("sparse_infill_pattern")->value; + bool is_safe_to_rotate = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine || + _sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag || + _sparse_infill_pattern == ipLockedZag; + if (!is_safe_to_rotate) { + wxString msg_text = _( + L("This project was created with an OrcaSlicer 2.3.1-alpha and uses " + "infill rotation template settings that may not work properly with your current infill pattern. " + "This could result in weak support or print quality issues.")); + msg_text += "\n\n" + + _(L("Would you like OrcaSlicer to automatically fix this by clearing the rotation template settings?")); + MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); + dialog.SetButtonLabel(wxID_YES, _L("Yes")); + dialog.SetButtonLabel(wxID_NO, _L("No")); + if (dialog.ShowModal() == wxID_YES) { + config_loaded.opt_string("sparse_infill_rotate_template") = ""; + } + } + } + } + else if (load_config && config_loaded.empty()) { + load_config = false; + show_info(q, _L("The 3MF file was generated by an older version, loading geometry data only."), _L("Load 3MF")); + } + } else { + // BambuStudio project (version > 2.3.2 without OrcaSlicer tag) + // Report that a BambuStudio project is being imported and compare with SLIC3R_VERSION + Semver slic3r_version = *(Semver::parse(SLIC3R_VERSION)); + if (load_config && config_loaded.empty()) { + load_config = false; + show_info(q, _L("The 3MF file was generated by BambuStudio, loading geometry data only."), _L("Load 3MF")); + } + else if (load_config && (file_version > slic3r_version)) { + // BambuStudio file version is newer than our compatible SLIC3R_VERSION + if (config_substitutions.unrecogized_keys.size() > 0) { + wxString text = wxString::Format(_L("The 3MF was created by BambuStudio (version %s), which is newer than the compatible version %s. Found unrecognized settings:"), + file_version.to_string(), slic3r_version.to_string()); + text += "\n"; + wxString context = text; + wxString append = _L("You'd better upgrade your software.\n"); + context += "\n\n"; + context += append; + show_info(q, context, _L("BambuStudio Project")); + } else { + wxString text = wxString::Format(_L("The 3MF was created by BambuStudio (version %s), which is newer than the compatible version %s. Some settings may not be fully compatible."), + file_version.to_string(), slic3r_version.to_string()); + text += "\n"; + show_info(q, text, _L("BambuStudio Project")); + } + } else if (load_config) { + // BambuStudio version is older or same as our SLIC3R_VERSION + wxString text = _L("The 3MF was created by BambuStudio. Some settings may differ from OrcaSlicer."); + show_info(q, text, _L("BambuStudio Project")); } } - } + } else if (load_config && config_loaded.empty()) { load_config = false; show_info(q, _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only."), _L("Load 3MF")); @@ -6120,7 +6155,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } Semver old_version(1, 5, 9); - if ((en_3mf_file_type == En3mfType::From_BBS) && (file_version < old_version) && load_model && load_config && !config_loaded.empty()) { + if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && (file_version < old_version) && load_model && load_config && !config_loaded.empty()) { translate_old = true; partplate_list.get_plate_size(current_width, current_depth, current_height); } @@ -6207,7 +6242,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ { // BBS: modify the prime tower params for old version file Semver old_version3(2, 0, 0); - if (en_3mf_file_type == En3mfType::From_BBS && file_version < old_version3) { + if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && file_version < old_version3) { double old_filament_prime_volume = 0.; int filament_count = 0; { diff --git a/version.inc b/version.inc index 24b1354ce0..ce4b024c74 100644 --- a/version.inc +++ b/version.inc @@ -14,7 +14,7 @@ set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1}) set(ORCA_VERSION_MINOR ${CMAKE_MATCH_2}) set(ORCA_VERSION_PATCH ${CMAKE_MATCH_3}) -set(SLIC3R_VERSION "01.10.01.50") +set(SLIC3R_VERSION "02.05.01.52") if (NOT DEFINED ORCA_UPDATER_SIG_KEY) set(ORCA_UPDATER_SIG_KEY "" CACHE STRING "Base64url encoded updater signature key") From fd1aece9c7d6f48d7008ac2e6096c92583cf6f37 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 03:22:26 -0300 Subject: [PATCH 27/42] Bug and PR Template enhancements (#13297) * Add nightly build reproducibility checkbox Add a required checkbox to the bug report template prompting reporters to verify the issue is reproducible in the latest nightly build (links to the nightly builds release). This helps catch issues already fixed in nightly releases and reduces duplicate reports. * Add PR artifact download guide to template Update the pull request template to include a commented section and a link to the OrcaSlicer wiki explaining how to download artifacts from a PR. This helps contributors and reviewers locate and retrieve build/test artifacts for verification and testing. --- .github/ISSUE_TEMPLATE/bug_report.yml | 7 +++++++ .github/pull_request_template.md | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c21e7778ca..cf8ac675ed 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -11,6 +11,13 @@ body: For this, please use the [Feature request](https://github.com/OrcaSlicer/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=feature_request.yml) issue type or you can discuss your idea on our [Discord server](https://discord.gg/P4VE9UY9gJ) with others. Before filing, please check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment. + - type: checkboxes + attributes: + label: Is this issue reproducible in the latest nightly build? + description: Please verify this issue still happens in the latest nightly build first. It may already be fixed there: [Nightly builds](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds). + options: + - label: I have checked the latest nightly build and the issue is still reproducible + required: true - type: checkboxes attributes: label: Is there an existing issue for this problem? diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 65e3a5ff9f..575b8f17fe 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,3 +19,9 @@ + + + +[How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) From dee9c0d72c99c5f3e1787e65cd86c6442f0b1672 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 03:25:04 -0300 Subject: [PATCH 28/42] Add extrusion role change G-code in Process and Filament (#11784) * Add extrusion role change G-code options Introduces new G-code options for handling extrusion role changes at the process and filament levels. Updates configuration, GUI, and GCode logic to support 'process_change_extrusion_role_gcode' and 'filament_change_extrusion_role_gcode', allowing custom G-code insertion when the extrusion role changes. Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Optimize extrusion role-change gcode handling Cache gcode template strings and current filament id, and guard creation of DynamicConfig/placeholder processing behind a check that at least one role-change gcode is non-empty. This avoids redundant config lookups and DynamicConfig/placeholder parsing when no custom role-change gcode is defined, improving clarity and performance without changing behavior. --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- src/libslic3r/GCode.cpp | 34 +++++++++++++++++++++++++++++----- src/libslic3r/Preset.cpp | 6 +++--- src/libslic3r/PrintConfig.cpp | 20 ++++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Tab.cpp | 25 +++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 159fe73f4d..b6618e75d9 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1946,6 +1946,7 @@ namespace DoExport { //if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); //Orca if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change extrusion role G-code")), config.change_extrusion_role_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Process change extrusion role G-code")), config.process_change_extrusion_role_gcode.value); if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value); if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value); if (ret.size() < MAX_TAGS_COUNT) { @@ -1962,6 +1963,13 @@ namespace DoExport { break; } } + if (ret.size() < MAX_TAGS_COUNT) { + for (const std::string& value : config.filament_change_extrusion_role_gcode.values) { + check(_(L("Filament change extrusion role G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } //BBS: no custom_gcode_per_print_z, don't need to check //if (ret.size() < MAX_TAGS_COUNT) { // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; @@ -5423,6 +5431,7 @@ void GCode::apply_print_config(const PrintConfig &print_config) &m_config.time_lapse_gcode, &m_config.change_filament_gcode, &m_config.change_extrusion_role_gcode, + &m_config.process_change_extrusion_role_gcode, &m_config.printing_by_object_gcode, &m_config.machine_pause_gcode, &m_config.template_custom_gcode, @@ -5432,7 +5441,8 @@ void GCode::apply_print_config(const PrintConfig &print_config) } for (auto opt : std::initializer_list{ &m_config.filament_start_gcode, - &m_config.filament_end_gcode + &m_config.filament_end_gcode, + &m_config.filament_change_extrusion_role_gcode }) { if (opt->empty()) for (int i = 0; i < opt->size(); ++i) @@ -6551,15 +6561,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // Orca: End of dynamic PA trigger flag segment //Orca: process custom gcode for extrusion role change - if (path.role() != m_last_extrusion_role && !m_config.change_extrusion_role_gcode.value.empty()) { + if (path.role() != m_last_extrusion_role) { + const auto current_filament_id = m_writer.filament()->id(); + const std::string& machine_role_change_gcode = m_config.change_extrusion_role_gcode.value; + const std::string& filament_role_change_gcode = m_config.filament_change_extrusion_role_gcode.get_at(current_filament_id); + const std::string& process_role_change_gcode = m_config.process_change_extrusion_role_gcode.value; + + if (!machine_role_change_gcode.empty() || !filament_role_change_gcode.empty() || !process_role_change_gcode.empty()) { DynamicConfig config; config.set_key_value("extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(path.role()))); config.set_key_value("last_extrusion_role", new ConfigOptionString(extrusion_role_to_string_for_parser(m_last_extrusion_role))); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z)); - gcode += this->placeholder_parser_process("change_extrusion_role_gcode", - m_config.change_extrusion_role_gcode.value, m_writer.filament()->id(), &config) - + "\n"; + + const auto append_role_gcode = [this, current_filament_id, &config, &gcode](const std::string& key, const std::string& templ) { + if (templ.empty()) + return; + gcode += this->placeholder_parser_process(key, templ, current_filament_id, &config) + "\n"; + }; + + append_role_gcode("change_extrusion_role_gcode", machine_role_change_gcode); + append_role_gcode("filament_change_extrusion_role_gcode", filament_role_change_gcode); + append_role_gcode("process_change_extrusion_role_gcode", process_role_change_gcode); + } } // extrude arc or line diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 65f094af38..6371a631e2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -934,8 +934,8 @@ static std::vector s_Preset_print_options { "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "only_one_wall_top", "timelapse_type", - "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "min_length_factor", + "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", + "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "process_change_extrusion_role_gcode", "min_length_factor", "small_perimeter_speed", "small_perimeter_threshold","bridge_angle","internal_bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface", "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk","default_junction_deviation", "top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap", @@ -970,7 +970,7 @@ static std::vector s_Preset_filament_options {/*"filament_colour", //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", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed", - "filament_start_gcode", "filament_end_gcode", + "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", // Retract overrides diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ed6315c447..e9d2fe8e74 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4674,6 +4674,15 @@ void PrintConfigDef::init_fff_params() def->height = 6; def->mode = comAdvanced; def->set_default_value(new ConfigOptionStrings()); + + def = this->add("process_change_extrusion_role_gcode", coString); + def->label = L("Change extrusion role G-code (process)"); + def->tooltip = L("This G-code is inserted when the extrusion role is changed. It runs after the machine and filament extrusion role G-code."); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString()); def = this->add("printer_model", coString); def->label = L("Printer type"); @@ -6234,6 +6243,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString()); + def = this->add("filament_change_extrusion_role_gcode", coStrings); + def->label = L("Change extrusion role G-code (filament)"); + def->tooltip = L("This G-code is inserted when the extrusion role is changed for the active filament."); + def->multiline = true; + def->full_width = true; + def->height = 5; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionStrings{ "" }); + def = this->add("top_surface_line_width", coFloatOrPercent); def->label = L("Top surface"); def->category = L("Quality"); @@ -10707,6 +10725,8 @@ static std::map s_CustomGcodeSpecificP "travel_point_1_x", "travel_point_1_y", "travel_point_2_x", "travel_point_2_y", "travel_point_3_x", "travel_point_3_y", "x_after_toolchange", "y_after_toolchange", "z_after_toolchange"}}, {"change_extrusion_role_gcode", {"layer_num", "layer_z", "extrusion_role", "last_extrusion_role"}}, + {"filament_change_extrusion_role_gcode", {"layer_num", "layer_z", "extrusion_role", "last_extrusion_role"}}, + {"process_change_extrusion_role_gcode", {"layer_num", "layer_z", "extrusion_role", "last_extrusion_role"}}, {"printing_by_object_gcode", {}}, {"machine_pause_gcode", {}}, {"template_custom_gcode", {}}, diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4b9ebe5901..a314727e6d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1336,6 +1336,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, wipe_tower_no_sparse_layers)) ((ConfigOptionString, change_filament_gcode)) ((ConfigOptionString, change_extrusion_role_gcode)) + ((ConfigOptionString, process_change_extrusion_role_gcode)) + ((ConfigOptionStrings, filament_change_extrusion_role_gcode)) ((ConfigOptionFloat, travel_speed)) ((ConfigOptionFloat, travel_speed_z)) ((ConfigOptionBool, silent_mode)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4bd29b9fbf..9d93098069 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -67,6 +67,9 @@ namespace GUI { #define DISABLE_UNDO_SYS +// Forward declaration for early use; definitions live later in this translation unit. +static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_config_option_key& opt_key, const boost::any& value); + static const std::vector plate_keys = { "curr_bed_type", "skirt_start_angle", "first_layer_print_sequence", "first_layer_sequence_choice", "other_layers_print_sequence", "other_layers_sequence_choice", "print_sequence", "spiral_mode"}; void Tab::Highlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/) @@ -2702,6 +2705,17 @@ void TabPrint::build() // option.opt.height = 5; optgroup->append_single_option_line(option, "others_settings_g_code_output#filename-format"); + optgroup = page->new_optgroup(L("Change extrusion role G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); + }; + optgroup->edit_custom_gcode = [this](const t_config_option_key& opt_key) { edit_custom_gcode(opt_key); }; + option = optgroup->get_option("process_change_extrusion_role_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = 15; + optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Post-processing Scripts"), L"param_gcode", 0); option = optgroup->get_option("post_process"); option.opt.full_width = true; @@ -4052,6 +4066,17 @@ void TabFilament::build() option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Change extrusion role G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup_title, opt_key, value); + }; + optgroup->edit_custom_gcode = edit_custom_gcode_fn; + option = optgroup->get_option("filament_change_extrusion_role_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height;// 150; + optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) { validate_custom_gcode_cb(this, optgroup_title, opt_key, value); From 0082b608522ce66cf067d8ada9977c70b229e941 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:26:41 +0200 Subject: [PATCH 29/42] Restructure Painting Tool UI Hierarchy (#12852) restructure painting tools place "On Overhangs only" checkbox under Overhand slider, place "Vertical"/"Horizontal" checkboxes under pen size, move some separators, fix changing window width in MMUSegmentation, show "Section View" slider for gap fill too Co-authored-by: Hanno Witzleb --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 76 +++++---- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 11 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 158 ++++++------------ src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 14 +- 4 files changed, 100 insertions(+), 159 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index ce0e1e476b..c27461e63c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -228,7 +228,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; const float sliders_width = m_imgui->scaled(7.0f); - const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_left_width + sliders_width - space_size; + const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_width - space_size; float drag_pos_times = 0.7; @@ -281,11 +281,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } } - m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); - ImGui::Separator(); - if (m_current_tool != old_tool) this->tool_changed(old_tool, m_current_tool); @@ -300,7 +295,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f"); } else if (m_current_tool == ImGui::SphereButtonIcon) { @@ -312,7 +307,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f"); } else if (m_current_tool == ImGui::FillButtonIcon) { @@ -320,16 +315,19 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_tool_type = ToolType::SMART_FILL; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("smart_fill_angle")); - std::string format_str = std::string("%.f") + I18N::translate_utf8("", "Face angle threshold, placed after the number with no whitespace in between."); + m_imgui->text(m_desc["smart_fill_angle"]); + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Face angle threshold," + "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); - if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true)) + if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, + format_str.data(), 1.0f, true)) for (auto& triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f"); } else if (m_current_tool == ImGui::GapFillIcon) { @@ -342,11 +340,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::PushItemWidth(sliders_width); std::string format_str = std::string("%.2f") + I18N::translate_utf8("", "Triangle patch area threshold,""triangle patch will be merged to neighbor if its area is less than threshold"); m_imgui->bbl_slider_float_style("##gap_area", &TriangleSelectorPatch::gap_area, TriangleSelectorPatch::GapAreaMin, TriangleSelectorPatch::GapAreaMax, format_str.data(), 1.0f, true); - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); } + ImGui::Separator(); float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["highlight_by_angle"]); @@ -380,35 +379,38 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } ImGui::EndTooltip(); } - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##angle_threshold_deg_input", &m_highlight_by_angle_threshold_deg, 0.05f, 0.0f, 0.0f, "%.2f"); - if (m_current_tool != ImGui::GapFillIcon) { - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } + m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), + max_tooltip_width); - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); - bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - - ImGui::SameLine(drag_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this]() { + m_c->object_clipper()->set_position_by_ratio(-1., false); + }); + } + } + auto clp_dist = float(m_c->object_clipper()->get_position()); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(sliders_width); + bool b_bbl_slider_float = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); + + ImGui::SameLine(drag_left_width + sliders_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + bool b_drag_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); + + if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); + ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index af56eef6a1..862ae1cdcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -271,13 +271,14 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"]); - std::string format_str = std::string("%.f") + - I18N::translate_utf8("°", "Degree sign to use in the respective slider in fuzzy skin gizmo, " - "placed after the number with no whitespace in between."); + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Face angle threshold," + "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); - if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true)) - for (auto &triangle_selector : m_triangle_selectors) { + if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, + format_str.data(), 1.0f, true)) + for (auto& triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 6cba9cbf09..24c1456a6d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -513,67 +513,45 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(circle_max_width); + ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + circle_max_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f"); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); + if (m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only)) { + if (m_vertical_only) { + m_horizontal_only = false; } } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(circle_max_width); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + circle_max_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } - - } else if (m_current_tool == ImGui::TriangleButtonIcon) { + if (m_imgui->bbl_checkbox(_L("Horizontal"), m_horizontal_only)) { + if (m_horizontal_only) { + m_vertical_only = false; + } + } + } + else if (m_current_tool == ImGui::TriangleButtonIcon) { m_cursor_type = TriangleSelector::CursorType::POINTER; m_tool_type = ToolType::BRUSH; - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); + if (m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only)) { + if (m_vertical_only) { + m_horizontal_only = false; } } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + clipping_slider_left); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } - - } else if (m_current_tool == ImGui::FillButtonIcon) { + if (m_imgui->bbl_checkbox(_L("Horizontal"), m_horizontal_only)) { + if (m_horizontal_only) { + m_vertical_only = false; + } + } + } + else if (m_current_tool == ImGui::FillButtonIcon) { m_cursor_type = TriangleSelector::CursorType::POINTER; - m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); m_tool_type = ToolType::BUCKET_FILL; + m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); + if (m_detect_geometry_edge) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"]); @@ -593,94 +571,57 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // set to negative value to disable edge detection m_smart_fill_angle = -1.f; } - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + sliders_left_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true);} - - } else if (m_current_tool == ImGui::HeightRangeIcon) { + } + else if (m_current_tool == ImGui::HeightRangeIcon) { m_tool_type = ToolType::BRUSH; m_cursor_type = TriangleSelector::CursorType::HEIGHT_RANGE; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["height_range"] + ":"); - ImGui::SameLine(height_max_width); + ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); std::string format_str = std::string("%.2f") + I18N::translate_utf8("mm", "Height range," "Facet in [cursor z, cursor z + height] will be selected."); m_imgui->bbl_slider_float_style("##cursor_height", &m_cursor_height, CursorHeightMin, CursorHeightMax, format_str.data(), 1.0f, true); - ImGui::SameLine(drag_left_width + height_max_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_height_input", &m_cursor_height, 0.05f, 0.0f, 0.0f, "%.2f"); - - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this]() { - m_c->object_clipper()->set_position_by_ratio(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(height_max_width); - ImGui::PushItemWidth(sliders_width); - bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width + height_max_width); - ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } } else if (m_current_tool == ImGui::GapFillIcon) { m_tool_type = ToolType::GAP_FILL; m_cursor_type = TriangleSelector::CursorType::POINTER; ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["gap_area"] + ":"); - ImGui::SameLine(gap_area_slider_left); + ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(sliders_width); std::string format_str = std::string("%.2f") + I18N::translate_utf8("", "Triangle patch area threshold,""triangle patch will be merged to neighbor if its area is less than threshold"); m_imgui->bbl_slider_float_style("##gap_area", &TriangleSelectorPatch::gap_area, TriangleSelectorPatch::GapAreaMin, TriangleSelectorPatch::GapAreaMax, format_str.data(), 1.0f, true); - ImGui::SameLine(drag_left_width + gap_area_slider_left); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); } ImGui::Separator(); - if(m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only)){ - if(m_vertical_only){ - m_horizontal_only = false; + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); }); } } - if(m_imgui->bbl_checkbox(_L("Horizontal"), m_horizontal_only)){ - if(m_horizontal_only){ - m_vertical_only = false; - } + + auto clp_dist = float(m_c->object_clipper()->get_position()); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(sliders_width); + bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); + ImGui::SameLine(drag_left_width + sliders_left_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); + + if (slider_clp_dist || b_clp_dist_input) { + m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } ImGui::Separator(); - ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.5f)); - // ORCA: Remap filaments section (Border only, Title in border). // Styled as a panel for visual grouping. if (m_imgui->button(m_desc.at("perform_remap"))) { @@ -689,6 +630,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (m_show_remap_panel) { + ImGui::Spacing(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); std::string title = into_u8(m_desc.at("perform_remap")); float available_width = ImGui::GetContentRegionAvail().x; @@ -719,7 +662,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott remap_panel_high = ImGui::GetCursorPos().y - start_y; // ORCA: Add Remap and Cancel buttons (outside the panel) - ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.2f)); + ImGui::Spacing(); if (m_imgui->button(m_desc.at("remap"))) { this->remap_filament_assignments(); // Reset mapping to identity after apply @@ -732,7 +675,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.5f)); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 68aaa331a6..e68d71513a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -184,7 +184,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float slider_icon_width = m_imgui->get_slider_icon_size().x; const float sliders_width = m_imgui->scaled(7.0f); - const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_left_width + sliders_width - space_size; + const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_width - space_size; const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -250,13 +250,14 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true); - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f"); + m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only); + ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); @@ -272,18 +273,13 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) auto clp_dist = float(m_c->object_clipper()->get_position()); ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(sliders_width); bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true); - - ImGui::SameLine(drag_left_width); + ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); } - ImGui::Separator(); - m_imgui->bbl_checkbox(_L("Vertical"), m_vertical_only); - ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); From 8bff921ade89c3e050a1b50858705f1aefbdcb4c Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:48:59 +0200 Subject: [PATCH 30/42] Require less clicks to change filament in object list (#12603) * reduces number of clicks to change filament in objectlist * dont set minimum width in dropdown if use_content_width == true --------- Co-authored-by: Hanno Witzleb Co-authored-by: yw4z --- src/slic3r/GUI/ExtraRenderers.cpp | 23 ++++++++++------------- src/slic3r/GUI/ExtraRenderers.hpp | 10 +++------- src/slic3r/GUI/GUI_ObjectList.cpp | 9 +++++++++ src/slic3r/GUI/Widgets/ComboBox.cpp | 17 +++++++++++++++++ src/slic3r/GUI/Widgets/ComboBox.hpp | 3 +++ src/slic3r/GUI/Widgets/DropDown.cpp | 2 +- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 068a463246..4c16ce8deb 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -235,7 +235,7 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0); + GetView()->GetModel()->GetValue(valueOld, m_item, Slic3r::GUI::ColumnNumber::colName); DataViewBitmapText bmpText; bmpText << valueOld; @@ -265,13 +265,10 @@ bool BitmapChoiceRenderer::GetValue(wxVariant& value) const bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) { -// int xoffset = 0; - const wxBitmap& icon = m_value.GetBitmap(); if (icon.IsOk()) { dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); -// xoffset = icon.GetWidth() + 4; if (rect.height == 0) rect.height = icon.GetHeight(); @@ -299,7 +296,6 @@ wxSize BitmapChoiceRenderer::GetSize() const return sz; } - wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) { if (can_create_editor_ctrl && !can_create_editor_ctrl()) @@ -316,7 +312,7 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), 0, nullptr, wxCB_READONLY | CB_NO_DROP_ICON | CB_NO_TEXT); c_editor->GetDropDown().SetUseContentWidth(true); - + if (has_default_extruder && has_default_extruder()) c_editor->Append(_L("default"), *get_default_extruder_color_icon()); @@ -328,18 +324,19 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR else c_editor->SetSelection(atoi(data.GetText().c_str()) - 1); -#ifdef __linux__ + // Open the dropdown immediately when the editor is focused. + c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) { + c_editor->ForceDropdownOpen(); + evt.Skip(); + }); + + // Close editor after selection is made c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { - // to avoid event propagation to other sidebar items - evt.StopPropagation(); + evt.StopPropagation(); // FinishEditing grabs new selection and triggers config update. We better call // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. this->FinishEditing(); }); -#else - // to avoid event propagation to other sidebar items - c_editor->Bind(wxEVT_COMBOBOX, [](wxCommandEvent& evt) { evt.StopPropagation(); }); -#endif return c_editor; } diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index f7be8040b7..18f1f49182 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -132,13 +132,9 @@ private: class BitmapChoiceRenderer : public wxDataViewCustomRenderer { public: - BitmapChoiceRenderer(wxDataViewCellMode mode = -//#ifdef __WXOSX__ -// wxDATAVIEW_CELL_INERT -//#else - wxDATAVIEW_CELL_EDITABLE -//#endif - , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + BitmapChoiceRenderer( + wxDataViewCellMode mode = wxDATAVIEW_CELL_EDITABLE, + int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} bool SetValue(const wxVariant& value) override; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index da0326704a..919b7217b1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -460,6 +460,15 @@ void ObjectList::create_objects_ctrl() AppendBitmapColumn(" ", colEditing, wxOSX ? wxDATAVIEW_CELL_EDITABLE : wxDATAVIEW_CELL_INERT, m_columns_width[colEditing] * em, wxALIGN_CENTER_HORIZONTAL, 0); + + // Open filament editor faster + this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& event) { + if (event.GetColumn() == colFilament) { + // Trigger the editor opening manually + this->EditItem(event.GetItem(), GetColumn(colFilament)); + } + }); + //for (int cn = colName; cn < colCount; cn++) { // GetColumn(cn)->SetResizeable(cn == colName); //} diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp index 1e2ad8c042..769ad0442b 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.cpp +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -241,6 +241,23 @@ void ComboBox::DoDeleteOneItem(unsigned int pos) drop.Invalidate(true); } +void ComboBox::ForceDropdownOpen() +{ + if (!IsEnabled()) + return; + + if (!drop_down) { + drop.need_sync = true; + drop.messureSize(); + drop.autoPosition(); + drop_down = true; + drop.Popup(&drop); + + wxCommandEvent e(wxEVT_COMBOBOX_DROPDOWN); + GetEventHandler()->ProcessEvent(e); + } +} + unsigned int ComboBox::GetCount() const { return items.size(); } void ComboBox::set_replace_text(wxString text, wxString image_name) diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp index c56dda52a9..5d570e8bf6 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.hpp +++ b/src/slic3r/GUI/Widgets/ComboBox.hpp @@ -76,6 +76,9 @@ public: void SetItemBitmap(unsigned int n, wxBitmap const &bitmap); bool is_drop_down(){return drop_down;} void DeleteOneItem(unsigned int pos) { DoDeleteOneItem(pos); } + + void ForceDropdownOpen(); + protected: virtual int DoInsertItems(const wxArrayStringsAdapter &items, unsigned int pos, diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index 1aee9377e1..2f8c04166e 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -525,7 +525,7 @@ void DropDown::messureSize() } if (!align_icon) iconSize.x = 0; wxSize szContent = textSize; - if (szContent.x < FromDIP(120)) + if (szContent.x < FromDIP(120) && !use_content_width) szContent.x = FromDIP(120); szContent.x += 10; if (check_bitmap.bmp().IsOk()) { From 6f1b1b876725f534c9a42933a8ac4b80c50be3a1 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 03:53:53 -0300 Subject: [PATCH 31/42] Partial sacrificial bridge anchor fix (#13052) * Partial sacrificial bridge anchor fix Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Simplification --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- src/libslic3r/PerimeterGenerator.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index f8827923f4..26b2660b87 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1870,6 +1870,20 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim bridges_temp = diff_ex(bridges_temp, unbridgeable); unsupported_filtered = offset_ex(bridges_temp, offset_to_do); unsupported_filtered = intersection_ex(unsupported_filtered, reference); + + // Normalize anchor size for partial bridges: + // derive the bridge core first, then add a fixed overlap into support. + const coordf_t anchor_overlap = bridged_infill_margin; + ExPolygons bridge_core = diff_ex(unsupported_filtered, support, ApplySafetyOffset::Yes); + if (bridge_core.empty()) { + bridge_core = unsupported_filtered; + } + ExPolygons anchor_overlap_area = intersection_ex( + offset_ex(bridge_core, anchor_overlap), + support, + ApplySafetyOffset::Yes); + unsupported_filtered = union_ex(bridge_core, anchor_overlap_area); + unsupported_filtered = intersection_ex(unsupported_filtered, reference); // } else { // ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2))); // unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width); From babbdee5f571d68bd2613cd8dc425d68619fe0f5 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:49:36 +0200 Subject: [PATCH 32/42] Refactor Tooltip Button (#12848) refactors tooltipbutton, one common implementation, one common shortcut data structure, add tooltip button to text gizmo, consistent shortcut wording Co-authored-by: Hanno Witzleb --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 138 +++++----------- src/slic3r/GUI/GLCanvas3D.hpp | 6 +- src/slic3r/GUI/GUI.cpp | 5 + src/slic3r/GUI/GUI.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp | 13 +- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp | 59 ++----- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 65 +++----- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 13 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 129 +++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 118 ++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 60 ++----- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 7 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 154 ++++++++---------- .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 61 ++----- src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp | 75 +++++++++ src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp | 25 +++ .../GUI/Gizmos/GizmoObjectManipulation.cpp | 153 +++-------------- .../GUI/Gizmos/GizmoObjectManipulation.hpp | 14 +- 26 files changed, 465 insertions(+), 678 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1cd2c149bf..59605d437a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -170,6 +170,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmosManager.hpp GUI/Gizmos/GLGizmoSVG.cpp GUI/Gizmos/GLGizmoSVG.hpp + GUI/Gizmos/GLGizmoUtils.cpp + GUI/Gizmos/GLGizmoUtils.hpp #GUI/Gizmos/GLGizmoText.cpp #GUI/Gizmos/GLGizmoText.hpp GUI/GLCanvas3D.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3be30551c5..642ec7f36f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -33,6 +33,7 @@ #include "format.hpp" #include "DailyTips.hpp" #include "FilamentMapDialog.hpp" +#include "Gizmos/GLGizmoUtils.hpp" #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -224,43 +225,6 @@ bool GLCanvas3D::LayersEditing::is_allowed() const float GLCanvas3D::LayersEditing::s_overlay_window_width; -void GLCanvas3D::LayersEditing::show_tooltip_information(const GLCanvas3D& canvas, std::map captions_texts, float x, float y) -{ - ImTextureID normal_id = canvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = canvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - float caption_max = 0.f; - for (auto caption_text : captions_texts) { - caption_max = std::max(imgui.calc_text_size(caption_text.first).x, caption_max); - } - caption_max += GImGui->Style.WindowPadding.x + imgui.scaled(1); - - float scale = canvas.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max, &imgui](const wxString& caption, const wxString& text) { - imgui.text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - imgui.text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto& caption_text : captions_texts) draw_text_with_caption(caption_text.first, caption_text.second); - - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanvas3D& canvas) { if (!m_enabled) return; @@ -358,15 +322,18 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv ImGui::Separator(); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + canvas.m_main_toolbar.get_height(); - std::map captions_texts = { - {_L("Left mouse button") + ":" , _L("Add detail")}, - {_L("Right mouse button") + ":", _L("Remove detail")}, - {_L("Shift+") + _L("Left mouse button") + ":", _L("Reset to base")}, - {_L("Shift+") + _L("Right mouse button") + ":", _L("Smoothing")}, - {_L("Mouse wheel:"), _L("Increase/decrease edit area")} + const wxString shift = GUI::shortkey_shift_prefix(); + std::vector> shortcuts = { + {_L("Left mouse button"), _L("Add detail")}, + {_L("Right mouse button"), _L("Remove detail")}, + {shift + _L("Left mouse button"), _L("Reset to base")}, + {shift + _L("Right mouse button"), _L("Smoothing")}, + {_L("Mouse wheel"), _L("Increase/decrease edit area")} }; - show_tooltip_information(canvas, captions_texts, x, get_cur_y); + + float y = canvas.m_main_toolbar.get_height(); + GLGizmoUtils::render_tooltip_button(&imgui, canvas, shortcuts, x, y); + ImGui::SameLine(); if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); @@ -1222,13 +1189,13 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) m_selection.set_volumes(&m_volumes.volumes); - m_assembly_view_desc["object_selection_caption"] = _L("Left mouse button"); - m_assembly_view_desc["object_selection"] = _L("Object selection"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - m_assembly_view_desc["part_selection_caption"] = _L("Alt+") + _L("Left mouse button"); - m_assembly_view_desc["part_selection"] = _L("Part selection"); - m_assembly_view_desc["number_key_caption"] = "1~16 " + _L("number keys"); - m_assembly_view_desc["number_key"] = _L("Number keys can quickly change the color of objects"); + const wxString alt = GUI::shortkey_alt_prefix(); + + m_shortcuts_assembly_view = { + {_L("Left mouse button"), _L("Object Selection")}, + {alt + _L("Left mouse button"), _L("Part Selection")}, + {"1~16 " + _L("number keys"), _L("Number keys can quickly change the color of objects")}, + }; } GLCanvas3D::~GLCanvas3D() @@ -8913,45 +8880,32 @@ void GLCanvas3D::_render_paint_toolbar() const ImGui::PopStyleColor(); } -float GLCanvas3D::_show_assembly_tooltip_information(float caption_max, float x, float y) const +float GLCanvas3D::_render_assembly_tooltip_button(ImGuiWrapper* imgui_wrapper) const { - ImGuiWrapper *imgui = wxGetApp().imgui(); - ImTextureID normal_id = m_gizmos.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_gizmos.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + const float text_height = imgui_wrapper->calc_text_size(_L("part selection")).y; + ImVec2 windowPos = ImGui::GetWindowPos(); + float x = windowPos.x; + float y = windowPos.y - ImGui::GetFrameHeight() - (5 * text_height); + y -= ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight(); // correct default ToolTipButton behaviour - caption_max += imgui->calc_text_size(": "sv).x + 35.f; + GLGizmoUtils::render_tooltip_button(imgui_wrapper, *this, m_shortcuts_assembly_view, x, y); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(12.f, 0.f)); - float scale = get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 + float scale = get_scale(); +#ifdef WIN32 + int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); + scale *= (float) dpi / (float) DPI_DEFAULT; +#endif // WIN32 ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); - ImGui::ImageButton3(normal_id, hover_id, button_size); + float same_line_width = button_size.x * 1.8; // with an space size + ImGui::SameLine(same_line_width); + same_line_width = imgui_wrapper->calc_text_size("|"sv).x + same_line_width + imgui_wrapper->calc_text_size(" "sv).x; + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, "|"); + ImGui::SameLine(same_line_width); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [&imgui, & caption_max](const wxString &caption, const wxString &text) { - imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"object_selection", "part_selection", "number_key"}) { - draw_text_with_caption(m_assembly_view_desc.at(t + "_caption") + ": ", m_assembly_view_desc.at(t)); - } - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); - auto same_line_size = button_size.x * 1.8;//with an space size - ImGui::SameLine(same_line_size); - same_line_size = imgui->calc_text_size("|"sv).x + same_line_size + imgui->calc_text_size(" "sv).x; - imgui->text_colored(ImGuiWrapper::COL_ACTIVE, "|"); - ImGui::SameLine(same_line_size); - return same_line_size; + return same_line_width; // return the width of the space taken by the tooltip button and the separator } //BBS @@ -8985,19 +8939,11 @@ void GLCanvas3D::_render_assemble_control() imgui->begin(_L("Assemble Control"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); ImGui::AlignTextToFramePadding(); - float tip_icon_size; + float tooltip_button_width; { - float caption_max = 0.f; - for (const auto &t : std::array{"object_selection", "part_selection", "number_key"}) { - caption_max = std::max(caption_max, imgui->calc_text_size(m_assembly_view_desc.at(t + "_caption")).x); - } - const ImVec2 pos = ImGui::GetCursorScreenPos(); - const float text_y = imgui->calc_text_size(_L("Part selection")).y; - float get_cur_x = pos.x; - float get_cur_y = pos.y - ImGui::GetFrameHeight() - 4 * text_y; - tip_icon_size =_show_assembly_tooltip_information(caption_max, get_cur_x, get_cur_y); + tooltip_button_width = _render_assembly_tooltip_button(imgui); } - float same_line_width = tip_icon_size; + float same_line_width = tooltip_button_width; { float clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position(); if (clp_dist == 0.f) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 8a3156db15..2895b110d3 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -289,7 +289,6 @@ class GLCanvas3D bool is_enabled() const { return m_enabled; } void set_enabled(bool enabled) { m_enabled = is_allowed() && enabled; } - void show_tooltip_information(const GLCanvas3D& canvas, std::map captions_texts, float x, float y); void render_variable_layer_height_dialog(const GLCanvas3D& canvas); void render_overlay(const GLCanvas3D& canvas); void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes); @@ -521,7 +520,8 @@ private: wxGLContext* m_context; SceneRaycaster m_scene_raycaster; Bed3D &m_bed; - std::map m_assembly_view_desc; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_assembly_view; #if ENABLE_RETINA_GL std::unique_ptr m_retina_helper; #endif @@ -1262,7 +1262,7 @@ private: // BBS //void _render_view_toolbar() const; void _render_paint_toolbar() const; - float _show_assembly_tooltip_information(float caption_max, float x, float y) const; + float _render_assembly_tooltip_button(ImGuiWrapper* imgui_wrapper) const; void _render_assemble_control(); void _render_assemble_info() const; #if ENABLE_SHOW_CAMERA_TARGET diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 328993beff..78d9c9839b 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -78,6 +78,11 @@ void break_to_debugger() #endif /* _WIN32 */ } +const std::string& shortkey_shift_prefix() +{ + return _u8L("Shift+"); +} + const std::string& shortkey_ctrl_prefix() { static const std::string str = diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index db8cf06a61..f14a2eb7c3 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -31,6 +31,7 @@ void break_to_debugger(); // Platform specific Ctrl+/Alt+ (Windows, Linux) vs. ⌘/⌥ (OSX) prefixes extern const std::string& shortkey_ctrl_prefix(); extern const std::string& shortkey_alt_prefix(); +extern const std::string& shortkey_shift_prefix(); // Shift is the same on all platforms, but we provide a function for consistency with Ctrl/Alt prefixes extern AppConfig* get_app_config(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp index a737f4209b..f6ce2533f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp @@ -4,6 +4,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/MeasureUtils.hpp" @@ -43,6 +44,8 @@ std::string GLGizmoAssembly::on_get_name() const bool GLGizmoAssembly::on_init() { + GLGizmoMeasure::on_init(); + m_shortcut_key = WXK_CONTROL_Y; return true; } @@ -108,14 +111,8 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"point_selection", "reset", "unselect"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); - } - show_tooltip_information(caption_max, x, get_cur_y); + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp index 8ec117e559..682be933ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/Plater.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ExPolygon.hpp" +#include "GLGizmoUtils.hpp" namespace Slic3r { namespace GUI { @@ -44,10 +45,8 @@ bool GLGizmoBrimEars::on_init() m_shortcut_key = WXK_CONTROL_E; - // FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent? - const wxString ctrl = _L("Ctrl+"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - const wxString alt = _L("Alt+"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); m_desc["head_diameter"] = _L("Head diameter"); m_desc["max_angle"] = _L("Max angle"); @@ -57,14 +56,12 @@ bool GLGizmoBrimEars::on_init() m_desc["auto_generate"] = _L("Auto-generate points"); m_desc["section_view"] = _L("Section view"); - m_desc["left_click_caption"] = _L("Left click"); - m_desc["left_click"] = _L("Add a brim ear"); - m_desc["right_click_caption"] = _L("Right click"); - m_desc["right_click"] = _L("Delete a brim ear"); - m_desc["ctrl_mouse_wheel_caption"] = ctrl + _L("Mouse wheel"); - m_desc["ctrl_mouse_wheel"] = _L("Adjust head diameter"); - m_desc["alt_mouse_wheel_caption"] = alt + _L("Mouse wheel"); - m_desc["alt_mouse_wheel"] = _L("Adjust section view"); + m_shortcuts = { + {_L("Left mouse button"), _L("Add a brim ear")}, + {_L("Right mouse button"), _L("Delete a brim ear")}, + {ctrl + _L("Mouse wheel"), _L("Adjust head diameter")}, + {alt + _L("Mouse wheel"), _L("Adjust section view")}, + }; return true; } @@ -737,7 +734,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::PopStyleVar(1); float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(x, get_cur_y); + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); if (glb_cfg.opt_enum("brim_type") != btPainted) { ImGui::SameLine(); @@ -790,41 +788,6 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGuiWrapper::pop_toolbar_style(); } -void GLGizmoBrimEars::show_tooltip_information(float x, float y) -{ - std::array info_array = std::array{"left_click", "right_click", "ctrl_mouse_wheel", "alt_mouse_wheel"}; - float caption_max = 0.f; - for (const auto &t : info_array) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); } - - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(": "sv).x + 35.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : info_array) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - bool GLGizmoBrimEars::on_is_activable() const { const Selection &selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp index 20fee730b3..8f50859130 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp @@ -117,6 +117,9 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts; + GLSelectionRectangle m_selection_rectangle; @@ -159,7 +162,6 @@ protected: void on_start_dragging() override; void on_stop_dragging() override; void on_render_input_window(float x, float y, float bottom_limit) override; - void show_tooltip_information(float x, float y); std::string on_get_name() const override; bool on_is_activable() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index b57bc007f2..6c5185b0d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -12,6 +12,7 @@ #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" +#include "GLGizmoUtils.hpp" #include "imgui/imgui_internal.h" #include "slic3r/GUI/Field.hpp" @@ -1186,16 +1187,21 @@ bool GLGizmoCut3D::on_init() // initiate info shortcuts const wxString ctrl = GUI::shortkey_ctrl_prefix(); const wxString alt = GUI::shortkey_alt_prefix(); - const wxString shift = _L("Shift+"); + const wxString shift = GUI::shortkey_shift_prefix(); - m_shortcuts_cut.push_back(std::make_pair(shift + _L("Drag"), _L("Draw cut line"))); + m_shortcuts_cut = { + {_L("Drag"), _L("Move cut line")}, + {shift + _L("Drag"), _L("Draw cut line")} + }; - m_shortcuts_connector.push_back(std::make_pair(_L("Left click"), _L("Add connector"))); - m_shortcuts_connector.push_back(std::make_pair(_L("Right click"), _L("Remove connector"))); - m_shortcuts_connector.push_back(std::make_pair(_L("Drag"), _L("Move connector"))); - m_shortcuts_connector.push_back(std::make_pair(shift + _L("Left click"), _L("Add connector to selection"))); - m_shortcuts_connector.push_back(std::make_pair(alt + _L("Left click"), _L("Remove connector from selection"))); - m_shortcuts_connector.push_back(std::make_pair(ctrl + "A", _L("Select all connectors"))); + m_shortcuts_connector = { + {_L("Left mouse button"), _L("Add connector")}, + {_L("Right mouse button"), _L("Remove connector")}, + {_L("Drag"), _L("Move connector")}, + {shift + _L("Left mouse button"), _L("Add connector to selection")}, + {alt + _L("Left mouse button"), _L("Remove connector from selection")}, + {ctrl + "A", _L("Select all connectors")}, + }; return true; } @@ -2313,8 +2319,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(x, get_cur_y); + render_tooltip_button(x, y); float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); @@ -2757,7 +2762,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(x, get_cur_y); + render_tooltip_button(x, get_cur_y); float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); @@ -2917,43 +2922,11 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) render_debug_input_window(x); } -void GLGizmoCut3D::show_tooltip_information(float x, float y) +void GLGizmoCut3D::render_tooltip_button(float x, float y) { - auto &shortcuts = m_connectors_editing ? m_shortcuts_connector : m_shortcuts_cut; + auto& shortcuts = m_connectors_editing ? m_shortcuts_connector : m_shortcuts_cut; - float caption_max = 0.f; - for (const auto &short_cut : shortcuts) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(short_cut.first).x); - } - - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 35.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &short_cut : shortcuts) - draw_text_with_caption(short_cut.first + ": ", short_cut.second); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, shortcuts, x, y); } bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 47a6d47f39..73838f43c3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -200,7 +200,9 @@ class GLGizmoCut3D : public GLGizmoBase PartSelection m_part_selection; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} std::vector> m_shortcuts_cut; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} std::vector> m_shortcuts_connector; enum class CutMode { @@ -321,7 +323,7 @@ protected: void update_plane_model(); void on_render_input_window(float x, float y, float bottom_limit) override; - void show_tooltip_information(float x, float y); + void render_tooltip_button(float x, float y); bool wants_enter_leave_snapshots() const override { return true; } std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index d158efd844..4225a256fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -15,6 +15,7 @@ #include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/WxFontUtils.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include "libslic3r/Geometry.hpp" // to range pi pi #include "libslic3r/Timer.hpp" @@ -725,6 +726,10 @@ bool GLGizmoEmboss::on_init() // NOTE: It has special handling in GLGizmosManager::handle_shortcut m_shortcut_key = WXK_CONTROL_T; + m_shortcuts = { + {_L("Drag"), _L("Position on surface")} + }; + // initialize text styles m_style_manager.init(wxGetApp().app_config); @@ -918,7 +923,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - draw_window(); + draw_window(x, y); GizmoImguiEnd(); @@ -1341,7 +1346,7 @@ void GLGizmoEmboss::close() mng.open_gizmo(GLGizmosManager::Emboss); } -void GLGizmoEmboss::draw_window() +void GLGizmoEmboss::draw_window(float x, float y) { #ifdef ALLOW_DEBUG_MODE if (ImGui::Button("re-process")) process(); @@ -1393,6 +1398,10 @@ void GLGizmoEmboss::draw_window() ImGui::Separator(); draw_model_type(); } + + ImGui::Separator(); + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 7d4d5cd3fc..f5916620f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -112,7 +112,7 @@ private: // create volume from text - main functionality bool process(bool make_snapshot = true); void close(); - void draw_window(); + void draw_window(float x, float y); void draw_text_input(); void draw_model_type(); void draw_style_list(); @@ -229,6 +229,9 @@ private: // only temporary solution static const std::string M_ICON_FILENAME; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts; }; } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index c27461e63c..6b3fd492c6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -14,6 +14,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include @@ -79,33 +80,47 @@ bool GLGizmoFdmSupports::on_init() // BBS m_shortcut_key = WXK_CONTROL_L; - // FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent? - const wxString ctrl = _L("Ctrl+"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - const wxString alt = _L("Alt+"); - const wxString shift = _L("Shift+"); + m_desc["perform"] = _L("Perform"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + m_desc["remove_all"] = _L("Erase all painting"); + m_desc["highlight_by_angle"] = _L("Highlight overhang areas"); + m_desc["tool_type"] = _L("Tool type"); + m_desc["gap_fill"] = _L("Gap fill"); + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["clipping_of_view"] = _L("Section view"); + m_desc["cursor_size"] = _L("Pen size"); + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["gap_area"] = _L("Gap area"); - m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); - m_desc["clipping_of_view"] = _L("Section view"); - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = ctrl + _L("Mouse wheel"); - m_desc["cursor_size"] = _L("Pen size"); - m_desc["enforce_caption"] = _L("Left mouse button"); - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button"); - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = shift + _L("Left mouse button"); - m_desc["remove"] = _L("Erase"); - m_desc["remove_all"] = _L("Erase all painting"); - m_desc["highlight_by_angle"] = _L("Highlight overhang areas"); - m_desc["gap_fill"] = _L("Gap fill"); - m_desc["perform"] = _L("Perform"); - m_desc["gap_area_caption"] = ctrl + _L("Mouse wheel"); - m_desc["gap_area"] = _L("Gap area"); - m_desc["tool_type"] = _L("Tool type"); - m_desc["smart_fill_angle_caption"] = ctrl + _L("Mouse wheel"); - m_desc["smart_fill_angle"] = _L("Smart fill angle"); - m_desc["on_overhangs_only"] = _L("On overhangs only"); + + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = GUI::shortkey_shift_prefix(); + + std::pair enforce_shortcut = {_L("Left mouse button"), _L("Enforce supports")}; + std::pair block_shortcut = {_L("Right mouse button"), _L("Block supports")}; + std::pair remove_shortcut = {shift + _L("Left mouse button"), _L("Erase")}; + std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; + + m_shortcuts_brush = { + enforce_shortcut, + block_shortcut, + remove_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, + clipping_shortcut + }; + + m_shortcuts_bucket_fill = { + enforce_shortcut, + block_shortcut, + remove_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, + clipping_shortcut + }; + + m_shortcuts_gap_fill = { + {ctrl + _L("Mouse wheel"), _L("Gap area")} + }; memset(&m_print_instance, 0, sizeof(m_print_instance)); return true; @@ -414,8 +429,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(caption_max, x, get_cur_y); + render_tooltip_button(x, y); float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); @@ -473,53 +487,22 @@ void GLGizmoFdmSupports::tool_changed(wchar_t old_tool, wchar_t new_tool) } } -void GLGizmoFdmSupports::show_tooltip_information(float caption_max, float x, float y) -{ - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - // BBS - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - std::vector tip_items; - switch (m_tool_type) { - case ToolType::BRUSH: - tip_items = {"enforce", "block", "remove", "cursor_size", "clipping_of_view"}; - break; - case ToolType::BUCKET_FILL: - break; - case ToolType::SMART_FILL: - tip_items = {"enforce", "block", "remove", "smart_fill_angle", "clipping_of_view"}; - break; - case ToolType::GAP_FILL: - tip_items = {"gap_area"}; - break; +void GLGizmoFdmSupports::render_tooltip_button(float x, float y) { + auto get_shortcuts = [this]() -> std::vector> { + switch (m_current_tool) { + case ImGui::CircleButtonIcon: + case ImGui::SphereButtonIcon: + return m_shortcuts_brush; + case ImGui::FillButtonIcon: + return m_shortcuts_bucket_fill; + case ImGui::GapFillIcon: + return m_shortcuts_gap_fill; default: - break; + return {}; } - for (const auto &t : tip_items) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); + }; + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, get_shortcuts(), x, y); } // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index ec0faeab80..133e71e2f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -33,7 +33,7 @@ protected: std::string on_get_name() const override; void on_set_state() override; - void show_tooltip_information(float caption_max, float x, float y); + void render_tooltip_button(float x, float y); wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; std::string get_gizmo_entering_text() const override { return "Entering Paint-on supports"; } @@ -90,6 +90,13 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_brush; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_bucket_fill; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_gap_fill; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index 862ae1cdcf..0c50c32e12 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include #include @@ -31,31 +32,47 @@ bool GLGizmoFuzzySkin::on_init() { m_shortcut_key = WXK_CONTROL_H; - // FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent? - const wxString ctrl = _L("Ctrl+"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - const wxString alt = _L("Alt+"); - const wxString shift = _L("Shift+"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); - m_desc["clipping_of_view"] = _L("Section view"); - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = ctrl + _L("Mouse wheel"); - m_desc["cursor_size"] = _L("Brush size"); - m_desc["cursor_type"] = _L("Brush shape") ; - m_desc["add_fuzzy_skin_caption"] = _L("Left mouse button"); - m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin"); - m_desc["remove_fuzzy_skin_caption"] = shift + _L("Left mouse button"); - m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin"); - m_desc["remove_all"] = _L("Erase all painting"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); - m_desc["tool_type"] = _L("Tool type"); - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); - m_desc["smart_fill_angle_caption"] = ctrl + _L("Mouse wheel"); - m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["remove_all"] = _L("Erase all painting"); + m_desc["circle"] = _L("Circle"); + m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Triangles"); + m_desc["tool_type"] = _L("Tool type"); + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); + m_desc["clipping_of_view"] = _L("Section view"); + m_desc["cursor_size"] = _L("Pen size"); + m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin"); + m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin"); + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + + std::pair add_fuzzy_skin_shortcut = {_L("Left mouse button"), m_desc["add_fuzzy_skin"]}; + std::pair remove_fuzzy_skin_shortcut = {shift + _L("Left mouse button"), m_desc["remove_fuzzy_skin"]}; + std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; + + m_shortcuts_brush = { + add_fuzzy_skin_shortcut, + remove_fuzzy_skin_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, + clipping_shortcut + }; + + m_shortcuts_triangle = { + add_fuzzy_skin_shortcut, + remove_fuzzy_skin_shortcut, + clipping_shortcut + }; + + m_shortcuts_smart_fill = { + add_fuzzy_skin_shortcut, + remove_fuzzy_skin_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, + clipping_shortcut + }; return true; } @@ -80,50 +97,18 @@ void GLGizmoFuzzySkin::render_painter_gizmo() glsafe(::glDisable(GL_BLEND)); } -void GLGizmoFuzzySkin::show_tooltip_information(float caption_max, float x, float y) +void GLGizmoFuzzySkin::render_tooltip_button(float x, float y) { - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - std::vector tip_items; + auto get_shortcuts = [this]() -> std::vector> { switch (m_tool_type) { - case ToolType::BRUSH: - if (m_cursor_type == TriangleSelector::POINTER) { - tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "clipping_of_view"}; - } else { - tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "cursor_size", "clipping_of_view"}; - } - break; - case ToolType::SMART_FILL: - tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "smart_fill_angle", "clipping_of_view"}; - break; - default: - break; + case ToolType::BRUSH: return m_cursor_type == TriangleSelector::POINTER ? m_shortcuts_triangle : m_shortcuts_brush; + + case ToolType::SMART_FILL: return m_shortcuts_smart_fill; + default: return {}; } - for (const auto &t : tip_items) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); + }; + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, get_shortcuts(), x, y); } void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_limit) @@ -314,8 +299,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(caption_max, x, get_cur_y); + render_tooltip_button(x, y); float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp index 798ff670f3..ed6a38e068 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp @@ -18,7 +18,7 @@ protected: void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; - void show_tooltip_information(float caption_max, float x, float y); + void render_tooltip_button(float x, float y); wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; @@ -45,6 +45,13 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated, and this class constructed again, so the change takes effect. std::map m_desc; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_brush; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_triangle; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_smart_fill; }; } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index fac6daf1cc..efc7b82fae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -4,6 +4,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/MeasureUtils.hpp" @@ -444,15 +445,14 @@ bool GLGizmoMeasure::on_init() { m_shortcut_key = WXK_CONTROL_U; - const wxString shift = _L("Shift+"); + const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["feature_selection"] = _L("Select feature"); - m_desc["point_selection_caption"] = shift + _L("Left mouse button"); - m_desc["point_selection"] = _L("Select point"); - m_desc["reset_caption"] = _L("Delete"); - m_desc["reset"] = _L("Restart selection"); - m_desc["unselect_caption"] = _L("Esc"); - m_desc["unselect"] = _L("Cancel a feature until exit"); + m_shortcuts = { + {_L("Left mouse button"), _L("Select")}, + {shift + _L("Left mouse button"), _L("Select point")}, + {_L("Delete"), _L("Restart selection")}, + {_L("Esc"), _L("Cancel a feature until exit")}, + }; return true; } @@ -2177,16 +2177,8 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit show_distance_xyz_ui(); ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"point_selection", "reset", "unselect"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); - } - show_tooltip_information(caption_max, x, get_cur_y); - ImGui::PopStyleVar(1); + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { // the dialog may have changed its size, ask for an extra frame to render it properly last_feature = m_curr_feature; @@ -2239,38 +2231,6 @@ void GLGizmoMeasure::update_measurement_result() m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle()))); } -void GLGizmoMeasure::show_tooltip_information(float caption_max, float x, float y) -{ - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(": "sv).x + 35.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"point_selection", "reset", "unselect"}) - draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - void GLGizmoMeasure::reset_all_pick() { std::map>().swap(m_mesh_raycaster_map); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 7fd54603f3..3965652c05 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -271,7 +271,6 @@ protected: void remove_selected_sphere_raycaster(int id); void update_measurement_result(); - void show_tooltip_information(float caption_max, float x, float y); void reset_all_pick(); void reset_gripper_pick(GripperType id,bool is_all = false); void register_single_mesh_pick(); @@ -303,9 +302,9 @@ protected: bool is_pick_meet_assembly_mode(const SelectedFeatures::Item& item); protected: - // This map holds all translated description texts, so they can be easily referenced during layout calculations - // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. - std::map m_desc; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts; + bool m_show_reset_first_tip{false}; bool m_selected_wrong_feature_waring_tip{false}; EMeasureMode m_measure_mode{EMeasureMode::ONLY_MEASURE}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 24c1456a6d..a351961b71 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -13,6 +13,7 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include @@ -88,55 +89,61 @@ bool GLGizmoMmuSegmentation::on_init() // BBS m_shortcut_key = WXK_CONTROL_N; - // FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent? - const wxString ctrl = _L("Ctrl+"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - const wxString alt = _L("Alt+"); - const wxString shift = _L("Shift+"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); - m_desc["clipping_of_view"] = _L("Section view"); - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = ctrl + _L("Mouse wheel"); - m_desc["cursor_size"] = _L("Pen size"); - m_desc["cursor_type"] = _L("Pen shape"); + m_desc["clipping_of_view"] = _L("Section view"); + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Pen size"); + m_desc["cursor_type"] = _L("Pen shape"); + m_desc["paint"] = _L("Paint"); + m_desc["erase"] = _L("Erase"); + m_desc["shortcut_key"] = _L("Choose filament"); + m_desc["edge_detection"] = _L("Edge detection"); + m_desc["gap_area"] = _L("Gap area"); + m_desc["perform"] = _L("Perform"); + m_desc["remove_all"] = _L("Erase all painting"); + m_desc["circle"] = _L("Circle"); + m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Triangles"); + m_desc["filaments"] = _L("Filaments"); + m_desc["tool_type"] = _L("Tool type"); + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); + m_desc["tool_bucket_fill"] = _L("Bucket fill"); + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + m_desc["height_range"] = _L("Height range"); + m_desc["toggle_wireframe"] = _L("Toggle Wireframe"); + m_desc["perform_remap"] = _L("Remap filaments"); + m_desc["remap"] = _L("Remap"); + m_desc["cancel_remap"] = _L("Cancel"); - m_desc["paint_caption"] = _L("Left mouse button"); - m_desc["paint"] = _L("Paint"); - m_desc["erase_caption"] = shift + _L("Left mouse button"); - m_desc["erase"] = _L("Erase"); - m_desc["shortcut_key_caption"] = _L("Key 1~9"); - m_desc["shortcut_key"] = _L("Choose filament"); - m_desc["edge_detection"] = _L("Edge detection"); - m_desc["gap_area_caption"] = ctrl + _L("Mouse wheel"); - m_desc["gap_area"] = _L("Gap area"); - m_desc["perform"] = _L("Perform"); + std::pair paint_shortcut = {_L("Left mouse button"), m_desc["paint"]}; + std::pair erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]}; + std::pair clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]}; + std::pair toggle_wireframe_shortcut = {alt + shift + _L("Enter"), m_desc["toggle_wireframe"]}; - m_desc["remove_all"] = _L("Erase all painting"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); + m_shortcuts_brush = { + paint_shortcut, + erase_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, + clipping_shortcut, + toggle_wireframe_shortcut + }; - m_desc["filaments"] = _L("Filaments"); - m_desc["tool_type"] = _L("Tool type"); - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); - m_desc["tool_bucket_fill"] = _L("Bucket fill"); + m_shortcuts_bucket_fill = { + paint_shortcut, + erase_shortcut, + {ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]}, + clipping_shortcut, + toggle_wireframe_shortcut + }; - m_desc["smart_fill_angle_caption"] = ctrl + _L("Mouse wheel"); - m_desc["smart_fill_angle"] = _L("Smart fill angle"); - - m_desc["height_range_caption"] = ctrl + _L("Mouse wheel"); - m_desc["height_range"] = _L("Height range"); - - //add toggle wire frame hint - m_desc["toggle_wireframe_caption"] = alt + shift + _L("Enter"); - m_desc["toggle_wireframe"] = _L("Toggle Wireframe"); - - // Filament remapping descriptions - m_desc["perform_remap"] = _L("Remap filaments"); - m_desc["remap"] = _L("Remap"); - m_desc["cancel_remap"] = _L("Cancel"); + m_shortcuts_gap_fill = { + {ctrl + _L("Mouse wheel"), m_desc["gap_area"]}, + toggle_wireframe_shortcut + }; init_extruders_data(); @@ -279,52 +286,22 @@ static void render_extruders_combo(const std::string& label, selection_idx = selection_out; } -void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x, float y) +void GLGizmoMmuSegmentation::render_tooltip_button(float x, float y) { - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - std::vector tip_items; + auto get_shortcuts = [this]() -> std::vector> { switch (m_tool_type) { - case ToolType::BRUSH: - tip_items = {"paint", "erase", "cursor_size", "clipping_of_view", "toggle_wireframe"}; - break; - case ToolType::BUCKET_FILL: - tip_items = {"paint", "erase", "smart_fill_angle", "clipping_of_view", "toggle_wireframe"}; - break; - case ToolType::SMART_FILL: - // TODO: - break; - case ToolType::GAP_FILL: - tip_items = {"gap_area", "toggle_wireframe"}; - break; - default: - break; + case ToolType::BRUSH: return m_shortcuts_brush; + + case ToolType::BUCKET_FILL: + case ToolType::SMART_FILL: return m_shortcuts_bucket_fill; + + case ToolType::GAP_FILL: return m_shortcuts_gap_fill; + + default: return {}; } - for (const auto &t : tip_items) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); + }; + + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, get_shortcuts(), x, y); } void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bottom_limit) @@ -678,8 +655,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(caption_max, x, get_cur_y); + render_tooltip_button(x, y); float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index f9bcfdee12..756ada7670 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -94,7 +94,7 @@ protected: void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; - void show_tooltip_information(float caption_max, float x, float y); + void render_tooltip_button(float x, float y); bool on_is_selectable() const override; bool on_is_activable() const override; @@ -149,6 +149,13 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_brush; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_bucket_fill; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_gap_fill; }; } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index e68d71513a..7ebb794bef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "GLGizmoUtils.hpp" #include @@ -29,28 +30,29 @@ bool GLGizmoSeam::on_init() { m_shortcut_key = WXK_CONTROL_P; - // FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent? - const wxString ctrl = _L("Ctrl+"); - // FIXME: maybe should be using GUI::shortkey_alt_prefix() or equivalent? - const wxString alt = _L("Alt+"); - const wxString shift = _L("Shift+"); + const wxString ctrl = GUI::shortkey_ctrl_prefix(); + const wxString alt = GUI::shortkey_alt_prefix(); + const wxString shift = GUI::shortkey_shift_prefix(); - m_desc["clipping_of_view_caption"] = alt + _L("Mouse wheel"); m_desc["clipping_of_view"] = _L("Section view"); m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size_caption"] = ctrl + _L("Mouse wheel"); - m_desc["cursor_size"] = _L("Brush size"); - m_desc["cursor_type"] = _L("Brush shape"); - m_desc["enforce_caption"] = _L("Left mouse button"); + m_desc["cursor_size"] = _L("Pen size"); + m_desc["tool_type"] = _L("Tool type"); m_desc["enforce"] = _L("Enforce seam"); - m_desc["block_caption"] = _L("Right mouse button"); m_desc["block"] = _L("Block seam"); - m_desc["remove_caption"] = shift + _L("Left mouse button"); m_desc["remove"] = _L("Erase"); m_desc["remove_all"] = _L("Erase all painting"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); + m_shortcuts = { + {_L("Left mouse button"), m_desc["enforce"]}, + {_L("Right mouse button"), m_desc["block"]}, + {shift + _L("Left mouse button"), m_desc["remove"]}, + {ctrl + _L("Mouse wheel"), m_desc["cursor_size"]}, + {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]} + }; + return true; } @@ -101,36 +103,6 @@ bool GLGizmoSeam::on_key_down_select_tool_type(int keyCode) { return true; } -void GLGizmoSeam::show_tooltip_information(float caption_max, float x, float y) -{ - ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 35.f; - - float scale = m_parent.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { - m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"enforce", "block", "remove", "cursor_size", "clipping_of_view"}) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} void GLGizmoSeam::tool_changed(wchar_t old_tool, wchar_t new_tool) { @@ -189,7 +161,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_type")); + m_imgui->text(m_desc.at("tool_type")); std::array tool_ids = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon }; std::array icons; if (m_is_dark_mode) @@ -283,8 +255,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - show_tooltip_information(caption_max, x, get_cur_y); + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 73242d470e..adb5acb272 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -24,8 +24,6 @@ protected: std::string on_get_name() const override; PainterGizmoType get_painter_type() const override; - void show_tooltip_information(float caption_max, float x, float y); - void tool_changed(wchar_t old_tool, wchar_t new_tool); wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; @@ -51,6 +49,9 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp new file mode 100644 index 0000000000..3566bccf12 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp @@ -0,0 +1,75 @@ +#include "GLGizmoUtils.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "GLGizmosManager.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include +#include + +#ifdef WIN32 +#include +#endif + +/* + GizmoUI Footer Structure: + + ~ Content ~ + ---------------------------------------- + [Button1] [Button2] + ---------------------------------------- + [?] [Reset] [Confirm] [Cancel] + ---------------------------------------- + ~ Warnings ~ + + + Additional details: + - [Confirm], [Cancel], [Done], ... are buttons that close the Tool Dialog + - [Reset], [Button1], ... are buttons that do not! + - Non-consequential buttons like [Cancel] and [Done] are always the right-most buttons + - Multiple warnings can show, but should only have one ImGui::Separator above + - If no warnings is shown, dont render the ImGui::Separator + +*/ + +namespace Slic3r::GUI::GLGizmoUtils { + +void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y) +{ + float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float caption_x_max = 0.f; + for (const auto& item : shortcuts) { + caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x); + } + caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; + + auto& gizmos_manager = canvas.get_gizmos_manager(); + ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + float scale = canvas.get_scale(); +#ifdef WIN32 + int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); + scale *= (float) dpi / (float) DPI_DEFAULT; +#endif + + ImVec2 button_size = ImVec2(25 * scale, 25 * scale); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); + + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, caption_y)); + for (const auto& item : shortcuts) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": "); + ImGui::SameLine(caption_x_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second); + } + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +} // namespace Slic3r::GUI::GLGizmoUtils \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp new file mode 100644 index 0000000000..acdff4ddfe --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp @@ -0,0 +1,25 @@ +#ifndef GL_GIZMO_UTIL_HPP +#define GL_GIZMO_UTIL_HPP + +#include +#include +#include +#include +#include "imgui.h" + +namespace Slic3r::GUI { + +// Forward declaration +class ImGuiWrapper; +class GLCanvas3D; + +namespace GLGizmoUtils { + +// Renders a tooltip button using the provided shortcuts +void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y); + +} // namespace GLGizmoUtils +} // namespace Slic3r::GUI + +#endif // GL_GIZMO_UTIL_HPP \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index 457274e32f..c40bc7d0ad 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -15,6 +15,7 @@ #include "slic3r/GUI/Selection.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/MainFrame.hpp" +#include "GLGizmoUtils.hpp" #include @@ -55,24 +56,22 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas) m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; m_new_unit_string = m_imperial_units ? L("in") : L("mm"); - const wxString shift = _L("Shift+"); + const wxString shift = GUI::shortkey_shift_prefix(); const wxString alt = GUI::shortkey_alt_prefix(); const wxString ctrl = GUI::shortkey_ctrl_prefix(); - m_desc_move["part_selection_caption"] = alt + _L("Left mouse button"); - m_desc_move["part_selection"] = _L("Part selection"); - m_desc_move["snap_step_caption"] = shift + _L("Left mouse button"); - m_desc_move["snap_step"] = _L("Fixed step drag"); + m_shortcuts_move = { + {alt + _L("Left mouse button"), _L("Part selection")}, + {shift + _L("Left mouse button"), _L("Fixed step drag")} + }; - m_desc_rotate["part_selection_caption"] = alt + _L("Left mouse button"); - m_desc_rotate["part_selection"] = _L("Part selection"); + m_shortcuts_rotate = { + {alt + _L("Left mouse button"), _L("Part selection")}}; - m_desc_scale["part_selection_caption"] = alt + _L("Left mouse button"); - m_desc_scale["part_selection"] = _L("Part selection"); - m_desc_scale["snap_step_caption"] = shift + _L("Left mouse button"); - m_desc_scale["snap_step"] = _L("Fixed step drag"); - m_desc_scale["single_sided_caption"] = ctrl + _L("Left mouse button"); - m_desc_scale["single_sided"] = _L("Single sided scaling"); + m_shortcuts_scale = { + {alt + _L("Left mouse button"), _L("Part selection")}, + {shift + _L("Left mouse button"), _L("Fixed step drag")}, + {ctrl + _L("Left mouse button"), _L("Single sided scaling")}}; } void GizmoObjectManipulation::UpdateAndShow(const bool show) @@ -719,102 +718,6 @@ bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, flo return result; } -void GizmoObjectManipulation::show_move_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) -{ - ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; - - float scale = m_glcanvas.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &imgui_wrapper,& caption_max](const wxString &caption, const wxString &text) { - imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"part_selection", "snap_step"}) - draw_text_with_caption(m_desc_move.at(t + "_caption") + ": ", m_desc_move.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - -void GizmoObjectManipulation::show_rotate_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) -{ - ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; - - float scale = m_glcanvas.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &imgui_wrapper, &caption_max](const wxString &caption, const wxString &text) { - imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"part_selection"}) - draw_text_with_caption(m_desc_rotate.at(t + "_caption") + ": ", m_desc_rotate.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - -void GizmoObjectManipulation::show_scale_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y) -{ - ImTextureID normal_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = m_glcanvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - - caption_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; - - float scale = m_glcanvas.get_scale(); - #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; - #endif // WIN32 - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y}); - ImGui::ImageButton3(normal_id, hover_id, button_size); - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, y)); - auto draw_text_with_caption = [this, &imgui_wrapper, &caption_max](const wxString &caption, const wxString &text) { - imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, caption); - ImGui::SameLine(caption_max); - imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); - }; - - for (const auto &t : std::array{"part_selection", "snap_step", "single_sided"}) - draw_text_with_caption(m_desc_scale.at(t + "_caption") + ": ", m_desc_scale.at(t)); - ImGui::EndTooltip(); - } - ImGui::PopStyleVar(2); -} - void GizmoObjectManipulation::set_init_rotation(const Geometry::Transformation &value) { m_init_rotation_scale_tran = value.get_matrix_no_offset(); m_init_rotation = value.get_rotation(); @@ -957,14 +860,9 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, } } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float tip_caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"part_selection", "snap_step"}) { - tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_move[t + "_caption"]).x); - total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_move[t]).x); - } - show_move_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); + + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_move, x, y); + m_last_active_item = current_active_id; last_move_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); @@ -1154,14 +1052,8 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe if (!focued_on_text && !absolute_focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float tip_caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"part_selection"}) { - tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_move[t + "_caption"]).x); - total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_move[t]).x); - } - show_rotate_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_rotate, x, y); + m_last_active_item = current_active_id; last_rotate_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); @@ -1394,14 +1286,9 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float tip_caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"part_selection", "snap_step", "single_sided"}) { - tip_caption_max = std::max(tip_caption_max, imgui_wrapper->calc_text_size(m_desc_scale[t + "_caption"]).x); - total_text_max = std::max(total_text_max, imgui_wrapper->calc_text_size(m_desc_scale[t]).x); - } - show_scale_tooltip_information(imgui_wrapper, tip_caption_max, x, get_cur_y); + + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_scale, x, y); + m_last_active_item = current_active_id; last_scale_input_window_width = ImGui::GetWindowWidth(); diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp index 2f8b03c160..2f8b532733 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp @@ -143,9 +143,6 @@ public: bool reset_zero_button(ImGuiWrapper *imgui_wrapper, float caption_max, float unit_size, float space_size, float end_text_size); bool bbl_checkbox(const wxString &label, bool &value); - void show_move_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); - void show_rotate_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); - void show_scale_tooltip_information(ImGuiWrapper *imgui_wrapper, float caption_max, float x, float y); void set_init_rotation(const Geometry::Transformation &value); private: @@ -171,9 +168,14 @@ private: GLCanvas3D& m_glcanvas; unsigned int m_last_active_item { 0 }; - std::map m_desc_move; - std::map m_desc_rotate; - std::map m_desc_scale; + + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_move; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_rotate; + // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")} + std::vector> m_shortcuts_scale; + Vec3d m_init_rotation; Transform3d m_init_rotation_scale_tran; }; From c26b6a2ef0319edb15dc8311cfbfdc5c08f58ef4 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:51:06 +0200 Subject: [PATCH 33/42] Restructure Text/Emboss Tool UI (#12864) * restructures text tool ui, moves style selector over font, moves reset buttons after settings, matches style popup styling to font popup * makes operation setting single-line * makes "Advanced" button bigger, matching BambuStudio --------- Co-authored-by: Hanno Witzleb --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 178 +++++++++++++++--------- 1 file changed, 112 insertions(+), 66 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4225a256fc..8766ad7b13 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1362,21 +1362,30 @@ void GLGizmoEmboss::draw_window(float x, float y) draw_text_input(); - ImGui::Indent(); - // When unknown font is inside .3mf only font selection is allowed - m_imgui->disabled_end(/*m_is_unknown_font*/); - draw_font_list_line(); - m_imgui->disabled_begin(m_is_unknown_font); - bool use_inch = wxGetApp().app_config->get_bool("use_inches"); - draw_height(use_inch); - draw_depth(use_inch); - ImGui::Unindent(); + // subtract 4.0f to counteract weird additional spacing/padding of the revert buttons + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f - 4.0f)); + ScopeGuard spacing_sc([](){ ImGui::PopStyleVar(/*ImGuiStyleVar_ItemSpacing*/); }); + + draw_style_list(); + + // When unknown font is inside .3mf only font selection is allowed + m_imgui->disabled_end(/*m_is_unknown_font*/); + draw_font_list_line(); + m_imgui->disabled_begin(m_is_unknown_font); + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + draw_height(use_inch); + draw_depth(use_inch); + + ImGui::Spacing(); // close advanced style property when unknown font is selected if (m_is_unknown_font && m_is_advanced_edit_style) ImGui::SetNextTreeNodeOpen(false); - if (ImGui::TreeNode(_u8L("Advanced").c_str())) { + // ImGui Bug: After switching to another window and switching back, clicking the text doesnt open/close the TreeNode anymore + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_FramePadding; + if (ImGui::TreeNodeEx(_u8L("Advanced").c_str(), flags)) { if (!m_is_advanced_edit_style) { m_is_advanced_edit_style = true; m_imgui->set_requires_extra_frame(); @@ -1389,9 +1398,7 @@ void GLGizmoEmboss::draw_window(float x, float y) m_imgui->set_requires_extra_frame(); } - ImGui::Separator(); - - draw_style_list(); + ImGui::Spacing(); // Do not select volume type, when it is text object if (!m_volume->is_the_only_one_part()) { @@ -1680,21 +1687,29 @@ void GLGizmoEmboss::draw_font_list_line() } EmbossStyle &style = m_style_manager.get_style(); - if (exist_change_in_font) { - ImGui::SameLine(ImGui::GetStyle().WindowPadding.x); - auto r_icon = get_icon(m_icons, IconType::undo, IconState::hovered); - if (Slic3r::GUI::button(r_icon, r_icon, r_icon)) { // ORCA draw bottom with same orange color - const EmbossStyle *stored_style = m_style_manager.get_stored_style(); + auto r_icon = get_icon(m_icons, IconType::undo, IconState::hovered); + if (exist_change_in_font == false) { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f); + m_imgui->disabled_begin(true); + } - style.path = stored_style->path; - style.prop.boldness = stored_style->prop.boldness; - style.prop.skew = stored_style->prop.skew; + ImGui::SameLine(); + if (Slic3r::GUI::button(r_icon, r_icon, r_icon)) { // ORCA draw bottom with same orange color + const EmbossStyle *stored_style = m_style_manager.get_stored_style(); - wxFont new_wx_font = WxFontUtils::load_wxFont(style.path); - if (new_wx_font.IsOk() && m_style_manager.set_wx_font(new_wx_font)) - exist_change = true; + style.path = stored_style->path; + style.prop.boldness = stored_style->prop.boldness; + style.prop.skew = stored_style->prop.skew; + + wxFont new_wx_font = WxFontUtils::load_wxFont(style.path); + if (new_wx_font.IsOk() && m_style_manager.set_wx_font(new_wx_font)) + exist_change = true; } else if (ImGui::IsItemHovered()) - m_imgui->tooltip(_u8L("Revert font changes."), m_gui_cfg->max_tooltip_width); + m_imgui->tooltip(_u8L("Revert font changes."), m_gui_cfg->max_tooltip_width); + + if (exist_change_in_font == false) { + ImGui::PopStyleVar(); + m_imgui->disabled_end(); } if (exist_change) { @@ -1750,6 +1765,7 @@ void GLGizmoEmboss::draw_font_list() ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f)); // vertically align label for (int i = 0; i < show_items_count; i++) { int idx = is_filtered ? filtered_items_idx[i] : i; @@ -1783,7 +1799,7 @@ void GLGizmoEmboss::draw_font_list() ::draw_font_preview(face, m_text, *m_face_names, *m_gui_cfg, ImGui::IsItemVisible()); } - ImGui::PopStyleVar(3); + ImGui::PopStyleVar(4); // ImGuiStyleVar_ItemSpacing, ImGuiStyleVar_ItemInnerSpacing, ImGuiStyleVar_FrameRounding, ImGuiStyleVar_SelectableTextAlign ImGui::EndListBox(); ImGui::EndPopup(); } else if (m_face_names->is_init) { @@ -1841,7 +1857,8 @@ void GLGizmoEmboss::draw_font_list() void GLGizmoEmboss::draw_model_type() { - ImGui::AlignTextToFramePadding(); + ImGui::Spacing(); + bool is_last_solid_part = m_volume->is_the_only_one_part(); std::string title = _u8L("Operation"); if (is_last_solid_part) { @@ -1858,13 +1875,18 @@ void GLGizmoEmboss::draw_model_type() ModelVolumeType type = m_volume->type(); //TRN EmbossOperation + float minimum_spacing_x = 8.0f; + float minimum_offset_x = ImGui::GetCursorPosX() + minimum_spacing_x; + float offset_x = std::max(m_gui_cfg->input_offset, minimum_offset_x); + ImGui::SameLine(offset_x); + ImGuiWrapper::push_radio_style(m_parent.get_scale()); // ORCA if (ImGui::RadioButton(_u8L("Join").c_str(), type == part)) new_type = part; else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L("Click to change text into object part."), m_gui_cfg->max_tooltip_width); - ImGui::SameLine(); + ImGui::SameLine(); std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative)) new_type = negative; @@ -2181,13 +2203,17 @@ void GLGizmoEmboss::draw_style_list() { trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width); } + ImGui::AlignTextToFramePadding(); + std::string title = _u8L("Style"); if (m_style_manager.exist_stored_style()) ImGui::Text("%s", title.c_str()); else ImGui::TextColored(ImGuiWrapper::COL_ORCA, "%s", title.c_str()); - ImGui::SetNextItemWidth(m_gui_cfg->input_width); + ImGui::SameLine(m_gui_cfg->input_offset); + + ImGui::SetNextItemWidth(2 * m_gui_cfg->input_width); auto add_text_modify = [&is_modified](const std::string& name) { if (!is_modified) return name; return name + Preset::suffix_modified(); @@ -2199,6 +2225,14 @@ void GLGizmoEmboss::draw_style_list() { m_style_manager.init_style_images(m_gui_cfg->max_style_image_size, m_text); m_style_manager.init_trunc_names(max_style_name_width); std::optional> swap_indexes; + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f)); // vertically align label + const StyleManager::Styles &styles = m_style_manager.get_styles(); for (const StyleManager::Style &style : styles) { size_t index = &style - &styles.front(); @@ -2206,12 +2240,10 @@ void GLGizmoEmboss::draw_style_list() { ImGui::PushID(actual_style_name.c_str()); bool is_selected = (index == m_style_manager.get_style_index()); - float select_height = static_cast(m_gui_cfg->max_style_image_size.y()); - ImVec2 select_size(0.f, select_height); // 0,0 --> calculate in draw - const std::optional &img = style.image; + ImVec2 selectable_size(0, m_imgui->scaled(32.f / 15.f)); // 0.0f -> calculated during draw // allow click delete button ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap; - if (ImGui::BBLSelectable(style.truncated_name.c_str(), is_selected, flags, select_size)) { + if (ImGui::BBLSelectable(style.truncated_name.c_str(), is_selected, flags, selectable_size)) { selected_style_index = index; } else if (ImGui::IsItemHovered()) tooltip = actual_style_name; @@ -2227,6 +2259,7 @@ void GLGizmoEmboss::draw_style_list() { ImGui::ResetMouseDragDelta(); } + const std::optional &img = style.image; // draw style name if (img.has_value()) { ImGui::SameLine(max_style_name_width); @@ -2239,6 +2272,10 @@ void GLGizmoEmboss::draw_style_list() { if (swap_indexes.has_value()) m_style_manager.swap(swap_indexes->first, swap_indexes->second); + + // ImGuiStyleVar_ItemSpacing, ImGuiStyleVar_ItemInnerSpacing, ImGuiStyleVar_FrameRounding, + // ImGuiStyleVar_WindowPadding, ImGuiStyleVar_FramePadding, ImGuiStyleVar_SelectableTextAlign + ImGui::PopStyleVar(6); ImGui::EndCombo(); } else { // do not keep in memory style images when no combo box open @@ -2413,30 +2450,43 @@ bool GLGizmoEmboss::revertible(const std::string &name, Draw draw) const { ImGui::AlignTextToFramePadding(); - bool changed = exist_change(value, default_value); - if (changed || default_value == nullptr) + bool changed_from_default = exist_change(value, default_value); + if (changed_from_default || default_value == nullptr) ImGuiWrapper::text_colored(ImGuiWrapper::COL_MODIFIED, name); // ORCA Match color else ImGuiWrapper::text(name); - // render revert changes button - if (changed) { - ImGuiWindow *window = ImGui::GetCurrentWindow(); - float prev_x = window->DC.CursorPosPrevLine.x; - ImGui::SameLine(undo_offset); // change cursor postion - auto r_icon = get_icon(m_icons, IconType::undo, IconState::hovered); - if (Slic3r::GUI::button(r_icon, r_icon, r_icon)) { // ORCA draw bottom with same orange color - value = *default_value; + bool changed = draw(); - // !! Fix to detect change of value after revert of float-slider - m_imgui->get_last_slider_status().deactivated_after_edit = true; - - return true; - } else if (ImGui::IsItemHovered()) - m_imgui->tooltip(undo_tooltip, m_gui_cfg->max_tooltip_width); - window->DC.CursorPosPrevLine.x = prev_x; // set back previous position + if (changed_from_default == false) { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f); + m_imgui->disabled_begin(true); } - return draw(); + + // render revert changes button + auto r_icon = get_icon(m_icons, IconType::undo, IconState::hovered); + ImGuiWindow *window = ImGui::GetCurrentWindow(); + float prev_x = window->DC.CursorPosPrevLine.x; + ImGui::SameLine(undo_offset); // change cursor postion + if (Slic3r::GUI::button(r_icon, r_icon, r_icon)) { // ORCA draw bottom with same orange color + value = *default_value; + + // !! Fix to detect change of value after revert of float-slider + m_imgui->get_last_slider_status().deactivated_after_edit = true; + + return true; + } else if (ImGui::IsItemHovered()) + m_imgui->tooltip(undo_tooltip, m_gui_cfg->max_tooltip_width); + + if (undo_offset != 0.0f) + window->DC.CursorPosPrevLine.x = prev_x; // set back previous position + + if (changed_from_default == false) { + ImGui::PopStyleVar(); + m_imgui->disabled_end(); + } + + return changed; } // May be move to ImGuiWrapper template bool imgui_input(const char *label, T *v, T step, T step_fast, const char *format, ImGuiInputTextFlags flags); @@ -2457,8 +2507,8 @@ bool GLGizmoEmboss::rev_input(const std::string &name, T &value, const T *defaul return imgui_input(("##" + name).c_str(), &value, step, step_fast, format, flags); }; - float undo_offset = ImGui::GetStyle().WindowPadding.x; - return revertible(name, value, default_value, undo_tooltip, undo_offset, draw_offseted_input); + return revertible(name, value, default_value, undo_tooltip, + 0.0f, draw_offseted_input); } template @@ -2509,9 +2559,8 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name, ImGui::SameLine(offset); return m_imgui->bbl_checkbox(wxString::FromUTF8("##" + name), value); }; - float undo_offset = ImGui::GetStyle().WindowPadding.x; return revertible(name, value, default_value, undo_tooltip, - undo_offset, draw_offseted_input); + 0.0f, draw_offseted_input); } bool GLGizmoEmboss::set_height() { @@ -2592,9 +2641,8 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, return m_imgui->slider_optional_int( ("##" + name).c_str(), value, v_min, v_max, format.c_str(), 1.f, false, tooltip); }; - float undo_offset = ImGui::GetStyle().WindowPadding.x; return revertible(name, value, default_value, - undo_tooltip, undo_offset, draw_slider_optional_int); + undo_tooltip, 0.0f, draw_slider_optional_int); } bool GLGizmoEmboss::rev_slider(const std::string &name, @@ -2614,9 +2662,8 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, return m_imgui->slider_optional_float(("##" + name).c_str(), value, v_min, v_max, format.c_str(), 1.f, false, tooltip); }; - float undo_offset = ImGui::GetStyle().WindowPadding.x; return revertible(name, value, default_value, - undo_tooltip, undo_offset, draw_slider_optional_float); + undo_tooltip, 0.0f, draw_slider_optional_float); } bool GLGizmoEmboss::rev_slider(const std::string &name, @@ -2636,9 +2683,8 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, return m_imgui->slider_float("##" + name, &value, v_min, v_max, format.c_str(), 1.f, false, tooltip); }; - float undo_offset = ImGui::GetStyle().WindowPadding.x; return revertible(name, value, default_value, - undo_tooltip, undo_offset, draw_slider_float); + undo_tooltip, 0.0f, draw_slider_float); } void GLGizmoEmboss::draw_advanced() @@ -2668,6 +2714,8 @@ void GLGizmoEmboss::draw_advanced() m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property); #endif // SHOW_FONT_FILE_PROPERTY + ImGui::Spacing(); + auto &tr = m_gui_cfg->translations; const StyleManager::Style *stored_style = nullptr; @@ -2744,8 +2792,7 @@ void GLGizmoEmboss::draw_advanced() return is_change; }; const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; - float undo_offset = ImGui::GetStyle().WindowPadding.x; - if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) { + if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), 0.0f, draw_align)) { if (font_prop.per_glyph) reinit_text_lines(m_text_lines.get_lines().size()); // TODO: move with text in finalize to not change position @@ -3641,7 +3688,7 @@ GuiCfg create_gui_configuration() ImGui::CalcTextSize(tr.font.c_str()).x, ImGui::CalcTextSize(tr.height.c_str()).x, ImGui::CalcTextSize(tr.depth.c_str()).x}); - cfg.indent = static_cast(cfg.icon_width); + cfg.indent = 20.0f; cfg.input_offset = style.WindowPadding.x + cfg.indent + max_text_width + space; // TRN - Input label. Be short as possible @@ -3708,8 +3755,7 @@ GuiCfg create_gui_configuration() cfg.height_of_volume_type_selector = separator_height + line_height_with_spacing + input_height; int max_style_image_width = static_cast(std::round(cfg.max_style_name_width - 2 * style.FramePadding.x)); - int max_style_image_height = static_cast(std::round(input_height)); - cfg.max_style_image_size = Vec2i32(max_style_image_width, line_height); + cfg.max_style_image_size = Vec2i32(max_style_image_width, line_height_with_spacing); cfg.face_name_size = Vec2i32(cfg.input_width, line_height_with_spacing); cfg.face_name_texture_offset_x = cfg.face_name_size.x() + style.WindowPadding.x + space; From a274b0e0accc715b63aa013f4c1b9f089cf21d95 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 05:28:28 -0300 Subject: [PATCH 34/42] CGAL Fix Model: New for Linux and Mac (#12155) * CGAL Fix Model Multi OS fix model with CGAL Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Clean unused variables * Early exit * Validation * Orca comments * Steps to fix * Simplify fixer * BY10 to BYCGAL Full refactor * repair_polygon_soup * Revert "repair_polygon_soup" This reverts commit cb88841e7a72a42c148e144fbf0cab146a54c3c8. * CGAL 6.1.1 Update CGAL.cmake * Update MeshBoolean.cpp * Revert "CGAL 6.1.1" This reverts commit c581887adb5f84ec4af97b320067b152f8812f49. * Funca with RF New remake, it seems to be working fine... por ahora Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update src/libslic3r/MeshBoolean.cpp Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Include cleanup Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update Part list Revert "Update Part list" This reverts commit 95cab337d7ea602682ee00be2986ef941d0b06c2. Reapply "Update Part list" This reverts commit e401bec579f64b97e3f6deeb4131a8e5a79a146d. * Comments * Update FixModelByCgal.cpp * Remove no 3d parts Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Remove netfabb and w10 sdk Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update src/slic3r/Utils/FixModelByCgal.cpp Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * redundant check * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revet: suggestion from @RF47 Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/build_orca.yml | 7 +- CMakeLists.txt | 75 ++-- src/libslic3r/MeshBoolean.cpp | 88 +++- src/libslic3r/MeshBoolean.hpp | 3 + src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/GUI_Factories.cpp | 32 +- src/slic3r/GUI/GUI_Factories.hpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 31 +- src/slic3r/GUI/GUI_ObjectList.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 8 +- src/slic3r/GUI/NotificationManager.hpp | 4 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 1 - src/slic3r/GUI/Plater.cpp | 27 +- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 1 - src/slic3r/Utils/FixModelByCgal.cpp | 214 +++++++++ src/slic3r/Utils/FixModelByCgal.hpp | 18 + src/slic3r/Utils/FixModelByWin10.cpp | 447 ------------------- src/slic3r/Utils/FixModelByWin10.hpp | 31 -- 20 files changed, 421 insertions(+), 584 deletions(-) create mode 100644 src/slic3r/Utils/FixModelByCgal.cpp create mode 100644 src/slic3r/Utils/FixModelByCgal.hpp delete mode 100644 src/slic3r/Utils/FixModelByWin10.cpp delete mode 100644 src/slic3r/Utils/FixModelByWin10.hpp diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 9f6a61025a..63217c038b 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -286,9 +286,10 @@ jobs: - name: Build slicer Win if: runner.os == 'Windows' working-directory: ${{ github.workspace }} - env: - WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\' - WindowsSDKVersion: '10.0.26100.0\' + # Orca: Removed Netfabb STL fixing service support in favor of CGAL. + # env: + # WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\' + # WindowsSDKVersion: '10.0.26100.0\' run: .\build_release_vs.bat slicer - name: Create installer Win diff --git a/CMakeLists.txt b/CMakeLists.txt index bc374f4251..64d042db4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,43 +323,44 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way -if(WIN32) - if(NOT DEFINED WIN10SDK_PATH) - if(DEFINED ENV{WIN10SDK_PATH}) - set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") - endif() - endif() - if(DEFINED WIN10SDK_PATH) - #BBS: modify win10sdk_path - if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h") - set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}") - else() - message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") - message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_PATH) - endif() - else() - # Try to use the default Windows 10 SDK path. - if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion}) - set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") - else () - set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0") - endif () - if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") - message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_INCLUDE_PATH) - endif() - endif() - if(WIN10SDK_INCLUDE_PATH) - message("Building with Win10 Netfabb STL fixing service support") - add_definitions(-DHAS_WIN10SDK) - include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}") - else() - message("Building without Win10 Netfabb STL fixing service support") - endif() -endif() +# ORCA: Removed Netfabb STL fixing service support in favor of CGAL. +# if(WIN32) +# if(NOT DEFINED WIN10SDK_PATH) +# if(DEFINED ENV{WIN10SDK_PATH}) +# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") +# endif() +# endif() +# if(DEFINED WIN10SDK_PATH) +# #BBS: modify win10sdk_path +# if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h") +# set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}") +# else() +# message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") +# message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found") +# message("STL fixing by the Netfabb service will not be compiled") +# unset(WIN10SDK_PATH) +# endif() +# else() +# # Try to use the default Windows 10 SDK path. +# if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion}) +# set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") +# else () +# set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0") +# endif () +# if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") +# message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") +# message("STL fixing by the Netfabb service will not be compiled") +# unset(WIN10SDK_INCLUDE_PATH) +# endif() +# endif() +# if(WIN10SDK_INCLUDE_PATH) +# message("Building with Win10 Netfabb STL fixing service support") +# add_definitions(-DHAS_WIN10SDK) +# include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}") +# else() +# message("Building without Win10 Netfabb STL fixing service support") +# endif() +# endif() if (APPLE) message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}") diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index c195e4358f..8b50681370 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include // BBS: for segment #include @@ -475,6 +475,92 @@ bool empty(const CGALMesh &mesh) return mesh.m.is_empty(); } +bool repair(TriangleMesh& mesh, RepairedMeshErrors* repaired_errors, std::string* error) +{ + using namespace CGAL; + namespace PMP = CGAL::Polygon_mesh_processing; + + if (mesh.empty()) + return true; + + try { + // 1) Convert to polygon soup + std::vector<_EpicMesh::Point> points; + std::vector> polygons; + + points.reserve(mesh.its.vertices.size()); + polygons.reserve(mesh.its.indices.size()); + + for (const auto& v : mesh.its.vertices) + points.emplace_back(v.x(), v.y(), v.z()); + + for (const auto& f : mesh.its.indices) + polygons.push_back({size_t(f[0]), size_t(f[1]), size_t(f[2])}); + + // 2) Aggressive soup cleanup + PMP::repair_polygon_soup(points, polygons); + + // 3) Convert soup → mesh + _EpicMesh cgal_mesh; + PMP::polygon_soup_to_polygon_mesh(points, polygons, cgal_mesh); + + // 4) Remove degenerate geometry + PMP::remove_degenerate_faces(cgal_mesh); + PMP::remove_isolated_vertices(cgal_mesh); + + // 5) Fix remaining non-manifold vertices + PMP::duplicate_non_manifold_vertices(cgal_mesh); + + // 6) Boolean union (keeps only outer shell) + _EpicMesh tmp; + if (PMP::corefine_and_compute_union(cgal_mesh, cgal_mesh, tmp)) { + cgal_mesh = std::move(tmp); + } + // If it fails, continue anyway with previous mesh + + // 7) Fill holes + if (!CGAL::is_closed(cgal_mesh)) { + using halfedge_descriptor = boost::graph_traits<_EpicMesh>::halfedge_descriptor; + + std::vector borders; + PMP::extract_boundary_cycles(cgal_mesh, std::back_inserter(borders)); + + for (halfedge_descriptor h : borders) { + PMP::triangulate_and_refine_hole(cgal_mesh, h); + } + } + + // 8) Final validity check + if (!CGAL::is_closed(cgal_mesh)) { + if (error) + *error = "Repair failed: mesh still open after hole filling."; + return false; + } + + // 9) Ensure outward orientation + if (!PMP::does_bound_a_volume(cgal_mesh)) + PMP::orient_to_bound_a_volume(cgal_mesh); + + // 10) Convert back + indexed_triangle_set its = cgal_to_indexed_triangle_set(cgal_mesh); + + RepairedMeshErrors errs{}; + errs.facets_removed = 0; + errs.edges_fixed = 0; + + mesh = TriangleMesh(std::move(its), errs); + + if (repaired_errors) + *repaired_errors = errs; + + return true; + } catch (const std::exception& e) { + if (error) + *error = e.what(); + return false; + } +} + CGALMeshPtr clone(const CGALMesh &m) { return CGALMeshPtr{new CGALMesh{m}}; diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 92592f5f61..d9a5d196fb 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -70,6 +70,9 @@ TriangleMesh merge(std::vector meshes); bool does_bound_a_volume(const CGALMesh &mesh); bool empty(const CGALMesh &mesh); + +// Repair a mesh using CGAL. Returns true on success. Optionally returns a summary of repairs and an error string. +bool repair(TriangleMesh &mesh, RepairedMeshErrors *repaired_errors = nullptr, std::string *error = nullptr); } namespace mcut { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 59605d437a..d06afca40b 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -580,8 +580,8 @@ set(SLIC3R_GUI_SOURCES Utils/ESP3D.hpp Utils/FileHelp.cpp Utils/FileHelp.hpp - Utils/FixModelByWin10.cpp - Utils/FixModelByWin10.hpp + Utils/FixModelByCgal.cpp + Utils/FixModelByCgal.hpp Utils/FlashAir.cpp Utils/FlashAir.hpp Utils/Flashforge.cpp diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 401b2e0e7f..9b98052cdd 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -21,7 +21,6 @@ #include #include "slic3r/GUI/Tab.hpp" -#include "slic3r/Utils/FixModelByWin10.hpp" #include "ParamsPanel.hpp" #include "MsgDialog.hpp" #include "wx/utils.h" @@ -879,14 +878,11 @@ void MenuFactory::append_menu_item_rename(wxMenu* menu) menu->AppendSeparator(); } -wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) +wxMenuItem* MenuFactory::append_menu_item_fix_through_cgal(wxMenu* menu) { - if (!is_windows10()) - return nullptr; - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix model"), "", - [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu, - []() {return plater()->can_fix_through_netfabb(); }, plater()); + [](wxCommandEvent&) { obj_list()->fix_through_cgal(); }, "", menu, + []() {return plater()->can_fix_through_cgal(); }, plater()); return menu_item; } @@ -1356,7 +1352,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) // "Scale to print volume" makes a sense just for whole object append_menu_item_scale_selection_to_fit_print_volume(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_items_mirror(menu); } @@ -1395,7 +1391,7 @@ void MenuFactory::create_extra_object_menu() // Object Clone append_menu_item_clone(&m_object_menu); // Object Repair - append_menu_item_fix_through_netfabb(&m_object_menu); + append_menu_item_fix_through_cgal(&m_object_menu); // Object Simplify append_menu_item_simplify(&m_object_menu); // Object Mesh Subdivision @@ -1452,7 +1448,7 @@ void MenuFactory::create_bbl_assemble_object_menu() // Delete append_menu_item_delete(&m_assemble_object_menu); // Object Repair - append_menu_item_fix_through_netfabb(&m_assemble_object_menu); + append_menu_item_fix_through_cgal(&m_assemble_object_menu); // Object Simplify append_menu_item_simplify(&m_assemble_object_menu); // Object Mesh Subdivision @@ -1482,7 +1478,7 @@ void MenuFactory::create_part_menu() append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); append_menu_item_export_drc(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_items_mirror(menu); append_menu_item_merge_parts_to_single_part(menu); @@ -1507,7 +1503,7 @@ void MenuFactory::create_text_part_menu() append_menu_item_edit_text(menu); append_menu_item_delete(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_item_center(menu); append_menu_items_mirror(menu); @@ -1523,7 +1519,7 @@ void MenuFactory::create_svg_part_menu() append_menu_item_edit_svg(menu); append_menu_item_delete(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_items_mirror(menu); menu->AppendSeparator(); @@ -1538,7 +1534,7 @@ void MenuFactory::create_bbl_part_menu() append_menu_item_delete(menu); append_menu_item_edit_text(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_item_smooth_mesh(menu); append_menu_item_center(menu); @@ -1859,7 +1855,7 @@ wxMenu* MenuFactory::multi_selection_menu() } append_menu_item_center(menu); append_menu_item_drop(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); menu->AppendSeparator(); @@ -1884,7 +1880,7 @@ wxMenu* MenuFactory::multi_selection_menu() else { append_menu_item_center(menu); append_menu_item_drop(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); append_menu_items_convert_unit(menu); @@ -1922,7 +1918,7 @@ wxMenu* MenuFactory::assemble_multi_selection_menu() wxMenu* menu = new MenuWithSeparators(); append_menu_item_set_visible(menu); - //append_menu_item_fix_through_netfabb(menu); + //append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); menu->AppendSeparator(); @@ -1972,7 +1968,7 @@ wxMenu* MenuFactory::assemble_object_menu() // Delete append_menu_item_delete(menu); //// Object Repair - //append_menu_item_fix_through_netfabb(menu); + //append_menu_item_fix_through_cgal(menu); //// Object Simplify //append_menu_item_simplify(menu); menu->AppendSeparator(); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index b3915c0705..f80ef85d53 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -139,7 +139,7 @@ private: wxMenuItem* append_menu_item_printable(wxMenu* menu); wxMenuItem* append_menu_item_auto_drop(wxMenu* menu); void append_menu_item_rename(wxMenu* menu); - wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_cgal(wxMenu* menu); //wxMenuItem* append_menu_item_simplify(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu = false); void append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu = false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 919b7217b1..726ea1aca3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -36,7 +36,7 @@ #include #include -#include "slic3r/Utils/FixModelByWin10.hpp" +#include "slic3r/Utils/FixModelByCgal.hpp" #include "libslic3r/Format/bbs_3mf.hpp" #include "libslic3r/PrintConfig.hpp" @@ -578,7 +578,7 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol if (non_manifold_edges) *non_manifold_edges = stats.open_edges; - if (is_windows10() && !sidebar_info) + if (!sidebar_info) tooltip += "\n" + _L("Click the icon to repair model object"); return { tooltip, get_warning_icon_name(stats) }; @@ -1550,9 +1550,9 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me } else if (col_num == colName) { - if (is_windows10() && m_objects_model->HasWarningIcon(item) && + if (m_objects_model->HasWarningIcon(item) && mouse_pos.x > 2 * wxGetApp().em_unit() && mouse_pos.x < 4 * wxGetApp().em_unit()) - fix_through_netfabb(); + fix_through_cgal(); else if (evt_context_menu) show_context_menu(evt_context_menu); // show context menu for "Name" column too } @@ -5984,7 +5984,7 @@ void ObjectList::rename_item() update_name_in_model(item); } -void ObjectList::fix_through_netfabb() +void ObjectList::fix_through_cgal() { // Do not fix anything when a gizmo is open. There might be issues with updates // and what is worse, the snapshot time would refer to the internal stack. @@ -6004,11 +6004,11 @@ void ObjectList::fix_through_netfabb() // clear selections from the non-broken models if any exists // and than fill names of models to repairing if (vol_idxs.empty()) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS for (int i = int(obj_idxs.size())-1; i >= 0; --i) if (object(obj_idxs[i])->get_repaired_errors_count() == 0) obj_idxs.erase(obj_idxs.begin()+i); -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS for (int obj_idx : obj_idxs) if (object(obj_idx)) model_names.push_back(object(obj_idx)->name); @@ -6016,11 +6016,11 @@ void ObjectList::fix_through_netfabb() else { ModelObject* obj = object(obj_idxs.front()); if (obj) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS for (int i = int(vol_idxs.size()) - 1; i >= 0; --i) if (obj->get_repaired_errors_count(vol_idxs[i]) == 0) vol_idxs.erase(vol_idxs.begin() + i); -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS for (int vol_idx : vol_idxs) model_names.push_back(obj->volumes[vol_idx]->name); } @@ -6049,13 +6049,18 @@ void ObjectList::fix_through_netfabb() } plater->clear_before_change_mesh(obj_idx); + const size_t volumes_before = object(obj_idx)->volumes.size(); std::string res; - if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) + if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) return false; //wxGetApp().plater()->changed_mesh(obj_idx); object(obj_idx)->ensure_on_bed(); plater->changed_mesh(obj_idx); + const size_t volumes_after = object(obj_idx)->volumes.size(); + if (volumes_after != volumes_before) + add_volumes_to_object_in_list(obj_idx); + plater->get_partplate_list().notify_instance_update(obj_idx, 0); plater->sidebar().obj_list()->update_plate_values_for_items(); @@ -6079,10 +6084,10 @@ void ObjectList::fix_through_netfabb() if (vol_idxs.empty()) { int vol_idx{ -1 }; for (int obj_idx : obj_idxs) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS if (object(obj_idx)->get_repaired_errors_count(vol_idx) == 0) continue; -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) break; model_idx++; @@ -6115,7 +6120,7 @@ void ObjectList::fix_through_netfabb() } if (msg.IsEmpty()) msg = _L("Repairing was canceled"); - plater->get_notification_manager()->push_notification(NotificationType::NetfabbFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); + plater->get_notification_manager()->push_notification(NotificationType::CgalFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); } void ObjectList::simplify() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ed0dfabd11..b97eff05ae 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -37,7 +37,7 @@ typedef std::pair t_layer_height_range; typedef std::map t_layer_config_ranges; // Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh. -#define FIX_THROUGH_NETFABB_ALWAYS 1 +#define FIX_THROUGH_CGAL_ALWAYS 1 namespace GUI { struct ObjectVolumeID { @@ -430,7 +430,7 @@ public: void instances_to_separated_objects(const int obj_idx); void split_instances(); void rename_item(); - void fix_through_netfabb(); + void fix_through_cgal(); void simplify(); void smooth_mesh(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index e807e477d3..bd6bf6c67e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -659,8 +659,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) cut_with_groove ? cut.perform_with_groove(m_groove, m_rotate_matrix) : cut.perform_with_plane(); // fix_non_manifold_edges -#ifdef HAS_WIN10SDK - if (is_windows10()) { + { bool is_showed_dialog = false; bool user_fix_model = false; for (size_t i = 0; i < new_objects.size(); i++) { @@ -687,7 +686,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_by_win10_sdk_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; + if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; return true; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); @@ -700,7 +699,6 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) } } } - #endif // set offset for new_objects // save cut_id to post update synchronization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 6c5185b0d2..2483f7bd1b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -17,7 +17,7 @@ #include "imgui/imgui_internal.h" #include "slic3r/GUI/Field.hpp" #include "slic3r/GUI/MsgDialog.hpp" -#include "FixModelByWin10.hpp" +#include "FixModelByCgal.hpp" namespace Slic3r { namespace GUI { @@ -3340,8 +3340,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) cut.perform_with_plane(); // fix_non_manifold_edges -#ifdef HAS_WIN10SDK - if (is_windows10()) { + { bool is_showed_dialog = false; bool user_fix_model = false; for (size_t i = 0; i < new_objects.size(); i++) { @@ -3368,7 +3367,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_by_win10_sdk_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; + if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; return true; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); @@ -3381,7 +3380,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } } } - #endif check_objects_after_cut(new_objects); // save cut_id to post update synchronization diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index c9bb132f68..4d31861390 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -126,8 +126,8 @@ enum class NotificationType SimplifySuggestion, // Change of text will change font to similar one on. UnknownFont, - // information about netfabb is finished repairing model (blocking proccess) - NetfabbFinished, + // information about cgal is finished repairing model (blocking process) + CgalFinished, // Short meesage to fill space between start and finish of export ExportOngoing, // Progressbar of download from prusaslicer://url diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 034fb653cd..6e2eee8496 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -31,7 +31,6 @@ #include "PrintHostDialogs.hpp" #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 22d590a969..65e239eebd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -112,7 +112,6 @@ #include "ConfigWizard.hpp" #include "SyncAmsInfoDialog.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Utils/Process.hpp" @@ -4731,7 +4730,7 @@ struct Plater::priv bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; - bool can_fix_through_netfabb() const; + bool can_fix_through_cgal() const; bool can_simplify() const; bool can_smooth_mesh() const; bool can_set_instance_to_object() const; @@ -10240,10 +10239,10 @@ void Plater::priv::on_object_select(SimpleEvent& evt) selection_changed(); } -//BBS: repair model through netfabb +//BBS: repair model through cgal void Plater::priv::on_repair_model(wxCommandEvent &event) { - wxGetApp().obj_list()->fix_through_netfabb(); + wxGetApp().obj_list()->fix_through_cgal(); } void Plater::priv::on_filament_color_changed(wxCommandEvent &event) @@ -11217,15 +11216,15 @@ bool Plater::priv::can_delete_plate() const return q->get_partplate_list().get_plate_count() > 1; } -bool Plater::priv::can_fix_through_netfabb() const +bool Plater::priv::can_fix_through_cgal() const { std::vector obj_idxs, vol_idxs; sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs); -#if FIX_THROUGH_NETFABB_ALWAYS +#if FIX_THROUGH_CGAL_ALWAYS // Fixing always. return ! obj_idxs.empty() || ! vol_idxs.empty(); -#else // FIX_THROUGH_NETFABB_ALWAYS +#else // FIX_THROUGH_CGAL_ALWAYS // Fixing only if the model is not manifold. if (vol_idxs.empty()) { for (auto obj_idx : obj_idxs) @@ -11239,7 +11238,7 @@ bool Plater::priv::can_fix_through_netfabb() const if (model.objects[obj_idx]->get_repaired_errors_count(vol_idx) > 0) return true; return false; -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS } bool Plater::priv::can_simplify() const @@ -17976,15 +17975,13 @@ void Plater::show_object_info() int non_manifold_edges = 0; auto mesh_errors = p->sidebar->obj_list()->get_mesh_errors_info(&info_manifold, &non_manifold_edges); - #ifndef __WINDOWS__ - if (non_manifold_edges > 0) { - info_manifold += into_u8("\n" + _L("Tips:") + "\n" +_L("\"Fix Model\" feature is currently only on Windows. Please repair the model on Orca Slicer(windows) or CAD softwares.")); - } - #endif //APPLE & LINUX + if (non_manifold_edges > 0) { + info_manifold += into_u8("\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh.")); + } info_manifold = "" + info_manifold + ""; info_text += into_u8(info_manifold); - notify_manager->bbl_show_objectsinfo_notification(info_text, is_windows10()&&(non_manifold_edges > 0), !(p->current_panel == p->view3D)); + notify_manager->bbl_show_objectsinfo_notification(info_text, non_manifold_edges > 0, !(p->current_panel == p->view3D)); } bool Plater::show_publish_dialog(bool show) @@ -18161,7 +18158,7 @@ bool Plater::can_delete_plate() const { return p->can_delete_plate(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); } -bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); } +bool Plater::can_fix_through_cgal() const { return p->can_fix_through_cgal(); } bool Plater::can_simplify() const { return p->can_simplify(); } bool Plater::can_smooth_mesh() const { return p->can_smooth_mesh(); } bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 83c5dd6b75..8fde64ead2 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -662,7 +662,7 @@ public: bool can_increase_instances() const; bool can_decrease_instances() const; bool can_set_instance_to_object() const; - bool can_fix_through_netfabb() const; + bool can_fix_through_cgal() const; bool can_simplify() const; bool can_smooth_mesh() const; bool can_split_to_objects() const; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index fa427cc493..55cc4d8d96 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -35,7 +35,6 @@ #include "Tab.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/ColorSpaceConvert.hpp" #include "BitmapCache.hpp" diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp new file mode 100644 index 0000000000..c014ceaf04 --- /dev/null +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -0,0 +1,214 @@ +#include "FixModelByCgal.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/MeshBoolean.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/format.hpp" +#include "../GUI/I18N.hpp" + +// Orca: This file provides utilities for repairing 3D model meshes using the CGAL library, handling mesh splitting, merging, and boolean operations. + +namespace Slic3r { + +namespace { + +// Orca: Helper functions for analyzing mesh properties and transformations. + +bool is_not_3dimensional_part(const TriangleMesh &mesh) +{ + // Orca: Determines if a mesh is degenerate or represents a non-3dimensional part by checking volume and bounding box dimensions. + if (mesh.its.indices.empty()) + return true; + + indexed_triangle_set tmp = mesh.its; + its_remove_degenerate_faces(tmp, true); + if (tmp.indices.empty()) + return true; + + const BoundingBoxf3 bbox = mesh.bounding_box(); + const Vec3d size = bbox.size(); + const double min_dim = std::min(size.x(), std::min(size.y(), size.z())); + const double max_dim = std::max(size.x(), std::max(size.y(), size.z())); + if (min_dim <= EPSILON) + return true; + + const double volume = std::abs(its_volume(mesh.its)); + const double bbox_volume = size.x() * size.y() * size.z(); + if (volume <= EPSILON) + return true; + + const double min_relative_thickness = 1e-6; + const double min_volume_ratio = 1e-6; + if (min_dim / max_dim <= min_relative_thickness) + return true; + if (bbox_volume > 0.0 && volume / bbox_volume <= min_volume_ratio) + return true; + + return false; +} + +} // namespace + +// Orca: Exception class for handling user-initiated cancellation of model repair operations. +class RepairCanceledException : public std::exception { +public: + const char* what() const noexcept override { return "Model repair has been canceled"; } +}; + +// Orca: Main function to repair model objects using CGAL, with progress dialog and cancellation support. +// Returns false if fixing was canceled. fix_result contains error message if failed. +bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result) +{ + // Orca: Synchronization primitives for progress updates between worker thread and GUI. + std::mutex mtx; + std::condition_variable condition; + struct Progress { + std::string message; + int percent = 0; + bool updated = false; + } progress; + + std::atomic canceled = false; + std::atomic finished = false; + + bool success = false; + size_t ivolume = 0; + + // Orca: Lambda for updating progress from worker thread. + auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](const char *msg, unsigned prcnt) { + std::unique_lock lock(mtx); + progress.message = msg; + const size_t total = std::max(1, model_object.volumes.size()); + progress.percent = int(std::floor((float(prcnt) + float(ivolume) * 100.f) / float(total))); + progress.updated = true; + condition.notify_all(); + }; + + // Orca: Worker thread that performs the actual model repair operations. + auto worker_thread = std::thread([&model_object, volume_idx, &ivolume, on_progress, &success, &canceled, &finished, &fix_result]() { + try { + size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); + size_t end_volume = volume_idx == -1 ? std::numeric_limits::max() : size_t(volume_idx); + + for (ivolume = start_volume; ivolume < model_object.volumes.size(); ++ivolume) { + if (volume_idx != -1 && ivolume > end_volume) + break; + if (canceled) + throw RepairCanceledException(); + + on_progress(L("Repairing model object"), 10); + + ModelVolume *volume = model_object.volumes[ivolume]; + + // Orca: Split splittable volumes into parts for individual processing. + size_t parts_count = 1; + if (volume->is_splittable()) { + parts_count = volume->split(1); + if (parts_count > 1) { + const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count); + on_progress(msg.c_str(), 10); + } + } + + size_t part_end = std::min(ivolume + parts_count - 1, model_object.volumes.size() - 1); + if (volume_idx != -1) + end_volume = part_end; + + size_t removed_parts = 0; + for (size_t idx = part_end + 1; idx > ivolume; --idx) { + const size_t part_idx = idx - 1; + const ModelVolume *part_volume = model_object.volumes[part_idx]; + if (!is_not_3dimensional_part(part_volume->mesh())) + continue; + + model_object.delete_volume(part_idx); + ++removed_parts; + if (part_end > 0) + --part_end; + else + part_end = 0; + if (volume_idx != -1) + end_volume = part_end; + } + + if (removed_parts >= parts_count) { + ivolume = part_end; + on_progress(L("Repair finished"), 100); + continue; + } + + for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { + ModelVolume *part_volume = model_object.volumes[part_idx]; + TriangleMesh mesh = part_volume->mesh(); + if (its_num_open_edges(mesh.its) != 0) { + std::string error; + if (!MeshBoolean::cgal::repair(mesh, nullptr, &error)) + throw Slic3r::RuntimeError(error.empty() ? L("Repair failed") : error.c_str()); + + part_volume->set_mesh(std::move(mesh)); + part_volume->calculate_convex_hull(); + part_volume->invalidate_convex_hull_2d(); + part_volume->set_new_unique_id(); + } + } + + ivolume = part_end; + + on_progress(L("Repair finished"), 100); + } + + model_object.invalidate_bounding_box(); + + if (ivolume > 0) + --ivolume; + on_progress(L("Repair finished"), 100); + success = true; + finished = true; + } catch (RepairCanceledException &) { + canceled = true; + finished = true; + on_progress(L("Repair canceled"), 100); + } catch (std::exception &ex) { + success = false; + finished = true; + fix_result = ex.what(); + on_progress(ex.what(), 100); + } + }); + + // Orca: Main GUI loop to update progress dialog and handle cancellation. + while (!finished) { + std::unique_lock lock(mtx); + condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); + + // Decrease progress percent slightly to avoid auto-closing. + if (!progress_dialog.Update(progress.percent - 1, msg_header + _(progress.message))) + canceled = true; + else + progress_dialog.Fit(); + + progress.updated = false; + } + + if (canceled) { + // Nothing to show. + } else if (success) { + fix_result.clear(); + } + + if (worker_thread.joinable()) + worker_thread.join(); + + return !canceled; +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/FixModelByCgal.hpp b/src/slic3r/Utils/FixModelByCgal.hpp new file mode 100644 index 0000000000..3caa6d7fb9 --- /dev/null +++ b/src/slic3r/Utils/FixModelByCgal.hpp @@ -0,0 +1,18 @@ +#ifndef slic3r_GUI_Utils_FixModelByCgal_hpp_ +#define slic3r_GUI_Utils_FixModelByCgal_hpp_ + +#include +#include "../GUI/Widgets/ProgressDialog.hpp" + +namespace Slic3r { + +class Model; +class ModelObject; +class Print; + +// Return false if fixing was canceled. fix_result is empty on success. +extern bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); + +} // namespace Slic3r + +#endif /* slic3r_GUI_Utils_FixModelByCgal_hpp_ */ diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp deleted file mode 100644 index 4967caf479..0000000000 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ /dev/null @@ -1,447 +0,0 @@ -#ifdef HAS_WIN10SDK - -#ifndef NOMINMAX -# define NOMINMAX -#endif - -// Windows Runtime -#include -// for ComPtr -#include - -// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ -#include -#include -#include - -#include "FixModelByWin10.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "../GUI/GUI.hpp" -#include "../GUI/I18N.hpp" -#include "../GUI/MsgDialog.hpp" - -#include -#include - -extern "C"{ - // from rapi.h - typedef HRESULT (__stdcall* FunctionRoInitialize)(int); - typedef HRESULT (__stdcall* FunctionRoUninitialize)(); - typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); - typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); - // from winstring.h - typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); - typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); -} - -namespace Slic3r { - -static std::string saving_failed_str = L("Saving objects into the 3MF failed."); - -HMODULE s_hRuntimeObjectLibrary = nullptr; -FunctionRoInitialize s_RoInitialize = nullptr; -FunctionRoUninitialize s_RoUninitialize = nullptr; -FunctionRoActivateInstance s_RoActivateInstance = nullptr; -FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; -FunctionWindowsCreateString s_WindowsCreateString = nullptr; -FunctionWindowsDelteString s_WindowsDeleteString = nullptr; - -bool winrt_load_runtime_object_library() -{ - if (s_hRuntimeObjectLibrary == nullptr) - s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); - if (s_hRuntimeObjectLibrary != nullptr) { - s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); - s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); - s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); - s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); - s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); - s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); - } - return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; -} - -static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoActivateInstance)(hClassName, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) -{ - IInspectable *pinspectable = nullptr; - HRESULT hr = winrt_activate_instance(class_name, &pinspectable); - if (S_OK != hr) - return hr; - hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); - pinspectable->Release(); - return hr; -} - -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) -{ - return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast(pinst)); -} - -// To be called often to test whether to cancel the operation. -typedef std::function ThrowOnCancelFn; - -template -static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) -{ - Microsoft::WRL::ComPtr asyncInfo; - asyncAction.As(&asyncInfo); - AsyncStatus status; - // Ugly blocking loop until the RepairAsync call finishes. -//FIXME replace with a callback. -// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage - for (;;) { - asyncInfo->get_Status(&status); - if (status != AsyncStatus::Started) - return status; - throw_on_cancel(); - ::Sleep(blocking_tick_ms); - } -} - -static HRESULT winrt_open_file_stream( - const std::wstring &path, - ABI::Windows::Storage::FileAccessMode mode, - ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, - ThrowOnCancelFn throw_on_cancel) -{ - // Get the file factory. - Microsoft::WRL::ComPtr fileFactory; - HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); - if (FAILED(hr)) return hr; - - // Open the file asynchronously. - HSTRING hstr_path; - hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); - if (FAILED(hr)) return hr; - Microsoft::WRL::ComPtr> fileOpenAsync; - hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - (*s_WindowsDeleteString)(hstr_path); - - // Wait until the file gets open, get the actual file. - AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); - Microsoft::WRL::ComPtr storageFile; - if (status == AsyncStatus::Completed) { - hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileOpenAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - return FAILED(hr) ? hr : err; - } - - Microsoft::WRL::ComPtr> fileStreamAsync; - hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - - status = winrt_async_await(fileStreamAsync, throw_on_cancel); - if (status == AsyncStatus::Completed) { - hr = fileStreamAsync->GetResults(fileStream); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileStreamAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - if (!FAILED(hr)) - hr = err; - } - return hr; -} - -bool is_windows10() -{ - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) { - WCHAR szBuffer[512]; - DWORD dwBufferSize = sizeof(szBuffer); - lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); - if (lRes == ERROR_SUCCESS) - return wcsncmp(szBuffer, L"Windows 10", 10) == 0; - RegCloseKey(hKey); - } - return false; -} - -// Progress function, to be called regularly to update the progress. -typedef std::function ProgressFn; - -void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) -{ - if (! is_windows10()) - throw Slic3r::RuntimeError(L("Only Windows 10 is supported.")); - - if (! winrt_load_runtime_object_library()) - throw Slic3r::RuntimeError(L("Failed to initialize the WinRT library.")); - - HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); - { - on_progress(L("Exporting objects"), 20); - - Microsoft::WRL::ComPtr fileStream; - hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); - - Microsoft::WRL::ComPtr printing3d3mfpackage; - hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); - - Microsoft::WRL::ComPtr> modelAsync; - hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); - - AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); - Microsoft::WRL::ComPtr model; - if (status == AsyncStatus::Completed) - hr = modelAsync->GetResults(model.GetAddressOf()); - else - throw Slic3r::RuntimeError(L("Failed loading objects.")); - - Microsoft::WRL::ComPtr> meshes; - hr = model->get_Meshes(meshes.GetAddressOf()); - unsigned num_meshes = 0; - hr = meshes->get_Size(&num_meshes); - on_progress(L("Repairing object by Windows service"), 40); - Microsoft::WRL::ComPtr repairAsync; - hr = model->RepairAsync(repairAsync.GetAddressOf()); - status = winrt_async_await(repairAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Repair failed.")); - repairAsync->GetResults(); - - on_progress(L("Loading repaired objects"), 60); - - // Verify the number of meshes returned after the repair action. - meshes.Reset(); - hr = model->get_Meshes(meshes.GetAddressOf()); - hr = meshes->get_Size(&num_meshes); - - // Save model to this class' Printing3D3MFPackage. - Microsoft::WRL::ComPtr saveToPackageAsync; - hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); - status = winrt_async_await(saveToPackageAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - hr = saveToPackageAsync->GetResults(); - - Microsoft::WRL::ComPtr> generatorStreamAsync; - hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); - status = winrt_async_await(generatorStreamAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - Microsoft::WRL::ComPtr generatorStream; - hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); - - // Go to the beginning of the stream. - generatorStream->Seek(0); - Microsoft::WRL::ComPtr inputStream; - hr = generatorStream.As(&inputStream); - - // Get the buffer factory. - Microsoft::WRL::ComPtr bufferFactory; - hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); - - // Open the destination file. - FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); - - Microsoft::WRL::ComPtr buffer; - byte *buffer_ptr; - bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); - { - Microsoft::WRL::ComPtr bufferByteAccess; - buffer.As(&bufferByteAccess); - hr = bufferByteAccess->Buffer(&buffer_ptr); - } - uint32_t length; - hr = buffer->get_Length(&length); - - Microsoft::WRL::ComPtr> asyncRead; - for (;;) { - hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); - status = winrt_async_await(asyncRead, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - hr = buffer->get_Length(&length); - if (length == 0) - break; - fwrite(buffer_ptr, length, 1, fout); - } - fclose(fout); - // Here all the COM objects will be released through the ComPtr destructors. - } - (*s_RoUninitialize)(); -} - -class RepairCanceledException : public std::exception { -public: - const char* what() const throw() { return "Model repair has been canceled"; } -}; - -// returt FALSE, if fixing was canceled -// fix_result is empty, if fixing finished successfully -// fix_result containes a message if fixing failed -bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) -{ - std::mutex mtx; - std::condition_variable condition; - struct Progress { - std::string message; - int percent = 0; - bool updated = false; - } progress; - std::atomic canceled = false; - std::atomic finished = false; - - std::vector volumes; - if (volume_idx == -1) - volumes = model_object.volumes; - else - volumes.emplace_back(model_object.volumes[volume_idx]); - - // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. - // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). - bool success = false; - size_t ivolume = 0; - auto on_progress = [&mtx, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::unique_lock lock(mtx); - progress.message = msg; - progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); - progress.updated = true; - condition.notify_all(); - }; - auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { - try { - std::vector meshes_repaired; - meshes_repaired.reserve(volumes.size()); - for (; ivolume < volumes.size(); ++ ivolume) { - on_progress(L("Exporting objects"), 0); - boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_src += ".3mf"; - Model model; - ModelObject *mo = model.add_object(); - mo->add_volume(*volumes[ivolume]); - - // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back. - // store_3mf currently bakes the volume transformation into the mesh itself. - // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume - // (which remembers the matrix the whole time), the transformation would be used twice. - // We will therefore set the volume transform on the dummy ModelVolume to identity. - mo->volumes.back()->set_transformation(Geometry::Transformation()); - - mo->add_instance(); - if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { - boost::filesystem::remove(path_src); - throw Slic3r::RuntimeError(L("Exporting 3MF file failed")); - } - model.clear_objects(); - model.clear_materials(); - boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_dst += ".3mf"; - fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, - [&canceled]() { if (canceled) throw RepairCanceledException(); }); - boost::filesystem::remove(path_src); - // PresetBundle bundle; - on_progress(L("Loading repaired objects"), 80); - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); - boost::filesystem::remove(path_dst); - if (! loaded) - throw Slic3r::RuntimeError(L("Import 3MF file failed")); - if (model.objects.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); - if (model.objects.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); - if (model.objects.front()->volumes.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); - if (model.objects.front()->volumes.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); - } - for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->set_mesh(std::move(meshes_repaired[i])); - volumes[i]->calculate_convex_hull(); - volumes[i]->invalidate_convex_hull_2d(); - volumes[i]->set_new_unique_id(); - } - model_object.invalidate_bounding_box(); - -- ivolume; - on_progress(L("Repair finished"), 100); - success = true; - finished = true; - } catch (RepairCanceledException & /* ex */) { - canceled = true; - finished = true; - on_progress(L("Repair canceled"), 100); - } catch (std::exception &ex) { - success = false; - finished = true; - on_progress(ex.what(), 100); - } - }); - while (! finished) { - std::unique_lock lock(mtx); - condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); - // decrease progress.percent value to avoid closing of the progress dialog - if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) - canceled = true; - else - progress_dialog.Fit(); - progress.updated = false; - } - - if (canceled) { - // Nothing to show. - } else if (success) { - fix_result = ""; - } else { - fix_result = progress.message; - } - worker_thread.join(); - return !canceled; -} - -} // namespace Slic3r - -#endif /* HAS_WIN10SDK */ diff --git a/src/slic3r/Utils/FixModelByWin10.hpp b/src/slic3r/Utils/FixModelByWin10.hpp deleted file mode 100644 index 7beb39e028..0000000000 --- a/src/slic3r/Utils/FixModelByWin10.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_ -#define slic3r_GUI_Utils_FixModelByWin10_hpp_ - -#include -#include "../GUI/Widgets/ProgressDialog.hpp" - -class ProgressDialog; - -namespace Slic3r { - -class Model; -class ModelObject; -class Print; - -#ifdef HAS_WIN10SDK - -extern bool is_windows10(); -// returt false, if fixing was canceled -extern bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx,GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); - -#else /* HAS_WIN10SDK */ - -inline bool is_windows10() { return false; } -// returt false, if fixing was canceled -inline bool fix_model_by_win10_sdk_gui(ModelObject &, int, GUI::ProgressDialog &, const wxString &, std::string &) { return false; } - -#endif /* HAS_WIN10SDK */ - -} // namespace Slic3r - -#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */ From a5171b902d0d3f3110ef210a9b228ff89d05fe1c Mon Sep 17 00:00:00 2001 From: yw4z Date: Fri, 24 Apr 2026 11:29:29 +0300 Subject: [PATCH 35/42] Fix vertical alignment issues (Printer name, Clone progress) (#13133) init Co-authored-by: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> --- src/slic3r/GUI/CloneDialog.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/CloneDialog.cpp b/src/slic3r/GUI/CloneDialog.cpp index b5f506ae4e..36243249f8 100644 --- a/src/slic3r/GUI/CloneDialog.cpp +++ b/src/slic3r/GUI/CloneDialog.cpp @@ -46,7 +46,7 @@ CloneDialog::CloneDialog(wxWindow *parent) m_progress->SetProgressForedColour(StateColor::darkModeColorFor(wxColour("#DFDFDF"))); m_progress->SetDoubleBuffered(true); m_progress->Hide(); - bottom_sizer->Add(m_progress, 2, wxEXPAND | wxLEFT, FromDIP(10)); + bottom_sizer->Add(m_progress, 2, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(10)); auto dlg_btns = new DialogButtons(this, {"Fill", "OK", "Cancel"}, "", 1 /*left_aligned*/); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 65e239eebd..234e37edf2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -582,7 +582,7 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual) //if (isBBL) { wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL); hsizer->Add(image_printer, 0, wxLEFT | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); - hsizer->Add(combo_printer, 1, wxEXPAND | wxALL, FromDIP(2)); + hsizer->Add(combo_printer, 1, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(2)); // 1 already triggers wxEXPAND hsizer->AddSpacer(FromDIP(2)); hsizer->Add(btn_edit_printer, 0, wxRIGHT | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(SidebarProps::IconSpacing())); //hsizer->Add(btn_connect_printer, 0, wxRIGHT | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(SidebarProps::IconSpacing())); From 66eb5fc4f45f2e522bf59c1fef9f6f06616a566f Mon Sep 17 00:00:00 2001 From: yw4z Date: Fri, 24 Apr 2026 11:51:00 +0300 Subject: [PATCH 36/42] Add option to place window buttons on left side for Linux (#12831) init --- src/libslic3r/AppConfig.cpp | 5 +++++ src/slic3r/GUI/BBLTopbar.cpp | 34 +++++++++++++++++++++++++++++++++- src/slic3r/GUI/Preferences.cpp | 5 +++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index af785e6515..68ea971424 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -523,6 +523,11 @@ void AppConfig::set_defaults() set_bool("installed_networking", false); } +#ifdef __linux__ + if (get("window_buttons_on_left").empty()) + set_bool("window_buttons_on_left", false); +#endif + // Remove legacy window positions/sizes erase("app", "main_frame_maximized"); erase("app", "main_frame_pos"); diff --git a/src/slic3r/GUI/BBLTopbar.cpp b/src/slic3r/GUI/BBLTopbar.cpp index 5722d26bec..cd25556317 100644 --- a/src/slic3r/GUI/BBLTopbar.cpp +++ b/src/slic3r/GUI/BBLTopbar.cpp @@ -236,7 +236,37 @@ void BBLTopbar::Init(wxFrame* parent) wxInitAllImageHandlers(); - this->AddSpacer(5); + bool window_btns_on_left = false; + +#ifdef __linux__ + window_btns_on_left = wxGetApp().app_config->get("window_buttons_on_left") == "true"; + + if(window_btns_on_left){ + wxBitmap close_bitmap = create_scaled_bitmap("topbar_close", nullptr, TOPBAR_ICON_SIZE); + wxAuiToolBarItem* close_btn = this->AddTool(wxID_CLOSE_FRAME, "", close_bitmap); + + this->AddSpacer(FromDIP(4)); + + maximize_bitmap = create_scaled_bitmap("topbar_max", nullptr, TOPBAR_ICON_SIZE); + window_bitmap = create_scaled_bitmap("topbar_win", nullptr, TOPBAR_ICON_SIZE); + if (m_frame->IsMaximized()) { + maximize_btn = this->AddTool(wxID_MAXIMIZE_FRAME, "", window_bitmap); + } + else { + maximize_btn = this->AddTool(wxID_MAXIMIZE_FRAME, "", maximize_bitmap); + } + + this->AddSpacer(FromDIP(4)); + + wxBitmap iconize_bitmap = create_scaled_bitmap("topbar_min", nullptr, TOPBAR_ICON_SIZE); + wxAuiToolBarItem* iconize_btn = this->AddTool(wxID_ICONIZE_FRAME, "", iconize_bitmap); + + this->AddSpacer(15); + } +#endif + + if(!window_btns_on_left) + this->AddSpacer(5); /*wxBitmap logo_bitmap = create_scaled_bitmap("topbar_logo", nullptr, TOPBAR_ICON_SIZE); wxAuiToolBarItem* logo_item = this->AddTool(ID_LOGO, "", logo_bitmap); @@ -312,6 +342,7 @@ void BBLTopbar::Init(wxFrame* parent) //this->AddSeparator(); //this->AddSpacer(FromDIP(4)); + if(!window_btns_on_left){ wxBitmap iconize_bitmap = create_scaled_bitmap("topbar_min", nullptr, TOPBAR_ICON_SIZE); wxAuiToolBarItem* iconize_btn = this->AddTool(wxID_ICONIZE_FRAME, "", iconize_bitmap); @@ -330,6 +361,7 @@ void BBLTopbar::Init(wxFrame* parent) wxBitmap close_bitmap = create_scaled_bitmap("topbar_close", nullptr, TOPBAR_ICON_SIZE); wxAuiToolBarItem* close_btn = this->AddTool(wxID_CLOSE_FRAME, "", close_bitmap); + } Realize(); // m_toolbar_h = this->GetSize().GetHeight(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 42fa277cc3..2774f6fcd7 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1360,6 +1360,11 @@ 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); +#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); +#endif + //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); From b30470a714b0e467e7793f6ae714fd63f9610b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mart=C3=ADnez=20Moreno?= Date: Fri, 24 Apr 2026 01:52:25 -0700 Subject: [PATCH 37/42] Fix the OnText handler in ObjectTableDialog to close on Esc. (#12979) The OnText handler in ObjectTableDialog catches WXK_ESCAPE but swallows it. Invert the logic to call Close() on Esc, and pass anything else to evt.Skip(). While at it, fix a typo in the dialog's title. --- localization/i18n/en/OrcaSlicer_en.po | 2 +- src/slic3r/GUI/GUI_ObjectTable.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/localization/i18n/en/OrcaSlicer_en.po b/localization/i18n/en/OrcaSlicer_en.po index b1b8aad458..b0b7dced49 100644 --- a/localization/i18n/en/OrcaSlicer_en.po +++ b/localization/i18n/en/OrcaSlicer_en.po @@ -2614,7 +2614,7 @@ msgid "Brim" msgstr "" msgid "Object/Part Setting" -msgstr "Object/part setting" +msgstr "Object/part settings" msgid "Reset parameter" msgstr "" diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index 4fc40db74b..71174665e1 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -3277,7 +3277,7 @@ void ObjectTablePanel::msw_rescale() { // ObjectTableDialog // ---------------------------------------------------------------------------- ObjectTableDialog::ObjectTableDialog(wxWindow* parent, Plater* platerObj, Model *modelObj, wxSize maxSize) - : GUI::DPIDialog(parent, wxID_ANY, _L("Object/Part Setting"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX | wxRESIZE_BORDER) + : GUI::DPIDialog(parent, wxID_ANY, _L("Object/Part Settings"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX | wxRESIZE_BORDER) , m_model(modelObj), m_plater(platerObj) { @@ -3410,7 +3410,9 @@ void ObjectTableDialog::OnClose(wxCloseEvent &evt) void ObjectTableDialog::OnText(wxKeyEvent &evt) { - if (evt.GetKeyCode() != WXK_ESCAPE) { + if (evt.GetKeyCode() == WXK_ESCAPE) { + Close(); + } else { evt.Skip(); } } From 09d5c92f6a7a4339bedfe25089481a6261dd783d Mon Sep 17 00:00:00 2001 From: Yevhen Date: Fri, 24 Apr 2026 06:36:00 -0400 Subject: [PATCH 38/42] fix gaps between walls and sparse infill, issue #12304 (#12770) --- src/libslic3r/Fill/Fill.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 9d9962dbc8..aecf658a14 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -808,8 +808,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n return; } - // Expand the normal infills a little bit to avoid gaps between normal and narrow infills - normal_infill = intersection_ex(offset_ex(normal_fill_areas_ex, scaled_spacing * 0.1), fill.expolygons); + // Expand the normal infills to avoid gaps between normal and narrow infills. + // The inner_area was shrunk by scaled_spacing * 0.5, so we need to expand + // by at least that amount to ensure proper coverage and avoid gaps. + normal_infill = intersection_ex(offset_ex(normal_fill_areas_ex, scaled_spacing * 0.5), fill.expolygons); narrow_infill = narrow_fill_areas; #ifdef DEBUG_SURFACE_SPLIT From 91f54a07b98bf1d1ffc420e4e14557f34d67aefb Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 09:09:00 -0300 Subject: [PATCH 39/42] Fix bug template: Wrap nightly build link in bug template (#13337) --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index cf8ac675ed..1b3ba3f407 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -14,7 +14,9 @@ body: - type: checkboxes attributes: label: Is this issue reproducible in the latest nightly build? - description: Please verify this issue still happens in the latest nightly build first. It may already be fixed there: [Nightly builds](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds). + description: > + Please verify this issue still happens in the latest nightly build first. It may already be fixed there: + [Nightly builds](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds). options: - label: I have checked the latest nightly build and the issue is still reproducible required: true From 2af2f0f98bd51a58ea34a56f78af9d95dafd258b Mon Sep 17 00:00:00 2001 From: tome9111991 <57866234+tome9111991@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:39:32 +0200 Subject: [PATCH 40/42] enh: flow rate pattern calibration menu (#11956) * feat: Add flow rate calibration pattern selector dialog * feat: Integrate PR #11881 and improve Flow Rate Calibration Wizard - Integrated PR #11881: Display layer duration in G-code viewer position window. - Enhanced Flow Rate Calibration: - Implemented a Wizard dialog (FlowRateCalibrationDialog) using DPIDialog for Dark Mode support. - Restricted pattern selection to 'Archimedean Chords' and 'Monotonic'. - Integrated logic to pass the selected pattern to the calibration generation. * fix(UI): Refactor FlowRateCalibrationDialog to calib_dlg for macOS build fix and UI consistency Moved FlowRateCalibrationDialog from MainFrame.cpp to calib_dlg.hpp/.cpp to resolve build errors on macOS. Updated UI to match Pressure Advance calibration dialog style (Dark Mode support, RadioGroups). Logic moved to dialog class. * Update Wiki link for flow rate calibration * ui: replace RadioGroup with BitmapComboBox for Flow Rate pattern selection * fix(ui): use custom ComboBox widget for Flow Rate Calibration Replaces wxBitmapComboBox with OrcaSlicer's custom ComboBox widget to fix rendering issues on Windows (empty selection) and compilation errors on Linux. * Refactor: Cleanup unused includes and members in FlowRate calibration dialog * Fix: Complete rename of Flow Rate to Flow ratio in calibration menus after merge * Fix: Update Flow Rate to Flow Ratio in Calibration Dialog and Wiki Link --------- Co-authored-by: yw4z Co-authored-by: SoftFever --- src/slic3r/GUI/CalibrationPanel.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 48 ++++++--------- src/slic3r/GUI/MainFrame.hpp | 1 + src/slic3r/GUI/Plater.cpp | 12 ++-- src/slic3r/GUI/Plater.hpp | 3 +- src/slic3r/GUI/calib_dlg.cpp | 94 +++++++++++++++++++++++++++++ src/slic3r/GUI/calib_dlg.hpp | 16 +++++ 7 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/CalibrationPanel.cpp b/src/slic3r/GUI/CalibrationPanel.cpp index 76c545f912..a0d52a228f 100644 --- a/src/slic3r/GUI/CalibrationPanel.cpp +++ b/src/slic3r/GUI/CalibrationPanel.cpp @@ -25,7 +25,7 @@ wxString get_calibration_type_name(CalibMode cali_mode) case CalibMode::Calib_PA_Line: return _L("Flow Dynamics"); case CalibMode::Calib_Flow_Rate: - return _L("Flow Rate"); + return _L("Flow ratio"); case CalibMode::Calib_Vol_speed_Tower: return _L("Max Volumetric Speed"); case CalibMode::Calib_Temp_Tower: diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 70c622ee6c..cbcbd0adaf 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -3278,23 +3278,15 @@ void MainFrame::init_menubar_as_editor() }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); - // Flow rate (with submenu) - auto flowrate_menu = new wxMenu(); - append_menu_item( - flowrate_menu, wxID_ANY, _L("Pass 1"), _L("Flow ratio test - Pass 1"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(false, 1); }, "", nullptr, + // Flow rate (Wizard Dialog) + append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Flow ratio"), _L("Flow Rate Calibration"), + [this](wxCommandEvent&) { + if (!m_plater) return; + if (!m_flow_rate_calib_dlg) + m_flow_rate_calib_dlg = new FlowRateCalibrationDialog((wxWindow*)this, wxID_ANY, m_plater); + m_flow_rate_calib_dlg->ShowModal(); + }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); - append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 2"), _L("Flow ratio test - Pass 2"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(false, 2); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - flowrate_menu->AppendSeparator(); - append_menu_item(flowrate_menu, wxID_ANY, _L("YOLO (Recommended)"), _L("Orca YOLO flowratio calibration, 0.01 step"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(true, 1); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - append_menu_item(flowrate_menu, wxID_ANY, _L("YOLO (perfectionist version)"), _L("Orca YOLO flowratio calibration, 0.005 step"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(true, 2); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - m_topbar->GetCalibMenu()->AppendSubMenu(flowrate_menu, _L("Flow ratio")); // Retraction append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Retraction"), _L("Retraction"), @@ -3390,21 +3382,14 @@ void MainFrame::init_menubar_as_editor() [this]() {return m_plater->is_view3D_shown();; }, this); // Flowrate (with submenu) - auto flowrate_menu = new wxMenu(); - append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 1"), _L("Flow ratio test - Pass 1"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(false, 1); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 2"), _L("Flow ratio test - Pass 2"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(false, 2); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - append_submenu(calib_menu,flowrate_menu,wxID_ANY,_L("Flow ratio"),_L("Flow ratio"),"", - [this]() {return m_plater->is_view3D_shown();; }); - flowrate_menu->AppendSeparator(); - append_menu_item(flowrate_menu, wxID_ANY, _L("YOLO (Recommended)"), _L("Orca YOLO flowratio calibration, 0.01 step"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(true, 1); }, "", nullptr, - [this]() {return m_plater->is_view3D_shown();; }, this); - append_menu_item(flowrate_menu, wxID_ANY, _L("YOLO (perfectionist version)"), _L("Orca YOLO flowratio calibration, 0.005 step"), - [this](wxCommandEvent&) { if (m_plater) m_plater->calib_flowrate(true, 2); }, "", nullptr, + // ORCA: Flow rate (Wizard Dialog) + append_menu_item(calib_menu, wxID_ANY, _L("Flow ratio"), _L("Flow Rate Calibration"), + [this](wxCommandEvent&) { + if (!m_plater) return; + if (!m_flow_rate_calib_dlg) + m_flow_rate_calib_dlg = new FlowRateCalibrationDialog((wxWindow*)this, wxID_ANY, m_plater); + m_flow_rate_calib_dlg->ShowModal(); + }, "", nullptr, [this]() {return m_plater->is_view3D_shown();; }, this); // Retraction @@ -4361,3 +4346,4 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) } // GUI } // Slic3r + diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 5cbeb48f93..20229a611e 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -362,6 +362,7 @@ public: void fit_tab_labels(); // ORCA PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr }; + FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr }; Temp_Calibration_Dlg* m_temp_calib_dlg{ nullptr }; MaxVolumetricSpeed_Test_Dlg* m_vol_test_dlg { nullptr }; VFA_Test_Dlg* m_vfa_test_dlg { nullptr }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 234e37edf2..532540fa72 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12769,7 +12769,8 @@ void Plater::_calib_pa_select_added_objects() { // Adjust settings for flowrate calibration // For linear mode, pass 1 means normal version while pass 2 mean "for perfectionists" version -void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, int pass) +// ORCA: Add pattern parameter +void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, int pass, InfillPattern pattern) { auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; auto printerConfig = &wxGetApp().preset_bundle->printers.get_edited_preset().config; @@ -12832,7 +12833,8 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i _obj->config.set_key_value("sparse_infill_pattern", new ConfigOptionEnum(ipRectilinear)); _obj->config.set_key_value("top_surface_line_width", new ConfigOptionFloatOrPercent(nozzle_diameter * 1.2f, false)); _obj->config.set_key_value("internal_solid_infill_line_width", new ConfigOptionFloatOrPercent(nozzle_diameter * 1.2f, false)); - _obj->config.set_key_value("top_surface_pattern", new ConfigOptionEnum(ipArchimedeanChords)); + // ORCA: use the pattern parameter + _obj->config.set_key_value("top_surface_pattern", new ConfigOptionEnum(pattern)); _obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f)); _obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45)); _obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135)); @@ -12883,7 +12885,8 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i wxGetApp().get_tab(Preset::TYPE_PRINTER)->reload_config(); } -void Plater::calib_flowrate(bool is_linear, int pass) { +// ORCA: Add pattern parameter +void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) { if (pass != 1 && pass != 2) return; wxString calib_name; @@ -12915,7 +12918,8 @@ void Plater::calib_flowrate(bool is_linear, int pass) { (boost::filesystem::path(Slic3r::resources_dir()) / "calib" / "filament_flow" / "flowrate-test-pass2.3mf").string()); } - adjust_settings_for_flowrate_calib(model().objects, is_linear, pass); + // ORCA: pass the pattern + adjust_settings_for_flowrate_calib(model().objects, is_linear, pass, pattern); wxGetApp().get_tab(Preset::TYPE_PRINTER)->reload_config(); auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 8fde64ead2..31e0e2f0e1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -331,7 +331,8 @@ public: // SoftFever void calib_pa(const Calib_Params& params); - void calib_flowrate(bool is_linear, int pass); + //ORCA: Add pattern parameter to calib_flowrate + void calib_flowrate(bool is_linear, int pass, InfillPattern pattern = ipArchimedeanChords); void calib_temp(const Calib_Params& params); void calib_max_vol_speed(const Calib_Params& params); void calib_retraction(const Calib_Params& params); diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp index ea7d1e02a5..50b69663e0 100644 --- a/src/slic3r/GUI/calib_dlg.cpp +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -9,6 +9,7 @@ #include #include #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { @@ -1436,4 +1437,97 @@ void Cornering_Test_Dlg::on_dpi_changed(const wxRect& suggested_rect) { Fit(); } +// FlowRateCalibrationDialog +// +FlowRateCalibrationDialog::FlowRateCalibrationDialog(wxWindow* parent, wxWindowID id, Plater* plater) + : DPIDialog(parent, id, _L("Flow Ratio Calibration"), wxDefaultPosition, parent->FromDIP(wxSize(-1, 280)), wxDEFAULT_DIALOG_STYLE), m_plater(plater) +{ + SetBackgroundColour(*wxWHITE); // make sure background color set for dialog + SetForegroundColour(wxColour("#363636")); + SetFont(Label::Body_14); + + wxBoxSizer* v_sizer = new wxBoxSizer(wxVERTICAL); + SetSizer(v_sizer); + + // Type selection + auto labeled_box_type = new LabeledStaticBox(this, _L("Calibration Test Type")); + auto type_box = new wxStaticBoxSizer(labeled_box_type, wxVERTICAL); + + m_rbType = new RadioGroup(this, { _L("Pass 1 (Coarse)"), _L("Pass 2 (Fine)"), _L("YOLO (Recommended)"), _L("YOLO (Perfectionist)") }, wxVERTICAL, 1); + m_rbType->SetSelection(2); // Default to YOLO Recommended + type_box->Add(m_rbType, 0, wxALL | wxEXPAND, FromDIP(4)); + v_sizer->Add(type_box, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10)); + + // Pattern selection + auto labeled_box_pattern = new LabeledStaticBox(this, _L("Top Surface Pattern")); + auto pattern_box = new wxStaticBoxSizer(labeled_box_pattern, wxVERTICAL); + + // ORCA: Use ComboBox with icons instead of RadioGroup + m_rbPattern = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); + + boost::filesystem::path image_path(Slic3r::resources_dir()); + image_path /= "images"; + + auto add_pattern_item = [&](const std::string& name, const wxString& label) { + auto icon_name = "param_" + name; + if (boost::filesystem::exists(image_path / (icon_name + ".svg"))) { + // Using 24px icon size to match other settings (Field.cpp uses 24) + ScalableBitmap bm(this, icon_name, 24); + m_rbPattern->Append(label, bm.bmp()); + } else { + m_rbPattern->Append(label); + } + }; + + add_pattern_item("archimedeanchords", _L("Archimedean Chords")); + add_pattern_item("monotonic", _L("Monotonic")); + m_rbPattern->SetSelection(0); // Default to Archimedean Chords + // ORCA: explicit set value to ensure display on Windows + m_rbPattern->SetValue(m_rbPattern->GetString(0)); + + pattern_box->Add(m_rbPattern, 0, wxALL | wxEXPAND, FromDIP(4)); + v_sizer->Add(pattern_box, 0, wxTOP | wxRIGHT | wxLEFT | wxEXPAND, FromDIP(10)); + + v_sizer->AddSpacer(FromDIP(5)); + + auto dlg_btns = new DialogButtons(this, {"OK"}); + + auto bottom_sizer = new wxBoxSizer(wxHORIZONTAL); + auto wiki = new HyperLink(this, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/calibration/flow-ratio-calib"); + bottom_sizer->Add(wiki, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20)); + bottom_sizer->AddStretchSpacer(); + bottom_sizer->Add(dlg_btns, 0, wxEXPAND); + v_sizer->Add(bottom_sizer, 0, wxEXPAND); + + dlg_btns->GetOK()->Bind(wxEVT_BUTTON, &FlowRateCalibrationDialog::on_start, this); + + wxGetApp().UpdateDlgDarkUI(this); + + Layout(); + Fit(); +} + +FlowRateCalibrationDialog::~FlowRateCalibrationDialog() { + // Disconnect Events +} + +void FlowRateCalibrationDialog::on_start(wxCommandEvent& event) { + int type = m_rbType->GetSelection(); + int patternIdx = m_rbPattern->GetSelection(); + + InfillPattern pattern = ipArchimedeanChords; + if (patternIdx == 1) pattern = ipMonotonic; + + bool is_linear = (type >= 2); + int pass = (type % 2) + 1; + + m_plater->calib_flowrate(is_linear, pass, pattern); + EndModal(wxID_OK); +} + +void FlowRateCalibrationDialog::on_dpi_changed(const wxRect& suggested_rect) { + this->Refresh(); + Fit(); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/calib_dlg.hpp b/src/slic3r/GUI/calib_dlg.hpp index 759cde1d0d..5ff0fbf3e3 100644 --- a/src/slic3r/GUI/calib_dlg.hpp +++ b/src/slic3r/GUI/calib_dlg.hpp @@ -180,5 +180,21 @@ protected: TextInput* m_tiJDEnd; Plater* m_plater; }; + +class FlowRateCalibrationDialog : public DPIDialog +{ +public: + FlowRateCalibrationDialog(wxWindow* parent, wxWindowID id, Plater* plater); + ~FlowRateCalibrationDialog(); + void on_dpi_changed(const wxRect& suggested_rect) override; + +protected: + virtual void on_start(wxCommandEvent& event); + + RadioGroup* m_rbType; + // ORCA: use standard OrcaSlicer ComboBox instead of BitmapComboBox + ComboBox* m_rbPattern; + Plater* m_plater; +}; }} // namespace Slic3r::GUI #endif From 6e30443c62a66efca70a71a3d73c4c92de88d60c Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Fri, 24 Apr 2026 22:29:26 +0800 Subject: [PATCH 41/42] Fix issue that filament combobox not working after adding extruder (#13194) --- src/libslic3r/PresetBundle.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7c6522310c..bb004ba1e2 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2197,16 +2197,32 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector ne filament_presets.resize(n); } ConfigOptionStrings* filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); + ConfigOptionStrings* filament_color_type = project_config.option("filament_colour_type"); + ConfigOptionInts* filament_map = project_config.option("filament_map"); + + filament_color->resize(n); + // Sync filament multi colour + filament_multi_color->values.resize(n); + for (size_t i = 0; i < n; i++) { + filament_multi_color->values[i] = filament_color->values[i]; + } + filament_color_type->resize(n); + filament_map->values.resize(n, 1); ams_multi_color_filment.resize(n); + // BBS set new filament color to new_color if (old_filament_count < n) { if (!new_colors.empty()) { for (int i = old_filament_count; i < n; i++) { filament_color->values[i] = new_colors[i - old_filament_count]; + filament_multi_color->values[i] = new_colors[i - old_filament_count]; + filament_color_type->values[i] = "1"; // default color type } } } + update_multi_material_filament_presets(); } void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) From 37de679533ab6b25400f457b4e2fa369461b98f9 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 25 Apr 2026 00:11:23 +0800 Subject: [PATCH 42/42] Fix crash on launch caused by ref of tmp object (#13341) --- src/slic3r/GUI/GUI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 78d9c9839b..481f335d39 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -80,7 +80,8 @@ void break_to_debugger() const std::string& shortkey_shift_prefix() { - return _u8L("Shift+"); + static const std::string str = _u8L("Shift+"); + return str; } const std::string& shortkey_ctrl_prefix()