From b04c4bbf556e59f3bb688c6da378aa081867b9dd Mon Sep 17 00:00:00 2001 From: cjw Date: Sun, 23 Feb 2025 17:11:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=85=8D=E7=BD=AE=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=A1=86=E5=88=B7=E6=96=B0=E3=80=82=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E4=BF=AE=E6=94=B9=E5=B0=9D=E8=AF=95=E3=80=82?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=9B=B4=E5=A4=9A=E5=8F=82=E6=95=B0=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/i18n/zh_cn/BambuStudio.mo | Bin 332580 -> 332848 bytes resources/images/back123.png | Bin 0 -> 74399 bytes resources/images/config_title.svg | 79 + resources/images/dalian_logo.svg | 1 + resources/images/ui_title.svg | 122 + resources/images/version_title.svg | 43 + src/libslic3r/PresetBundle.cpp | 36 +- src/libslic3r/PresetBundle.hpp | 1 + src/slic3r/GUI/CreatePresetsDialog.cpp | 5081 +++++++++++++++++++++++ src/slic3r/GUI/GCodeViewer.cpp | 1529 +++---- src/slic3r/GUI/ParamsPanel.cpp | 20 +- src/slic3r/GUI/Plater.cpp | 12 + src/slic3r/GUI/Tab.cpp | 717 ++-- src/slic3r/GUI/Widgets/Button.cpp | 336 ++ src/slic3r/GUI/Widgets/SwitchButton.cpp | 157 + 15 files changed, 7106 insertions(+), 1028 deletions(-) create mode 100644 resources/images/back123.png create mode 100644 resources/images/config_title.svg create mode 100644 resources/images/dalian_logo.svg create mode 100644 resources/images/ui_title.svg create mode 100644 resources/images/version_title.svg create mode 100644 src/slic3r/GUI/CreatePresetsDialog.cpp create mode 100644 src/slic3r/GUI/Widgets/Button.cpp create mode 100644 src/slic3r/GUI/Widgets/SwitchButton.cpp diff --git a/resources/i18n/zh_cn/BambuStudio.mo b/resources/i18n/zh_cn/BambuStudio.mo index d05f269dde5560801e616fdd7bc881f1c73866b4..a83927ce7252b49aa29d0d2f311dd711e47809b4 100644 GIT binary patch delta 72181 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≻h)wcZEk14iUej^8q<6S@g?jXy$N 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 R?}jJ4w+GdJ;C(jx{{xl;Z5{vs delta 71962 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

t> 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 ae8HPeg)LbrI81!+yeETOJoG-3`6{dc diff --git a/resources/images/back123.png b/resources/images/back123.png new file mode 100644 index 0000000000000000000000000000000000000000..8bad283879110e0582705eae73f7045c640bf5dc GIT binary patch literal 74399 zcmeI5&yO4B8OLV{B!nL&0t5(%7)=1Bz}PeX?E#a7^};T~X_~A=DlQq%jCZZ9J;pPM zy+McwkP4MrwQ3I>dWb-X69_Jls-onO3U2ft04^YqKtx3XRH>Evj_oy%Vb_}r(nk8M zwCmU7XWn_<&-;15dxdOcWb zwkyVkzrSbbO}}D1p08Q8&TO#UoIlkKj+{EU=$$(5m3?F1gZcw~M@`TQVps3CR@#x% zuNZ@Q9W_oarwx5j5+AP^2a*TsM{9@l*{~bv`BcI5>|9ANmQ!{iXJrfb=^4u|r>#=j z&YG6(SOq6v)JI>2dRo_SIESkbkDjI8RgC30?l|dmuh&cUvZ=7Ul(x%dHAg0$$(X9b zj8@yR+c(?Mp0ydOLF9Ftowymc^<+l39-fFRhU#>M5gs*PS?8TU-&6 zC1|O#NHu1UAJth7<1ktdH(1B`5w?|r=4_QPKE z%cP^x4MEk7gNl){GI`U=m{wuYDm&S-leh1+N{(d>OKZvl-;LdiD-Qi;WA$Q6Yc*$~ z9mQ_j3l^#sLp7CZHhrh)HX04z56rS%w#-I3XPG6>E|_j1>seklTP~FCL0{_rYUrIv z@Fn*Twe>?!%`qnA1jTYO@7Hr`wp`Y98#&Lcm+ZV*^0T>8-pT}-Vz4&buewcja@-X; zYtogkX3Pejuf!~vg`8b7gL1BH*4<*!ECr3apZ79(Kkpj4?>UXI+j5mB&6c|qq&w{; zL;sEN7)>H#o4Q zwq#Qa(;GD(xhJQ$esPBUjIEEbddqHmDNqTUHj>4plPnr;O8Uq9HTX>A)>^p=!(=#4 zVfAL5<3U?Z-Bl^QXWu{nrp?^GxKKT?*#GNSAK5dfE$#fdXP^1-!<(*-&mP&eWA*LU z{e#~E8~^zHf16dD{q2mi@%_msySByC3-s+@OfT4f&A(=*R-C@&%(kfon!SI=^n&{7 zUraAJukD^*@B$!)fB-QP1rUIk%nXdc2#lB{fDw~C$Ow$Uh)Dt%G0B6BzzB?(B!Cf< zJje))z=%l#7%|C%jKBztm?VG^lRU@>jKGLV0vIvLgN(okjF=>_)r`2~sos4_Pu2&Y zocd(%qj#>`?dam^otyoO3kn9P05voQ4saM{2j@b7;9PJngbo@$-~&EL0{9?lkPrBP z50U^rNE+k=KH!5SfDe)e`G61jAPL}uq(MI513pLs_#kPJ5BPu&k^nwP8sx(k^r3#? z4_72@ZU4^`7jBz-@p8Fga{Sn4f8&CJAuc$;frBFn;De+=KHvjBNCNmEX^;>2fDe)Y zK1dqm13ut`B!CZ+2Kj&w_#g@3gQP(|-~&EL0{9?lkPrBP50U^rNE+k=KH!5Sur++$RmOe_<#?R06s_> z2fDe)YK1dqm13ut`B!CZ+2Kj&w z_#g@3gQP(|TmnAm-+g^FY4g}0-~09-zy8boXamn({>5mt!3zaLFmQl_JR2fDe)YK1dqm13ut`B!CZ+2Kj&w_#g@3gQP(|-~&EL0{9?l zkPrBP50U^rNE+nBrQ*Z+k9S|4^m@%V?|fYR-Ss=w@b3LPhTU)UlY$``IKV-k5q!W0 ze2@h2LDC=}@Btqr0ep}&$OnAD2T1@QBn|QbAMimEzz0c#e82~MkOc5S(jXu30Usm* ze2_HA2YkQ>NdO-t4f5fV^5M1h|7eHYQ8Bh=9Pw_QAq7JyaDan69{7L{_#g@3gQP(| o-~&EL0{9?lkPnxT4{vIpeRlIbw?1|DSkm;u+`;M_4;_2<|MwR6UjP6A literal 0 HcmV?d00001 diff --git a/resources/images/config_title.svg b/resources/images/config_title.svg new file mode 100644 index 000000000..989e2ceb5 --- /dev/null +++ b/resources/images/config_title.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + diff --git a/resources/images/dalian_logo.svg b/resources/images/dalian_logo.svg new file mode 100644 index 000000000..faf1886be --- /dev/null +++ b/resources/images/dalian_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/ui_title.svg b/resources/images/ui_title.svg new file mode 100644 index 000000000..a727bb013 --- /dev/null +++ b/resources/images/ui_title.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/version_title.svg b/resources/images/version_title.svg new file mode 100644 index 000000000..74139d7a2 --- /dev/null +++ b/resources/images/version_title.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 50301422e..7f755bbe6 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -391,6 +391,9 @@ std::vector PresetBundle::get_current_project_embedded_presets() auto printer_presets = this->printers.get_project_embedded_presets(); if (!printer_presets.empty()) std::copy(printer_presets.begin(), printer_presets.end(), std::back_inserter(project_presets)); + auto config_presets = this->configs.get_project_embedded_presets(); + if (!config_presets.empty()) + std::copy(config_presets.begin(), config_presets.end(), std::back_inserter(project_presets)); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, returned project_presets count %1%")%project_presets.size(); return project_presets; @@ -409,6 +412,7 @@ void PresetBundle::reset_project_embedded_presets() bool printer_reselect = this->printers.reset_project_embedded_presets(); bool filament_reselect = this->filaments.reset_project_embedded_presets(); bool print_reselect = this->prints.reset_project_embedded_presets(); + bool config_reselect = this->configs.reset_project_embedded_presets(); if (printer_reselect) { if (!prefer_printer.empty()) @@ -419,7 +423,7 @@ void PresetBundle::reset_project_embedded_presets() //this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); } - else if (filament_reselect || print_reselect) { + else if (filament_reselect || print_reselect || config_reselect) { //Preset& current_printer = this->printers.get_selected_preset(); /*if (filament_reselect) { const std::vector &prefered_filament_profiles = current_printer.config.option("default_filament_profile")->values; @@ -577,8 +581,7 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For std::string config_selected_preset_name = configs.get_selected_preset().name; this->configs.load_presets(dir_user_presets, PRESET_CONFIG_NAME, substitutions, substitution_rule); configs.select_preset_by_name(config_selected_preset_name, false); - } - catch (const std::runtime_error& err) { + }catch (const std::runtime_error& err) { errors_cummulative += err.what(); } if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); @@ -1396,15 +1399,18 @@ std::vector PresetBundle::merge_presets(PresetBundle &&other) std::vector duplicate_filaments = this->filaments .merge_presets(std::move(other.filaments), this->vendors); std::vector duplicate_sla_materials = this->sla_materials.merge_presets(std::move(other.sla_materials), this->vendors); std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); + std::vector duplicate_configs = this->configs .merge_presets(std::move(other.configs), this->vendors); append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); append(this->obsolete_presets.sla_prints, std::move(other.obsolete_presets.sla_prints)); append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); append(this->obsolete_presets.sla_materials, std::move(other.obsolete_presets.sla_materials)); append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); + append(this->obsolete_presets.configs, std::move(other.obsolete_presets.configs)); append(duplicate_prints, std::move(duplicate_sla_prints)); append(duplicate_prints, std::move(duplicate_filaments)); append(duplicate_prints, std::move(duplicate_sla_materials)); append(duplicate_prints, std::move(duplicate_printers)); + append(duplicate_configs, std::move(duplicate_configs)); return duplicate_prints; } @@ -1415,11 +1421,14 @@ void PresetBundle::update_system_maps() this->filaments .update_map_system_profile_renamed(); this->sla_materials.update_map_system_profile_renamed(); this->printers .update_map_system_profile_renamed(); + //xiamian+ + this->configs .update_map_system_profile_renamed(); this->prints .update_map_alias_to_profile_name(); this->sla_prints .update_map_alias_to_profile_name(); this->filaments .update_map_alias_to_profile_name(); this->sla_materials.update_map_alias_to_profile_name(); + this->configs .update_map_alias_to_profile_name(); } static inline std::string remove_ini_suffix(const std::string &name) @@ -1439,6 +1448,12 @@ void PresetBundle::load_installed_printers(const AppConfig &config) for (auto &preset : printers) preset.set_visible_from_appconfig(config); } +void PresetBundle::load_installed_configs(const AppConfig& config) +{ + this->update_system_maps(); + for (auto& preset : configs) + preset.set_visible_from_appconfig(config); +} const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const { @@ -1449,6 +1464,7 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : preset_type == Preset::TYPE_FILAMENT ? filaments : + preset_type == Preset::TYPE_CONFIG ? configs : sla_materials; return presets.get_preset_name_by_alias(alias); @@ -1480,10 +1496,13 @@ const int PresetBundle::get_required_hrc_by_filament_type(const std::string& fil void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options, bool save_to_project) { - PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : - type == Preset::TYPE_SLA_PRINT ? sla_prints : - type == Preset::TYPE_FILAMENT ? filaments : - type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; + PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : + type == Preset::TYPE_SLA_PRINT ? sla_prints : + type == Preset::TYPE_FILAMENT ? filaments : + type == Preset::TYPE_SLA_MATERIAL ? sla_materials : + type == Preset::TYPE_CONFIG ? configs : + printers; + // if we want to save just some from selected options if (!unselected_options.empty()) { @@ -1589,6 +1608,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": enter, preferred printer_model_id %1%")%preferred_selection.printer_model_id; // Update visibility of presets based on application vendor / model / variant configuration. this->load_installed_printers(config); + this->load_installed_configs(config); // Update visibility of filament and sla material presets this->load_installed_filaments(config); @@ -1631,6 +1651,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p filaments.select_preset_by_name_strict(initial_filament_profile_name); sla_prints.select_preset_by_name_strict(initial_sla_print_profile_name); sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); + configs.select_preset_by_name_strict(initial_config_profile_name); // Load the names of the other filament profiles selected for a multi-material printer. // Load it even if the current printer technology is SLA. @@ -4043,6 +4064,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) sla_prints.set_default_suppressed(default_suppressed); sla_materials.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed); + configs.set_default_suppressed(default_suppressed); } } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index a26c58121..a41dcca5e 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -227,6 +227,7 @@ public: // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. void load_installed_printers(const AppConfig &config); + void load_installed_configs(const AppConfig &config); const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const; diff --git a/src/slic3r/GUI/CreatePresetsDialog.cpp b/src/slic3r/GUI/CreatePresetsDialog.cpp new file mode 100644 index 000000000..27034b1de --- /dev/null +++ b/src/slic3r/GUI/CreatePresetsDialog.cpp @@ -0,0 +1,5081 @@ +#include "CreatePresetsDialog.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libslic3r/PresetBundle.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MsgDialog.hpp" +#include "FileHelp.hpp" +#include "Tab.hpp" +#include "MainFrame.hpp" + +#define NAME_OPTION_COMBOBOX_SIZE wxSize(FromDIP(200), FromDIP(24)) +#define FILAMENT_PRESET_COMBOBOX_SIZE wxSize(FromDIP(300), FromDIP(24)) +#define OPTION_SIZE wxSize(FromDIP(100), FromDIP(24)) +#define PRINTER_LIST_SIZE wxSize(-1, FromDIP(100)) +#define FILAMENT_LIST_SIZE wxSize(FromDIP(560), FromDIP(100)) +#define FILAMENT_OPTION_SIZE wxSize(FromDIP(-1), FromDIP(30)) +#define PRESET_TEMPLATE_SIZE wxSize(FromDIP(-1), FromDIP(100)) +#define PRINTER_SPACE_SIZE wxSize(FromDIP(80), FromDIP(24)) +#define ORIGIN_TEXT_SIZE wxSize(FromDIP(10), FromDIP(24)) +#define PRINTER_PRESET_VENDOR_SIZE wxSize(FromDIP(150), FromDIP(24)) +#define PRINTER_PRESET_MODEL_SIZE wxSize(FromDIP(280), FromDIP(24)) +#define STATIC_TEXT_COLOUR wxColour("#363636") +#define PRINTER_LIST_COLOUR wxColour("#EEEEEE") +#define FILAMENT_OPTION_COLOUR wxColour("#D9D9D9") +//#define SELECT_ALL_OPTION_COLOUR wxColour("#00AE42") +#define SELECT_ALL_OPTION_COLOUR wxColour("#009FF3") +#define DEFAULT_PROMPT_TEXT_COLOUR wxColour("#ACACAC") + +namespace Slic3r { +namespace GUI { + +static const std::vector filament_vendors = {"Polymaker", "OVERTURE", "Kexcelled", "HATCHBOX", "eSUN", "SUNLU", "Prusament", "Creality", "Protopasta", + "Anycubic", "Basf", "ELEGOO", "INLAND", "FLASHFORGE", "AMOLEN", "MIKA3D", "3DXTECH", "Duramic", + "Priline", "Eryone", "3Dgunius", "Novamaker", "Justmaker", "Giantarm", "iProspect"}; + +static const std::vector filament_types = {"PLA", "PLA+", "PLA Tough", "PETG", "ABS", "ASA", "FLEX", "HIPS", "PA", "PACF", + "NYLON", "PVA", "PC", "PCABS", "PCTG", "PCCF", "PP", "PEI", "PET", "PETG", + "PETGCF", "PTBA", "PTBA90A", "PEEK", "TPU93A", "TPU75D", "TPU", "TPU92A", "TPU98A", "Misc", + "TPE", "GLAZE", "Nylon", "CPE", "METAL", "ABST", "Carbon Fiber"}; + +static const std::vector printer_vendors = {"Anycubic", "Artillery", "BIBO", "BIQU", "Creality ENDER", "Creality CR", "Creality SERMOON", + "FLSun", "gCreate", "Geeetech", "INAT", "Infinity3D", "Jubilee", "LNL3D", + "LulzBot", "MakerGear", "Original Prusa", "Papapiu", "Print4Taste", "RatRig", "Rigid3D", + "Snapmaker", "Sovol", "TriLAB", "Trimaker", "Ultimaker", "Voron", "Zonestar"}; + +static const std::unordered_map> printer_model_map = + {{"Anycubic", {"Kossel Linear Plus", "Kossel Pulley(Linear)", "Mega Zero", "i3 Mega", "Predator"}}, + {"Artillery", {"sidewinder X1", "Genius", "Hornet"}}, + {"BIBO", {"BIBO2 Touch"}}, + {"BIQU", {"BX"}}, + {"Creality ENDER", {"Ender-3", "Ender-3 BLTouch", "Ender-3 Pro", "Ender-3 Neo", + "Ender-3 V2 Neo", "Ender-3 S1 Plus", "Ender-3 Max", "Ender-3 Max Neo", + "Ender-4", "Ender-5 Pro", "Ender-5 Pro", + "Ender-7", "Ender-2", "Ender-2 Pro"}}, + {"Creality CR", {"CR-5 Pro", "CR-5 Pro H", "CR-10 SMART", "CR-10 SMART Pro", "CR-10 Mini", + "CR-10", "CR-10 v3", "CR-10 S", "CR-10 v2", "CR-10 v2", + "CR-10 S Pro", "CR-10 S Pro v2", "CR-10 S4", "CR-10 S5", "CR-20", "CR-20 Pro", "CR-200B", + "CR-8"}}, + {"Creality SERMOON",{"Sermoon-D1", "Sermoon-V1", "Sermoon-V1 Pro"}}, + {"FLSun", {"FLSun QQs Pro", "FLSun Q5"}}, + {"gCreate", {"gMax 1.5XT Plus", "gMax 2", "gMax 2 Pro", "gMax 2 Dual 2in1", "gMax 2 Dual Chimera"}}, + {"Geeetech", {"Thunder", "Thunder Pro", "Mizar s", "Mizar Pro", "Mizar", "Mizar Max", + "Mizar M", "A10 Pro", "A10 M", "A10 T", "A20", "A20 M", + "A20T", "A30 Pro", "A30 M", "A30 T", "E180", "Me Ducer", + "Me creator", "Me Creator2", "GiantArmD200", "l3 ProB", "l3 Prow", "l3 ProC"}}, + {"INAT", {"Proton X Rail", "Proton x Rod", "Proton XE-750"}}, + {"Infinity3D", {"DEV-200", "DEV-350"}}, + {"Jubilee", {"Jubilee"}}, + {"LNL3D", {"D3 v2", "D3 Vulcan", "D5", "D6"}}, + {"LulzBot", {"Mini Aero", "Taz6 Aero"}}, + {"MakerGear", {"Micro", "M2(V4 Hotend)", "M2 Dual", "M3-single Extruder", "M3-Independent Dual Rev.0", "M3-Independent Dual Rev.0(Duplication Mode)", + "M3-Independent Dual Rev.1", "M3-Independent Dual Rev.1(Duplication Mode)", "ultra One", "Ultra One (DuplicationMode)"}}, + {"Original Prusa", {"MK4", "SL1S SPEED", "MMU3"}}, + {"Papapiu", {"N1s"}}, + {"Print4Taste", {"mycusini 2.0"}}, + {"RatRig", {"V-core-3 300mm", "V-Core-3 400mm", "V-Core-3 500mm", "V-Minion"}}, + {"Rigid3D", {"Zero2", "Zero3"}}, + {"Snapmaker", {"A250", "A350"}}, + {"Sovol", {"SV06", "SV06 PLUS", "SV05", "SV04", "SV03 / SV03 BLTOUCH", "SVO2 / SV02 BLTOUCH", "SVO1 / SV01 BLToUCH", "SV01 PRO"}}, + {"TriLAB", {"AzteQ Industrial","AzteQ Dynamic", "DeltiQ 2", "DeltiQ 2 Plus", "DeltiQ 2 + FlexPrint 2", "DeltiQ 2 Plus + FlexPrint 2", "DeltiQ 2 +FlexPrint", + "DeltiQ 2 Plus + FlexPrint", "DeltiQ M", "DeltiQ L", "DeltiQ XL"}}, + {"Trimaker", {"Nebula cloud", "Nebula", "Cosmos ll"}}, + {"Ultimaker", {"Ultimaker 2"}}, + {"Voron", {"v2 250mm3", "v2 300mm3", "v2 350mm3", "v1 250mm3", "v1 300mm3", "v1 350mm3", + "Zero 120mm3", "Switchwire"}}, + {"Zonestar", {"Z5", "Z6", "Z5x", "Z8", "Z9"}}}; + +static std::vector nozzle_diameter_vec = {"0.4", "0.2", "0.25", "0.3", "0.35", "0.5", "0.6", "0.75", "0.8", "1.0", "1.2"}; +static std::unordered_map nozzle_diameter_map = {{"0.2", 0.2}, {"0.25", 0.25}, {"0.3", 0.3}, {"0.35", 0.35}, + {"0.4", 0.4}, {"0.5", 0.5}, {"0.6", 0.6}, {"0.75", 0.75}, + {"0.8", 0.8}, {"1.0", 1.0}, {"1.2", 1.2}}; + +static std::set cannot_input_key = {9, 10, 13, 33, 35, 36, 37, 38, 40, 41, 42, 44, 46, 47, 59, 60, 62, 63, 64, 92, 94, 95, 124, 126}; + +static std::set special_key = {'\n', '\t', '\r', '\v', '@', ';'}; + +static std::string remove_special_key(const std::string &str) +{ + std::string res_str; + for (char c : str) { + if (special_key.find(c) == special_key.end()) { + res_str.push_back(c); + } + } + return res_str; +} + +static bool str_is_all_digit(const std::string &str) { + for (const char &c : str) { + if (!std::isdigit(c)) return false; + } + return true; +} + +static bool delete_filament_preset_by_name(std::string delete_preset_name, std::string &selected_preset_name) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("select preset, name %1%") % delete_preset_name; + if (delete_preset_name.empty()) return false; + + // Find an alternate preset to be selected after the current preset is deleted. + PresetCollection &m_presets = wxGetApp().preset_bundle->filaments; + if (delete_preset_name == selected_preset_name) { + const std::deque &presets = m_presets.get_presets(); + size_t idx_current = m_presets.get_idx_selected(); + + // Find the visible preset. + size_t idx_new = idx_current; + if (idx_current > presets.size()) idx_current = presets.size(); + if (idx_current < 0) idx_current = 0; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && (presets[idx_new].name == delete_preset_name || !presets[idx_new].is_visible); ++idx_new) + ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && (presets[idx_new].name == delete_preset_name || !presets[idx_new].is_visible); --idx_new) + ; + selected_preset_name = presets[idx_new].name; + BOOST_LOG_TRIVIAL(info) << boost::format("cause by delete current ,choose the next visible, idx %1%, name %2%") % idx_new % selected_preset_name; + } + + try { + // BBS delete preset + Preset *need_delete_preset = m_presets.find_preset(delete_preset_name); + if (!need_delete_preset) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't find delete preset and name: %1%") % delete_preset_name; + if (!need_delete_preset->setting_id.empty()) { + BOOST_LOG_TRIVIAL(info) << "delete preset = " << need_delete_preset->name << ", setting_id = " << need_delete_preset->setting_id; + m_presets.set_sync_info_and_save(need_delete_preset->name, need_delete_preset->setting_id, "delete", 0); + wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't preset setting id is empty and name: %1%") % delete_preset_name; + } + if (m_presets.get_edited_preset().name == delete_preset_name) { + m_presets.discard_current_changes(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("delete preset dirty and cancelled"); + } + m_presets.delete_preset(need_delete_preset->name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " preset has been delete from filaments, and preset name is: " << delete_preset_name; + } catch (const std::exception &ex) { + // FIXME add some error reporting! + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("found exception when delete: %1% and preset name: %%") % ex.what() % delete_preset_name; + return false; + } + + return true; +} + +static std::string get_curr_time() +{ + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + + std::time_t time = std::chrono::system_clock::to_time_t(now); + + std::tm local_time = *std::localtime(&time); + std::ostringstream time_stream; + time_stream << std::put_time(&local_time, "%Y_%m_%d_%H_%M_%S"); + + std::string current_time = time_stream.str(); + return current_time; +} + +static std::string get_curr_timestmp() +{ + std::time_t currentTime = std::time(nullptr); + std::ostringstream oss; + oss << currentTime; + std::string timestampString = oss.str(); + return timestampString; +} + +static void get_filament_compatible_printer(Preset* preset, vector& printers) +{ + auto compatible_printers = dynamic_cast(preset->config.option("compatible_printers")); + if (compatible_printers == nullptr) return; + for (const std::string &printer_name : compatible_printers->values) { + printers.push_back(printer_name); + } +} + +static wxBoxSizer* create_checkbox(wxWindow* parent, Preset* preset, wxString& preset_name, std::vector>& preset_checkbox) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + ::CheckBox * checkbox = new ::CheckBox(parent); + sizer->Add(checkbox, 0, 0, 0); + preset_checkbox.push_back(std::make_pair(checkbox, preset)); + wxStaticText *preset_name_str = new wxStaticText(parent, wxID_ANY, preset_name); + wxToolTip * toolTip = new wxToolTip(preset_name); + preset_name_str->SetToolTip(toolTip); + sizer->Add(preset_name_str, 0, wxLEFT, 5); + return sizer; +} + +static wxBoxSizer *create_checkbox(wxWindow *parent, std::string &compatible_printer, Preset* preset, std::unordered_map<::CheckBox *, std::pair> &ptinter_compatible_filament_preset) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + ::CheckBox *checkbox = new ::CheckBox(parent); + sizer->Add(checkbox, 0, 0, 0); + ptinter_compatible_filament_preset[checkbox] = std::make_pair(compatible_printer, preset); + wxStaticText *preset_name_str = new wxStaticText(parent, wxID_ANY, wxString::FromUTF8(compatible_printer)); + sizer->Add(preset_name_str, 0, wxLEFT, 5); + return sizer; +} + +static wxBoxSizer *create_checkbox(wxWindow *parent, wxString &preset_name, std::vector> &preset_checkbox) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + ::CheckBox *checkbox = new ::CheckBox(parent); + sizer->Add(checkbox, 0, 0, 0); + preset_checkbox.push_back(std::make_pair(checkbox, into_u8(preset_name))); + wxStaticText *preset_name_str = new wxStaticText(parent, wxID_ANY, preset_name); + sizer->Add(preset_name_str, 0, wxLEFT, 5); + return sizer; +} + +static wxArrayString get_exist_vendor_choices(VendorMap& vendors) +{ + wxArrayString choices; + PresetBundle temp_preset_bundle; + temp_preset_bundle.load_system_models_from_json(ForwardCompatibilitySubstitutionRule::EnableSystemSilent); + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + + VendorProfile users_models = preset_bundle->get_custom_vendor_models(); + + vendors = temp_preset_bundle.vendors; + + if (!users_models.models.empty()) { + vendors[users_models.name] = users_models; + } + + for (const pair &vendor : vendors) { + if (vendor.second.models.empty() || vendor.second.id.empty()) continue; + choices.Add(vendor.first); + } + return choices; +} + +static std::string get_machine_name(const std::string &preset_name) +{ + size_t index_at = preset_name.find_last_of("@"); + if (std::string::npos == index_at) { + return ""; + } else { + return preset_name.substr(index_at + 1); + } +} + +static std::string get_filament_name(std::string &preset_name) +{ + size_t index_at = preset_name.find_last_of("@"); + if (std::string::npos == index_at) { + return preset_name; + } else { + return preset_name.substr(0, index_at - 1); + } +} + +static wxBoxSizer *create_preset_tree(wxWindow *parent, std::pair>> printer_and_preset) +{ + wxTreeCtrl *treeCtrl = new wxTreeCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxNO_BORDER); + wxColour backgroundColor = parent->GetBackgroundColour(); + treeCtrl->SetBackgroundColour(backgroundColor); + + wxString printer_name = wxString::FromUTF8(printer_and_preset.first); + wxTreeItemId rootId = treeCtrl->AddRoot(printer_name); + int row = 1; + for (std::shared_ptr preset : printer_and_preset.second) { + wxString preset_name = wxString::FromUTF8(preset->name); + wxTreeItemId childId1 = treeCtrl->AppendItem(rootId, preset_name); + row++; + } + + treeCtrl->Expand(rootId); + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + treeCtrl->SetMinSize(wxSize(-1, row * 22)); + treeCtrl->SetMaxSize(wxSize(-1, row * 22)); + sizer->Add(treeCtrl, 0, wxEXPAND | wxALL, 0); + + return sizer; +} + +static std::string get_vendor_name(std::string& preset_name) +{ + if (preset_name.empty()) return ""; + std::string vendor_name = preset_name.substr(preset_name.find_first_not_of(' ')); //remove the name prefix space + size_t index_at = vendor_name.find(" "); + if (std::string::npos == index_at) { + return vendor_name; + } else { + vendor_name = vendor_name.substr(0, index_at); + return vendor_name; + } +} + +static wxBoxSizer *create_select_filament_preset_checkbox(wxWindow * parent, + std::string & compatible_printer, + std::vector presets, + std::unordered_map<::CheckBox *, std::pair> &machine_filament_preset) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *checkbox_sizer = new wxBoxSizer(wxVERTICAL); + ::CheckBox *checkbox = new ::CheckBox(parent); + checkbox_sizer->Add(checkbox, 0, wxEXPAND | wxRIGHT, 5); + + wxBoxSizer *combobox_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *machine_name_str = new wxStaticText(parent, wxID_ANY, wxString::FromUTF8(compatible_printer)); + ComboBox * combobox = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, 24), 0, nullptr, wxCB_READONLY); + combobox->SetBackgroundColor(PRINTER_LIST_COLOUR); + combobox->SetBorderColor(*wxWHITE); + combobox->SetLabel(_L("Select filament preset")); + combobox->Bind(wxEVT_COMBOBOX, [combobox, checkbox, presets, &machine_filament_preset, compatible_printer](wxCommandEvent &e) { + combobox->SetLabelColor(*wxBLACK); + wxString preset_name = combobox->GetStringSelection(); + checkbox->SetValue(true); + for (Preset *preset : presets) { + if (preset_name == wxString::FromUTF8(preset->name)) { + machine_filament_preset[checkbox] = std::make_pair(compatible_printer, preset); + } + } + e.Skip(); + }); + combobox_sizer->Add(machine_name_str, 0, wxEXPAND, 0); + combobox_sizer->Add(combobox, 0, wxEXPAND | wxTOP, 5); + + wxArrayString choices; + for (Preset *preset : presets) { + choices.Add(wxString::FromUTF8(preset->name)); + } + combobox->Set(choices); + + horizontal_sizer->Add(checkbox_sizer); + horizontal_sizer->Add(combobox_sizer); + return horizontal_sizer; +} + +static wxString get_curr_radio_type(std::vector> &radio_btns) +{ + for (std::pair radio_string : radio_btns) { + if (radio_string.first->GetValue()) { + return radio_string.second; + } + } + return ""; +} + +static std::string calculate_md5(const std::string &input) +{ + unsigned char digest[MD5_DIGEST_LENGTH]; + std::string md5; + + EVP_MD_CTX *mdContext = EVP_MD_CTX_new(); + EVP_DigestInit(mdContext, EVP_md5()); + EVP_DigestUpdate(mdContext, input.c_str(), input.length()); + EVP_DigestFinal(mdContext, digest, nullptr); + EVP_MD_CTX_free(mdContext); + + char hexDigest[MD5_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) { sprintf(hexDigest + (i * 2), "%02x", digest[i]); } + hexDigest[MD5_DIGEST_LENGTH * 2] = '\0'; + + md5 = std::string(hexDigest); + return md5; +} + +static std::string get_filament_id(std::string vendor_typr_serial) +{ + std::unordered_map> filament_id_to_filament_name; + + // temp filament presets + PresetBundle temp_preset_bundle; + temp_preset_bundle.load_system_filaments_json(Slic3r::ForwardCompatibilitySubstitutionRule::EnableSilent); + std::string dir_user_presets = wxGetApp().app_config->get("preset_folder"); + if (dir_user_presets.empty()) { + temp_preset_bundle.load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::EnableSilent); + } else { + temp_preset_bundle.load_user_presets(dir_user_presets, ForwardCompatibilitySubstitutionRule::EnableSilent); + } + const std::deque &filament_presets = temp_preset_bundle.filaments.get_presets(); + + for (const Preset &preset : filament_presets) { + std::string preset_name = preset.name; + size_t index_at = preset_name.find_first_of('@'); + if (index_at == std::string::npos) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament preset name has no @ and name is: " << preset_name; + continue; + } + std::string filament_name = preset_name.substr(0, index_at - 1); + if (filament_name == vendor_typr_serial && preset.filament_id != "null") + return preset.filament_id; + filament_id_to_filament_name[preset.filament_id].insert(filament_name); + } + // global filament presets + PresetBundle * preset_bundle = wxGetApp().preset_bundle; + std::map> temp_filament_id_to_presets = preset_bundle->filaments.get_filament_presets(); + for (std::pair> filament_id_to_presets : temp_filament_id_to_presets) { + if (filament_id_to_presets.first.empty()) continue; + for (const Preset *preset : filament_id_to_presets.second) { + std::string preset_name = preset->name; + size_t index_at = preset_name.find_first_of('@'); + if (index_at == std::string::npos) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament preset name has no @ and name is: " << preset_name; + continue; + } + std::string filament_name = preset_name.substr(0, index_at - 1); + if (filament_name == vendor_typr_serial && preset->filament_id != "null") + return preset->filament_id; + filament_id_to_filament_name[preset->filament_id].insert(filament_name); + } + } + + std::string user_filament_id = "P" + calculate_md5(vendor_typr_serial).substr(0, 7); + + while (filament_id_to_filament_name.find(user_filament_id) != filament_id_to_filament_name.end()) {//find same filament id + bool have_same_filament_name = false; + for (const std::string &name : filament_id_to_filament_name.find(user_filament_id)->second) { + if (name == vendor_typr_serial) { + have_same_filament_name = true; + break; + } + } + if (have_same_filament_name) { + break; + } + else { //Different names correspond to the same filament id + user_filament_id = "P" + calculate_md5(vendor_typr_serial + get_curr_time()).substr(0, 7); + } + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament name is: " << vendor_typr_serial << "and create filament_id is: " << user_filament_id; + return user_filament_id; +} + +static json get_config_json(const Preset* preset) { + json j; + // record the headers + j[BBL_JSON_KEY_VERSION] = preset->version.to_string(); + j[BBL_JSON_KEY_NAME] = preset->name; + j[BBL_JSON_KEY_FROM] = ""; + + DynamicPrintConfig config = preset->config; + + // record all the key-values + for (const std::string &opt_key : config.keys()) { + const ConfigOption *opt = config.option(opt_key); + if (opt->is_scalar()) { + if (opt->type() == coString) + // keep \n, \r, \t + j[opt_key] = (dynamic_cast(opt))->value; + else + j[opt_key] = opt->serialize(); + } else { + const ConfigOptionVectorBase *vec = static_cast(opt); + std::vector string_values = vec->vserialize(); + + json j_array(string_values); + j[opt_key] = j_array; + } + } + + return j; +} + +static char* read_json_file(const std::string &preset_path) +{ + FILE *json_file = boost::nowide::fopen(boost::filesystem::path(preset_path).make_preferred().string().c_str(), "rb"); + if (json_file == NULL) { + BOOST_LOG_TRIVIAL(info) << "Failed to open JSON file: " << preset_path; + return NULL; + } + fseek(json_file, 0, SEEK_END); // seek to end + long file_size = ftell(json_file); // get file size + fseek(json_file, 0, SEEK_SET); // seek to start + + char * json_contents = (char *) malloc(file_size); + if (json_contents == NULL) { + BOOST_LOG_TRIVIAL(info) << "Failed to allocate memory for JSON file "; + fclose(json_file); + return NULL; + } + + fread(json_contents, 1, file_size, json_file); + fclose(json_file); + + return json_contents; +} + +static std::string get_printer_nozzle_diameter(std::string printer_name) { + + size_t index = printer_name.find(" nozzle"); + if (std::string::npos == index) { + return ""; + } + std::string nozzle = printer_name.substr(0, index); + size_t last_space_index = nozzle.find_last_of(" "); + if (std::string::npos == index) { + return ""; + } + return nozzle.substr(last_space_index + 1); +} + +static void adjust_dialog_in_screen(DPIDialog* dialog) { + wxSize screen_size = wxGetDisplaySize(); + int pos_x, pos_y, size_x, size_y, screen_width, screen_height, dialog_x, dialog_y; + pos_x = dialog->GetPosition().x; + pos_y = dialog->GetPosition().y; + size_x = dialog->GetSize().x; + size_y = dialog->GetSize().y; + screen_width = screen_size.GetWidth(); + screen_height = screen_size.GetHeight(); + dialog_x = pos_x; + dialog_y = pos_y; + if (pos_x + size_x > screen_width) { + int exceed_x = pos_x + size_x - screen_width; + dialog_x -= exceed_x; + } + if (pos_y + size_y > screen_height - 50) { + int exceed_y = pos_y + size_y - screen_height + 50; + dialog_y -= exceed_y; + } + if (pos_x != dialog_x || pos_y != dialog_y) { dialog->SetPosition(wxPoint(dialog_x, dialog_y)); } +} + +CreateFilamentPresetDialog::CreateFilamentPresetDialog(wxWindow *parent) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Create Filament"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX | wxCENTRE) +{ + m_create_type.base_filament = _L("Create Based on Current Filament"); + m_create_type.base_filament_preset = _L("Copy Current Filament Preset "); + get_all_filament_presets(); + + this->SetBackgroundColour(*wxWHITE); + this->SetSize(wxSize(FromDIP(600), FromDIP(480))); + + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + wxBoxSizer *m_main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxStaticText *basic_infomation = new wxStaticText(this, wxID_ANY, _L("Basic Information")); + basic_infomation->SetFont(Label::Head_16); + m_main_sizer->Add(basic_infomation, 0, wxLEFT, FromDIP(10)); + + m_main_sizer->Add(create_item(FilamentOptionType::VENDOR), 0, wxEXPAND | wxALL, FromDIP(5)); + m_main_sizer->Add(create_item(FilamentOptionType::TYPE), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_main_sizer->Add(create_item(FilamentOptionType::SERIAL), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + + // divider line + auto line_divider = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + line_divider->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(line_divider, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxStaticText *presets_infomation = new wxStaticText(this, wxID_ANY, _L("Add Filament Preset under this filament")); + presets_infomation->SetFont(Label::Head_16); + m_main_sizer->Add(presets_infomation, 0, wxLEFT | wxRIGHT, FromDIP(15)); + + m_main_sizer->Add(create_item(FilamentOptionType::FILAMENT_PRESET), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + + m_filament_preset_text = new wxStaticText(this, wxID_ANY, _L("We could create the filament presets for your following printer:"), wxDefaultPosition, wxDefaultSize); + m_main_sizer->Add(m_filament_preset_text, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15)); + + m_scrolled_preset_panel = new wxScrolledWindow(this, wxID_ANY); + m_scrolled_preset_panel->SetMaxSize(wxSize(-1, FromDIP(350))); + m_scrolled_preset_panel->SetBackgroundColour(*wxWHITE); + m_scrolled_preset_panel->SetScrollRate(5, 5); + m_scrolled_sizer = new wxBoxSizer(wxVERTICAL); + m_scrolled_sizer->Add(create_item(FilamentOptionType::PRESET_FOR_PRINTER), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_scrolled_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + m_scrolled_preset_panel->SetSizerAndFit(m_scrolled_sizer); + m_main_sizer->Add(m_scrolled_preset_panel, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + m_main_sizer->Add(create_button_item(), 0, wxEXPAND | wxALL, FromDIP(10)); + + get_all_visible_printer_name(); + select_curr_radiobox(m_create_type_btns, 0); + + this->SetSizer(m_main_sizer); + + Layout(); + Fit(); + + wxGetApp().UpdateDlgDarkUI(this); +} + +CreateFilamentPresetDialog::~CreateFilamentPresetDialog() +{ + for (std::pair preset : m_all_presets_map) { + Preset *p = preset.second; + if (p) { + delete p; + p = nullptr; + } + } +} + +void CreateFilamentPresetDialog::on_dpi_changed(const wxRect &suggested_rect) { + + m_button_create->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetCornerRadius(FromDIP(12)); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + + Layout(); +} + +bool CreateFilamentPresetDialog::is_check_box_selected() +{ + for (const std::pair<::CheckBox *, std::pair> &checkbox_preset : m_filament_preset) { + if (checkbox_preset.first->GetValue()) { return true; } + } + + for (const std::pair<::CheckBox *, std::pair> &checkbox_preset : m_machint_filament_preset) { + if (checkbox_preset.first->GetValue()) { return true; } + } + + return false; +} + +wxBoxSizer *CreateFilamentPresetDialog::create_item(FilamentOptionType option_type) +{ + + wxSizer *item = nullptr; + switch (option_type) { + case VENDOR: return create_vendor_item(); + case TYPE: return create_type_item(); + case SERIAL: return create_serial_item(); + case FILAMENT_PRESET: return create_filament_preset_item(); + case PRESET_FOR_PRINTER: return create_filament_preset_for_printer_item(); + default: return nullptr; + } +} +wxBoxSizer *CreateFilamentPresetDialog::create_vendor_item() +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_vendor_text = new wxStaticText(this, wxID_ANY, _L("Vendor"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_vendor_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + wxArrayString choices; + for (const wxString &vendor : filament_vendors) { + choices.push_back(vendor); + } + + wxBoxSizer *vendor_sizer = new wxBoxSizer(wxHORIZONTAL); + m_filament_vendor_combobox = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + m_filament_vendor_combobox->SetLabel(_L("Select Vendor")); + m_filament_vendor_combobox->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + m_filament_vendor_combobox->Set(choices); + m_filament_vendor_combobox->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + m_filament_vendor_combobox->SetLabelColor(*wxBLACK); + e.Skip(); + }); + vendor_sizer->Add(m_filament_vendor_combobox, 0, wxEXPAND | wxALL, 0); + wxBoxSizer *textInputSizer = new wxBoxSizer(wxVERTICAL); + m_filament_custom_vendor_input = new TextInput(this, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, wxTE_PROCESS_ENTER); + m_filament_custom_vendor_input->GetTextCtrl()->SetMaxLength(50); + m_filament_custom_vendor_input->SetSize(NAME_OPTION_COMBOBOX_SIZE); + textInputSizer->Add(m_filament_custom_vendor_input, 0, wxEXPAND | wxALL, 0); + m_filament_custom_vendor_input->GetTextCtrl()->SetHint(_L("Input Custom Vendor")); + m_filament_custom_vendor_input->GetTextCtrl()->Bind(wxEVT_CHAR, [this](wxKeyEvent &event) { + int key = event.GetKeyCode(); + if (cannot_input_key.find(key) != cannot_input_key.end()) { + event.Skip(false); + return; + } + event.Skip(); + }); + m_filament_custom_vendor_input->Hide(); + vendor_sizer->Add(textInputSizer, 0, wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *comboBoxSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *checkbox_sizer = new wxBoxSizer(wxHORIZONTAL); + m_can_not_find_vendor_checkbox = new ::CheckBox(this); + + checkbox_sizer->Add(m_can_not_find_vendor_checkbox, 0, wxALIGN_CENTER, 0); + checkbox_sizer->Add(0, 0, 0, wxEXPAND | wxRIGHT, FromDIP(5)); + + wxStaticText *m_can_not_find_vendor_text = new wxStaticText(this, wxID_ANY, _L("Can't find vendor I want"), wxDefaultPosition, wxDefaultSize, 0); + m_can_not_find_vendor_text->SetFont(::Label::Body_13); + + wxSize size = m_can_not_find_vendor_text->GetTextExtent(_L("Can't find vendor I want")); + m_can_not_find_vendor_text->SetMinSize(wxSize(size.x + FromDIP(4), -1)); + m_can_not_find_vendor_text->Wrap(-1); + checkbox_sizer->Add(m_can_not_find_vendor_text, 0, wxALIGN_CENTER, 0); + + m_can_not_find_vendor_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &e) { + bool value = m_can_not_find_vendor_checkbox->GetValue(); + if (value) { + m_can_not_find_vendor_checkbox->SetValue(true); + m_filament_vendor_combobox->Hide(); + m_filament_custom_vendor_input->Show(); + } else { + m_can_not_find_vendor_checkbox->SetValue(false); + m_filament_vendor_combobox->Show(); + m_filament_custom_vendor_input->Hide(); + } + Refresh(); + Layout(); + Fit(); + }); + + comboBoxSizer->Add(vendor_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); + comboBoxSizer->Add(checkbox_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); + horizontal_sizer->Add(comboBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + return horizontal_sizer; + +} + +wxBoxSizer *CreateFilamentPresetDialog::create_type_item() +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(this, wxID_ANY, _L("Type"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + wxArrayString filament_type; + for (const wxString &filament : m_system_filament_types_set) { + filament_type.Add(filament); + } + + wxBoxSizer *comboBoxSizer = new wxBoxSizer(wxVERTICAL); + m_filament_type_combobox = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + m_filament_type_combobox->SetLabel(_L("Select Type")); + m_filament_type_combobox->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + m_filament_type_combobox->Set(filament_type); + comboBoxSizer->Add(m_filament_type_combobox, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(comboBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + m_filament_type_combobox->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + m_filament_type_combobox->SetLabelColor(*wxBLACK); + const wxString &curr_create_type = curr_create_filament_type(); + clear_filament_preset_map(); + if (curr_create_type == m_create_type.base_filament) { + wxArrayString filament_preset_choice = get_filament_preset_choices(); + m_filament_preset_combobox->Set(filament_preset_choice); + m_filament_preset_combobox->SetLabel(_L("Select Filament Preset")); + m_filament_preset_combobox->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + + } else if (curr_create_type == m_create_type.base_filament_preset) { + get_filament_presets_by_machine(); + } + m_scrolled_preset_panel->SetSizerAndFit(m_scrolled_sizer); + + update_dialog_size(); + e.Skip(); + }); + + return horizontal_sizer; +} + +wxBoxSizer *CreateFilamentPresetDialog::create_serial_item() +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_serial_text = new wxStaticText(this, wxID_ANY, _L("Serial"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_serial_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + wxBoxSizer *comboBoxSizer = new wxBoxSizer(wxVERTICAL); + m_filament_serial_input = new TextInput(this, "", "", "", wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, wxTE_PROCESS_ENTER); + m_filament_serial_input->GetTextCtrl()->SetMaxLength(50); + comboBoxSizer->Add(m_filament_serial_input, 0, wxEXPAND | wxALL, 0); + m_filament_serial_input->GetTextCtrl()->Bind(wxEVT_CHAR, [this](wxKeyEvent &event) { + int key = event.GetKeyCode(); + if (cannot_input_key.find(key) != cannot_input_key.end()) { + event.Skip(false); + return; + } + event.Skip(); + }); + + wxStaticText *static_eg_text = new wxStaticText(this, wxID_ANY, _L("e.g. Basic, Matte, Silk, Marble"), wxDefaultPosition, wxDefaultSize); + static_eg_text->SetForegroundColour(wxColour("#6B6B6B")); + static_eg_text->SetFont(::Label::Body_12); + comboBoxSizer->Add(static_eg_text, 0, wxEXPAND | wxTOP, FromDIP(5)); + horizontal_sizer->Add(comboBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + return horizontal_sizer; +} + +wxBoxSizer *CreateFilamentPresetDialog::create_filament_preset_item() +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_filament_preset_text = new wxStaticText(this, wxID_ANY, _L("Filament Preset"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_filament_preset_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * comboBoxSizer = new wxBoxSizer(wxVERTICAL); + comboBoxSizer->Add(create_radio_item(m_create_type.base_filament, this, wxEmptyString, m_create_type_btns), 0, wxEXPAND | wxALL, 0); + + m_filament_preset_combobox = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, FILAMENT_PRESET_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + m_filament_preset_combobox->SetLabel(_L("Select Filament Preset")); + m_filament_preset_combobox->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + + + m_filament_preset_combobox->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + m_filament_preset_combobox->SetLabelColor(*wxBLACK); + wxString filament_type = m_filament_preset_combobox->GetStringSelection(); + std::unordered_map>::iterator iter = m_filament_choice_map.find(m_public_name_to_filament_id_map[filament_type]); + + m_scrolled_preset_panel->Freeze(); + m_filament_presets_sizer->Clear(true); + m_filament_preset.clear(); + + std::vector> printer_name_to_filament_preset; + if (iter != m_filament_choice_map.end()) { + std::unordered_map nozzle_diameter = nozzle_diameter_map; + for (Preset* preset : iter->second) { + auto compatible_printers = preset->config.option("compatible_printers", true); + if (!compatible_printers || compatible_printers->values.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "there is a preset has no compatible printers and the preset name is: " << preset->name; + continue; + } + for (std::string &compatible_printer_name : compatible_printers->values) { + if (m_visible_printers.find(compatible_printer_name) == m_visible_printers.end()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "there is a comppatible printer no exist: " << compatible_printer_name + << "and the preset name is: " << preset->name; + continue; + } + std::string nozzle = get_printer_nozzle_diameter(compatible_printer_name); + if (nozzle_diameter[nozzle] == 0) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " compatible printer nozzle encounter exception and name is: " << compatible_printer_name; + continue; + } + printer_name_to_filament_preset.push_back(std::make_pair(compatible_printer_name,preset)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "show compatible printer name: " << compatible_printer_name << "and preset name is: " << preset; + } + } + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " not find filament_id corresponding to the type: and the type is" << filament_type; + } + sort_printer_by_nozzle(printer_name_to_filament_preset); + for (std::pair printer_to_preset : printer_name_to_filament_preset) + m_filament_presets_sizer->Add(create_checkbox(m_filament_preset_panel, printer_to_preset.first, printer_to_preset.second, m_filament_preset), 0, + wxEXPAND | wxTOP | wxLEFT, FromDIP(5)); + m_scrolled_preset_panel->SetSizerAndFit(m_scrolled_sizer); + m_scrolled_preset_panel->Thaw(); + + update_dialog_size(); + e.Skip(); + }); + + comboBoxSizer->Add(m_filament_preset_combobox, 0, wxEXPAND | wxTOP, FromDIP(5)); + + comboBoxSizer->Add(create_radio_item(m_create_type.base_filament_preset, this, wxEmptyString, m_create_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + + horizontal_sizer->Add(comboBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + horizontal_sizer->Add(0, 0, 0, wxLEFT, FromDIP(30)); + + return horizontal_sizer; + +} + +wxBoxSizer *CreateFilamentPresetDialog::create_filament_preset_for_printer_item() +{ + wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL); + m_filament_preset_panel = new wxPanel(m_scrolled_preset_panel, wxID_ANY); + m_filament_preset_panel->SetBackgroundColour(PRINTER_LIST_COLOUR); + m_filament_preset_panel->SetSize(PRINTER_LIST_SIZE); + m_filament_presets_sizer = new wxGridSizer(3, FromDIP(5), FromDIP(5)); + m_filament_preset_panel->SetSizer(m_filament_presets_sizer); + vertical_sizer->Add(m_filament_preset_panel, 0, wxEXPAND | wxTOP | wxALIGN_CENTER_HORIZONTAL, FromDIP(5)); + + return vertical_sizer; +} + +wxBoxSizer *CreateFilamentPresetDialog::create_button_item() +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_button_create = new Button(this, _L("Create")); + m_button_create->SetBackgroundColor(btn_bg_green); + m_button_create->SetBorderColor(*wxWHITE); + m_button_create->SetTextColor(wxColour(0xFFFFFE)); + m_button_create->SetFont(Label::Body_12); + m_button_create->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_create, 0, wxRIGHT, FromDIP(10)); + + m_button_create->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + //get vendor name + wxString vendor_str = m_filament_vendor_combobox->GetLabel(); + std::string vendor_name; + + if (!m_can_not_find_vendor_checkbox->GetValue()) { + if (_L("Select Vendor") == vendor_str) { + MessageDialog dlg(this, _L("Vendor is not selected, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } else { + vendor_name = into_u8(vendor_str); + } + } else { + if (m_filament_custom_vendor_input->GetTextCtrl()->GetValue().empty()) { + MessageDialog dlg(this, _L("Custom vendor is not input, please input custom vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } else { + vendor_name = into_u8(m_filament_custom_vendor_input->GetTextCtrl()->GetValue()); + if (vendor_name == "Bambu" || vendor_name == "Generic") { + MessageDialog dlg(this, _L("\"Bambu\" or \"Generic\" can not be used as a Vendor for custom filaments."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + } + } + + //get fialment type name + wxString type_str = m_filament_type_combobox->GetLabel(); + std::string type_name; + if (_L("Select Type") == type_str) { + MessageDialog dlg(this, _L("Filament type is not selected, please reselect type."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } else { + type_name = into_u8(type_str); + } + //get filament serial + wxString serial_str = m_filament_serial_input->GetTextCtrl()->GetValue(); + std::string serial_name; + if (serial_str.empty()) { + MessageDialog dlg(this, _L("Filament serial is not inputed, please input serial."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } else { + serial_name = into_u8(serial_str); + } + vendor_name = remove_special_key(vendor_name); + serial_name = remove_special_key(serial_name); + + if (vendor_name.empty() || serial_name.empty()) { + MessageDialog dlg(this, _L("There may be escape characters in the vendor or serial input of filament. Please delete and re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + boost::algorithm::trim(vendor_name); + boost::algorithm::trim(serial_name); + if (vendor_name.empty() || serial_name.empty()) { + MessageDialog dlg(this, _L("All inputs in the custom vendor or serial are spaces. Please re-enter."), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + if (m_can_not_find_vendor_checkbox->GetValue() && str_is_all_digit(vendor_name)) { + MessageDialog dlg(this, _L("The vendor can not be a number. Please re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + if (!is_check_box_selected()) { + MessageDialog dlg(this, _L("You have not selected a printer or preset yet. Please select at least one."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + std::string filament_preset_name = vendor_name + " " + (type_name == "PLA-AERO" ? "PLA Aero" : type_name) + " " + serial_name; + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle->filaments.is_alias_exist(filament_preset_name)) { + MessageDialog dlg(this, + wxString::Format(_L("The Filament name %s you created already exists. \nIf you continue creating, the preset created will be displayed with its " + "full name. Do you want to continue?"), + from_u8(filament_preset_name)), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + if (wxID_YES != dlg.ShowModal()) { return; } + } + + std::string user_filament_id = get_filament_id(filament_preset_name); + + const wxString &curr_create_type = curr_create_filament_type(); + + if (curr_create_type == m_create_type.base_filament) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":clone filament create type filament "; + for (const std::pair<::CheckBox *, std::pair> &checkbox_preset : m_filament_preset) { + if (checkbox_preset.first->GetValue()) { + std::string compatible_printer_name = checkbox_preset.second.first; + std::vector failures; + Preset const *const checked_preset = checkbox_preset.second.second; + DynamicConfig dynamic_config; + dynamic_config.set_key_value("filament_vendor", new ConfigOptionStrings({vendor_name})); + dynamic_config.set_key_value("compatible_printers", new ConfigOptionStrings({compatible_printer_name})); + dynamic_config.set_key_value("filament_type", new ConfigOptionStrings({type_name})); + bool res = preset_bundle->filaments.clone_presets_for_filament(checked_preset, failures, filament_preset_name, user_filament_id, dynamic_config, + compatible_printer_name); + if (!res) { + std::string failure_names; + for (std::string &failure : failures) { failure_names += failure + "\n"; } + MessageDialog dlg(this, _L("Some existing presets have failed to be created, as follows:\n") + from_u8(failure_names) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + if (dlg.ShowModal() == wxID_YES) { + res = preset_bundle->filaments.clone_presets_for_filament(checked_preset, failures, filament_preset_name, user_filament_id, dynamic_config, + compatible_printer_name, true); + BOOST_LOG_TRIVIAL(info) << "clone filament have failures rewritten is successful? " << res; + } + } + BOOST_LOG_TRIVIAL(info) << "clone filament no failures is successful? " << res; + } + } + } else if (curr_create_type == m_create_type.base_filament_preset) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":clone filament presets create type filament preset"; + for (const std::pair<::CheckBox *, std::pair> &checkbox_preset : m_machint_filament_preset) { + if (checkbox_preset.first->GetValue()) { + std::string compatible_printer_name = checkbox_preset.second.first; + std::vector failures; + Preset const *const checked_preset = checkbox_preset.second.second; + DynamicConfig dynamic_config; + dynamic_config.set_key_value("filament_vendor", new ConfigOptionStrings({vendor_name})); + dynamic_config.set_key_value("compatible_printers", new ConfigOptionStrings({compatible_printer_name})); + dynamic_config.set_key_value("filament_type", new ConfigOptionStrings({type_name})); + bool res = preset_bundle->filaments.clone_presets_for_filament(checked_preset, failures, filament_preset_name, user_filament_id, dynamic_config, + compatible_printer_name); + if (!res) { + std::string failure_names; + for (std::string &failure : failures) { failure_names += failure + "\n"; } + MessageDialog dlg(this, _L("Some existing presets have failed to be created, as follows:\n") + from_u8(failure_names) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + if (wxID_YES == dlg.ShowModal()) { + res = preset_bundle->filaments.clone_presets_for_filament(checked_preset, failures, filament_preset_name, user_filament_id, dynamic_config, + compatible_printer_name, true); + BOOST_LOG_TRIVIAL(info) << "clone filament presets have failures rewritten is successful? " << res; + } + } + BOOST_LOG_TRIVIAL(info) << "clone filament presets no failures is successful? " << res << " old preset is: " << checked_preset->name + << " compatible_printer_name is: " << compatible_printer_name; + } + } + } + preset_bundle->update_compatible(PresetSelectCompatibleType::Always); + EndModal(wxID_OK); + }); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_button_cancel = new Button(this, _L("Cancel")); + m_button_cancel->SetBackgroundColor(btn_bg_white); + m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_cancel->SetFont(Label::Body_12); + m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_cancel, 0, wxRIGHT, FromDIP(10)); + + m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + EndModal(wxID_CANCEL); + }); + + return bSizer_button; +} + +wxArrayString CreateFilamentPresetDialog::get_filament_preset_choices() +{ + wxArrayString choices; + // get fialment type name + wxString type_str = m_filament_type_combobox->GetLabel(); + std::string type_name; + if (_L("Select Type") == type_str) { + /*MessageDialog dlg(this, _L("Filament type is not selected, please reselect type."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal();*/ + return choices; + } else { + type_name = into_u8(type_str); + } + + for (std::pair filament_presets : m_all_presets_map) { + Preset *preset = filament_presets.second; + auto inherit = preset->config.option("inherits"); + if (inherit && !inherit->value.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherit user preset is:" << preset->name << " and inherits is: " << inherit->value; + continue; + } + auto fila_type = preset->config.option("filament_type"); + if (!fila_type || fila_type->values.empty() || type_name != fila_type->values[0]) continue; + m_filament_choice_map[preset->filament_id].push_back(preset); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base user preset is:" << preset->name; + } + + int suffix = 0; + for (const pair> &preset : m_filament_choice_map) { + if (preset.second.empty()) continue; + std::set preset_name_set; + for (Preset* filament_preset : preset.second) { + std::string preset_name = filament_preset->name; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament_id: " << filament_preset->filament_id << " preset name: " << filament_preset->name; + size_t index_at = preset_name.find(" @"); + if (std::string::npos != index_at) { + std::string cur_preset_name = preset_name.substr(0, index_at); + preset_name_set.insert(from_u8(cur_preset_name)); + } + } + assert(1 == preset_name_set.size()); + if (preset_name_set.size() > 1) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " the same filament has different filament(vendor type serial)"; + } + for (const wxString& public_name : preset_name_set) { + if (m_public_name_to_filament_id_map.find(public_name) != m_public_name_to_filament_id_map.end()) { + suffix++; + m_public_name_to_filament_id_map[public_name + "_" + std::to_string(suffix)] = preset.first; + choices.Add(public_name + "_" + std::to_string(suffix)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " add filament choice: " << choices.back(); + } else { + m_public_name_to_filament_id_map[public_name] = preset.first; + choices.Add(public_name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " add filament choice: " << choices.back(); + } + } + } + + return choices; +} + +wxBoxSizer *CreateFilamentPresetDialog::create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector> &radiobox_list) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + RadioBox * radiobox = new RadioBox(parent); + horizontal_sizer->Add(radiobox, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(5)); + radiobox_list.push_back(std::make_pair(radiobox, title)); + int btn_idx = radiobox_list.size() - 1; + radiobox->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { select_curr_radiobox(radiobox_list, btn_idx); }); + + wxStaticText *text = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize); + text->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { select_curr_radiobox(radiobox_list, btn_idx); }); + horizontal_sizer->Add(text, 0, wxEXPAND | wxLEFT, 0); + + radiobox->SetToolTip(tooltip); + text->SetToolTip(tooltip); + return horizontal_sizer; +} + +void CreateFilamentPresetDialog::select_curr_radiobox(std::vector> &radiobox_list, int btn_idx) +{ + int len = radiobox_list.size(); + for (int i = 0; i < len; ++i) { + if (i == btn_idx) { + radiobox_list[i].first->SetValue(true); + const wxString &curr_selected_type = radiobox_list[i].second; + this->Freeze(); + if (curr_selected_type == m_create_type.base_filament) { + m_filament_preset_text->SetLabel(_L("We could create the filament presets for your following printer:")); + m_filament_preset_combobox->Show(); + if (_L("Select Type") != m_filament_type_combobox->GetLabel()) { + clear_filament_preset_map(); + wxArrayString filament_preset_choice = get_filament_preset_choices(); + m_filament_preset_combobox->Set(filament_preset_choice); + m_filament_preset_combobox->SetLabel(_L("Select Filament Preset")); + m_filament_preset_combobox->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + } + } else if (curr_selected_type == m_create_type.base_filament_preset) { + m_filament_preset_text->SetLabel(_L("We would rename the presets as \"Vendor Type Serial @printer you selected\". \nTo add preset for more prinetrs, Please go to printer selection")); + m_filament_preset_combobox->Hide(); + if (_L("Select Type") != m_filament_type_combobox->GetLabel()) { + + clear_filament_preset_map(); + get_filament_presets_by_machine(); + + } + } + m_scrolled_preset_panel->SetSizerAndFit(m_scrolled_sizer); + this->Thaw(); + } else { + radiobox_list[i].first->SetValue(false); + } + } + update_dialog_size(); +} + +wxString CreateFilamentPresetDialog::curr_create_filament_type() +{ + wxString curr_filament_type; + for (const std::pair &printer_radio : m_create_type_btns) { + if (printer_radio.first->GetValue()) { + curr_filament_type = printer_radio.second; + } + } + return curr_filament_type; +} + +void CreateFilamentPresetDialog::get_filament_presets_by_machine() +{ + wxArrayString choices; + // get fialment type name + wxString type_str = m_filament_type_combobox->GetLabel(); + std::string type_name; + if (_L("Select Type") == type_str) { + /*MessageDialog dlg(this, _L("Filament type is not selected, please reselect type."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | + wxCENTRE); dlg.ShowModal();*/ + return; + } else { + type_name = into_u8(type_str); + } + + std::unordered_map nozzle_diameter = nozzle_diameter_map; + std::unordered_map> machine_name_to_presets; + PresetBundle * preset_bundle = wxGetApp().preset_bundle; + for (std::pair filament_preset : m_all_presets_map) { + Preset * preset = filament_preset.second; + auto compatible_printers = preset->config.option("compatible_printers", true); + if (!compatible_printers || compatible_printers->values.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "there is a preset has no compatible printers and the preset name is: " << preset->name; + continue; + } + for (std::string &compatible_printer_name : compatible_printers->values) { + if (m_visible_printers.find(compatible_printer_name) == m_visible_printers.end()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " compatable printer is not visible and preset name is: " << preset->name; + continue; + } + Preset * inherit_preset = nullptr; + auto inherit = dynamic_cast(preset->config.option(BBL_JSON_KEY_INHERITS,false)); + if (inherit && !inherit->value.empty()) { + std::string inherits_value = inherit->value; + inherit_preset = preset_bundle->filaments.find_preset(inherits_value, false, true); + } + ConfigOptionStrings *filament_types; + if (!inherit_preset) { + filament_types = dynamic_cast(preset->config.option("filament_type")); + } else { + filament_types = dynamic_cast(inherit_preset->config.option("filament_type")); + } + + if (filament_types && filament_types->values.empty()) continue; + const std::string filament_type = filament_types->values[0]; + if (filament_type != type_name) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " preset type is not selected type and preset name is: " << preset->name; + continue; + } + std::string nozzle = get_printer_nozzle_diameter(compatible_printer_name); + if (nozzle_diameter[nozzle] == 0) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " compatible printer nozzle encounter exception and name is: " << compatible_printer_name; + continue; + } + machine_name_to_presets[compatible_printer_name].push_back(preset); + } + } + std::vector>> printer_name_to_filament_presets; + for (std::pair> machine_filament_presets : machine_name_to_presets) { + printer_name_to_filament_presets.push_back(machine_filament_presets); + } + sort_printer_by_nozzle(printer_name_to_filament_presets); + m_filament_preset_panel->Freeze(); + for (std::pair> machine_filament_presets : printer_name_to_filament_presets) { + std::string compatible_printer = machine_filament_presets.first; + std::vector &presets = machine_filament_presets.second; + m_filament_presets_sizer->Add(create_select_filament_preset_checkbox(m_filament_preset_panel, compatible_printer, presets, m_machint_filament_preset), 0, wxEXPAND | wxALL, FromDIP(5)); + } + m_filament_preset_panel->Thaw(); +} + +void CreateFilamentPresetDialog::get_all_filament_presets() +{ + // temp filament presets + PresetBundle temp_preset_bundle; + std::string dir_user_presets = wxGetApp().app_config->get("preset_folder"); + if (dir_user_presets.empty()) { + temp_preset_bundle.load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::EnableSilent); + } else { + temp_preset_bundle.load_user_presets(dir_user_presets, ForwardCompatibilitySubstitutionRule::EnableSilent); + } + const std::deque &filament_presets = temp_preset_bundle.filaments.get_presets(); + + for (const Preset &preset : filament_presets) { + if (preset.filament_id.empty() || "null" == preset.filament_id) continue; + std::string filament_preset_name = preset.name; + Preset *filament_preset = new Preset(preset); + m_all_presets_map[filament_preset_name] = filament_preset; + } + // global filament presets + PresetBundle * preset_bundle = wxGetApp().preset_bundle; + const std::deque &temp_filament_presets = preset_bundle->filaments.get_presets(); + for (const Preset& preset : temp_filament_presets) { + if (preset.filament_id.empty() || "null" == preset.filament_id) continue; + auto filament_type = preset.config.option("filament_type"); + if (filament_type && filament_type->values.size()) + m_system_filament_types_set.insert(filament_type->values[0]); + if (!preset.is_visible) continue; + std::string filament_preset_name = preset.name; + Preset *filament_preset = new Preset(preset); + m_all_presets_map[filament_preset_name] = filament_preset; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " loaded preset name is: " << filament_preset->name; + } +} + +void CreateFilamentPresetDialog::get_all_visible_printer_name() +{ + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + for (const Preset &printer_preset : preset_bundle->printers.get_presets()) { + if (!printer_preset.is_visible) continue; + assert(m_visible_printers.find(printer_preset.name) == m_visible_printers.end()); + m_visible_printers.insert(printer_preset.name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry, and visible printer is: " << printer_preset.name; + } + +} + +void CreateFilamentPresetDialog::update_dialog_size() +{ + this->Freeze(); + m_filament_preset_panel->SetSizerAndFit(m_filament_presets_sizer); + int width = m_filament_preset_panel->GetSize().GetWidth(); + int height = m_filament_preset_panel->GetSize().GetHeight(); + m_scrolled_preset_panel->SetMinSize(wxSize(std::min(1400, width + FromDIP(26)), std::min(600, height + FromDIP(18)))); + m_scrolled_preset_panel->SetMaxSize(wxSize(std::min(1400, width + FromDIP(26)), std::min(600, height + FromDIP(18)))); + m_scrolled_preset_panel->SetSize(wxSize(std::min(1500, width + FromDIP(26)), std::min(600, height + FromDIP(18)))); + Layout(); + Fit(); + Refresh(); + adjust_dialog_in_screen(this); + this->Thaw(); +} + +template +void CreateFilamentPresetDialog::sort_printer_by_nozzle(std::vector> &printer_name_to_filament_preset) +{ + std::unordered_map nozzle_diameter = nozzle_diameter_map; + std::sort(printer_name_to_filament_preset.begin(), printer_name_to_filament_preset.end(), + [&nozzle_diameter](const std::pair &a, const std::pair &b) { + size_t nozzle_index_a = a.first.find(" nozzle"); + size_t nozzle_index_b = b.first.find(" nozzle"); + if (nozzle_index_a == std::string::npos || nozzle_index_b == std::string::npos) return a.first < b.first; + std::string nozzle_str_a; + std::string nozzle_str_b; + try { + nozzle_str_a = a.first.substr(0, nozzle_index_a); + nozzle_str_b = b.first.substr(0, nozzle_index_b); + size_t last_space_index = nozzle_str_a.find_last_of(" "); + nozzle_str_a = nozzle_str_a.substr(last_space_index + 1); + last_space_index = nozzle_str_b.find_last_of(" "); + nozzle_str_b = nozzle_str_b.substr(last_space_index + 1); + } catch (...) { + BOOST_LOG_TRIVIAL(info) << "substr filed, and printer name is: " << a.first << " and " << b.first; + return a.first < b.first; + } + float nozzle_a, nozzle_b; + try { + nozzle_a = nozzle_diameter[nozzle_str_a]; + nozzle_b = nozzle_diameter[nozzle_str_b]; + assert(nozzle_a != 0 && nozzle_b != 0); + } catch (...) { + BOOST_LOG_TRIVIAL(info) << "find nozzle filed, and nozzle is: " << nozzle_str_a << "mm and " << nozzle_str_b << "mm"; + return a.first < b.first; + } + float diff_nozzle_a = std::abs(nozzle_a - 0.4); + float diff_nozzle_b = std::abs(nozzle_b - 0.4); + if (nozzle_a == nozzle_b) return a.first < b.first; + if (diff_nozzle_a == diff_nozzle_b) return nozzle_a < nozzle_b; + + return diff_nozzle_a < diff_nozzle_b; + }); +} + +void CreateFilamentPresetDialog::clear_filament_preset_map() +{ + m_filament_choice_map.clear(); + m_filament_preset.clear(); + m_machint_filament_preset.clear(); + m_public_name_to_filament_id_map.clear(); + m_filament_preset_panel->Freeze(); + m_filament_presets_sizer->Clear(true); + m_filament_preset_panel->Thaw(); +} + +CreatePrinterPresetDialog::CreatePrinterPresetDialog(wxWindow *parent) +: DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Create Printer/Nozzle"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX | wxCENTER) +{ + m_create_type.create_printer = _L("Create Printer"); + m_create_type.create_nozzle = _L("Create Nozzle for Existing Printer"); + m_create_type.base_template = _L("Create from Template"); + m_create_type.base_curr_printer = _L("Create Based on Current Printer"); + this->SetBackgroundColour(*wxWHITE); + SetSizeHints(wxDefaultSize, wxDefaultSize); + + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + wxBoxSizer *m_main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 2), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + m_main_sizer->Add(create_step_switch_item(), 0, wxEXPAND | wxALL, FromDIP(5)); + + wxBoxSizer *page_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_page1 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + m_page1->SetBackgroundColour(*wxWHITE); + m_page1->SetScrollRate(5, 5); + m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);\ + m_page2->SetBackgroundColour(*wxWHITE); + + create_printer_page1(m_page1); + create_printer_page2(m_page2); + m_page2->Hide(); + + page_sizer->Add(m_page1, 1, wxEXPAND, 0); + page_sizer->Add(m_page2, 1, wxEXPAND, 0); + m_main_sizer->Add(page_sizer, 0, wxEXPAND | wxRIGHT, FromDIP(10)); + select_curr_radiobox(m_create_type_btns, 0); + select_curr_radiobox(m_create_presets_btns, 0); + + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + + this->SetSizer(m_main_sizer); + + Layout(); + Fit(); + + wxSize screen_size = wxGetDisplaySize(); + int dialogX = (screen_size.GetWidth() - GetSize().GetWidth()) / 2; + int dialogY = (screen_size.GetHeight() - GetSize().GetHeight()) / 2; + SetPosition(wxPoint(dialogX, dialogY)); + + wxGetApp().UpdateDlgDarkUI(this); +} + +CreatePrinterPresetDialog::~CreatePrinterPresetDialog() +{ + clear_preset_combobox(); + if (m_printer_preset) { + delete m_printer_preset; + m_printer_preset = nullptr; + } +} + +void CreatePrinterPresetDialog::on_dpi_changed(const wxRect &suggested_rect) { + m_button_OK->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_OK->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_OK->SetCornerRadius(FromDIP(12)); + m_button_create->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetCornerRadius(FromDIP(12)); + m_button_page1_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page1_cancel->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page1_cancel->SetCornerRadius(FromDIP(12)); + m_button_page2_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_cancel->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_cancel->SetCornerRadius(FromDIP(12)); + m_button_page2_back->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_back->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_back->SetCornerRadius(FromDIP(12)); + Layout(); +} + +wxBoxSizer *CreatePrinterPresetDialog::create_step_switch_item() +{ + wxBoxSizer *step_switch_sizer = new wxBoxSizer(wxVERTICAL); + + std::string wiki_url = "https://wiki.bambulab.com/en/software/bambu-studio/3rd-party-printer-profile"; + wxHyperlinkCtrl *m_download_hyperlink = new wxHyperlinkCtrl(this, wxID_ANY, _L("wiki"), wiki_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); + step_switch_sizer->Add(m_download_hyperlink, 0, wxRIGHT | wxALIGN_RIGHT, FromDIP(5)); + + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel * step_switch_panel = new wxPanel(this); + step_switch_panel->SetBackgroundColour(*wxWHITE); + horizontal_sizer->Add(0, 0, 1, wxEXPAND,0); + m_step_1 = new wxStaticBitmap(step_switch_panel, wxID_ANY, create_scaled_bitmap("step_1", nullptr, FromDIP(20)), wxDefaultPosition, wxDefaultSize); + horizontal_sizer->Add(m_step_1, 0, wxEXPAND | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(3)); + wxStaticText *static_create_printer_text = new wxStaticText(step_switch_panel, wxID_ANY, m_create_type.create_printer, wxDefaultPosition, wxDefaultSize); + horizontal_sizer->Add(static_create_printer_text, 0, wxEXPAND | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(3)); + auto divider_line = new wxPanel(step_switch_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(50), 1)); + divider_line->SetBackgroundColour(PRINTER_LIST_COLOUR); + horizontal_sizer->Add(divider_line, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(3)); + m_step_2 = new wxStaticBitmap(step_switch_panel, wxID_ANY, create_scaled_bitmap("step_2_ready", nullptr, FromDIP(20)), wxDefaultPosition, wxDefaultSize); + horizontal_sizer->Add(m_step_2, 0, wxEXPAND | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(3)); + wxStaticText *static_import_presets_text = new wxStaticText(step_switch_panel, wxID_ANY, _L("Import Preset"), wxDefaultPosition, wxDefaultSize); + horizontal_sizer->Add(static_import_presets_text, 0, wxEXPAND | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(3)); + horizontal_sizer->Add(0, 0, 1, wxEXPAND, 0); + + step_switch_panel->SetSizer(horizontal_sizer); + + step_switch_sizer->Add(step_switch_panel, 0, wxBOTTOM | wxALIGN_CENTER_HORIZONTAL, FromDIP(10)); + + auto line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + line_top->SetBackgroundColour(PRINTER_LIST_COLOUR); + + step_switch_sizer->Add(line_top, 0, wxEXPAND | wxALL, FromDIP(10)); + + return step_switch_sizer; +} + +void CreatePrinterPresetDialog::create_printer_page1(wxWindow *parent) +{ + this->SetBackgroundColour(*wxWHITE); + + m_page1_sizer = new wxBoxSizer(wxVERTICAL); + + m_page1_sizer->Add(create_type_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_page1_sizer->Add(create_printer_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_page1_sizer->Add(create_nozzle_diameter_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_panel = new wxPanel(parent); + m_printer_info_panel->SetBackgroundColour(*wxWHITE); + m_printer_info_sizer = new wxBoxSizer(wxVERTICAL); + m_printer_info_sizer->Add(create_bed_shape_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_sizer->Add(create_bed_size_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_sizer->Add(create_origin_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_sizer->Add(create_hot_bed_stl_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_sizer->Add(create_hot_bed_svg_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_sizer->Add(create_max_print_height_item(m_printer_info_panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_printer_info_panel->SetSizer(m_printer_info_sizer); + m_page1_sizer->Add(m_printer_info_panel, 0, wxEXPAND, 0); + m_page1_sizer->Add(create_page1_btns_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + + parent->SetSizerAndFit(m_page1_sizer); + Layout(); + + wxGetApp().UpdateDlgDarkUI(this); +} + +wxBoxSizer *CreatePrinterPresetDialog::create_type_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_serial_text = new wxStaticText(parent, wxID_ANY, _L("Create Type"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_serial_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *radioBoxSizer = new wxBoxSizer(wxVERTICAL); + + radioBoxSizer->Add(create_radio_item(m_create_type.create_printer, parent, wxEmptyString, m_create_type_btns), 0, wxEXPAND | wxALL, 0); + radioBoxSizer->Add(create_radio_item(m_create_type.create_nozzle, parent, wxEmptyString, m_create_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + horizontal_sizer->Add(radioBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_printer_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_vendor_text = new wxStaticText(parent, wxID_ANY, _L("Printer"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_vendor_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *comboBoxSizer = new wxBoxSizer(wxHORIZONTAL); + m_select_vendor = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + m_select_vendor->SetValue(_L("Select Vendor")); + m_select_vendor->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + wxArrayString printer_vendor; + for (const std::string &vendor : printer_vendors) { + printer_vendor.Add(vendor); + } + m_select_vendor->Set(printer_vendor); + m_select_vendor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent e) { + m_select_vendor->SetLabelColor(*wxBLACK); + std::string curr_selected_vendor = into_u8(m_select_vendor->GetStringSelection()); + std::unordered_map>::const_iterator iter = printer_model_map.find(curr_selected_vendor); + if (iter != printer_model_map.end()) + { + std::vector vendor_model = iter->second; + wxArrayString model_choice; + for (const std::string &model : vendor_model) { + model_choice.Add(model); + } + m_select_model->Set(model_choice); + if (!model_choice.empty()) { + m_select_model->SetSelection(0); + m_select_model->SetLabelColor(*wxBLACK); + } + } else { + MessageDialog dlg(this, _L("The model is not fond, place reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + } + e.Skip(); + }); + + comboBoxSizer->Add(m_select_vendor, 0, wxEXPAND | wxALL, 0); + + m_select_model = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); + comboBoxSizer->Add(m_select_model, 0, wxEXPAND | wxLEFT, FromDIP(5)); + m_select_model->SetValue(_L("Select Model")); + m_select_model->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + m_select_model->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent e) { + m_select_model->SetLabelColor(*wxBLACK); + e.Skip(); + }); + + m_select_printer = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, PRINTER_PRESET_MODEL_SIZE, 0, nullptr, wxCB_READONLY); + comboBoxSizer->Add(m_select_printer, 0, wxEXPAND | wxALL, 0); + m_select_printer->SetValue(_L("Select Printer")); + m_select_printer->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + m_select_printer->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent e) { + m_select_printer->SetLabelColor(*wxBLACK); + + e.Skip(); + }); + m_select_printer->Hide(); + + m_custom_vendor_text_ctrl = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE); + m_custom_vendor_text_ctrl->SetHint(_L("Input Custom Vendor")); + m_custom_vendor_text_ctrl->Bind(wxEVT_CHAR, [this](wxKeyEvent &event) { + int key = event.GetKeyCode(); + if (cannot_input_key.find(key) != cannot_input_key.end()) { // "@" can not be inputed + event.Skip(false); + return; + } + event.Skip(); + }); + comboBoxSizer->Add(m_custom_vendor_text_ctrl, 0, wxEXPAND | wxALL, 0); + m_custom_vendor_text_ctrl->Hide(); + m_custom_model_text_ctrl = new wxTextCtrl(parent, wxID_ANY, "", wxDefaultPosition, NAME_OPTION_COMBOBOX_SIZE); + m_custom_model_text_ctrl->SetHint(_L("Input Custom Model")); + m_custom_model_text_ctrl->Bind(wxEVT_CHAR, [this](wxKeyEvent &event) { + int key = event.GetKeyCode(); + if (cannot_input_key.find(key) != cannot_input_key.end()) { // "@" can not be inputed + event.Skip(false); + return; + } + event.Skip(); + }); + comboBoxSizer->Add(m_custom_model_text_ctrl, 0, wxEXPAND | wxLEFT, FromDIP(5)); + m_custom_model_text_ctrl->Hide(); + + vertical_sizer->Add(comboBoxSizer, 0, wxEXPAND, 0); + + wxBoxSizer *checkbox_sizer = new wxBoxSizer(wxHORIZONTAL); + m_can_not_find_vendor_combox = new ::CheckBox(parent); + + checkbox_sizer->Add(m_can_not_find_vendor_combox, 0, wxALIGN_CENTER, 0); + checkbox_sizer->Add(0, 0, 0, wxEXPAND | wxRIGHT, FromDIP(5)); + + m_can_not_find_vendor_text = new wxStaticText(parent, wxID_ANY, _L("Can't find my printer model"), wxDefaultPosition, wxDefaultSize, 0); + m_can_not_find_vendor_text->SetFont(::Label::Body_13); + + wxSize size = m_can_not_find_vendor_text->GetTextExtent(_L("Can't find my printer model")); + m_can_not_find_vendor_text->SetMinSize(wxSize(size.x + FromDIP(4), -1)); + m_can_not_find_vendor_text->Wrap(-1); + checkbox_sizer->Add(m_can_not_find_vendor_text, 0, wxALIGN_CENTER, 0); + + m_can_not_find_vendor_combox->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &e) { + bool value = m_can_not_find_vendor_combox->GetValue(); + if (value) { + m_can_not_find_vendor_combox->SetValue(true); + m_custom_vendor_text_ctrl->Show(); + m_custom_model_text_ctrl->Show(); + m_select_vendor->Hide(); + m_select_model->Hide(); + } else { + m_can_not_find_vendor_combox->SetValue(false); + m_custom_vendor_text_ctrl->Hide(); + m_custom_model_text_ctrl->Hide(); + m_select_vendor->Show(); + m_select_model->Show(); + } + Refresh(); + Layout(); + m_page1->SetSizerAndFit(m_page1_sizer); + Fit(); + }); + + vertical_sizer->Add(checkbox_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); + + horizontal_sizer->Add(vertical_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; + +} + +wxBoxSizer *CreatePrinterPresetDialog::create_nozzle_diameter_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Nozzle Diameter"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *comboBoxSizer = new wxBoxSizer(wxVERTICAL); + m_nozzle_diameter = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, OPTION_SIZE, 0, nullptr, wxCB_READONLY); + wxArrayString nozzle_diameters; + for (const std::string nozzle : nozzle_diameter_vec) { + nozzle_diameters.Add(nozzle + " mm"); + } + m_nozzle_diameter->Set(nozzle_diameters); + m_nozzle_diameter->SetSelection(0); + comboBoxSizer->Add(m_nozzle_diameter, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(comboBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + horizontal_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(200)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_bed_shape_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Bed Shape"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * bed_shape_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_bed_shape_text = new wxStaticText(parent, wxID_ANY, _L("Rectangle"), wxDefaultPosition, wxDefaultSize); + bed_shape_sizer->Add(static_bed_shape_text, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(bed_shape_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_bed_size_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Printable Space"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * length_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_length_text = new wxStaticText(parent, wxID_ANY, "X", wxDefaultPosition, wxDefaultSize); + static_length_text->SetMinSize(ORIGIN_TEXT_SIZE); + static_length_text->SetSize(ORIGIN_TEXT_SIZE); + length_sizer->Add(static_length_text, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(length_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + wxBoxSizer *length_input_sizer = new wxBoxSizer(wxVERTICAL); + m_bed_size_x_input = new TextInput(parent, "200", "mm", wxEmptyString, wxDefaultPosition, PRINTER_SPACE_SIZE, wxTE_CENTRE | wxTE_PROCESS_ENTER); + wxTextValidator validator(wxFILTER_DIGITS); + m_bed_size_x_input->GetTextCtrl()->SetValidator(validator); + length_input_sizer->Add(m_bed_size_x_input, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(length_input_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + wxBoxSizer * width_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_width_text = new wxStaticText(parent, wxID_ANY, "Y", wxDefaultPosition, wxDefaultSize); + static_width_text->SetMinSize(ORIGIN_TEXT_SIZE); + static_width_text->SetSize(ORIGIN_TEXT_SIZE); + width_sizer->Add(static_width_text, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(width_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + wxBoxSizer *width_input_sizer = new wxBoxSizer(wxVERTICAL); + m_bed_size_y_input = new TextInput(parent, "200", "mm", wxEmptyString, wxDefaultPosition, PRINTER_SPACE_SIZE, wxTE_CENTRE | wxTE_PROCESS_ENTER); + m_bed_size_y_input->GetTextCtrl()->SetValidator(validator); + width_input_sizer->Add(m_bed_size_y_input, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(width_input_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + return horizontal_sizer; + +} + +wxBoxSizer *CreatePrinterPresetDialog::create_origin_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Origin"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * length_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_origin_x_text = new wxStaticText(parent, wxID_ANY, "X", wxDefaultPosition, wxDefaultSize); + static_origin_x_text->SetMinSize(ORIGIN_TEXT_SIZE); + static_origin_x_text->SetSize(ORIGIN_TEXT_SIZE); + length_sizer->Add(static_origin_x_text, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(length_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + wxBoxSizer *length_input_sizer = new wxBoxSizer(wxVERTICAL); + m_bed_origin_x_input = new TextInput(parent, "0", "mm", wxEmptyString, wxDefaultPosition, PRINTER_SPACE_SIZE, wxTE_CENTRE | wxTE_PROCESS_ENTER); + wxTextValidator validator(wxFILTER_DIGITS); + m_bed_origin_x_input->GetTextCtrl()->SetValidator(validator); + length_input_sizer->Add(m_bed_origin_x_input, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(length_input_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + wxBoxSizer * width_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_origin_y_text = new wxStaticText(parent, wxID_ANY, "Y", wxDefaultPosition, wxDefaultSize); + static_origin_y_text->SetMinSize(ORIGIN_TEXT_SIZE); + static_origin_y_text->SetSize(ORIGIN_TEXT_SIZE); + width_sizer->Add(static_origin_y_text, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(width_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + wxBoxSizer *width_input_sizer = new wxBoxSizer(wxVERTICAL); + m_bed_origin_y_input = new TextInput(parent, "0", "mm", wxEmptyString, wxDefaultPosition, PRINTER_SPACE_SIZE, wxTE_CENTRE | wxTE_PROCESS_ENTER); + m_bed_origin_y_input->GetTextCtrl()->SetValidator(validator); + width_input_sizer->Add(m_bed_origin_y_input, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(width_input_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_hot_bed_stl_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Hot Bed STL"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *hot_bed_stl_sizer = new wxBoxSizer(wxVERTICAL); + + StateColor flush_bg_col(std::pair(wxColour(219, 253, 231), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(238, 238, 238), StateColor::Normal)); + + StateColor flush_bd_col(std::pair(wxColour(0, 174, 66), StateColor::Pressed), std::pair(wxColour(0, 174, 66), StateColor::Hovered), + std::pair(wxColour(172, 172, 172), StateColor::Normal)); + + m_button_bed_stl = new Button(parent, _L("Load stl")); + m_button_bed_stl->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) { load_model_stl(); })); + m_button_bed_stl->SetFont(Label::Body_10); + + m_button_bed_stl->SetPaddingSize(wxSize(FromDIP(30), FromDIP(8))); + m_button_bed_stl->SetFont(Label::Body_13); + m_button_bed_stl->SetCornerRadius(FromDIP(8)); + m_button_bed_stl->SetBackgroundColor(flush_bg_col); + m_button_bed_stl->SetBorderColor(flush_bd_col); + hot_bed_stl_sizer->Add(m_button_bed_stl, 0, wxEXPAND | wxALL, 0); + + horizontal_sizer->Add(hot_bed_stl_sizer, 0, wxEXPAND | wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + m_upload_stl_tip_text = new wxStaticText(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize); + m_upload_stl_tip_text->SetLabelText(_L("Empty")); + horizontal_sizer->Add(m_upload_stl_tip_text, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_hot_bed_svg_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Hot Bed SVG"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *hot_bed_stl_sizer = new wxBoxSizer(wxVERTICAL); + + StateColor flush_bg_col(std::pair(wxColour(219, 253, 231), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(238, 238, 238), StateColor::Normal)); + + StateColor flush_bd_col(std::pair(wxColour(0, 174, 66), StateColor::Pressed), std::pair(wxColour(0, 174, 66), StateColor::Hovered), + std::pair(wxColour(172, 172, 172), StateColor::Normal)); + + m_button_bed_svg = new Button(parent, _L("Load svg")); + m_button_bed_svg->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) { load_texture(); })); + m_button_bed_svg->SetFont(Label::Body_10); + + m_button_bed_svg->SetPaddingSize(wxSize(FromDIP(30), FromDIP(8))); + m_button_bed_svg->SetFont(Label::Body_13); + m_button_bed_svg->SetCornerRadius(FromDIP(8)); + m_button_bed_svg->SetBackgroundColor(flush_bg_col); + m_button_bed_svg->SetBorderColor(flush_bd_col); + hot_bed_stl_sizer->Add(m_button_bed_svg, 0, wxEXPAND | wxALL, 0); + + horizontal_sizer->Add(hot_bed_stl_sizer, 0, wxEXPAND | wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + m_upload_svg_tip_text = new wxStaticText(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize); + m_upload_svg_tip_text->SetLabelText(_L("Empty")); + horizontal_sizer->Add(m_upload_svg_tip_text, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_max_print_height_item(wxWindow *parent) +{ + wxBoxSizer * horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(parent, wxID_ANY, _L("Max Print Height"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *hight_input_sizer = new wxBoxSizer(wxVERTICAL); + m_print_height_input = new TextInput(parent, "200", "mm", wxEmptyString, wxDefaultPosition, PRINTER_SPACE_SIZE, wxTE_CENTRE | wxTE_PROCESS_ENTER); + wxTextValidator validator(wxFILTER_DIGITS); + m_print_height_input->GetTextCtrl()->SetValidator(validator); + hight_input_sizer->Add(m_print_height_input, 0, wxEXPAND | wxLEFT, FromDIP(5)); + horizontal_sizer->Add(hight_input_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_page1_btns_item(wxWindow *parent) +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_button_OK = new Button(parent, _L("OK")); + m_button_OK->SetBackgroundColor(btn_bg_green); + m_button_OK->SetBorderColor(*wxWHITE); + m_button_OK->SetTextColor(wxColour(0xFFFFFE)); + m_button_OK->SetFont(Label::Body_12); + m_button_OK->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_OK->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_OK->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_OK, 0, wxRIGHT, FromDIP(10)); + + m_button_OK->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + if (!validate_input_valid()) return; + data_init(); + show_page2(); + }); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_button_page1_cancel = new Button(parent, _L("Cancel")); + m_button_page1_cancel->SetBackgroundColor(btn_bg_white); + m_button_page1_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_page1_cancel->SetFont(Label::Body_12); + m_button_page1_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page1_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page1_cancel->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_page1_cancel, 0, wxRIGHT, FromDIP(10)); + + m_button_page1_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_CANCEL); }); + + return bSizer_button; +} +static std::string last_directory = ""; +void CreatePrinterPresetDialog::load_texture() { + wxFileDialog dialog(this, _L("Choose a file to import bed texture from (PNG/SVG):"), last_directory, "", file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_texture = ""; + m_upload_svg_tip_text->SetLabelText(_L("Empty")); + last_directory = dialog.GetDirectory().ToUTF8().data(); + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) { + show_error(this, _L("Invalid file format.")); + return; + } + bool try_ok; + if (Utils::is_file_too_large(file_name, try_ok)) { + if (try_ok) { + m_upload_svg_tip_text->SetLabelText(wxString::Format(_L("The file exceeds %d MB, please import again."), STL_SVG_MAX_FILE_SIZE_MB)); + } else { + m_upload_svg_tip_text->SetLabelText(_L("Exception in obtaining file size, please import again.")); + } + return; + } + m_custom_texture = file_name; + wxGCDC dc; + auto text = wxControl::Ellipsize(_L(boost::filesystem::path(file_name).filename().string()), dc, wxELLIPSIZE_END, FromDIP(200)); + m_upload_svg_tip_text->SetLabelText(text); +} + +void CreatePrinterPresetDialog::load_model_stl() +{ + wxFileDialog dialog(this, _L("Choose an STL file to import bed model from:"), last_directory, "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_model = ""; + m_upload_stl_tip_text->SetLabelText(_L("Empty")); + last_directory = dialog.GetDirectory().ToUTF8().data(); + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".stl")) { + show_error(this, _L("Invalid file format.")); + return; + } + bool try_ok; + if (Utils::is_file_too_large(file_name, try_ok)) { + if (try_ok) { + m_upload_stl_tip_text->SetLabelText(wxString::Format(_L("The file exceeds %d MB, please import again."), STL_SVG_MAX_FILE_SIZE_MB)); + } + else { + m_upload_stl_tip_text->SetLabelText(_L("Exception in obtaining file size, please import again.")); + } + return; + } + m_custom_model = file_name; + wxGCDC dc; + auto text = wxControl::Ellipsize(_L(boost::filesystem::path(file_name).filename().string()), dc, wxELLIPSIZE_END, FromDIP(200)); + m_upload_stl_tip_text->SetLabelText(text); +} + +bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(PresetBundle &temp_preset_bundle, bool just_template) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " is load template: "<< just_template; + std::string selected_vendor_id; + std::string preset_path; + if (m_printer_preset) { + delete m_printer_preset; + m_printer_preset = nullptr; + } + + std::string curr_selected_model = into_u8(m_printer_model->GetStringSelection()); + int nozzle_index = curr_selected_model.find_first_of("@"); + std::string select_model = curr_selected_model.substr(0, nozzle_index - 1); + for (const Slic3r::VendorProfile::PrinterModel &model : m_printer_preset_vendor_selected.models) { + if (model.name == select_model) { + m_printer_preset_model_selected = model; + break; + } + } + if (m_printer_preset_vendor_selected.id.empty() || m_printer_preset_model_selected.id.empty()) { + BOOST_LOG_TRIVIAL(info) << "selected id is not found"; + MessageDialog dlg(this, _L("Preset path is not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + bool is_custom_vendor = false; + if (PRESET_CUSTOM_VENDOR == m_printer_preset_vendor_selected.name || PRESET_CUSTOM_VENDOR == m_printer_preset_vendor_selected.id) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " select custom vendor "; + is_custom_vendor = true; + temp_preset_bundle = *(wxGetApp().preset_bundle); + } else { + selected_vendor_id = m_printer_preset_vendor_selected.id; + + if (boost::filesystem::exists(boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR / selected_vendor_id)) { + preset_path = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).string(); + } else if (boost::filesystem::exists(boost::filesystem::path(Slic3r::resources_dir()) / "profiles" / selected_vendor_id)) { + preset_path = (boost::filesystem::path(Slic3r::resources_dir()) / "profiles").string(); + } + + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Preset path is not found"; + MessageDialog dlg(this, _L("Preset path is not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + try { + temp_preset_bundle.load_vendor_configs_from_json(preset_path, selected_vendor_id, PresetBundle::LoadConfigBundleAttribute::LoadSystem, + ForwardCompatibilitySubstitutionRule::EnableSilent); + } catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "load vendor fonfigs form json failed"; + MessageDialog dlg(this, _L("The printer model was not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + if (!just_template) { + std::string dir_user_presets = wxGetApp().app_config->get("preset_folder"); + if (dir_user_presets.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "default user presets path"; + temp_preset_bundle.load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::EnableSilent); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "user presets path"; + temp_preset_bundle.load_user_presets(dir_user_presets, ForwardCompatibilitySubstitutionRule::EnableSilent); + } + } + } + //get model varient + std::string model_varient = into_u8(m_printer_model->GetStringSelection()); + size_t index_at = model_varient.find(" @ "); + size_t index_nozzle = model_varient.find("nozzle"); + std::string varient; + if (index_at != std::string::npos && index_nozzle != std::string::npos) { + varient = model_varient.substr(index_at + 3, index_nozzle - index_at - 4); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "get nozzle failed"; + MessageDialog dlg(this, _L("The nozzle diameter is not fond, place reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + const Preset *temp_printer_preset = is_custom_vendor ? temp_preset_bundle.printers.find_custom_preset_by_model_and_variant(m_printer_preset_model_selected.id, varient) : + temp_preset_bundle.printers.find_system_preset_by_model_and_variant(m_printer_preset_model_selected.id, varient); + + if (temp_printer_preset) { + m_printer_preset = new Preset(*temp_printer_preset); + } else { + MessageDialog dlg(this, _L("The printer preset is not fond, place reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + if (!just_template) { + temp_preset_bundle.printers.select_preset_by_name(m_printer_preset->name, true); + temp_preset_bundle.update_compatible(PresetSelectCompatibleType::Always); + } else { + selected_vendor_id = PRESET_TEMPLATE_DIR; + preset_path.clear(); + if (boost::filesystem::exists(boost::filesystem::path(Slic3r::resources_dir()) / PRESET_PROFILES_TEMOLATE_DIR / selected_vendor_id)) { + preset_path = (boost::filesystem::path(Slic3r::resources_dir()) / PRESET_PROFILES_TEMOLATE_DIR).string(); + } + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Preset path is not found"; + MessageDialog dlg(this, _L("Preset path is not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + try { + temp_preset_bundle.load_vendor_configs_from_json(preset_path, selected_vendor_id, PresetBundle::LoadConfigBundleAttribute::LoadSystem, + ForwardCompatibilitySubstitutionRule::EnableSilent); + } catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "load template vendor configs form json failed"; + MessageDialog dlg(this, _L("The printer model was not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + } + + return true; +} + +void CreatePrinterPresetDialog::generate_process_presets_data(std::vector presets, std::string nozzle) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry, and nozzle is: " << nozzle; + std::unordered_map nozzle_diameter_map_ = nozzle_diameter_map; + for (const Preset *preset : presets) { + float nozzle_dia = nozzle_diameter_map_[nozzle]; + assert(nozzle_dia != 0); + + auto layer_height = dynamic_cast(const_cast(preset)->config.option("layer_height", true)); + if (layer_height) + layer_height->value = nozzle_dia / 2; + else + BOOST_LOG_TRIVIAL(info) << "process template has no layer_height"; + + auto initial_layer_print_height = dynamic_cast(const_cast(preset)->config.option("initial_layer_print_height", true)); + if (initial_layer_print_height) + initial_layer_print_height->value = nozzle_dia / 2; + else + BOOST_LOG_TRIVIAL(info) << "process template has no initial_layer_print_height"; + + auto line_width = dynamic_cast(const_cast(preset)->config.option("line_width", true)); + if (line_width) + line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no line_width"; + + auto initial_layer_line_width = dynamic_cast(const_cast(preset)->config.option("initial_layer_line_width", true)); + if (initial_layer_line_width) + initial_layer_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no initial_layer_line_width"; + + auto outer_wall_line_width = dynamic_cast(const_cast(preset)->config.option("outer_wall_line_width", true)); + if (outer_wall_line_width) + outer_wall_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no outer_wall_line_width"; + + auto inner_wall_line_width = dynamic_cast(const_cast(preset)->config.option("inner_wall_line_width", true)); + if (inner_wall_line_width) + inner_wall_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no inner_wall_line_width"; + + auto top_surface_line_width = dynamic_cast(const_cast(preset)->config.option("top_surface_line_width", true)); + if (top_surface_line_width) + top_surface_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no top_surface_line_width"; + + auto sparse_infill_line_width = dynamic_cast(const_cast(preset)->config.option("sparse_infill_line_width", true)); + if (sparse_infill_line_width) + sparse_infill_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no sparse_infill_line_width"; + + auto internal_solid_infill_line_width = dynamic_cast(const_cast(preset)->config.option("internal_solid_infill_line_width", true)); + if (internal_solid_infill_line_width) + internal_solid_infill_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no internal_solid_infill_line_width"; + + auto support_line_width = dynamic_cast(const_cast(preset)->config.option("support_line_width", true)); + if (support_line_width) + support_line_width->value = nozzle_dia; + else + BOOST_LOG_TRIVIAL(info) << "process template has no support_line_width"; + + auto wall_loops = dynamic_cast(const_cast(preset)->config.option("wall_loops", true)); + if (wall_loops) + wall_loops->value = std::max(2, (int) std::ceil(2 * 0.4 / nozzle_dia)); + else + BOOST_LOG_TRIVIAL(info) << "process template has no wall_loops"; + + auto top_shell_layers = dynamic_cast(const_cast(preset)->config.option("top_shell_layers", true)); + if (top_shell_layers) + top_shell_layers->value = std::max(5, (int) std::ceil(5 * 0.4 / nozzle_dia)); + else + BOOST_LOG_TRIVIAL(info) << "process template has no top_shell_layers"; + + auto bottom_shell_layers = dynamic_cast(const_cast(preset)->config.option("bottom_shell_layers", true)); + if (bottom_shell_layers) + bottom_shell_layers->value = std::max(3, (int) std::ceil(3 * 0.4 / nozzle_dia)); + else + BOOST_LOG_TRIVIAL(info) << "process template has no bottom_shell_layers"; + } +} + +void CreatePrinterPresetDialog::update_preset_list_size() +{ + m_scrolled_preset_window->Freeze(); + m_preset_template_panel->SetSizerAndFit(m_filament_sizer); + m_preset_template_panel->SetMinSize(wxSize(FromDIP(660), -1)); + m_preset_template_panel->SetSize(wxSize(FromDIP(660), -1)); + int width = m_preset_template_panel->GetSize().GetWidth(); + int height = m_preset_template_panel->GetSize().GetHeight(); + m_scrolled_preset_window->SetMinSize(wxSize(std::min(1500, width + FromDIP(26)), std::min(600, height))); + m_scrolled_preset_window->SetMaxSize(wxSize(std::min(1500, width + FromDIP(26)), std::min(600, height))); + m_scrolled_preset_window->SetSize(wxSize(std::min(1500, width + FromDIP(26)), std::min(600, height))); + m_page2->SetSizerAndFit(m_page2_sizer); + Layout(); + Fit(); + Refresh(); + adjust_dialog_in_screen(this); + m_scrolled_preset_window->Thaw(); +} + +wxBoxSizer *CreatePrinterPresetDialog::create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector> &radiobox_list) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + RadioBox * radiobox = new RadioBox(parent); + horizontal_sizer->Add(radiobox, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(5)); + radiobox_list.push_back(std::make_pair(radiobox,title)); + int btn_idx = radiobox_list.size() - 1; + radiobox->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { + select_curr_radiobox(radiobox_list, btn_idx); + }); + + wxStaticText *text = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize); + text->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { + select_curr_radiobox(radiobox_list, btn_idx); + }); + horizontal_sizer->Add(text, 0, wxEXPAND | wxLEFT, 0); + + radiobox->SetToolTip(tooltip); + text->SetToolTip(tooltip); + return horizontal_sizer; + +} + +void CreatePrinterPresetDialog::select_curr_radiobox(std::vector> &radiobox_list, int btn_idx) +{ + int len = radiobox_list.size(); + for (int i = 0; i < len; ++i) { + if (i == btn_idx) { + radiobox_list[i].first->SetValue(true); + wxString curr_selected_type = radiobox_list[i].second; + this->Freeze(); + if (curr_selected_type == m_create_type.base_template) { + if (m_printer_model->GetValue() == _L("Select Model")) { + m_filament_preset_template_sizer->Clear(true); + m_filament_preset.clear(); + m_process_preset_template_sizer->Clear(true); + m_process_preset.clear(); + } else { + update_presets_list(true); + } + m_page2->SetSizerAndFit(m_page2_sizer); + } else if (curr_selected_type == m_create_type.base_curr_printer) { + if (m_printer_model->GetValue() == _L("Select Model")) { + m_filament_preset_template_sizer->Clear(true); + m_filament_preset.clear(); + m_process_preset_template_sizer->Clear(true); + m_process_preset.clear(); + } else { + update_presets_list(); + } + m_page2->SetSizerAndFit(m_page2_sizer); + } else if (curr_selected_type == m_create_type.create_printer) { + m_select_printer->Hide(); + m_can_not_find_vendor_combox->Show(); + m_can_not_find_vendor_text->Show(); + m_printer_info_panel->Show(); + if (m_can_not_find_vendor_combox->GetValue()) { + m_custom_vendor_text_ctrl->Show(); + m_custom_model_text_ctrl->Show(); + m_select_vendor->Hide(); + m_select_model->Hide(); + } else { + m_select_vendor->Show(); + m_select_model->Show(); + } + m_page1->SetSizerAndFit(m_page1_sizer); + } else if (curr_selected_type == m_create_type.create_nozzle) { + set_current_visible_printer(); + m_select_vendor->Hide(); + m_select_model->Hide(); + m_can_not_find_vendor_combox->Hide(); + m_can_not_find_vendor_text->Hide(); + m_custom_vendor_text_ctrl->Hide(); + m_custom_model_text_ctrl->Hide(); + m_printer_info_panel->Hide(); + m_select_printer->Show(); + m_page1->SetSizerAndFit(m_page1_sizer); + } + this->Thaw(); + } else { + radiobox_list[i].first->SetValue(false); + } + } + + update_preset_list_size(); +} + +void CreatePrinterPresetDialog::create_printer_page2(wxWindow *parent) +{ + this->SetBackgroundColour(*wxWHITE); + + m_page2_sizer = new wxBoxSizer(wxVERTICAL); + + m_page2_sizer->Add(create_printer_preset_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_page2_sizer->Add(create_presets_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_page2_sizer->Add(create_presets_template_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_page2_sizer->Add(create_page2_btns_item(parent), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + + parent->SetSizerAndFit(m_page2_sizer); + Layout(); + Fit(); + + wxGetApp().UpdateDlgDarkUI(this); +} + +wxBoxSizer *CreatePrinterPresetDialog::create_printer_preset_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_vendor_text = new wxStaticText(parent, wxID_ANY, _L("Printer Preset"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_vendor_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * vertical_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *combobox_title = new wxStaticText(parent, wxID_ANY, m_create_type.base_curr_printer, wxDefaultPosition, wxDefaultSize, 0); + combobox_title->SetFont(::Label::Body_13); + auto size = combobox_title->GetTextExtent(m_create_type.base_curr_printer); + combobox_title->SetMinSize(wxSize(size.x + FromDIP(4), -1)); + combobox_title->Wrap(-1); + vertical_sizer->Add(combobox_title, 0, wxEXPAND | wxALL, 0); + + wxBoxSizer *comboBox_sizer = new wxBoxSizer(wxHORIZONTAL); + m_printer_vendor = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, PRINTER_PRESET_VENDOR_SIZE, 0, nullptr, wxCB_READONLY); + m_printer_vendor->SetValue(_L("Select Vendor")); + m_printer_vendor->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + comboBox_sizer->Add(m_printer_vendor, 0, wxEXPAND, 0); + m_printer_model = new ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, PRINTER_PRESET_MODEL_SIZE, 0, nullptr, wxCB_READONLY); + m_printer_model->SetLabelColor(DEFAULT_PROMPT_TEXT_COLOUR); + m_printer_model->SetValue(_L("Select Model")); + + comboBox_sizer->Add(m_printer_model, 0, wxEXPAND | wxLEFT, FromDIP(10)); + vertical_sizer->Add(comboBox_sizer, 0, wxEXPAND | wxTOP, FromDIP(5)); + + horizontal_sizer->Add(vertical_sizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; + +} + +wxBoxSizer *CreatePrinterPresetDialog::create_presets_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_serial_text = new wxStaticText(parent, wxID_ANY, _L("Presets"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_serial_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *radioBoxSizer = new wxBoxSizer(wxVERTICAL); + + radioBoxSizer->Add(create_radio_item(m_create_type.base_template, parent, wxEmptyString, m_create_presets_btns), 0, wxEXPAND | wxALL, 0); + radioBoxSizer->Add(create_radio_item(m_create_type.base_curr_printer, parent, wxEmptyString, m_create_presets_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + horizontal_sizer->Add(radioBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_presets_template_item(wxWindow *parent) +{ + wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL); + + m_scrolled_preset_window = new wxScrolledWindow(parent); + m_scrolled_preset_window->SetScrollRate(5, 5); + m_scrolled_preset_window->SetBackgroundColour(*wxWHITE); + //m_scrolled_preset_window->SetMinSize(wxSize(FromDIP(1500), FromDIP(-1))); + m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(1500), FromDIP(-1))); + m_scrolled_preset_window->SetSize(wxSize(FromDIP(1500), FromDIP(-1))); + m_scrooled_preset_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_preset_template_panel = new wxPanel(m_scrolled_preset_window); + m_preset_template_panel->SetSize(wxSize(-1, -1)); + m_preset_template_panel->SetBackgroundColour(PRINTER_LIST_COLOUR); + m_preset_template_panel->SetMinSize(wxSize(FromDIP(660), -1)); + m_filament_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_filament_preset_text = new wxStaticText(m_preset_template_panel, wxID_ANY, _L("Filament Preset Template"), wxDefaultPosition, wxDefaultSize); + m_filament_sizer->Add(static_filament_preset_text, 0, wxEXPAND | wxALL, FromDIP(5)); + m_filament_preset_panel = new wxPanel(m_preset_template_panel); + m_filament_preset_template_sizer = new wxGridSizer(3, FromDIP(5), FromDIP(5)); + m_filament_preset_panel->SetSize(PRESET_TEMPLATE_SIZE); + m_filament_preset_panel->SetSizer(m_filament_preset_template_sizer); + m_filament_sizer->Add(m_filament_preset_panel, 0, wxEXPAND | wxALL, FromDIP(5)); + + wxBoxSizer *hori_filament_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel * filament_btn_panel = new wxPanel(m_preset_template_panel); + filament_btn_panel->SetBackgroundColour(FILAMENT_OPTION_COLOUR); + wxStaticText *filament_sel_all_text = new wxStaticText(filament_btn_panel, wxID_ANY, _L("Select All"), wxDefaultPosition, wxDefaultSize); + filament_sel_all_text->SetForegroundColour(SELECT_ALL_OPTION_COLOUR); + filament_sel_all_text->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + select_all_preset_template(m_filament_preset); + e.Skip(); + }); + wxStaticText *filament_desel_all_text = new wxStaticText(filament_btn_panel, wxID_ANY, _L("Deselect All"), wxDefaultPosition, wxDefaultSize); + filament_desel_all_text->SetForegroundColour(SELECT_ALL_OPTION_COLOUR); + filament_desel_all_text->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + deselect_all_preset_template(m_filament_preset); + e.Skip(); + }); + hori_filament_btn_sizer->Add(filament_sel_all_text, 0, wxEXPAND | wxALL, FromDIP(5)); + hori_filament_btn_sizer->Add(filament_desel_all_text, 0, wxEXPAND | wxALL, FromDIP(5)); + filament_btn_panel->SetSizer(hori_filament_btn_sizer); + m_filament_sizer->Add(filament_btn_panel, 0, wxEXPAND, 0); + + wxPanel *split_panel = new wxPanel(m_preset_template_panel, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(10))); + split_panel->SetBackgroundColour(wxColour(*wxWHITE)); + m_filament_sizer->Add(split_panel, 0, wxEXPAND, 0); + + wxStaticText *static_process_preset_text = new wxStaticText(m_preset_template_panel, wxID_ANY, _L("Process Preset Template"), wxDefaultPosition, wxDefaultSize); + m_filament_sizer->Add(static_process_preset_text, 0, wxEXPAND | wxALL, FromDIP(5)); + m_process_preset_panel = new wxPanel(m_preset_template_panel); + m_process_preset_panel->SetSize(PRESET_TEMPLATE_SIZE); + m_process_preset_template_sizer = new wxGridSizer(3, FromDIP(5), FromDIP(5)); + m_process_preset_panel->SetSizer(m_process_preset_template_sizer); + m_filament_sizer->Add(m_process_preset_panel, 0, wxEXPAND | wxALL, FromDIP(5)); + + + wxBoxSizer *hori_process_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel * process_btn_panel = new wxPanel(m_preset_template_panel); + process_btn_panel->SetBackgroundColour(FILAMENT_OPTION_COLOUR); + wxStaticText *process_sel_all_text = new wxStaticText(process_btn_panel, wxID_ANY, _L("Select All"), wxDefaultPosition, wxDefaultSize); + process_sel_all_text->SetForegroundColour(SELECT_ALL_OPTION_COLOUR); + process_sel_all_text->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + select_all_preset_template(m_process_preset); + e.Skip(); + }); + wxStaticText *process_desel_all_text = new wxStaticText(process_btn_panel, wxID_ANY, _L("Deselect All"), wxDefaultPosition, wxDefaultSize); + process_desel_all_text->SetForegroundColour(SELECT_ALL_OPTION_COLOUR); + process_desel_all_text->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + deselect_all_preset_template(m_process_preset); + e.Skip(); + }); + hori_process_btn_sizer->Add(process_sel_all_text, 0, wxEXPAND | wxALL, FromDIP(5)); + hori_process_btn_sizer->Add(process_desel_all_text, 0, wxEXPAND | wxALL, FromDIP(5)); + process_btn_panel->SetSizer(hori_process_btn_sizer); + m_filament_sizer->Add(process_btn_panel, 0, wxEXPAND, 0); + + m_preset_template_panel->SetSizer(m_filament_sizer); + m_scrooled_preset_sizer->Add(m_preset_template_panel, 0, wxEXPAND | wxALL, 0); + m_scrolled_preset_window->SetSizerAndFit(m_scrooled_preset_sizer); + vertical_sizer->Add(m_scrolled_preset_window, 0, wxEXPAND | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return vertical_sizer; +} + +wxBoxSizer *CreatePrinterPresetDialog::create_page2_btns_item(wxWindow *parent) +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_button_page2_back = new Button(parent, _L("Back Page 1")); + m_button_page2_back->SetBackgroundColor(btn_bg_white); + m_button_page2_back->SetBorderColor(wxColour(38, 46, 48)); + m_button_page2_back->SetFont(Label::Body_12); + m_button_page2_back->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_back->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_back->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_page2_back, 0, wxRIGHT, FromDIP(10)); + + m_button_page2_back->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { show_page1(); }); + + m_button_create = new Button(parent, _L("Create")); + m_button_create->SetBackgroundColor(btn_bg_green); + m_button_create->SetBorderColor(*wxWHITE); + m_button_create->SetTextColor(wxColour(0xFFFFFE)); + m_button_create->SetFont(Label::Body_12); + m_button_create->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_create->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_create, 0, wxRIGHT, FromDIP(10)); + + m_button_create->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + const wxString curr_selected_printer_type = curr_create_printer_type(); + const wxString curr_selected_preset_type = curr_create_preset_type(); + + // Confirm if the printer preset exists + if (!m_printer_preset) { + MessageDialog dlg(this, _L("You have not yet chosen which printer preset to create based on. Please choose the vendor and model of the printer"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + if (!save_printable_area_config(m_printer_preset)) { + MessageDialog dlg(this, _L("You have entered an illegal input in the printable area section on the first page. Please check before creating it."), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + show_page1(); + return; + } + + // create preset name + std::string printer_preset_name; + std::string printer_model_name; + std::string printer_nozzle_name; + std::string nozzle_diameter = into_u8(m_nozzle_diameter->GetStringSelection()); + size_t index_mm = nozzle_diameter.find("mm"); + if (std::string::npos != index_mm) { + nozzle_diameter.replace(index_mm, 2, "nozzle"); + } + if (curr_selected_printer_type == m_create_type.create_printer) { + if (m_can_not_find_vendor_combox->GetValue()) { + std::string custom_vendor = into_u8(m_custom_vendor_text_ctrl->GetValue()); + std::string custom_model = into_u8(m_custom_model_text_ctrl->GetValue()); + if (custom_vendor.empty() || custom_model.empty()) { + MessageDialog dlg(this, _L("The custom printer or model is not inputed, place input."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + show_page1(); + return; + } + custom_vendor = remove_special_key(custom_vendor); + custom_model = remove_special_key(custom_model); + boost::algorithm::trim(custom_vendor); + boost::algorithm::trim(custom_model); + + printer_preset_name = custom_vendor + " " + custom_model + " " + nozzle_diameter; + printer_model_name = custom_vendor + " " + custom_model; + } else { + std::string vender_name = into_u8(m_select_vendor->GetStringSelection()); + std::string model_name = into_u8(m_select_model->GetStringSelection()); + printer_preset_name = vender_name + " " + model_name + " " + nozzle_diameter; + printer_model_name = vender_name + " " + model_name; + + } + } else if (curr_selected_printer_type == m_create_type.create_nozzle) { + std::string selected_printer_preset_name = into_u8(m_select_printer->GetStringSelection()); + std::unordered_map>::iterator itor = m_printer_name_to_preset.find(selected_printer_preset_name); + assert(m_printer_name_to_preset.end() != itor); + if (m_printer_name_to_preset.end() != itor) { + std::shared_ptr printer_preset = itor->second; + try{ + printer_model_name = printer_preset->config.opt_string("printer_model", true); + printer_preset_name = printer_model_name + " " + nozzle_diameter; + } + catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " get config printer_model or , and the name is: " << selected_printer_preset_name; + } + + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " don't get printer preset, and the name is: " << selected_printer_preset_name; + } + } + printer_nozzle_name = nozzle_diameter.substr(0, nozzle_diameter.find(" nozzle")); + + // Confirm if the printer preset has a duplicate name + if (!rewritten && preset_bundle->printers.find_preset(printer_preset_name)) { + MessageDialog dlg(this, + _L("The printer preset you created already has a preset with the same name. Do you want to overwrite it?\n\tYes: Overwrite the printer preset with the " + "same name, and filament and process presets with the same preset name will be recreated \nand filament and process presets without the same preset name will be reserve.\n\tCancel: Do not create a preset, return to the " + "creation interface."), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxCANCEL | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (res == wxID_YES) { + rewritten = true; + } else { + return; + } + } + + // Confirm if the filament preset is exist + bool filament_preset_is_exist = false; + std::vector selected_filament_presets; + for (std::pair<::CheckBox *, Preset const *> filament_preset : m_filament_preset) { + if (filament_preset.first->GetValue()) { selected_filament_presets.push_back(filament_preset.second); } + if (!filament_preset_is_exist && preset_bundle->filaments.find_preset(filament_preset.second->alias + " @ " + printer_preset_name) != nullptr) { + filament_preset_is_exist = true; + } + } + if (selected_filament_presets.empty() && !filament_preset_is_exist) { + MessageDialog dlg(this, _L("You need to select at least one filament preset."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + // Confirm if the process preset is exist + bool process_preset_is_exist = false; + std::vector selected_process_presets; + for (std::pair<::CheckBox *, Preset const *> process_preset : m_process_preset) { + if (process_preset.first->GetValue()) { selected_process_presets.push_back(process_preset.second); } + if (!process_preset_is_exist && preset_bundle->prints.find_preset(process_preset.second->alias + " @" + printer_preset_name) != nullptr) { + process_preset_is_exist = true; + } + } + if (selected_process_presets.empty() && !process_preset_is_exist) { + MessageDialog dlg(this, _L("You need to select at least one process preset."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + std::vector successful_preset_names; + if (curr_selected_preset_type == m_create_type.base_template) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base template"; + /****************************** clone filament preset ********************************/ + std::vector failures; + if (!selected_filament_presets.empty()) { + bool create_preset_result = preset_bundle->filaments.clone_presets_for_printer(selected_filament_presets, failures, printer_preset_name, get_filament_id, rewritten); + if (!create_preset_result) { + std::string message; + for (const std::string &failure : failures) { message += "\t" + failure + "\n"; } + MessageDialog dlg(this, _L("Create filament presets failed. As follows:\n") + from_u8(message) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + create_preset_result = preset_bundle->filaments.clone_presets_for_printer(selected_filament_presets, failures, printer_preset_name, + get_filament_id, true); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " printer preset no same preset but filament has same preset, user cancel create the printer preset"; + return; + } + } + // save created successfully preset name + for (Preset const *sucessful_preset : selected_filament_presets) + successful_preset_names.push_back(sucessful_preset->name.substr(0, sucessful_preset->name.find(" @")) + " @" + printer_preset_name); + } + + /****************************** clone process preset ********************************/ + failures.clear(); + if (!selected_process_presets.empty()) { + generate_process_presets_data(selected_process_presets, printer_nozzle_name); + bool create_preset_result = preset_bundle->prints.clone_presets_for_printer(selected_process_presets, failures, printer_preset_name, + get_filament_id, rewritten); + if (!create_preset_result) { + std::string message; + for (const std::string &failure : failures) { message += "\t" + failure + "\n"; } + MessageDialog dlg(this, _L("Create process presets failed. As follows:\n") + from_u8(message) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + create_preset_result = preset_bundle->prints.clone_presets_for_printer(selected_process_presets, failures, printer_preset_name, get_filament_id, true); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " printer preset no same preset but process has same preset, user cancel create the printer preset"; + return; + } + } + } + } else if (curr_selected_preset_type == m_create_type.base_curr_printer) { // create printer and based on printer + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base curr printer"; + /****************************** clone filament preset ********************************/ + std::vector failures; + if (!selected_filament_presets.empty()) { + bool create_preset_result = preset_bundle->filaments.clone_presets_for_printer(selected_filament_presets, failures, printer_preset_name, get_filament_id, rewritten); + if (!create_preset_result) { + std::string message; + for (const std::string& failure : failures) { + message += "\t" + failure + "\n"; + } + MessageDialog dlg(this, _L("Create filament presets failed. As follows:\n") + from_u8(message) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + create_preset_result = preset_bundle->filaments.clone_presets_for_printer(selected_filament_presets, failures, printer_preset_name, get_filament_id, true); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " printer preset no same preset but filament has same preset, user cancel create the printer preset"; + return; + } + } + } + + /****************************** clone process preset ********************************/ + failures.clear(); + if (!selected_process_presets.empty()) { + bool create_preset_result = preset_bundle->prints.clone_presets_for_printer(selected_process_presets, failures, printer_preset_name, get_filament_id, rewritten); + if (!create_preset_result) { + std::string message; + for (const std::string& failure : failures) { + message += "\t" + failure + "\n"; + } + MessageDialog dlg(this, _L("Create process presets failed. As follows:\n") + from_u8(message) + _L("\nDo you want to rewrite it?"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + create_preset_result = preset_bundle->prints.clone_presets_for_printer(selected_process_presets, failures, printer_preset_name, get_filament_id, true); + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " printer preset no same preset but filament has same preset, user cancel create the printer preset"; + return; + } + } + // save created successfully preset name + for (Preset const *sucessful_preset : selected_filament_presets) + successful_preset_names.push_back(sucessful_preset->name.substr(0, sucessful_preset->name.find(" @")) + " @" + printer_preset_name); + } + } + + /****************************** clone printer preset ********************************/ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":creater printer "; + try { + auto printer_model = dynamic_cast(m_printer_preset->config.option("printer_model", true)); + if (printer_model) + printer_model->value = printer_model_name; + + auto printer_variant = dynamic_cast(m_printer_preset->config.option("printer_variant", true)); + if (printer_variant) + printer_variant->value = printer_nozzle_name; + + auto nozzle_diameter = dynamic_cast(m_printer_preset->config.option("nozzle_diameter", true)); + if (nozzle_diameter) { + std::unordered_map::const_iterator iter = nozzle_diameter_map.find(printer_nozzle_name); + if (nozzle_diameter_map.end() != iter) { + nozzle_diameter->values = {iter->second}; + } + } + } + catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " bisic info is not rewritten, may be printer_model, printer_variant, or nozzle_diameter"; + } + preset_bundle->printers.save_current_preset(printer_preset_name, true, false, m_printer_preset); + preset_bundle->update_compatible(PresetSelectCompatibleType::Always); + EndModal(wxID_OK); + + }); + + m_button_page2_cancel = new Button(parent, _L("Cancel")); + m_button_page2_cancel->SetBackgroundColor(btn_bg_white); + m_button_page2_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_page2_cancel->SetFont(Label::Body_12); + m_button_page2_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_page2_cancel->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_page2_cancel, 0, wxRIGHT, FromDIP(10)); + + m_button_page2_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_CANCEL); }); + + return bSizer_button; +} + +void CreatePrinterPresetDialog::show_page1() +{ + m_step_1->SetBitmap(create_scaled_bitmap("step_1", nullptr, FromDIP(20))); + m_step_2->SetBitmap(create_scaled_bitmap("step_2_ready", nullptr, FromDIP(20))); + m_page1->Show(); + m_page2->Hide(); + Refresh(); + Layout(); + Fit(); +} + +void CreatePrinterPresetDialog::show_page2() +{ + m_step_1->SetBitmap(create_scaled_bitmap("step_is_ok", nullptr, FromDIP(20))); + m_step_2->SetBitmap(create_scaled_bitmap("step_2", nullptr, FromDIP(20))); + m_page2->Show(); + m_page1->Hide(); + Refresh(); + Layout(); + Fit(); +} + +bool CreatePrinterPresetDialog::data_init() +{ + std::string nozzle_type = into_u8(m_nozzle_diameter->GetStringSelection()); + size_t index_mm = nozzle_type.find(" mm"); + if (std::string::npos != index_mm) { + nozzle_type = nozzle_type.substr(0, index_mm); + } + float nozzle = nozzle_diameter_map[nozzle_type]; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry and nozzle type is: " << nozzle_type << " and nozzle is: " << nozzle; + + VendorMap vendors; + wxArrayString exist_vendor_choice = get_exist_vendor_choices(vendors); + m_printer_vendor->Set(exist_vendor_choice); + + m_printer_model->Bind(wxEVT_COMBOBOX, &CreatePrinterPresetDialog::on_preset_model_value_change, this); + + m_printer_vendor->Bind(wxEVT_COMBOBOX, [this, vendors, nozzle](wxCommandEvent e) { + m_printer_vendor->SetLabelColor(*wxBLACK); + + std::string curr_selected_vendor = into_u8(m_printer_vendor->GetStringSelection()); + auto iterator = vendors.find(curr_selected_vendor); + if (iterator != vendors.end()) { + m_printer_preset_vendor_selected = iterator->second; + } else { + MessageDialog dlg(this, _L("Vendor is not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + wxArrayString printer_preset_model = printer_preset_sort_with_nozzle_diameter(m_printer_preset_vendor_selected, nozzle); + if (printer_preset_model.size() == 0) { + MessageDialog dlg(this, _L("Current vendor has no models, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + m_printer_model->Set(printer_preset_model); + if (!printer_preset_model.empty()) { + m_printer_model->SetSelection(0); + wxCommandEvent e; + on_preset_model_value_change(e); + update_preset_list_size(); + } + rewritten = false; + e.Skip(); + + }); + return true; + +} + +void CreatePrinterPresetDialog::set_current_visible_printer() +{ + //The entire process of creating a custom printer only needs to be done once + if (m_printer_name_to_preset.size() > 0) return; + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + const std::deque &printer_presets = preset_bundle->printers.get_presets(); + wxArrayString printer_choice; + m_printer_name_to_preset.clear(); + for (const Preset &printer_preset : printer_presets) { + if (printer_preset.is_system || !printer_preset.is_visible) continue; + if (preset_bundle->printers.get_preset_base(printer_preset)->name != printer_preset.name) continue; + printer_choice.push_back(from_u8(printer_preset.name)); + m_printer_name_to_preset[printer_preset.name] = std::make_shared(printer_preset); + } + m_select_printer->Set(printer_choice); +} + +wxArrayString CreatePrinterPresetDialog::printer_preset_sort_with_nozzle_diameter(const VendorProfile &vendor_profile, float nozzle_diameter) +{ + std::vector> preset_sort; + + for (const Slic3r::VendorProfile::PrinterModel &model : vendor_profile.models) { + std::string model_name = model.name; + for (const Slic3r::VendorProfile::PrinterVariant &variant : model.variants) { + try { + float variant_diameter = std::stof(variant.name); + preset_sort.push_back(std::make_pair(variant_diameter, model_name + " @ " + variant.name + " nozzle")); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "nozzle: " << variant_diameter << "model: " << preset_sort.back().second; + } + catch (...) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " prase varient fialed and the model_name is: " << model_name; + continue; + } + } + } + + std::sort(preset_sort.begin(), preset_sort.end(), [](const std::pair &a, const std::pair &b) { return a.first < b.first; }); + + int index_nearest_nozzle = -1; + float nozzle_diameter_diff = 1; + for (int i = 0; i < preset_sort.size(); ++i) { + float curr_nozzle_diameter_diff = std::abs(nozzle_diameter - preset_sort[i].first); + if (curr_nozzle_diameter_diff < nozzle_diameter_diff) { + index_nearest_nozzle = i; + nozzle_diameter_diff = curr_nozzle_diameter_diff; + if (curr_nozzle_diameter_diff == 0) break; + } + } + wxArrayString printer_preset_model_selection; + int right_index = index_nearest_nozzle + 1; + while (index_nearest_nozzle >= 0 || right_index < preset_sort.size()) { + if (index_nearest_nozzle >= 0 && right_index < preset_sort.size()) { + float left_nozzle_diff = std::abs(nozzle_diameter - preset_sort[index_nearest_nozzle].first); + float right_nozzle_diff = std::abs(nozzle_diameter - preset_sort[right_index].first); + bool left_is_little = left_nozzle_diff < right_nozzle_diff; + if (left_is_little) { + printer_preset_model_selection.Add(from_u8(preset_sort[index_nearest_nozzle].second)); + index_nearest_nozzle--; + } else { + printer_preset_model_selection.Add(from_u8(preset_sort[right_index].second)); + right_index++; + } + } else if (index_nearest_nozzle >= 0) { + printer_preset_model_selection.Add(from_u8(preset_sort[index_nearest_nozzle].second)); + index_nearest_nozzle--; + } else if (right_index < preset_sort.size()) { + printer_preset_model_selection.Add(from_u8(preset_sort[right_index].second)); + right_index++; + } + } + return printer_preset_model_selection; +} + +void CreatePrinterPresetDialog::select_all_preset_template(std::vector> &preset_templates) +{ + for (std::pair<::CheckBox *, Preset const *> filament_preset : preset_templates) { + filament_preset.first->SetValue(true); + } +} + +void CreatePrinterPresetDialog::deselect_all_preset_template(std::vector> &preset_templates) +{ + for (std::pair<::CheckBox *, Preset const *> filament_preset : preset_templates) { + filament_preset.first->SetValue(false); + } +} + +void CreatePrinterPresetDialog::update_presets_list(bool just_template) +{ + PresetBundle temp_preset_bundle; + if (!load_system_and_user_presets_with_curr_model(temp_preset_bundle, just_template)) return; + + const std::deque &filament_presets = temp_preset_bundle.filaments.get_presets(); + const std::deque &process_presets = temp_preset_bundle.prints.get_presets(); + + // clear filament preset window sizer + m_preset_template_panel->Freeze(); + clear_preset_combobox(); + + // update filament preset window sizer + for (const Preset &filament_preset : filament_presets) { + if (filament_preset.is_compatible) { + if (filament_preset.is_default) continue; + Preset *temp_filament = new Preset(filament_preset); + wxString filament_name = wxString::FromUTF8(temp_filament->name); + m_filament_preset_template_sizer->Add(create_checkbox(m_filament_preset_panel, temp_filament, filament_name, m_filament_preset), 0, + wxEXPAND, FromDIP(5)); + } + } + + for (const Preset &process_preset : process_presets) { + if (process_preset.is_compatible) { + if (process_preset.is_default) continue; + + Preset *temp_process = new Preset(process_preset); + wxString process_name = wxString::FromUTF8(temp_process->name); + m_process_preset_template_sizer->Add(create_checkbox(m_process_preset_panel, temp_process, process_name, m_process_preset), 0, wxEXPAND, + FromDIP(5)); + } + } + m_preset_template_panel->Thaw(); +} + +void CreatePrinterPresetDialog::clear_preset_combobox() +{ + for (std::pair<::CheckBox *, Preset *> preset : m_filament_preset) { + if (preset.second) { + delete preset.second; + preset.second = nullptr; + } + } + m_filament_preset.clear(); + m_filament_preset_template_sizer->Clear(true); + + for (std::pair<::CheckBox *, Preset *> preset : m_process_preset) { + if (preset.second) { + delete preset.second; + preset.second = nullptr; + } + } + m_process_preset.clear(); + m_process_preset_template_sizer->Clear(true); +} + +bool CreatePrinterPresetDialog::save_printable_area_config(Preset *preset) +{ + const wxString curr_selected_printer_type = curr_create_printer_type(); + DynamicPrintConfig &config = preset->config; + + if (curr_selected_printer_type == m_create_type.create_printer) { + double x = 0; + m_bed_size_x_input->GetTextCtrl()->GetValue().ToDouble(&x); + double y = 0; + m_bed_size_y_input->GetTextCtrl()->GetValue().ToDouble(&y); + double dx = 0; + m_bed_origin_x_input->GetTextCtrl()->GetValue().ToDouble(&dx); + double dy = 0; + m_bed_origin_y_input->GetTextCtrl()->GetValue().ToDouble(&dy); + // range check begin + if (x == 0 || y == 0) { return false; } + double x0 = 0.0; + double y0 = 0.0; + double x1 = x; + double y1 = y; + if (dx >= x || dy >= y) { return false; } + x0 -= dx; + x1 -= dx; + y0 -= dy; + y1 -= dy; + // range check end + std::vector points = {Vec2d(x0, y0), Vec2d(x1, y0), Vec2d(x1, y1), Vec2d(x0, y1)}; + config.set_key_value("printable_area", new ConfigOptionPoints(points)); + + double max_print_height = 0; + m_print_height_input->GetTextCtrl()->GetValue().ToDouble(&max_print_height); + config.set("printable_height", max_print_height); + + Utils::slash_to_back_slash(m_custom_texture); + Utils::slash_to_back_slash(m_custom_model); + config.set("bed_custom_model", m_custom_model); + config.set("bed_custom_texture", m_custom_texture); + } else if(m_create_type.create_nozzle){ + std::string selected_printer_preset_name = into_u8(m_select_printer->GetStringSelection()); + std::unordered_map>::iterator itor = m_printer_name_to_preset.find(selected_printer_preset_name); + assert(m_printer_name_to_preset.end() != itor); + if (m_printer_name_to_preset.end() != itor) { + std::shared_ptr printer_preset = itor->second; + std::vector keys = {"printable_area", "printable_height", "bed_custom_model", "bed_custom_texture"}; + config.apply_only(printer_preset->config, keys, true); + } + } + return true; +} + +bool CreatePrinterPresetDialog::check_printable_area() { + double x = 0; + m_bed_size_x_input->GetTextCtrl()->GetValue().ToDouble(&x); + double y = 0; + m_bed_size_y_input->GetTextCtrl()->GetValue().ToDouble(&y); + double dx = 0; + m_bed_origin_x_input->GetTextCtrl()->GetValue().ToDouble(&dx); + double dy = 0; + m_bed_origin_y_input->GetTextCtrl()->GetValue().ToDouble(&dy); + // range check begin + if (x == 0 || y == 0) { + return false; + } + double x0 = 0.0; + double y0 = 0.0; + double x1 = x; + double y1 = y; + if (dx >= x || dy >= y) { + return false; + } + return true; +} + +bool CreatePrinterPresetDialog::validate_input_valid() +{ + const wxString curr_selected_printer_type = curr_create_printer_type(); + if (curr_selected_printer_type == m_create_type.create_printer) { + std::string vendor_name, model_name; + if (m_can_not_find_vendor_combox->GetValue()) { + vendor_name = into_u8(m_custom_vendor_text_ctrl->GetValue()); + model_name = into_u8(m_custom_model_text_ctrl->GetValue()); + + } else { + vendor_name = into_u8(m_select_vendor->GetStringSelection()); + model_name = into_u8(m_select_model->GetStringSelection()); + } + if ((vendor_name.empty() || model_name.empty())) { + MessageDialog dlg(this, _L("You have not selected the vendor and model or inputed the custom vendor and model."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + vendor_name = remove_special_key(vendor_name); + model_name = remove_special_key(model_name); + if (vendor_name.empty() || model_name.empty()) { + MessageDialog dlg(this, _L("There may be escape characters in the custom printer vendor or model. Please delete and re-enter."), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + boost::algorithm::trim(vendor_name); + boost::algorithm::trim(model_name); + if (vendor_name.empty() || model_name.empty()) { + MessageDialog dlg(this, _L("All inputs in the custom printer vendor or model are spaces. Please re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + + if (check_printable_area() == false) { + MessageDialog dlg(this, _L("Please check bed printable shape and origin input."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + } else if (curr_selected_printer_type == m_create_type.create_nozzle) { + wxString printer_name = m_select_printer->GetStringSelection(); + if (printer_name.empty()) { + MessageDialog dlg(this, _L("You have not yet selected the printer to replace the nozzle, please choose."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return false; + } + } + + return true; +} + +void CreatePrinterPresetDialog::on_preset_model_value_change(wxCommandEvent &e) +{ + m_printer_model->SetLabelColor(*wxBLACK); + if (m_printer_preset_vendor_selected.models.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " selected vendor has no models, and the vendor is: " << m_printer_preset_vendor_selected.id; + return; + } + + wxString curr_selected_preset_type = curr_create_preset_type(); + if (curr_selected_preset_type == m_create_type.base_curr_printer) { + update_presets_list(); + } else if (curr_selected_preset_type == m_create_type.base_template) { + update_presets_list(true); + } + rewritten = false; + + update_preset_list_size(); + + e.Skip(); +} + +wxString CreatePrinterPresetDialog::curr_create_preset_type() +{ + wxString curr_selected_preset_type; + for (const std::pair &presets_radio : m_create_presets_btns) { + if (presets_radio.first->GetValue()) { + curr_selected_preset_type = presets_radio.second; + } + } + return curr_selected_preset_type; +} + +wxString CreatePrinterPresetDialog::curr_create_printer_type() +{ + wxString curr_selected_printer_type; + for (const std::pair &printer_radio : m_create_type_btns) { + if (printer_radio.first->GetValue()) { curr_selected_printer_type = printer_radio.second; } + } + return curr_selected_printer_type; +} + +CreatePresetSuccessfulDialog::CreatePresetSuccessfulDialog(wxWindow *parent, const SuccessType &create_success_type) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, PRINTER == create_success_type ? _L("Create Printer Successful") : _L("Create Filament Successful"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + this->SetBackgroundColour(*wxWHITE); + this->SetSize(wxSize(FromDIP(450), FromDIP(200))); + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + wxBoxSizer *m_main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + horizontal_sizer->Add(0, 0, 0, wxLEFT, FromDIP(30)); + + wxBoxSizer *success_bitmap_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticBitmap *success_bitmap = new wxStaticBitmap(this,wxID_ANY, create_scaled_bitmap("create_success", nullptr, FromDIP(24))); + success_bitmap_sizer->Add(success_bitmap, 0, wxEXPAND, 0); + horizontal_sizer->Add(success_bitmap_sizer, 0, wxEXPAND | wxALL, FromDIP(5)); + + wxBoxSizer *success_text_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *success_text; + wxStaticText *next_step_text; + bool sync_user_preset_need_enabled = wxGetApp().getAgent() && wxGetApp().app_config->get("sync_user_preset") == "false"; + switch (create_success_type) { + case PRINTER: + success_text = new wxStaticText(this, wxID_ANY, _L("Printer Created")); + next_step_text = new wxStaticText(this, wxID_ANY, _L("Please go to printer settings to edit your presets")); + break; + case FILAMENT: + success_text = new wxStaticText(this, wxID_ANY, _L("Filament Created")); + wxString prompt_text = _L("Please go to filament setting to edit your presets if you need.\nPlease note that nozzle temperature, hot bed temperature, and maximum " + "volumetric speed has a significant impact on printing quality. Please set them carefully."); + wxString sync_text = sync_user_preset_need_enabled ? _L("Studio has detected that your user presets synchronization function is not enabled, which may result in unsuccessful Filament settings on " + "the Device page. \nClick \"Sync user presets\" to enable the synchronization function.") : ""; + next_step_text = new wxStaticText(this, wxID_ANY, prompt_text + "\n\n" + sync_text); + break; + } + success_text->SetFont(Label::Head_18); + success_text_sizer->Add(success_text, 0, wxEXPAND, 0); + success_text_sizer->Add(next_step_text, 0, wxEXPAND | wxTOP, FromDIP(5)); + horizontal_sizer->Add(success_text_sizer, 0, wxEXPAND | wxALL, FromDIP(5)); + horizontal_sizer->Add(0, 0, 0, wxLEFT, FromDIP(60)); + + m_main_sizer->Add(horizontal_sizer, 0, wxALL, FromDIP(5)); + + wxBoxSizer *btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(0, 0, 1, wxEXPAND, 0); + switch (create_success_type) { + case PRINTER: + m_button_ok = new Button(this, _L("Printer Setting")); + break; + case FILAMENT: m_button_ok = sync_user_preset_need_enabled ? new Button(this, _L("Sync user presets")) : new Button(this, _L("OK")); + break; + } + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + m_button_ok->SetBackgroundColor(btn_bg_green); + m_button_ok->SetBorderColor(wxColour(*wxWHITE)); + m_button_ok->SetTextColor(wxColour(*wxWHITE)); + m_button_ok->SetFont(Label::Body_12); + m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetCornerRadius(FromDIP(12)); + btn_sizer->Add(m_button_ok, 0, wxRIGHT, FromDIP(10)); + + m_button_ok->Bind(wxEVT_LEFT_DOWN, [this, sync_user_preset_need_enabled](wxMouseEvent &e) { + if (sync_user_preset_need_enabled) { + wxGetApp().app_config->set("sync_user_preset", "true"); + wxGetApp().start_sync_user_preset(); + } + EndModal(wxID_OK); + }); + + if (PRINTER == create_success_type || sync_user_preset_need_enabled) { + m_button_cancel = new Button(this, _L("Cancel")); + m_button_cancel->SetBackgroundColor(btn_bg_white); + m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_cancel->SetTextColor(wxColour(38, 46, 48)); + m_button_cancel->SetFont(Label::Body_12); + m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + btn_sizer->Add(m_button_cancel, 0, wxRIGHT, FromDIP(10)); + m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_CANCEL); }); + } + + m_main_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, FromDIP(15)); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + + SetSizer(m_main_sizer); + Layout(); + Fit(); + wxGetApp().UpdateDlgDarkUI(this); +} + +CreatePresetSuccessfulDialog::~CreatePresetSuccessfulDialog() {} + +void CreatePresetSuccessfulDialog::on_dpi_changed(const wxRect &suggested_rect) { + m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetCornerRadius(FromDIP(12)); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + Layout(); +} + +ExportConfigsDialog::ExportConfigsDialog(wxWindow *parent) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Export Preset Bundle"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + m_exprot_type.preset_bundle = _L("Printer preset bundle(.bbscfg)"); + m_exprot_type.filament_bundle = _L("Filament preset bundle(.bbsflmt)"); + m_exprot_type.printer_preset = _L("Printer presets(.zip)"); + m_exprot_type.filament_preset = _L("Filament presets(.zip)"); + m_exprot_type.process_preset = _L("Process presets(.zip)"); + + this->SetBackgroundColour(*wxWHITE); + this->SetSize(wxSize(FromDIP(600), FromDIP(600))); + + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + m_main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + m_main_sizer->Add(create_export_config_item(this), 0, wxEXPAND | wxALL, FromDIP(5)); + m_main_sizer->Add(create_select_printer(this), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + m_main_sizer->Add(create_button_item(this), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5)); + + data_init(); + + this->SetSizer(m_main_sizer); + + this->Layout(); + this->Fit(); + + wxGetApp().UpdateDlgDarkUI(this); + +} + +ExportConfigsDialog::~ExportConfigsDialog() +{ + for (std::pair printer_preset : m_printer_presets) { + Preset *preset = printer_preset.second; + if (preset) { + delete preset; + preset = nullptr; + } + } + for (std::pair> filament_presets : m_filament_presets) { + for (Preset* preset : filament_presets.second) { + if (preset) { + delete preset; + preset = nullptr; + } + } + } + for (std::pair> filament_presets : m_process_presets) { + for (Preset *preset : filament_presets.second) { + if (preset) { + delete preset; + preset = nullptr; + } + } + } + for (std::pair>> filament_preset : m_filament_name_to_presets) { + for (std::pair printer_name_preset : filament_preset.second) { + Preset *preset = printer_name_preset.second; + if (preset) { + delete preset; + preset = nullptr; + } + } + } + + // Delete the Temp folder + boost::filesystem::path temp_folder(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp"); + if (boost::filesystem::exists(temp_folder)) boost::filesystem::remove_all(temp_folder); +} + +void ExportConfigsDialog::on_dpi_changed(const wxRect &suggested_rect) { + m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetCornerRadius(FromDIP(12)); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + Layout(); +} + +void ExportConfigsDialog::show_export_result(const ExportCase &export_case) +{ + MessageDialog *msg_dlg = nullptr; + switch (export_case) { + case ExportCase::INITIALIZE_FAIL: + msg_dlg = new MessageDialog(this, _L("initialize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + case ExportCase::ADD_FILE_FAIL: + msg_dlg = new MessageDialog(this, _L("add file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + case ExportCase::ADD_BUNDLE_STRUCTURE_FAIL: + msg_dlg = new MessageDialog(this, _L("add bundle structure file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + case ExportCase::FINALIZE_FAIL: + msg_dlg = new MessageDialog(this, _L("finalize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + case ExportCase::OPEN_ZIP_WRITTEN_FILE: + msg_dlg = new MessageDialog(this, _L("open zip written fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + case ExportCase::EXPORT_SUCCESS: + msg_dlg = new MessageDialog(this, _L("Export successful"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + break; + } + + if (msg_dlg) { + msg_dlg->ShowModal(); + delete msg_dlg; + msg_dlg = nullptr; + } +} + +bool ExportConfigsDialog::has_check_box_selected() +{ + for (std::pair<::CheckBox *, Preset *> checkbox_preset : m_preset) { + if (checkbox_preset.first->GetValue()) return true; + } + for (std::pair<::CheckBox *, std::string> checkbox_filament_name : m_printer_name) { + if (checkbox_filament_name.first->GetValue()) return true; + } + + return false; +} + +bool ExportConfigsDialog::preset_is_not_compatible_bbl_printer(Preset *preset) +{ + if (preset->type != Preset::Type::TYPE_PRINT && preset->type != Preset::Type::TYPE_FILAMENT) return true; + PresetBundle * preset_bundle = wxGetApp().preset_bundle; + vector printers; + get_filament_compatible_printer(preset, printers); + if (printers.empty()) return true; + Preset *printer_preset = preset_bundle->printers.find_preset(printers[0], false); + if (!printer_preset) return true; + if (!printer_preset->is_bbl_vendor_preset(preset_bundle)) return true; + return false; +} + +bool ExportConfigsDialog::earse_preset_fields_for_safe(Preset *preset) +{ + if (preset->type != Preset::Type::TYPE_PRINTER) return true; + + boost::filesystem::path file_path(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp" + "/" + (preset->name + ".json")); + preset->file = file_path.make_preferred().string(); + + DynamicPrintConfig &config = preset->config; + config.erase("print_host"); + config.erase("print_host_webui"); + config.erase("printhost_apikey"); + config.erase("printhost_cafile"); + config.erase("printhost_user"); + config.erase("printhost_password"); + config.erase("printhost_port"); + + preset->save(nullptr); + return true; +} + +std::string ExportConfigsDialog::initial_file_path(const wxString &path, const std::string &sub_file_path) +{ + std::string export_path = into_u8(path); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "initial file path and path is:" << export_path << " and sub path is: " << sub_file_path; + boost::filesystem::path printer_export_path = (boost::filesystem::path(export_path) / sub_file_path).make_preferred(); + if (boost::filesystem::exists(printer_export_path)) { + MessageDialog dlg(this, wxString::Format(_L("The '%s' folder already exists in the current directory. Do you want to clear it and rebuild it.\nIf not, a time suffix will be " + "added, and you can modify the name after creation."), sub_file_path), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Same path exists, delete and need to rebuild, and path is: " << printer_export_path.string(); + try { + boost::filesystem::remove_all(printer_export_path); + } catch (...) { + MessageDialog dlg(this, _L(wxString::Format("The file: %s \nin the directory may have been opened by another program. \nPlease close it and try again.", + encode_path(printer_export_path.string().c_str()))), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return "initial_failed"; + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "delete path"; + boost::filesystem::create_directories(printer_export_path); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "create path"; + export_path = printer_export_path.string(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Same path exists, delete and rebuild, and path is: " << export_path; + } else if (wxID_NO == res) { + export_path = printer_export_path.string(); + std::string export_path_with_time; + boost::filesystem::path *printer_export_path_with_time = nullptr; + do { + if (printer_export_path_with_time) { + delete printer_export_path_with_time; + printer_export_path_with_time = nullptr; + } + export_path_with_time = export_path + " " + get_curr_time(); + printer_export_path_with_time = new boost::filesystem::path(export_path_with_time); + } while (boost::filesystem::exists(*printer_export_path_with_time)); + export_path = export_path_with_time; + boost::filesystem::create_directories(*printer_export_path_with_time); + if (printer_export_path_with_time) { + delete printer_export_path_with_time; + printer_export_path_with_time = nullptr; + } + } else { + return ""; + } + } else { + boost::filesystem::create_directories(printer_export_path); + export_path = printer_export_path.string(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Same path exists, delete and rebuild, and path is: " << export_path; + } + return export_path; +} + +std::string ExportConfigsDialog::initial_file_name(const wxString &path, const std::string file_name) +{ + std::string export_path = into_u8(path); + boost::filesystem::path printer_export_path = (boost::filesystem::path(export_path) / file_name).make_preferred(); + if (boost::filesystem::exists(printer_export_path)) { + MessageDialog dlg(this, wxString::Format(_L("The '%s' folder already exists in the current directory. Do you want to clear it and rebuild it.\nIf not, a time suffix will be " + "added, and you can modify the name after creation."), file_name), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + try { + boost::filesystem::remove_all(printer_export_path); + } + catch(...) { + MessageDialog dlg(this, + _L(wxString::Format("The file: %s \nmay have been opened by another program. \nPlease close it and try again.", + encode_path(printer_export_path.string().c_str()))), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return "initial_failed"; + } + export_path = printer_export_path.string(); + } else if (wxID_NO == res) { + export_path = printer_export_path.string(); + export_path = export_path.substr(0, export_path.find(".zip")); + std::string export_path_with_time; + boost::filesystem::path *printer_export_path_with_time = nullptr; + do { + if (printer_export_path_with_time) { + delete printer_export_path_with_time; + printer_export_path_with_time = nullptr; + } + export_path_with_time = export_path + " " + get_curr_time() + ".zip"; + printer_export_path_with_time = new boost::filesystem::path(export_path_with_time); + } while (boost::filesystem::exists(*printer_export_path_with_time)); + export_path = export_path_with_time; + if (printer_export_path_with_time) { + delete printer_export_path_with_time; + printer_export_path_with_time = nullptr; + } + } else { + return ""; + } + } else { + export_path = printer_export_path.string(); + } + return export_path; +} + +wxBoxSizer *ExportConfigsDialog::create_export_config_item(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_serial_text = new wxStaticText(parent, wxID_ANY, _L("Presets"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(static_serial_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *radioBoxSizer = new wxBoxSizer(wxVERTICAL); + + radioBoxSizer->Add(create_radio_item(m_exprot_type.preset_bundle, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxALL, 0); + radioBoxSizer->Add(0, 0, 0, wxTOP, FromDIP(6)); + wxStaticText *static_export_printer_preset_bundle_text = new wxStaticText(parent, wxID_ANY, _L("Printer and all the filament&process presets that belong to the printer. \nCan be shared with others."), wxDefaultPosition, wxDefaultSize); + static_export_printer_preset_bundle_text->SetFont(Label::Body_12); + static_export_printer_preset_bundle_text->SetForegroundColour(wxColour("#6B6B6B")); + radioBoxSizer->Add(static_export_printer_preset_bundle_text, 0, wxEXPAND | wxLEFT, FromDIP(22)); + radioBoxSizer->Add(create_radio_item(m_exprot_type.filament_bundle, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + wxStaticText *static_export_filament_preset_bundle_text = new wxStaticText(parent, wxID_ANY, _L("User's fillment preset set. \nCan be shared with others."), wxDefaultPosition, wxDefaultSize); + static_export_filament_preset_bundle_text->SetFont(Label::Body_12); + static_export_filament_preset_bundle_text->SetForegroundColour(wxColour("#6B6B6B")); + radioBoxSizer->Add(static_export_filament_preset_bundle_text, 0, wxEXPAND | wxLEFT, FromDIP(22)); + radioBoxSizer->Add(create_radio_item(m_exprot_type.printer_preset, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + radioBoxSizer->Add(create_radio_item(m_exprot_type.filament_preset, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + radioBoxSizer->Add(create_radio_item(m_exprot_type.process_preset, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10)); + horizontal_sizer->Add(radioBoxSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return horizontal_sizer; +} + +wxBoxSizer *ExportConfigsDialog::create_radio_item(wxString title, wxWindow *parent, wxString tooltip, std::vector> &radiobox_list) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxHORIZONTAL); + RadioBox * radiobox = new RadioBox(parent); + horizontal_sizer->Add(radiobox, 0, wxEXPAND | wxALL, 0); + horizontal_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(5)); + radiobox_list.push_back(std::make_pair(radiobox, title)); + int btn_idx = radiobox_list.size() - 1; + radiobox->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { + select_curr_radiobox(radiobox_list, btn_idx); + }); + + wxStaticText *text = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize); + text->Bind(wxEVT_LEFT_DOWN, [this, &radiobox_list, btn_idx](wxMouseEvent &e) { + select_curr_radiobox(radiobox_list, btn_idx); + }); + horizontal_sizer->Add(text, 0, wxEXPAND | wxLEFT, 0); + + radiobox->SetToolTip(tooltip); + text->SetToolTip(tooltip); + return horizontal_sizer; +} + +mz_bool ExportConfigsDialog::initial_zip_archive(mz_zip_archive &zip_archive, const std::string &file_path) +{ + mz_zip_zero_struct(&zip_archive); + mz_bool status; + + // Initialize the ZIP file to write to the structure, using memory storage + + std::string export_dir = encode_path(file_path.c_str()); + status = mz_zip_writer_init_file(&zip_archive, export_dir.c_str(), 0); + return status; +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::save_zip_archive_to_file(mz_zip_archive &zip_archive) +{ + // Complete writing of ZIP file + mz_bool status = mz_zip_writer_finalize_archive(&zip_archive); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << "Failed to finalize ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::FINALIZE_FAIL; + } + + // Release ZIP file to write structure and related resources + mz_zip_writer_end(&zip_archive); + + return ExportCase::CASE_COUNT; +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::save_presets_to_zip(const std::string &export_file, const std::vector> &config_paths) +{ + mz_zip_archive zip_archive; + mz_bool status = initial_zip_archive(zip_archive, export_file); + + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << "Failed to initialize ZIP archive"; + return ExportCase::INITIALIZE_FAIL; + } + + for (std::pair config_path : config_paths) { + std::string preset_name = config_path.first; + + // Add a file to the ZIP file + status = mz_zip_writer_add_file(&zip_archive, (preset_name).c_str(), encode_path(config_path.second.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + // status = mz_zip_writer_add_mem(&zip_archive, ("printer/" + printer_preset->name + ".json").c_str(), json_contents, strlen(json_contents), MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << preset_name << " Filament preset failed to add file to ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_FILE_FAIL; + } + BOOST_LOG_TRIVIAL(info) << "Printer preset json add successful: " << preset_name; + } + return save_zip_archive_to_file(zip_archive); +} + +void ExportConfigsDialog::select_curr_radiobox(std::vector> &radiobox_list, int btn_idx) +{ + int len = radiobox_list.size(); + for (int i = 0; i < len; ++i) { + if (i == btn_idx) { + radiobox_list[i].first->SetValue(true); + const wxString &export_type = radiobox_list[i].second; + m_preset_sizer->Clear(true); + m_printer_name.clear(); + m_preset.clear(); + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + this->Freeze(); + if (export_type == m_exprot_type.preset_bundle) { + for (std::pair preset : m_printer_presets) { + std::string preset_name = preset.first; + //printer preset mast have user's filament or process preset or printer preset is user preset + if (m_filament_presets.find(preset_name) == m_filament_presets.end() && m_process_presets.find(preset_name) == m_process_presets.end() && preset.second->is_system) continue; + wxString printer_name = wxString::FromUTF8(preset_name); + m_preset_sizer->Add(create_checkbox(m_presets_window, preset.second, printer_name, m_preset), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(5)); + } + m_serial_text->SetLabel(_L("Only display printer names with changes to printer, filament, and process presets.")); + }else if (export_type == m_exprot_type.filament_bundle) { + for (std::pair>> filament_name_to_preset : m_filament_name_to_presets) { + if (filament_name_to_preset.second.empty()) continue; + bool all_preset_is_compatible_third_printer = true; + for (std::pair filament_preset : filament_name_to_preset.second) { + if (!preset_is_not_compatible_bbl_printer(filament_preset.second)) + all_preset_is_compatible_third_printer = false; + } + if (all_preset_is_compatible_third_printer) continue; + wxString filament_name = wxString::FromUTF8(filament_name_to_preset.first); + m_preset_sizer->Add(create_checkbox(m_presets_window, filament_name, m_printer_name), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(5)); + } + m_serial_text->SetLabel(_L("Only display the filament names with changes to filament presets.")); + } else if (export_type == m_exprot_type.printer_preset) { + for (std::pair preset : m_printer_presets) { + if (preset.second->is_system) continue; + wxString printer_name = wxString::FromUTF8(preset.first); + m_preset_sizer->Add(create_checkbox(m_presets_window, preset.second, printer_name, m_preset), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, + FromDIP(5)); + } + m_serial_text->SetLabel(_L("Only printer names with user printer presets will be displayed, and each preset you choose will be exported as a zip.")); + } else if (export_type == m_exprot_type.filament_preset) { + for (std::pair>> filament_name_to_preset : m_filament_name_to_presets) { + if (filament_name_to_preset.second.empty()) continue; + bool all_preset_is_compatible_third_printer = true; + for (std::pair filament_preset : filament_name_to_preset.second) { + if (!preset_is_not_compatible_bbl_printer(filament_preset.second)) + all_preset_is_compatible_third_printer = false; + } + if (all_preset_is_compatible_third_printer) continue; + wxString filament_name = wxString::FromUTF8(filament_name_to_preset.first); + m_preset_sizer->Add(create_checkbox(m_presets_window, filament_name, m_printer_name), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(5)); + } + m_serial_text->SetLabel(_L("Only the filament names with user filament presets will be displayed, \nand all user filament presets in each filament name you select will be exported as a zip.")); + } else if (export_type == m_exprot_type.process_preset) { + for (std::pair> presets : m_process_presets) { + Preset * printer_preset = preset_bundle->printers.find_preset(presets.first, false); + if (!printer_preset) continue; + if (!printer_preset->is_system) continue; + if (preset_bundle->printers.get_preset_base(*printer_preset) != printer_preset) continue; + for (Preset *preset : presets.second) { + if (!preset->is_system) { + wxString printer_name = wxString::FromUTF8(presets.first); + m_preset_sizer->Add(create_checkbox(m_presets_window, printer_name, m_printer_name), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(5)); + break; + } + } + + } + m_serial_text->SetLabel(_L("Only printer names with changed process presets will be displayed, \nand all user process presets in each printer name you select will be exported as a zip.")); + } + //m_presets_window->SetSizerAndFit(m_preset_sizer); + m_presets_window->Layout(); + m_presets_window->Fit(); + int width = m_presets_window->GetSize().GetWidth(); + int height = m_presets_window->GetSize().GetHeight(); + m_scrolled_preset_window->SetMinSize(wxSize(std::min(1200, width), std::min(600, height))); + m_scrolled_preset_window->SetMaxSize(wxSize(std::min(1200, width), std::min(600, height))); + m_scrolled_preset_window->SetSize(wxSize(std::min(1200, width), std::min(600, height))); + this->SetSizerAndFit(m_main_sizer); + Layout(); + Fit(); + Refresh(); + adjust_dialog_in_screen(this); + this->Thaw(); + } else { + radiobox_list[i].first->SetValue(false); + } + } +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_file(const wxString &path) +{ + std::string export_path = initial_file_path(path, "Printer config bundle"); + if (export_path.empty() || "initial_failed" == export_path) return ExportCase::EXPORT_CANCEL; + BOOST_LOG_TRIVIAL(info) << "Export printer preset bundle"; + + for (std::pair<::CheckBox *, Preset *> checkbox_preset : m_preset) { + if (checkbox_preset.first->GetValue()) { + Preset *printer_preset = checkbox_preset.second; + std::string printer_preset_name_ = printer_preset->name; + + json bundle_structure; + NetworkAgent *agent = wxGetApp().getAgent(); + std::string clock = get_curr_timestmp(); + if (agent) { + bundle_structure["version"] = agent->get_version(); + bundle_structure["bundle_id"] = agent->get_user_id() + "_" + printer_preset_name_ + "_" + clock; + } else { + bundle_structure["version"] = ""; + bundle_structure["bundle_id"] = "offline_" + printer_preset_name_ + "_" + clock; + } + bundle_structure["bundle_type"] = "printer config bundle"; + bundle_structure["printer_preset_name"] = printer_preset_name_; + json printer_config = json::array(); + json filament_configs = json::array(); + json process_configs = json::array(); + + mz_zip_archive zip_archive; + mz_bool status = initial_zip_archive(zip_archive, export_path + "/" + printer_preset->name + ".bbscfg"); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << "Failed to initialize ZIP archive"; + return ExportCase::INITIALIZE_FAIL; + } + + boost::filesystem::path pronter_file_path = boost::filesystem::path(printer_preset->file); + std::string preset_path = pronter_file_path.make_preferred().string(); + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << printer_preset->name << " skip because of the preset file path is empty."; + continue; + } + + // Add a file to the ZIP file + std::string printer_config_file_name = "printer/" + pronter_file_path.filename().string(); + status = mz_zip_writer_add_file(&zip_archive, printer_config_file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + //status = mz_zip_writer_add_mem(&zip_archive, ("printer/" + printer_preset->name + ".json").c_str(), json_contents, strlen(json_contents), MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << printer_preset->name << " Failed to add file to ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_FILE_FAIL; + } + printer_config.push_back(printer_config_file_name); + BOOST_LOG_TRIVIAL(info) << "Printer preset json add successful: " << printer_preset->name; + + const std::string printer_preset_name = printer_preset->name; + std::unordered_map>::iterator iter = m_filament_presets.find(printer_preset_name); + if (m_filament_presets.end() != iter) { + for (Preset *preset : iter->second) { + boost::filesystem::path filament_file_path = boost::filesystem::path(preset->file); + std::string filament_preset_path = filament_file_path.make_preferred().string(); + if (filament_preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export filament preset: " << preset->name << " skip because of the preset file path is empty."; + continue; + } + + std::string filament_config_file_name = "filament/" + filament_file_path.filename().string(); + status = mz_zip_writer_add_file(&zip_archive, filament_config_file_name.c_str(), encode_path(filament_preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << preset->name << " Failed to add file to ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_FILE_FAIL; + } + filament_configs.push_back(filament_config_file_name); + BOOST_LOG_TRIVIAL(info) << "Filament preset json add successful."; + } + } + + iter = m_process_presets.find(printer_preset_name); + if (m_process_presets.end() != iter) { + for (Preset *preset : iter->second) { + boost::filesystem::path process_file_path = boost::filesystem::path(preset->file); + std::string process_preset_path = process_file_path.make_preferred().string(); + if (process_preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export process preset: " << preset->name << " skip because of the preset file path is empty."; + continue; + } + + std::string process_config_file_name = "process/" + process_file_path.filename().string(); + status = mz_zip_writer_add_file(&zip_archive, process_config_file_name.c_str(), encode_path(process_preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << preset->name << " Failed to add file to ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_FILE_FAIL; + } + process_configs.push_back(process_config_file_name); + BOOST_LOG_TRIVIAL(info) << "Process preset json add successful: "; + } + } + + bundle_structure["printer_config"] = printer_config; + bundle_structure["filament_config"] = filament_configs; + bundle_structure["process_config"] = process_configs; + + std::string bundle_structure_str = bundle_structure.dump(); + status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(), MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_BUNDLE_STRUCTURE_FAIL; + } + BOOST_LOG_TRIVIAL(info) << " Success to add file: " << BUNDLE_STRUCTURE_JSON_NAME; + + ExportCase save_result = save_zip_archive_to_file(zip_archive); + if (ExportCase::CASE_COUNT != save_result) return save_result; + } + } + BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully"; + + return ExportCase::EXPORT_SUCCESS; +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_bundle_to_file(const wxString &path) +{ + std::string export_path = initial_file_path(path, "Filament bundle"); + if (export_path.empty() || "initial_failed" == export_path) return ExportCase::EXPORT_CANCEL; + BOOST_LOG_TRIVIAL(info) << "Export filament preset bundle"; + + for (std::pair<::CheckBox *, std::string> checkbox_filament_name : m_printer_name) { + if (checkbox_filament_name.first->GetValue()) { + std::string filament_name = checkbox_filament_name.second; + + json bundle_structure; + NetworkAgent *agent = wxGetApp().getAgent(); + std::string clock = get_curr_timestmp(); + if (agent) { + bundle_structure["version"] = agent->get_version(); + bundle_structure["bundle_id"] = agent->get_user_id() + "_" + filament_name + "_" + clock; + } else { + bundle_structure["version"] = ""; + bundle_structure["bundle_id"] = "offline_" + filament_name + "_" + clock; + } + bundle_structure["bundle_type"] = "filament config bundle"; + bundle_structure["filament_name"] = filament_name; + std::unordered_map vendor_structure; + + mz_zip_archive zip_archive; + mz_bool status = initial_zip_archive(zip_archive, export_path + "/" + filament_name + ".bbsflmt"); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << "Failed to initialize ZIP archive"; + return ExportCase::INITIALIZE_FAIL; + } + + std::unordered_map>>::iterator iter = m_filament_name_to_presets.find(filament_name); + if (m_filament_name_to_presets.end() == iter) { + BOOST_LOG_TRIVIAL(info) << "Filament name do not find, filament name:" << filament_name; + continue; + } + std::set> vendor_to_filament_name; + for (std::pair printer_name_to_preset : iter->second) { + std::string printer_vendor = printer_name_to_preset.first; + if (printer_vendor.empty()) continue; + Preset * filament_preset = printer_name_to_preset.second; + if (preset_is_not_compatible_bbl_printer(filament_preset)) continue; + if (vendor_to_filament_name.find(std::make_pair(printer_vendor, filament_preset->name)) != vendor_to_filament_name.end()) continue; + vendor_to_filament_name.insert(std::make_pair(printer_vendor, filament_preset->name)); + std::string preset_path = boost::filesystem::path(filament_preset->file).make_preferred().string(); + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << filament_preset->name << " skip because of the preset file path is empty."; + continue; + } + // Add a file to the ZIP file + std::string file_name = printer_vendor + "/" + filament_preset->name + ".json"; + status = mz_zip_writer_add_file(&zip_archive, file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0, MZ_DEFAULT_COMPRESSION); + // status = mz_zip_writer_add_mem(&zip_archive, ("printer/" + printer_preset->name + ".json").c_str(), json_contents, strlen(json_contents), MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << filament_preset->name << " Failed to add file to ZIP archive"; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_FILE_FAIL; + } + std::unordered_map::iterator iter = vendor_structure.find(printer_vendor); + if (vendor_structure.end() == iter) { + json j = json::array(); + j.push_back(file_name); + vendor_structure[printer_vendor] = j; + } else { + iter->second.push_back(file_name); + } + BOOST_LOG_TRIVIAL(info) << "Filament preset json add successful: " << filament_preset->name; + } + + for (const std::pair& vendor_name_to_json : vendor_structure) { + json j; + std::string printer_vendor = vendor_name_to_json.first; + j["vendor"] = printer_vendor; + j["filament_path"] = vendor_name_to_json.second; + bundle_structure["printer_vendor"].push_back(j); + } + + std::string bundle_structure_str = bundle_structure.dump(); + status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(), MZ_DEFAULT_COMPRESSION); + if (MZ_FALSE == status) { + BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME; + mz_zip_writer_end(&zip_archive); + return ExportCase::ADD_BUNDLE_STRUCTURE_FAIL; + } + BOOST_LOG_TRIVIAL(info) << " Success to add file: " << BUNDLE_STRUCTURE_JSON_NAME; + + // Complete writing of ZIP file + ExportCase save_result = save_zip_archive_to_file(zip_archive); + if (ExportCase::CASE_COUNT != save_result) return save_result; + } + } + BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully"; + + return ExportCase::EXPORT_SUCCESS; +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_printer_preset_to_file(const wxString &path) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "start exprot printer presets"; + std::string export_file = "Printer presets.zip"; + export_file = initial_file_name(path, export_file); + if (export_file.empty() || "initial_failed" == export_file) return ExportCase::EXPORT_CANCEL; + + std::vector> config_paths; + + for (std::pair<::CheckBox *, Preset *> checkbox_preset : m_preset) { + if (checkbox_preset.first->GetValue()) { + Preset * printer_preset = checkbox_preset.second; + std::string preset_path = boost::filesystem::path(printer_preset->file).make_preferred().string(); + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << printer_preset->name << " skip because of the preset file path is empty."; + continue; + } + std::string preset_name = printer_preset->name + ".json"; + config_paths.push_back(std::make_pair(preset_name, preset_path)); + } + } + + ExportCase save_result = save_presets_to_zip(export_file, config_paths); + if (ExportCase::CASE_COUNT != save_result) return save_result; + + BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully"; + + return ExportCase::EXPORT_SUCCESS; + +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_filament_preset_to_file(const wxString &path) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "start exprot filament presets"; + std::string export_file = "Filament presets.zip"; + export_file = initial_file_name(path, export_file); + if (export_file.empty() || "initial_failed" == export_file) return ExportCase::EXPORT_CANCEL; + + std::vector> config_paths; + + std::set filament_presets; + for (std::pair<::CheckBox *, std::string> checkbox_preset : m_printer_name) { + if (checkbox_preset.first->GetValue()) { + std::string filament_name = checkbox_preset.second; + + std::unordered_map>>::iterator iter = m_filament_name_to_presets.find(filament_name); + if (m_filament_name_to_presets.end() == iter) { + BOOST_LOG_TRIVIAL(info) << "Filament name do not find, filament name:" << filament_name; + continue; + } + for (std::pair printer_name_preset : iter->second) { + Preset * filament_preset = printer_name_preset.second; + if (preset_is_not_compatible_bbl_printer(filament_preset)) continue; + if (filament_presets.find(filament_preset->name) != filament_presets.end()) continue; + filament_presets.insert(filament_preset->name); + std::string preset_path = boost::filesystem::path(filament_preset->file).make_preferred().string(); + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export filament preset: " << filament_preset->name << " skip because of the filament file path is empty."; + continue; + } + + std::string preset_name = filament_preset->name + ".json"; + config_paths.push_back(std::make_pair(preset_name, preset_path)); + } + } + } + + ExportCase save_result = save_presets_to_zip(export_file, config_paths); + if (ExportCase::CASE_COUNT != save_result) return save_result; + + BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully"; + + return ExportCase::EXPORT_SUCCESS; +} + +ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_process_preset_to_file(const wxString &path) +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "start exprot process presets"; + std::string export_file = "Process presets.zip"; + export_file = initial_file_name(path, export_file); + if (export_file.empty() || "initial_failed" == export_file) return ExportCase::EXPORT_CANCEL; + + std::vector> config_paths; + + std::set process_presets; + for (std::pair<::CheckBox *, std::string> checkbox_preset : m_printer_name) { + if (checkbox_preset.first->GetValue()) { + std::string printer_name = checkbox_preset.second; + std::unordered_map>::iterator iter = m_process_presets.find(printer_name); + if (m_process_presets.end() != iter) { + for (Preset *process_preset : iter->second) { + if (preset_is_not_compatible_bbl_printer(process_preset)) continue; + if (process_presets.find(process_preset->name) != process_presets.end()) continue; + process_presets.insert(process_preset->name); + std::string preset_path = boost::filesystem::path(process_preset->file).make_preferred().string(); + if (preset_path.empty()) { + BOOST_LOG_TRIVIAL(info) << "Export process preset: " << process_preset->name << " skip because of the preset file path is empty."; + continue; + } + + std::string preset_name = process_preset->name + ".json"; + config_paths.push_back(std::make_pair(preset_name, preset_path)); + } + } + } + } + + ExportCase save_result = save_presets_to_zip(export_file, config_paths); + if (ExportCase::CASE_COUNT != save_result) return save_result; + + BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully"; + + return ExportCase::EXPORT_SUCCESS; +} + +wxBoxSizer *ExportConfigsDialog::create_button_item(wxWindow* parent) +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_button_ok = new Button(this, _L("OK")); + m_button_ok->SetBackgroundColor(btn_bg_green); + m_button_ok->SetBorderColor(*wxWHITE); + m_button_ok->SetTextColor(wxColour(0xFFFFFE)); + m_button_ok->SetFont(Label::Body_12); + m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_ok->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_ok, 0, wxRIGHT, FromDIP(10)); + + m_button_ok->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + if (!has_check_box_selected()) { + MessageDialog dlg(this, _L("Please select at least one printer or filament."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + wxDirDialog dlg(this, _L("Choose a directory"), from_u8(wxGetApp().app_config->get_last_dir()), wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + wxString path; + if (dlg.ShowModal() == wxID_OK) path = dlg.GetPath(); + ExportCase export_case = ExportCase::EXPORT_CANCEL; + if (!path.IsEmpty()) { + wxGetApp().app_config->update_config_dir(into_u8(path)); + wxGetApp().app_config->save(); + const wxString curr_radio_type = get_curr_radio_type(m_export_type_btns); + + if (curr_radio_type == m_exprot_type.preset_bundle) { + export_case = archive_preset_bundle_to_file(path); + } else if (curr_radio_type == m_exprot_type.filament_bundle) { + export_case = archive_filament_bundle_to_file(path); + } else if (curr_radio_type == m_exprot_type.printer_preset) { + export_case = archive_printer_preset_to_file(path); + } else if (curr_radio_type == m_exprot_type.filament_preset) { + export_case = archive_filament_preset_to_file(path); + } else if (curr_radio_type == m_exprot_type.process_preset) { + export_case = archive_process_preset_to_file(path); + } + } else { + return; + } + show_export_result(export_case); + if (ExportCase::EXPORT_SUCCESS != export_case) return; + + EndModal(wxID_OK); + }); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_button_cancel = new Button(this, _L("Cancel")); + m_button_cancel->SetBackgroundColor(btn_bg_white); + m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); + m_button_cancel->SetFont(Label::Body_12); + m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_button_cancel->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_button_cancel, 0, wxRIGHT, FromDIP(10)); + + m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_CANCEL); }); + + return bSizer_button; +} + +wxBoxSizer *ExportConfigsDialog::create_select_printer(wxWindow *parent) +{ + wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL); + m_serial_text = new wxStaticText(parent, wxID_ANY, _L("Please select a type you want to export"), wxDefaultPosition, wxDefaultSize); + optionSizer->Add(m_serial_text, 0, wxEXPAND | wxALL, 0); + optionSizer->SetMinSize(OPTION_SIZE); + horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + m_scrolled_preset_window = new wxScrolledWindow(parent); + m_scrolled_preset_window->SetScrollRate(5, 5); + m_scrolled_preset_window->SetBackgroundColour(PRINTER_LIST_COLOUR); + m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(660), FromDIP(400))); + m_scrolled_preset_window->SetSize(wxSize(FromDIP(660), FromDIP(400))); + wxBoxSizer *scrolled_window = new wxBoxSizer(wxHORIZONTAL); + + m_presets_window = new wxPanel(m_scrolled_preset_window, wxID_ANY); + m_presets_window->SetBackgroundColour(PRINTER_LIST_COLOUR); + wxBoxSizer *select_printer_sizer = new wxBoxSizer(wxVERTICAL); + + m_preset_sizer = new wxGridSizer(3, FromDIP(5), FromDIP(5)); + select_printer_sizer->Add(m_preset_sizer, 0, wxEXPAND, FromDIP(5)); + m_presets_window->SetSizer(select_printer_sizer); + scrolled_window->Add(m_presets_window, 0, wxEXPAND, 0); + m_scrolled_preset_window->SetSizerAndFit(scrolled_window); + + horizontal_sizer->Add(m_scrolled_preset_window, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + + return horizontal_sizer; +} + +void ExportConfigsDialog::data_init() +{ + // Delete the Temp folder + boost::filesystem::path folder(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp"); + if (boost::filesystem::exists(folder)) boost::filesystem::remove_all(folder); + + boost::system::error_code ec; + boost::filesystem::path user_folder(data_dir() + "/" + PRESET_USER_DIR); + bool temp_folder_exist = true; + if (!boost::filesystem::exists(user_folder)) { + if (!boost::filesystem::create_directories(user_folder, ec)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << user_folder << " "< & printer_presets = preset_bundle.printers.get_presets(); + for (const Preset &printer_preset : printer_presets) { + + std::string preset_name = printer_preset.name; + if (!printer_preset.is_visible || printer_preset.is_default || printer_preset.is_project_embedded) continue; + if (preset_bundle.printers.select_preset_by_name(preset_name, false)) { + preset_bundle.update_compatible(PresetSelectCompatibleType::Always); + + const std::deque &filament_presets = preset_bundle.filaments.get_presets(); + for (const Preset &filament_preset : filament_presets) { + if (filament_preset.is_system || filament_preset.is_default || filament_preset.is_project_embedded) continue; + if (filament_preset.is_compatible) { + Preset *new_filament_preset = new Preset(filament_preset); + m_filament_presets[preset_name].push_back(new_filament_preset); + } + } + + const std::deque &process_presets = preset_bundle.prints.get_presets(); + for (const Preset &process_preset : process_presets) { + if (process_preset.is_system || process_preset.is_default || process_preset.is_project_embedded) continue; + if (process_preset.is_compatible) { + Preset *new_prpcess_preset = new Preset(process_preset); + m_process_presets[preset_name].push_back(new_prpcess_preset); + } + } + + Preset *new_printer_preset = new Preset(printer_preset); + earse_preset_fields_for_safe(new_printer_preset); + m_printer_presets[preset_name] = new_printer_preset; + } + } + const std::deque &filament_presets = preset_bundle.filaments.get_presets(); + for (const Preset &filament_preset : filament_presets) { + if (filament_preset.is_system || filament_preset.is_default) continue; + Preset *new_filament_preset = new Preset(filament_preset); + const Preset *base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset); + + std::string filament_preset_name = base_filament_preset->name; + std::string machine_name = get_machine_name(filament_preset_name); + m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(std::make_pair(get_vendor_name(machine_name), new_filament_preset)); + } +} + +EditFilamentPresetDialog::EditFilamentPresetDialog(wxWindow *parent, FilamentInfomation *filament_info) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Edit Filament"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) + , m_filament_id("") + , m_filament_name("") + , m_vendor_name("") + , m_filament_type("") + , m_filament_serial("") +{ + m_preset_tree_creater = new PresetTree(this); + + this->SetBackgroundColour(*wxWHITE); + this->SetMinSize(wxSize(FromDIP(600), -1)); + + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + m_main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxStaticText* basic_infomation = new wxStaticText(this, wxID_ANY, _L("Basic Information")); + basic_infomation->SetFont(Label::Head_16); + + m_main_sizer->Add(basic_infomation, 0, wxALL, FromDIP(10)); + m_filament_id = filament_info->filament_id; + //std::string filament_name = filament_info->filament_name; + bool get_filament_presets = get_same_filament_id_presets(m_filament_id); + // get filament vendor, type, serial, and name + if (get_filament_presets && !m_printer_compatible_presets.empty()) { + std::shared_ptr preset; + for (std::pair>> pair : m_printer_compatible_presets) { + for (std::shared_ptr fialment_preset : pair.second) { + if (fialment_preset->inherits().empty()) { + preset = fialment_preset; + break; + } + } + } + if (!preset.get()) preset = m_printer_compatible_presets.begin()->second[0]; + m_filament_name = get_filament_name(preset->name); + auto vendor_names = dynamic_cast(preset->config.option("filament_vendor")); + if (vendor_names && !vendor_names->values.empty()) m_vendor_name = vendor_names->values[0]; + auto filament_types = dynamic_cast(preset->config.option("filament_type")); + if (filament_types && !filament_types->values.empty()) m_filament_type = filament_types->values[0]; + std::string filament_type = m_filament_type == "PLA-AERO" ? "PLA Aero" : m_filament_type; + size_t index = m_filament_name.find(filament_type); + if (std::string::npos != index && index + filament_type.size() < m_filament_name.size()) { + m_filament_serial = m_filament_name.substr(index + filament_type.size()); + if (m_filament_serial.size() > 2 && m_filament_serial[0] == ' ') { + m_filament_serial = m_filament_serial.substr(1); + } + } + } + + m_main_sizer->Add(create_filament_basic_info(), 0, wxEXPAND | wxALL, 0); + + // divider line + auto line_divider = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + line_divider->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_main_sizer->Add(line_divider, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + m_main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxStaticText *presets_infomation = new wxStaticText(this, wxID_ANY, _L("Filament presets under this filament")); + presets_infomation->SetFont(Label::Head_16); + m_main_sizer->Add(presets_infomation, 0, wxLEFT | wxRIGHT, FromDIP(10)); + + m_main_sizer->Add(create_add_filament_btn(), 0, wxEXPAND | wxALL, 0); + m_main_sizer->Add(create_preset_tree_sizer(), 0, wxEXPAND | wxALL, 0); + m_note_text = new wxStaticText(this, wxID_ANY, _L("Note: If the only preset under this filament is deleted, the filament will be deleted after exiting the dialog.")); + m_main_sizer->Add(m_note_text, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + m_note_text->Hide(); + m_main_sizer->Add(create_button_sizer(), 0, wxEXPAND | wxALL, 0); + + update_preset_tree(); + + this->SetSizer(m_main_sizer); + this->Layout(); + this->Fit(); + wxGetApp().UpdateDlgDarkUI(this); +} +EditFilamentPresetDialog::~EditFilamentPresetDialog() {} + +void EditFilamentPresetDialog::on_dpi_changed(const wxRect &suggested_rect) { + /*m_add_filament_btn->Rescale(); + m_del_filament_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_del_filament_btn->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_del_filament_btn->SetCornerRadius(FromDIP(12)); + m_ok_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetCornerRadius(FromDIP(12));*/ + Layout(); +} + +bool EditFilamentPresetDialog::get_same_filament_id_presets(std::string filament_id) +{ + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + const std::deque &filament_presets = preset_bundle->filaments.get_presets(); + + m_printer_compatible_presets.clear(); + for (Preset const &preset : filament_presets) { + if (preset.is_system || preset.filament_id != filament_id) continue; + std::shared_ptr new_preset = std::make_shared(preset); + std::vector printers; + get_filament_compatible_printer(new_preset.get(), printers); + for (const std::string &printer_name : printers) { + m_printer_compatible_presets[printer_name].push_back(new_preset); + } + } + if (m_printer_compatible_presets.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " no filament presets "; + return false; + } + + return true; +} + +void EditFilamentPresetDialog::update_preset_tree() +{ + this->Freeze(); + m_preset_tree_sizer->Clear(true); + for (std::pair>> printer_and_presets : m_printer_compatible_presets) { + m_preset_tree_sizer->Add(m_preset_tree_creater->get_preset_tree(printer_and_presets), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 5); + } + if (m_printer_compatible_presets.size() == 1 && m_printer_compatible_presets.begin()->second.size() == 1) { + m_note_text->Show(); + } else { + m_note_text->Hide(); + } + + m_preset_tree_panel->SetSizerAndFit(m_preset_tree_sizer); + int width = m_preset_tree_panel->GetSize().GetWidth(); + int height = m_preset_tree_panel->GetSize().GetHeight(); + if (width < m_note_text->GetSize().GetWidth()) { + width = m_note_text->GetSize().GetWidth(); + m_preset_tree_panel->SetMinSize(wxSize(width, -1)); + } + int width_extend = 0; + int height_extend = 0; + if (width > 1000) height_extend = 22; + if (height > 400) width_extend = 22; + m_preset_tree_window->SetMinSize(wxSize(std::min(1000, width + width_extend), std::min(400, height + height_extend))); + m_preset_tree_window->SetMaxSize(wxSize(std::min(1000, width + width_extend), std::min(400, height + height_extend))); + m_preset_tree_window->SetSize(wxSize(std::min(1000, width + width_extend), std::min(400, height + height_extend))); + this->SetSizerAndFit(m_main_sizer); + + this->Layout(); + this->Fit(); + this->Refresh(); + wxGetApp().UpdateDlgDarkUI(this); + adjust_dialog_in_screen(this); + this->Thaw(); +} + +void EditFilamentPresetDialog::delete_preset() +{ + if (m_selected_printer.empty()) return; + if (m_need_delete_preset_index < 0) return; + std::unordered_map>>::iterator iter = m_printer_compatible_presets.find(m_selected_printer); + if (m_printer_compatible_presets.end() == iter) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " can not find printer and printer name is: " << m_selected_printer; + return; + } + std::vector>& filament_presets = iter->second; + if (m_need_delete_preset_index >= filament_presets.size()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " index error and selected printer is: " << m_selected_printer << " and index: " << m_need_delete_preset_index; + return; + } + std::shared_ptr need_delete_preset = filament_presets[m_need_delete_preset_index]; + // is selecetd filament preset + if (need_delete_preset->name == wxGetApp().preset_bundle->filaments.get_selected_preset_name()) { + wxGetApp().get_tab(need_delete_preset->type)->delete_preset(); + // is preset exist? exist: not delete + Preset *delete_preset = wxGetApp().preset_bundle->filaments.find_preset(need_delete_preset->name, false); + if (delete_preset) { + m_selected_printer.clear(); + m_need_delete_preset_index = -1; + return; + } + } else { + Preset *filament_preset = wxGetApp().preset_bundle->filaments.find_preset(need_delete_preset->name); + + // is root preset ? + bool is_base_preset = false; + if (filament_preset && wxGetApp().preset_bundle->filaments.get_preset_base(*filament_preset) == filament_preset) { + is_base_preset = true; + int count = 0; + wxString presets; + for (auto &preset2 : wxGetApp().preset_bundle->filaments) + if (preset2.inherits() == filament_preset->name) { + ++count; + presets += "\n - " + from_u8(preset2.name); + } + wxString msg; + if (count > 0) { + msg = _L("Presets inherited by other presets can not be deleted"); + msg += "\n"; + msg += _L_PLURAL("The following presets inherits this preset.", "The following preset inherits this preset.", count); + wxString title = _L("Delete Preset"); + MessageDialog(this, msg + presets, title, wxOK | wxICON_ERROR).ShowModal(); + m_selected_printer.clear(); + m_need_delete_preset_index = -1; + return; + } + } + wxString msg; + if (is_base_preset) { + msg = _L("Are you sure to delete the selected preset? \nIf the preset corresponds to a filament currently in use on your printer, please reset the filament information for that slot."); + } else { + msg = _L("Are you sure to delete the selected preset?"); + } + if (wxID_YES != MessageDialog(this, msg, _L("Delete preset"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) { + m_selected_printer.clear(); + m_need_delete_preset_index = -1; + return; + } + + // delete preset + std::string next_selected_preset_name = wxGetApp().preset_bundle->filaments.get_selected_preset().name; + bool delete_result = delete_filament_preset_by_name(need_delete_preset->name, next_selected_preset_name); + BOOST_LOG_TRIVIAL(info) << __LINE__ << " filament preset name: " << need_delete_preset->name << (delete_result ? " delete successful" : " delete failed"); + + wxGetApp().preset_bundle->filaments.select_preset_by_name(next_selected_preset_name, true); + for (size_t i = 0; i < wxGetApp().preset_bundle->filament_presets.size(); ++i) { + auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); + if (preset == nullptr) wxGetApp().preset_bundle->filament_presets[i] = wxGetApp().preset_bundle->filaments.get_selected_preset_name(); + } + } + + // remove preset shared_ptr from m_printer_compatible_presets + int last_index = filament_presets.size() - 1; + if (m_need_delete_preset_index != last_index) { + std::swap(filament_presets[m_need_delete_preset_index], filament_presets[last_index]); + } + filament_presets.pop_back(); + if (filament_presets.empty()) m_printer_compatible_presets.erase(iter); + + update_preset_tree(); + + m_selected_printer.clear(); + m_need_delete_preset_index = -1; +} + +void EditFilamentPresetDialog::edit_preset() +{ + if (m_selected_printer.empty()) return; + if (m_need_edit_preset_index < 0) return; + std::unordered_map>>::iterator iter = m_printer_compatible_presets.find(m_selected_printer); + if (m_printer_compatible_presets.end() == iter) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " can not find printer and printer name is: " << m_selected_printer; + return; + } + std::vector> &filament_presets = iter->second; + if (m_need_edit_preset_index >= filament_presets.size()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " index error and selected printer is: " << m_selected_printer << " and index: " << m_need_edit_preset_index; + return; + } + + // edit preset + m_need_edit_preset = filament_presets[m_need_edit_preset_index]; + wxGetApp().params_dialog()->set_editing_filament_id(m_filament_id); + + EndModal(wxID_EDIT); +} + +wxBoxSizer *EditFilamentPresetDialog::create_filament_basic_info() +{ + wxBoxSizer *basic_info_sizer = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer *vendor_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *type_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *serial_sizer = new wxBoxSizer(wxHORIZONTAL); + + //vendor + wxBoxSizer * vendor_key_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_vendor_text = new wxStaticText(this, wxID_ANY, _L("Vendor"), wxDefaultPosition, wxDefaultSize); + vendor_key_sizer->Add(static_vendor_text, 0, wxEXPAND | wxALL, 0); + vendor_key_sizer->SetMinSize(OPTION_SIZE); + vendor_sizer->Add(vendor_key_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer *vendor_value_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *vendor_text = new wxStaticText(this, wxID_ANY, from_u8(m_vendor_name), wxDefaultPosition, wxDefaultSize); + vendor_value_sizer->Add(vendor_text, 0, wxEXPAND | wxALL, 0); + vendor_sizer->Add(vendor_value_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + //type + wxBoxSizer * type_key_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_type_text = new wxStaticText(this, wxID_ANY, _L("Type"), wxDefaultPosition, wxDefaultSize); + type_key_sizer->Add(static_type_text, 0, wxEXPAND | wxALL, 0); + type_key_sizer->SetMinSize(OPTION_SIZE); + type_sizer->Add(type_key_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * type_value_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *type_text = new wxStaticText(this, wxID_ANY, from_u8(m_filament_type), wxDefaultPosition, wxDefaultSize); + type_value_sizer->Add(type_text, 0, wxEXPAND | wxALL, 0); + type_sizer->Add(type_value_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + //serial + wxBoxSizer * serial_key_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *static_serial_text = new wxStaticText(this, wxID_ANY, _L("Serial"), wxDefaultPosition, wxDefaultSize); + serial_key_sizer->Add(static_serial_text, 0, wxEXPAND | wxALL, 0); + serial_key_sizer->SetMinSize(OPTION_SIZE); + serial_sizer->Add(serial_key_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + wxBoxSizer * serial_value_sizer = new wxBoxSizer(wxVERTICAL); + wxString full_filamnet_serial = from_u8(m_filament_serial); + wxString show_filament_serial = full_filamnet_serial; + if (m_filament_serial.size() > 40) { + show_filament_serial = from_u8(m_filament_serial.substr(0, 20)) + "..."; + } + wxStaticText *serial_text = new wxStaticText(this, wxID_ANY, show_filament_serial, wxDefaultPosition, wxDefaultSize); + wxToolTip * toolTip = new wxToolTip(full_filamnet_serial); + serial_text->SetToolTip(toolTip); + serial_value_sizer->Add(serial_text, 0, wxEXPAND | wxALL, 0); + serial_sizer->Add(serial_value_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + basic_info_sizer->Add(vendor_sizer, 0, wxEXPAND | wxALL, 0); + basic_info_sizer->Add(type_sizer, 0, wxEXPAND | wxALL, 0); + basic_info_sizer->Add(serial_sizer, 0, wxEXPAND | wxALL, 0); + + return basic_info_sizer; +} + +wxBoxSizer *EditFilamentPresetDialog::create_add_filament_btn() +{ + wxBoxSizer *add_filament_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + m_add_filament_btn = new Button(this, _L("+ Add Preset")); + m_add_filament_btn->SetFont(Label::Body_10); + m_add_filament_btn->SetPaddingSize(wxSize(FromDIP(8), FromDIP(3))); + m_add_filament_btn->SetCornerRadius(FromDIP(8)); + + StateColor flush_bg_col(std::pair(wxColour(219, 253, 231), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(238, 238, 238), StateColor::Normal)); + + StateColor flush_fg_col(std::pair(wxColour(107, 107, 106), StateColor::Pressed), std::pair(wxColour(107, 107, 106), StateColor::Hovered), + std::pair(wxColour(107, 107, 106), StateColor::Normal)); + + StateColor flush_bd_col(std::pair(wxColour(0, 174, 66), StateColor::Pressed), std::pair(wxColour(0, 174, 66), StateColor::Hovered), + std::pair(wxColour(172, 172, 172), StateColor::Normal)); + + m_add_filament_btn->SetBackgroundColor(flush_bg_col); + m_add_filament_btn->SetBorderColor(flush_bd_col); + m_add_filament_btn->SetTextColor(flush_fg_col); + add_filament_btn_sizer->Add(m_add_filament_btn, 0, wxEXPAND | wxALL, FromDIP(10)); + + m_add_filament_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + CreatePresetForPrinterDialog dlg(nullptr, m_filament_type, m_filament_id, m_vendor_name, m_filament_name); + int res = dlg.ShowModal(); + if (res == wxID_OK) { + if (get_same_filament_id_presets(m_filament_id)) { + update_preset_tree(); + } + } + }); + + return add_filament_btn_sizer; +} + +wxBoxSizer *EditFilamentPresetDialog::create_preset_tree_sizer() +{ + wxBoxSizer *filament_preset_tree_sizer = new wxBoxSizer(wxHORIZONTAL); + m_preset_tree_window = new wxScrolledWindow(this); + m_preset_tree_window->SetScrollRate(5, 5); + m_preset_tree_window->SetBackgroundColour(PRINTER_LIST_COLOUR); + m_preset_tree_window->SetMinSize(wxSize(-1, FromDIP(400))); + m_preset_tree_window->SetMaxSize(wxSize(-1, FromDIP(300))); + m_preset_tree_window->SetSize(wxSize(-1, FromDIP(300))); + m_preset_tree_panel = new wxPanel(m_preset_tree_window); + m_preset_tree_sizer = new wxBoxSizer(wxVERTICAL); + m_preset_tree_panel->SetSizer(m_preset_tree_sizer); + m_preset_tree_panel->SetMinSize(wxSize(580, -1)); + m_preset_tree_panel->SetBackgroundColour(PRINTER_LIST_COLOUR); + wxBoxSizer* m_preset_tree_window_sizer = new wxBoxSizer(wxVERTICAL); + m_preset_tree_window_sizer->Add(m_preset_tree_panel, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); + m_preset_tree_window->SetSizerAndFit(m_preset_tree_window_sizer); + filament_preset_tree_sizer->Add(m_preset_tree_window, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + return filament_preset_tree_sizer; +} + +wxBoxSizer *EditFilamentPresetDialog::create_button_sizer() +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + + m_del_filament_btn = new Button(this, _L("Delete Filament")); + m_del_filament_btn->SetBackgroundColor(*wxRED); + m_del_filament_btn->SetBorderColor(*wxWHITE); + m_del_filament_btn->SetTextColor(wxColour(0xFFFFFE)); + m_del_filament_btn->SetFont(Label::Body_12); + m_del_filament_btn->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_del_filament_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_del_filament_btn->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_del_filament_btn, 0, wxLEFT | wxBOTTOM, FromDIP(10)); + + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_ok_btn = new Button(this, _L("OK")); + m_ok_btn->SetBackgroundColor(btn_bg_green); + m_ok_btn->SetBorderColor(*wxWHITE); + m_ok_btn->SetTextColor(wxColour(0xFFFFFE)); + m_ok_btn->SetFont(Label::Body_12); + m_ok_btn->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_ok_btn, 0, wxRIGHT | wxBOTTOM, FromDIP(10)); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_del_filament_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent &e) { + WarningDialog dlg(this, _L("All the filament presets belong to this filament would be deleted. \nIf you are using this filament on your printer, please reset the filament information for that slot."), _L("Delete filament"), wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxCENTRE); + int res = dlg.ShowModal(); + if (wxID_YES == res) { + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + std::set> inherit_preset_names; + std::set> root_preset_names; + for (std::pair>> printer_and_preset : m_printer_compatible_presets) { + for (std::shared_ptr preset : printer_and_preset.second) { + if (preset->inherits().empty()) { + root_preset_names.insert(preset); + } else { + inherit_preset_names.insert(preset); + } + } + } + // delete inherit preset first + std::string next_selected_preset_name = wxGetApp().preset_bundle->filaments.get_selected_preset().name; + for (std::shared_ptr preset : inherit_preset_names) { + bool delete_result = delete_filament_preset_by_name(preset->name, next_selected_preset_name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherit filament name: " << preset->name << (delete_result ? " delete successful" : " delete failed"); + } + for (std::shared_ptr preset : root_preset_names) { + bool delete_result = delete_filament_preset_by_name(preset->name, next_selected_preset_name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " root filament name: " << preset->name << (delete_result ? " delete successful" : " delete failed"); + } + m_printer_compatible_presets.clear(); + wxGetApp().preset_bundle->filaments.select_preset_by_name(next_selected_preset_name,true); + + for (size_t i = 0; i < wxGetApp().preset_bundle->filament_presets.size(); ++i) { + auto preset = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); + if (preset == nullptr) wxGetApp().preset_bundle->filament_presets[i] = wxGetApp().preset_bundle->filaments.get_selected_preset_name(); + } + EndModal(wxID_OK); + } + e.Skip(); + })); + + m_ok_btn->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_OK); }); + + return bSizer_button; + +} + +CreatePresetForPrinterDialog::CreatePresetForPrinterDialog(wxWindow *parent, std::string filament_type, std::string filament_id, std::string filament_vendor, std::string filament_name) + : DPIDialog(parent ? parent : nullptr, wxID_ANY, _L("Add Preset"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) + , m_filament_id(filament_id) + , m_filament_name(filament_name) + , m_filament_vendor(filament_vendor) + , m_filament_type(filament_type) +{ + m_preset_bundle = std::make_shared(*(wxGetApp().preset_bundle)); + get_visible_printer_and_compatible_filament_presets(); + + this->SetBackgroundColour(*wxWHITE); + + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL); + // top line + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + wxStaticText *basic_infomation = new wxStaticText(this, wxID_ANY, _L("Add preset for new printer")); + basic_infomation->SetFont(Label::Head_16); + main_sizer->Add(basic_infomation, 0, wxALL, FromDIP(10)); + + main_sizer->Add(create_selected_printer_preset_sizer(), 0, wxALL, FromDIP(10)); + main_sizer->Add(create_selected_filament_preset_sizer(), 0, wxALL, FromDIP(10)); + main_sizer->Add(create_button_sizer(), 0, wxEXPAND | wxALL, FromDIP(10)); + + this->SetSizer(main_sizer); + this->Layout(); + this->Fit(); + wxGetApp().UpdateDlgDarkUI(this); +} + +CreatePresetForPrinterDialog::~CreatePresetForPrinterDialog() {} + +void CreatePresetForPrinterDialog::on_dpi_changed(const wxRect &suggested_rect) { + m_ok_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetCornerRadius(FromDIP(12)); + m_cancel_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_cancel_btn->SetMaxSize(wxSize(FromDIP(58), FromDIP(24))); + m_cancel_btn->SetCornerRadius(FromDIP(12)); + Layout(); +} + +void CreatePresetForPrinterDialog::get_visible_printer_and_compatible_filament_presets() +{ + const std::deque &printer_presets = m_preset_bundle->printers.get_presets(); + m_printer_compatible_filament_presets.clear(); + for (const Preset &printer_preset : printer_presets) { + if (printer_preset.is_visible) { + if (m_preset_bundle->printers.get_preset_base(printer_preset) != &printer_preset) continue; + if (m_preset_bundle->printers.select_preset_by_name(printer_preset.name, true)) { + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Always); + const std::deque &filament_presets = m_preset_bundle->filaments.get_presets(); + for (const Preset &filament_preset : filament_presets) { + if (filament_preset.is_default || !filament_preset.is_compatible || filament_preset.is_project_embedded) continue; + ConfigOptionStrings *filament_types; + const Preset * filament_preset_base = m_preset_bundle->filaments.get_preset_base(filament_preset); + if (filament_preset_base == &filament_preset) { + filament_types = dynamic_cast(const_cast(&filament_preset)->config.option("filament_type")); + } else { + filament_types = dynamic_cast(const_cast(filament_preset_base)->config.option("filament_type")); + } + + if (filament_types && filament_types->values.empty()) continue; + const std::string filament_type = filament_types->values[0]; + std::string filament_type_ = m_filament_type == "PLA Aero" ? "PLA-AERO" : m_filament_type; + if (filament_type == filament_type_) { + m_printer_compatible_filament_presets[printer_preset.name].push_back(std::make_shared(filament_preset)); + } + } + } + } + } +} + +wxBoxSizer *CreatePresetForPrinterDialog::create_selected_printer_preset_sizer() +{ + wxBoxSizer *select_preseter_preset_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *printer_text = new wxStaticText(this, wxID_ANY, _L("Printer"), wxDefaultPosition, wxDefaultSize); + select_preseter_preset_sizer->Add(printer_text, 0, wxEXPAND | wxALL, 0); + m_selected_printer = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, PRINTER_PRESET_MODEL_SIZE, 0, nullptr, wxCB_READONLY); + select_preseter_preset_sizer->Add(m_selected_printer, 0, wxEXPAND | wxTOP, FromDIP(5)); + + wxArrayString printer_choices; + for (std::pair>> printer_to_filament_presets : m_printer_compatible_filament_presets) { + auto compatible_printer_name = printer_to_filament_presets.first; + if (compatible_printer_name.empty()) { + BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << " a printer has no name"; + continue; + } + wxString printer_name = from_u8(compatible_printer_name); + printer_choices.push_back(printer_name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry, and visible printer is: " << compatible_printer_name; + } + m_selected_printer->Set(printer_choices); + + return select_preseter_preset_sizer; +} + +wxBoxSizer *CreatePresetForPrinterDialog::create_selected_filament_preset_sizer() +{ + wxBoxSizer * select_filament_preset_sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText *printer_text = new wxStaticText(this, wxID_ANY, _L("Copy preset from filament"), wxDefaultPosition, wxDefaultSize); + select_filament_preset_sizer->Add(printer_text, 0, wxEXPAND | wxALL, 0); + m_selected_filament = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, PRINTER_PRESET_MODEL_SIZE, 0, nullptr, wxCB_READONLY); + select_filament_preset_sizer->Add(m_selected_filament, 0, wxEXPAND | wxTOP, FromDIP(5)); + + m_selected_printer->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + wxString printer_name = m_selected_printer->GetStringSelection(); + std::unordered_map>>::iterator filament_iter = m_printer_compatible_filament_presets.find(into_u8(printer_name)); + if (m_printer_compatible_filament_presets.end() != filament_iter) { + filament_choice_to_filament_preset.clear(); + wxArrayString filament_choices; + for (std::shared_ptr filament_preset : filament_iter->second) { + wxString filament_name = wxString::FromUTF8(filament_preset->name); + filament_choice_to_filament_preset[filament_name] = filament_preset; + filament_choices.push_back(filament_name); + } + m_selected_filament->Set(filament_choices); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " count of compatible filament presets :" << filament_choices.size(); + if (filament_choices.size()) { m_selected_filament->SetSelection(0); } + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "printer preset not find compatible filament presets"; + } + }); + + return select_filament_preset_sizer; +} + +wxBoxSizer *CreatePresetForPrinterDialog::create_button_sizer() +{ + wxBoxSizer *bSizer_button = new wxBoxSizer(wxHORIZONTAL); + + bSizer_button->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + m_ok_btn = new Button(this, _L("OK")); + m_ok_btn->SetBackgroundColor(btn_bg_green); + m_ok_btn->SetBorderColor(*wxWHITE); + m_ok_btn->SetTextColor(wxColour(0xFFFFFE)); + m_ok_btn->SetFont(Label::Body_12); + m_ok_btn->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_ok_btn->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_ok_btn, 0, wxRIGHT | wxBOTTOM, FromDIP(10)); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + m_cancel_btn = new Button(this, _L("Cancel")); + m_cancel_btn->SetBackgroundColor(btn_bg_white); + m_cancel_btn->SetBorderColor(wxColour(38, 46, 48)); + m_cancel_btn->SetFont(Label::Body_12); + m_cancel_btn->SetSize(wxSize(FromDIP(58), FromDIP(24))); + m_cancel_btn->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); + m_cancel_btn->SetCornerRadius(FromDIP(12)); + bSizer_button->Add(m_cancel_btn, 0, wxRIGHT | wxBOTTOM, FromDIP(10)); + + m_ok_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + wxString selected_printer_name = m_selected_printer->GetStringSelection(); + std::string printer_name = into_u8(selected_printer_name); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " add preset: get compatible printer name:"; + + wxString filament_preset_name = m_selected_filament->GetStringSelection(); + std::unordered_map>::iterator iter = filament_choice_to_filament_preset.find(filament_preset_name); + if (filament_choice_to_filament_preset.end() != iter) { + std::shared_ptr filament_preset = iter->second; + PresetBundle * preset_bundle = wxGetApp().preset_bundle; + std::vector failures; + DynamicConfig dynamic_config; + dynamic_config.set_key_value("filament_vendor", new ConfigOptionStrings({m_filament_vendor})); + dynamic_config.set_key_value("compatible_printers", new ConfigOptionStrings({printer_name})); + dynamic_config.set_key_value("filament_type", new ConfigOptionStrings({m_filament_type})); + bool res = preset_bundle->filaments.clone_presets_for_filament(filament_preset.get(), failures, m_filament_name, m_filament_id, dynamic_config, printer_name); + if (!res) { + std::string failure_names; + for (std::string &failure : failures) { failure_names += failure + "\n"; } + MessageDialog dlg(this, _L("Some existing presets have failed to be created, as follows:\n") + from_u8(failure_names) + _L("\nDo you want to rewrite it?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE); + if (dlg.ShowModal() == wxID_YES) { + res = preset_bundle->filaments.clone_presets_for_filament(filament_preset.get(), failures, m_filament_name, m_filament_id, dynamic_config, printer_name, true); + BOOST_LOG_TRIVIAL(info) << "clone filament have failures rewritten is successful? " << res; + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "have same name preset and not rewritten"; + return; + } + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "create filament preset successful and name is:" << m_filament_name + " @" + printer_name; + } + + } else { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "filament choice not find filament preset and choice is:" << filament_preset_name; + MessageDialog dlg(this, _L("The filament choice not find filament preset, please reselect it"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), + wxYES | wxYES_DEFAULT | wxCENTRE); + dlg.ShowModal(); + return; + } + + EndModal(wxID_OK); + }); + m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); }); + + return bSizer_button; +} + +PresetTree::PresetTree(EditFilamentPresetDialog * dialog) +: m_parent_dialog(dialog) +{} + +wxPanel *PresetTree::get_preset_tree(std::pair>> printer_and_presets) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + wxPanel * parent = m_parent_dialog->get_preset_tree_panel(); + wxPanel * panel = new wxPanel(parent); + wxColour backgroundColor = parent->GetBackgroundColour(); + panel->SetBackgroundColour(backgroundColor); + const std::string &printer_name = printer_and_presets.first; + sizer->Add(get_root_item(panel, printer_name), 0, wxEXPAND, 0); + int child_count = printer_and_presets.second.size(); + for (int i = 0; i < child_count; i++) { + if (i == child_count - 1) { + sizer->Add(get_child_item(panel, printer_and_presets.second[i], printer_name, i, true), 0, wxEXPAND, 0); + } else { + sizer->Add(get_child_item(panel, printer_and_presets.second[i], printer_name, i, false), 0, wxEXPAND, 0); + } + } + panel->SetSizerAndFit(sizer); + return panel; +} + +wxPanel *PresetTree::get_root_item(wxPanel *parent, const std::string &printer_name) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel * panel = new wxPanel(parent); + wxColour backgroundColor = parent->GetBackgroundColour(); + panel->SetBackgroundColour(backgroundColor); + wxStaticText *preset_name = new wxStaticText(panel, wxID_ANY, from_u8(printer_name)); + preset_name->SetFont(Label::Body_11); + preset_name->SetForegroundColour(*wxBLACK); + sizer->Add(preset_name, 0, wxEXPAND | wxALL, 5); + panel->SetSizer(sizer); + + return panel; +} + +wxPanel *PresetTree::get_child_item(wxPanel *parent, std::shared_ptr preset, std::string printer_name, int preset_index, bool is_last) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + wxPanel * panel = new wxPanel(parent); + wxColour backgroundColor = parent->GetBackgroundColour(); + panel->SetBackgroundColour(backgroundColor); + sizer->Add(0, 0, 0, wxLEFT, 10); + wxPanel *line_left = new wxPanel(panel, wxID_ANY, wxDefaultPosition, is_last ? wxSize(1, 12) : wxSize(1, -1)); + line_left->SetBackgroundColour(*wxBLACK); + sizer->Add(line_left, 0, is_last ? wxALL : wxEXPAND | wxALL, 0); + wxPanel *line_right = new wxPanel(panel, wxID_ANY, wxDefaultPosition, wxSize(10, 1)); + line_right->SetBackgroundColour(*wxBLACK); + sizer->Add(line_right, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(0, 0, 0, wxLEFT, 5); + wxStaticText *preset_name = new wxStaticText(panel, wxID_ANY, from_u8(preset->name)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " create child item: " << preset->name; + preset_name->SetFont(Label::Body_10); + preset_name->SetForegroundColour(*wxBLACK); + sizer->Add(preset_name, 0, wxEXPAND | wxALL, 5); + bool base_id_error = false; + if (preset->inherits() == "" && preset->base_id != "") base_id_error = true; + if (base_id_error) { + std::string wiki_url = "https://wiki.bambulab.com/en/software/bambu-studio/custom-filament-issue"; + wxHyperlinkCtrl *m_download_hyperlink = new wxHyperlinkCtrl(panel, wxID_ANY, _L("[Delete Required]"), wiki_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); + m_download_hyperlink->SetFont(Label::Body_10); + sizer->Add(m_download_hyperlink, 0, wxEXPAND | wxALL, 5); + } + sizer->Add(0, 0, 1, wxEXPAND, 0); + + StateColor flush_bg_col(std::pair(wxColour(219, 253, 231), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(238, 238, 238), StateColor::Normal)); + + StateColor flush_fg_col(std::pair(wxColour(107, 107, 106), StateColor::Pressed), std::pair(wxColour(107, 107, 106), StateColor::Hovered), + std::pair(wxColour(107, 107, 106), StateColor::Normal)); + + StateColor flush_bd_col(std::pair(wxColour(0, 174, 66), StateColor::Pressed), std::pair(wxColour(0, 174, 66), StateColor::Hovered), + std::pair(wxColour(172, 172, 172), StateColor::Normal)); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + + Button *edit_preset_btn = new Button(panel, _L("Edit Preset")); + edit_preset_btn->SetFont(Label::Body_10); + edit_preset_btn->SetPaddingSize(wxSize(8, 3)); + edit_preset_btn->SetCornerRadius(8); + edit_preset_btn->SetBackgroundColor(flush_bg_col); + edit_preset_btn->SetBorderColor(flush_bd_col); + edit_preset_btn->SetTextColor(flush_fg_col); + //edit_preset_btn->Hide(); + sizer->Add(edit_preset_btn, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(0, 0, 0, wxLEFT, 5); + + Button *del_preset_btn = new Button(panel, _L("Delete Preset")); + del_preset_btn->SetFont(Label::Body_10); + del_preset_btn->SetPaddingSize(wxSize(8, 3)); + del_preset_btn->SetCornerRadius(8); + if (base_id_error) { + del_preset_btn->SetBackgroundColor(btn_bg_green); + del_preset_btn->SetBorderColor(btn_bg_green); + del_preset_btn->SetTextColor(wxColour(0xFFFFFE)); + } else { + del_preset_btn->SetBackgroundColor(flush_bg_col); + del_preset_btn->SetBorderColor(flush_bd_col); + del_preset_btn->SetTextColor(flush_fg_col); + } + + //del_preset_btn->Hide(); + sizer->Add(del_preset_btn, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + + edit_preset_btn->Bind(wxEVT_BUTTON, [this, printer_name, preset_index](wxCommandEvent &e) { + wxGetApp().CallAfter([this, printer_name, preset_index]() { edit_preset(printer_name, preset_index); }); + }); + del_preset_btn->Bind(wxEVT_BUTTON, [this, printer_name, preset_index](wxCommandEvent &e) { + wxGetApp().CallAfter([this, printer_name, preset_index]() { delete_preset(printer_name, preset_index); }); + }); + + panel->SetSizer(sizer); + + return panel; +} + +void PresetTree::delete_preset(std::string printer_name, int need_delete_preset_index) +{ + m_parent_dialog->set_printer_name(printer_name); + m_parent_dialog->set_need_delete_preset_index(need_delete_preset_index); + m_parent_dialog->delete_preset(); +} + +void PresetTree::edit_preset(std::string printer_name, int need_edit_preset_index) +{ + m_parent_dialog->set_printer_name(printer_name); + m_parent_dialog->set_need_edit_preset_index(need_edit_preset_index); + m_parent_dialog->edit_preset(); +} + +} // namespace GUI + +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8d8615644..1bce721ef 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4556,11 +4556,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv return; const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - + //Plater s_panel = wxGetApp().plater(); + //GLCanvas3D* s_panel = wxGetApp().plater()->get_current_canvas3D(); + //wxPanel* s_panel = p->status_panel; ImGuiWrapper& imgui = *wxGetApp().imgui(); + //ImGuiWrapper& imgui2 = *wxGetApp().imgui(); + //wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); //BBS: GUI refactor: move to the right imgui.set_next_window_pos(float(canvas_width - right_margin * m_scale), 0.0f, ImGuiCond_Always, 1.0f, 0.0f); + //imgui2.set_next_window_pos(float(canvas_width - right_margin * m_scale), 1.0f, ImGuiCond_Always, 2.0f, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0,0.0)); ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f)); @@ -4673,16 +4678,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv // BBS render column item { - if(callback && !checkbox && !visible) + if (callback && !checkbox && !visible) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(172 / 255.0f, 172 / 255.0f, 172 / 255.0f, 1.00f)); float dummy_size = type == EItemType::None ? window_padding * 3 : ImGui::GetStyle().ItemSpacing.x + icon_size; ImGui::SameLine(dummy_size); imgui.text(columns_offsets[0].first); - - for (auto i = 1; i < columns_offsets.size(); i++) { - ImGui::SameLine(columns_offsets[i].second); - imgui.text(columns_offsets[i].first); - } + for (auto i = 1; i < columns_offsets.size(); i++) { + ImGui::SameLine(columns_offsets[i].second); + imgui.text(columns_offsets[i].first); + } + if (callback && !checkbox && !visible) ImGui::PopStyleColor(1); } @@ -4863,46 +4868,84 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::PopStyleColor(3); ImGui::PopStyleVar(1); ImGui::SameLine(); - imgui.bold_text(_u8L("Color Scheme")); - push_combo_style(); - //xiamian+ - //wxBoxSizer* moreOptionsSizer = new wxBoxSizer(wxVERTICAL); + imgui.bold_text(_u8L("More parameters")); + //push_combo_style(); + + ////xiamian+ + ////wxBoxSizer* moreOptionsSizer = new wxBoxSizer(wxVERTICAL); - ImGui::SameLine(); - const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); - ImGuiComboFlags flags = 0; - if (ImGui::BBLBeginCombo("", view_type_value, flags)) { - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); - for (int i = 0; i < view_type_items_str.size(); i++) { - const bool is_selected = (m_view_type_sel == i); - if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { - m_fold = false; - m_view_type_sel = i; - set_view_type(view_type_items[m_view_type_sel]); - reset_visible(view_type_items[m_view_type_sel]); - // update buffers' render paths - refresh_render_paths(false, false); - update_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - } - if (is_selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::PopStyleVar(1); - ImGui::EndCombo(); - } - pop_combo_style(); - ImGui::SameLine(); - ImGui::Dummy({ window_padding, window_padding }); + //ImGui::SameLine(); + //const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); + //ImGuiComboFlags flags = 0; + //if (ImGui::BBLBeginCombo("", view_type_value, flags)) { + // ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + // //select controller + // for (int i = 0; i < view_type_items_str.size(); i++) { + // const bool is_selected = (m_view_type_sel == i); + // if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { + // m_fold = false; + // m_view_type_sel = i; + // set_view_type(view_type_items[m_view_type_sel]); + // reset_visible(view_type_items[m_view_type_sel]); + // // update buffers' render paths + // refresh_render_paths(false, false); + // update_moves_slider(); + // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + // } + // if (is_selected) { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::PopStyleVar(1); + // ImGui::EndCombo(); + //} + //pop_combo_style(); + - if (m_fold) { + //ImGui::Dummy({ window_padding, window_padding }); + + /* if (m_fold) { legend_height = ImGui::GetStyle().WindowPadding.y + ImGui::GetFrameHeight() + window_padding * 2.5; imgui.end(); ImGui::PopStyleColor(6); ImGui::PopStyleVar(2); return; - } + }*/ + + //ImGui::Dummy({ window_padding, window_padding }); + + //imgui.bold_text(_u8L("Color Scheme")); + + //push_combo_style(); + //ImGui::SameLine(); + //const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); + //ImGuiComboFlags flags = 0; + //if (ImGui::BBLBeginCombo("", view_type_value, flags)) { + // ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + // //select controller + // for (int i = 0; i < view_type_items_str.size(); i++) { + // const bool is_selected = (m_view_type_sel == i); + // if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { + // m_fold = false; + // m_view_type_sel = i; + // set_view_type(view_type_items[m_view_type_sel]); + // reset_visible(view_type_items[m_view_type_sel]); + // // update buffers' render paths + // refresh_render_paths(false, false); + // update_moves_slider(); + // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + // } + // if (is_selected) { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::PopStyleVar(1); + // ImGui::EndCombo(); + //} + //pop_combo_style(); + + //ImGui::SameLine(); + //ImGui::Dummy({ window_padding, window_padding }); // data used to properly align items in columns when showing time std::vector offsets; @@ -4939,199 +4982,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv double koef = imperial_units ? GizmoObjectManipulation::in_to_mm : 1000.0; double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1; - - // used filament statistics - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) { - model_used_filaments_m.push_back(0.0); - model_used_filaments_g.push_back(0.0); - } - else { - double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); - auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - model_used_filaments_m.push_back(model_used_filament_m); - model_used_filaments_g.push_back(model_used_filament_g); - total_model_used_filament_m += model_used_filament_m; - total_model_used_filament_g += model_used_filament_g; - displayed_columns |= ColumnData::Model; - } - } - - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == m_print_statistics.wipe_tower_volumes_per_extruder.end()) { - wipe_tower_used_filaments_m.push_back(0.0); - wipe_tower_used_filaments_g.push_back(0.0); - } - else { - double volume = m_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id); - auto [wipe_tower_used_filament_m, wipe_tower_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - wipe_tower_used_filaments_m.push_back(wipe_tower_used_filament_m); - wipe_tower_used_filaments_g.push_back(wipe_tower_used_filament_g); - total_wipe_tower_used_filament_m += wipe_tower_used_filament_m; - total_wipe_tower_used_filament_g += wipe_tower_used_filament_g; - displayed_columns |= ColumnData::WipeTower; - } - } - - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.flush_per_filament.find(extruder_id) == m_print_statistics.flush_per_filament.end()) { - flushed_filaments_m.push_back(0.0); - flushed_filaments_g.push_back(0.0); - } - else { - double volume = m_print_statistics.flush_per_filament.at(extruder_id); - auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(volume, extruder_id); - flushed_filaments_m.push_back(flushed_filament_m); - flushed_filaments_g.push_back(flushed_filament_g); - total_flushed_filament_m += flushed_filament_m; - total_flushed_filament_g += flushed_filament_g; - displayed_columns |= ColumnData::Flushed; - } - } - - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.support_volumes_per_extruder.find(extruder_id) == m_print_statistics.support_volumes_per_extruder.end()) { - support_used_filaments_m.push_back(0.0); - support_used_filaments_g.push_back(0.0); - } - else { - double volume = m_print_statistics.support_volumes_per_extruder.at(extruder_id); - auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - support_used_filaments_m.push_back(used_filament_m); - support_used_filaments_g.push_back(used_filament_g); - total_support_used_filament_m += used_filament_m; - total_support_used_filament_g += used_filament_g; - displayed_columns |= ColumnData::Support; - } - } - - - // extrusion paths section -> title - ImGui::Dummy({ window_padding, window_padding }); - ImGui::SameLine(); - switch (m_view_type) - { - case EViewType::FeatureType: - { - // calculate offsets to align time/percentage data - char buffer[64]; - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role < erCount) { - labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); - auto [time, percent] = role_time_and_percent(role); - times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); - if (percent == 0) - ::sprintf(buffer, "0%%"); - else - percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); - percents.push_back(buffer); - auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(role); - //model_used_filaments_m.push_back(model_used_filament_m); - //model_used_filaments_g.push_back(model_used_filament_g); - memset(&buffer, 0, sizeof(buffer)); - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", model_used_filament_m); - used_filaments_m.push_back(buffer); - - memset(&buffer, 0, sizeof(buffer)); - ::sprintf(buffer, "%.2f g", model_used_filament_g); - used_filaments_g.push_back(buffer); - } - } - - //BBS: get travel time and percent - { - auto [time, percent] = move_time_and_percent(EMoveType::Travel); - travel_time = (time > 0.0f) ? short_time(get_time_dhms(time)) : ""; - if (percent == 0) - ::sprintf(buffer, "0%%"); - else - percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); - travel_percent = buffer; - } - - offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {_u8L("Percent"), percents}, {_u8L("Used filament"), used_filaments_m}, {"", used_filaments_g}, {_u8L("Display"), {""}}}, icon_size); - append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {_u8L("Percent"), offsets[2]}, {_u8L("Used filament"), offsets[3]}, {"", offsets[4]}, {_u8L("Display"), offsets[5]}}); - break; - } - case EViewType::Height: { imgui.title(_u8L("Layer Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Line Width (mm)")); break; } - case EViewType::Feedrate: - { - imgui.title(_u8L("Speed (mm/s)")); - break; - } - - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } - case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } - case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::LayerTime: { imgui.title(_u8L("Layer Time (s)")); break; } - - case EViewType::Tool: - { - // calculate used filaments data - for (size_t extruder_id : m_extruder_ids) { - if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) - continue; - double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); - - auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - model_used_filaments_m.push_back(model_used_filament_m); - model_used_filaments_g.push_back(model_used_filament_g); - } - - offsets = calculate_offsets({ { "Extruder NNN", {""}}}, icon_size); - append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Used filament"), offsets[1]} }); - break; - } - case EViewType::ColorPrint: - { - std::vector total_filaments; - char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver); - total_filaments.push_back(buffer); - - - std::vector>> title_columns; - if (displayed_columns & ColumnData::Model) { - title_columns.push_back({ _u8L("Filament"), {""} }); - title_columns.push_back({ _u8L("Model"), total_filaments }); - } - if (displayed_columns & ColumnData::Support) { - title_columns.push_back({ _u8L("Support"), total_filaments }); - } - if (displayed_columns & ColumnData::Flushed) { - title_columns.push_back({ _u8L("Flushed"), total_filaments }); - } - if (displayed_columns & ColumnData::WipeTower) { - title_columns.push_back({ _u8L("Tower"), total_filaments }); - } - if ((displayed_columns & ~ColumnData::Model) > 0) { - title_columns.push_back({ _u8L("Total"), total_filaments }); - } - auto offsets_ = calculate_offsets(title_columns, icon_size); - std::vector> title_offsets; - for (int i = 0; i < offsets_.size(); i++) { - title_offsets.push_back({ title_columns[i].first, offsets_[i] }); - color_print_offsets[title_columns[i].first] = offsets_[i]; - } - append_headers(title_offsets); - - break; - } - default: { break; } - } - auto append_option_item = [this, append_item](EMoveType type, std::vector offsets) { auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) { - append_item(EItemType::Rect, color, {{ label , offsets[0] }}, true, visible, [this, type, visible]() { + append_item(EItemType::Rect, color, { { label , offsets[0] } }, true, visible, [this, type, visible]() { m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); }); - }; + }; const bool visible = m_buffers[buffer_id(type)].visible; if (type == EMoveType::Travel) { //BBS: only display travel time in FeatureType view @@ -5147,544 +5007,797 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::ToolChanges], _u8L("Filament Changes"), visible); else if (type == EMoveType::Wipe) append_option_item_with_type(type, Wipe_Color, _u8L("Wipe"), visible); - }; + }; - // extrusion paths section -> items - switch (m_view_type) - { - case EViewType::FeatureType: - { - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role >= erCount) - continue; - const bool visible = is_visible(role); - std::vector> columns_offsets; - columns_offsets.push_back({ labels[i], offsets[0] }); - columns_offsets.push_back({ times[i], offsets[1] }); - columns_offsets.push_back({ percents[i], offsets[2] }); - columns_offsets.push_back({ used_filaments_m[i], offsets[3] }); - columns_offsets.push_back({ used_filaments_g[i], offsets[4] }); - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], columns_offsets, - true, visible, [this, role, visible]() { - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + if (!m_fold) { + //ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1)); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::Spacing(); + ImGui::Dummy({ window_padding, window_padding }); + //ImGui::Spacing(); + //ImGui::Dummy({ window_padding, window_padding }); + //imgui.bold_text(_u8L("Color Scheme")); + ImGui::SameLine(); + imgui.text(_u8L("Color Scheme")); + + push_combo_style(); + ImGui::SameLine(); + const char* view_type_value = view_type_items_str[m_view_type_sel].c_str(); + ImGuiComboFlags flags = 0; + if (ImGui::BBLBeginCombo("", view_type_value, flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + //select controller + for (int i = 0; i < view_type_items_str.size(); i++) { + const bool is_selected = (m_view_type_sel == i); + if (ImGui::BBLSelectable(view_type_items_str[i].c_str(), is_selected)) { + m_fold = false; + m_view_type_sel = i; + set_view_type(view_type_items[m_view_type_sel]); + reset_visible(view_type_items[m_view_type_sel]); // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - }); + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::PopStyleVar(1); + ImGui::EndCombo(); + } + pop_combo_style(); + //ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); + ImGui::Dummy({ window_padding, window_padding }); + ImGui::Dummy({ window_padding, window_padding }); + // used filament statistics + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) { + model_used_filaments_m.push_back(0.0); + model_used_filaments_g.push_back(0.0); + } + else { + double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); + auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + model_used_filaments_m.push_back(model_used_filament_m); + model_used_filaments_g.push_back(model_used_filament_g); + total_model_used_filament_m += model_used_filament_m; + total_model_used_filament_g += model_used_filament_g; + displayed_columns |= ColumnData::Model; + } } - for(auto item : options_items) { - if (item != EMoveType::Travel) { - append_option_item(item, offsets); - } else { - //BBS: show travel time in FeatureType view - const bool visible = m_buffers[buffer_id(item)].visible; + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.wipe_tower_volumes_per_extruder.find(extruder_id) == m_print_statistics.wipe_tower_volumes_per_extruder.end()) { + wipe_tower_used_filaments_m.push_back(0.0); + wipe_tower_used_filaments_g.push_back(0.0); + } + else { + double volume = m_print_statistics.wipe_tower_volumes_per_extruder.at(extruder_id); + auto [wipe_tower_used_filament_m, wipe_tower_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + wipe_tower_used_filaments_m.push_back(wipe_tower_used_filament_m); + wipe_tower_used_filaments_g.push_back(wipe_tower_used_filament_g); + total_wipe_tower_used_filament_m += wipe_tower_used_filament_m; + total_wipe_tower_used_filament_g += wipe_tower_used_filament_g; + displayed_columns |= ColumnData::WipeTower; + } + } + + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.flush_per_filament.find(extruder_id) == m_print_statistics.flush_per_filament.end()) { + flushed_filaments_m.push_back(0.0); + flushed_filaments_g.push_back(0.0); + } + else { + double volume = m_print_statistics.flush_per_filament.at(extruder_id); + auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(volume, extruder_id); + flushed_filaments_m.push_back(flushed_filament_m); + flushed_filaments_g.push_back(flushed_filament_g); + total_flushed_filament_m += flushed_filament_m; + total_flushed_filament_g += flushed_filament_g; + displayed_columns |= ColumnData::Flushed; + } + } + + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.support_volumes_per_extruder.find(extruder_id) == m_print_statistics.support_volumes_per_extruder.end()) { + support_used_filaments_m.push_back(0.0); + support_used_filaments_g.push_back(0.0); + } + else { + double volume = m_print_statistics.support_volumes_per_extruder.at(extruder_id); + auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + support_used_filaments_m.push_back(used_filament_m); + support_used_filaments_g.push_back(used_filament_g); + total_support_used_filament_m += used_filament_m; + total_support_used_filament_g += used_filament_g; + displayed_columns |= ColumnData::Support; + } + } + + + // extrusion paths section -> title + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); + switch (m_view_type) + { + case EViewType::FeatureType: + { + // calculate offsets to align time/percentage data + char buffer[64]; + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role < erCount) { + labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); + auto [time, percent] = role_time_and_percent(role); + times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); + if (percent == 0) + ::sprintf(buffer, "0%%"); + else + percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); + percents.push_back(buffer); + auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(role); + //model_used_filaments_m.push_back(model_used_filament_m); + //model_used_filaments_g.push_back(model_used_filament_g); + memset(&buffer, 0, sizeof(buffer)); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", model_used_filament_m); + used_filaments_m.push_back(buffer); + + memset(&buffer, 0, sizeof(buffer)); + ::sprintf(buffer, "%.2f g", model_used_filament_g); + used_filaments_g.push_back(buffer); + } + } + + //BBS: get travel time and percent + { + auto [time, percent] = move_time_and_percent(EMoveType::Travel); + travel_time = (time > 0.0f) ? short_time(get_time_dhms(time)) : ""; + if (percent == 0) + ::sprintf(buffer, "0%%"); + else + percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%"); + travel_percent = buffer; + } + + offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {_u8L("Percent"), percents}, {_u8L("Used filament"), used_filaments_m}, {"", used_filaments_g}, {_u8L("Display"), {""}} }, icon_size); + append_headers({ {_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {_u8L("Percent"), offsets[2]}, {_u8L("Used filament"), offsets[3]}, {"", offsets[4]}, {_u8L("Display"), offsets[5]} }); + break; + } + case EViewType::Height: { imgui.title(_u8L("Layer Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Line Width (mm)")); break; } + case EViewType::Feedrate: + { + imgui.title(_u8L("Speed (mm/s)")); + break; + } + + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } + case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } + case EViewType::LayerTime: { imgui.title(_u8L("Layer Time (s)")); break; } + + case EViewType::Tool: + { + // calculate used filaments data + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.model_volumes_per_extruder.find(extruder_id) == m_print_statistics.model_volumes_per_extruder.end()) + continue; + double volume = m_print_statistics.model_volumes_per_extruder.at(extruder_id); + + auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + model_used_filaments_m.push_back(model_used_filament_m); + model_used_filaments_g.push_back(model_used_filament_g); + } + + offsets = calculate_offsets({ { "Extruder NNN", {""}} }, icon_size); + append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Used filament"), offsets[1]} }); + break; + } + case EViewType::ColorPrint: + { + std::vector total_filaments; + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver); + total_filaments.push_back(buffer); + + + std::vector>> title_columns; + if (displayed_columns & ColumnData::Model) { + title_columns.push_back({ _u8L("Filament"), {""} }); + title_columns.push_back({ _u8L("Model"), total_filaments }); + } + if (displayed_columns & ColumnData::Support) { + title_columns.push_back({ _u8L("Support"), total_filaments }); + } + if (displayed_columns & ColumnData::Flushed) { + title_columns.push_back({ _u8L("Flushed"), total_filaments }); + } + if (displayed_columns & ColumnData::WipeTower) { + title_columns.push_back({ _u8L("Tower"), total_filaments }); + } + if ((displayed_columns & ~ColumnData::Model) > 0) { + title_columns.push_back({ _u8L("Total"), total_filaments }); + } + auto offsets_ = calculate_offsets(title_columns, icon_size); + std::vector> title_offsets; + for (int i = 0; i < offsets_.size(); i++) { + title_offsets.push_back({ title_columns[i].first, offsets_[i] }); + color_print_offsets[title_columns[i].first] = offsets_[i]; + } + append_headers(title_offsets); + + break; + } + default: { break; } + } + + //auto append_option_item = [this, append_item](EMoveType type, std::vector offsets) { + // auto append_option_item_with_type = [this, offsets, append_item](EMoveType type, const Color& color, const std::string& label, bool visible) { + // append_item(EItemType::Rect, color, { { label , offsets[0] } }, true, visible, [this, type, visible]() { + // m_buffers[buffer_id(type)].visible = !m_buffers[buffer_id(type)].visible; + // // update buffers' render paths + // refresh_render_paths(false, false); + // update_moves_slider(); + // wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + // }); + // }; + // const bool visible = m_buffers[buffer_id(type)].visible; + // if (type == EMoveType::Travel) { + // //BBS: only display travel time in FeatureType view + // append_option_item_with_type(type, Travel_Colors[0], _u8L("Travel"), visible); + // } + // else if (type == EMoveType::Seam) + // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Seams], _u8L("Seams"), visible); + // else if (type == EMoveType::Retract) + // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Retractions], _u8L("Retract"), visible); + // else if (type == EMoveType::Unretract) + // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::Unretractions], _u8L("Unretract"), visible); + // else if (type == EMoveType::Tool_change) + // append_option_item_with_type(type, Options_Colors[(int)EOptionsColors::ToolChanges], _u8L("Filament Changes"), visible); + // else if (type == EMoveType::Wipe) + // append_option_item_with_type(type, Wipe_Color, _u8L("Wipe"), visible); + // }; + + // extrusion paths section -> items + switch (m_view_type) + { + case EViewType::FeatureType: + { + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role >= erCount) + continue; + const bool visible = is_visible(role); std::vector> columns_offsets; - columns_offsets.push_back({ _u8L("Travel"), offsets[0] }); - columns_offsets.push_back({ travel_time, offsets[1] }); - columns_offsets.push_back({ travel_percent, offsets[2] }); - append_item(EItemType::Rect, Travel_Colors[0], columns_offsets, true, visible, [this, item, visible]() { + columns_offsets.push_back({ labels[i], offsets[0] }); + columns_offsets.push_back({ times[i], offsets[1] }); + columns_offsets.push_back({ percents[i], offsets[2] }); + columns_offsets.push_back({ used_filaments_m[i], offsets[3] }); + columns_offsets.push_back({ used_filaments_g[i], offsets[4] }); + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], columns_offsets, + true, visible, [this, role, visible]() { + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + // update buffers' render paths + refresh_render_paths(false, false); + update_moves_slider(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + }); + } + + for (auto item : options_items) { + if (item != EMoveType::Travel) { + append_option_item(item, offsets); + } + else { + //BBS: show travel time in FeatureType view + const bool visible = m_buffers[buffer_id(item)].visible; + std::vector> columns_offsets; + columns_offsets.push_back({ _u8L("Travel"), offsets[0] }); + columns_offsets.push_back({ travel_time, offsets[1] }); + columns_offsets.push_back({ travel_percent, offsets[2] }); + append_item(EItemType::Rect, Travel_Colors[0], columns_offsets, true, visible, [this, item, visible]() { m_buffers[buffer_id(item)].visible = !m_buffers[buffer_id(item)].visible; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - }); + }); + } } + break; } - break; - } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 2); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 2); break; } - case EViewType::Feedrate: { - append_range(m_extrusions.ranges.feedrate, 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_buffers[buffer_id(EMoveType::Travel)].visible; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); - append_item(EItemType::None, Travel_Colors[0], { {_u8L("travel"), offsets[0] }}, true, travel_visible, [this, travel_visible]() { - m_buffers[buffer_id(EMoveType::Travel)].visible = !m_buffers[buffer_id(EMoveType::Travel)].visible; - // update buffers' render paths, and update m_tools.m_tool_colors and m_extrusions.ranges - refresh(*m_gcode_result, wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result)); - update_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - }); - ImGui::PopStyleVar(1); - break; - } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::LayerTime: { append_range(m_extrusions.ranges.layer_duration, 1); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 2); break; } - case EViewType::Tool: - { - // shows only extruders actually used - char buf[64]; - size_t i = 0; - for (unsigned char extruder_id : m_extruder_ids) { - ::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]); - append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_id], { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} }); - i++; + case EViewType::Height: { append_range(m_extrusions.ranges.height, 2); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 2); break; } + case EViewType::Feedrate: { + append_range(m_extrusions.ranges.feedrate, 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_buffers[buffer_id(EMoveType::Travel)].visible; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f)); + append_item(EItemType::None, Travel_Colors[0], { {_u8L("travel"), offsets[0] } }, true, travel_visible, [this, travel_visible]() { + m_buffers[buffer_id(EMoveType::Travel)].visible = !m_buffers[buffer_id(EMoveType::Travel)].visible; + // update buffers' render paths, and update m_tools.m_tool_colors and m_extrusions.ranges + refresh(*m_gcode_result, wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result)); + update_moves_slider(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + }); + ImGui::PopStyleVar(1); + break; } - break; - } - case EViewType::ColorPrint: - { - //BBS: replace model custom gcode with current plate custom gcode - const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; - size_t total_items = 1; - // BBS: no ColorChange type, use ToolChange - //for (size_t extruder_id : m_extruder_ids) { - // total_items += color_print_ranges(extruder_id, custom_gcode_per_print_z).size(); - //} + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } + case EViewType::LayerTime: { append_range(m_extrusions.ranges.layer_duration, 1); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 2); break; } + case EViewType::Tool: + { + // shows only extruders actually used + char buf[64]; + size_t i = 0; + for (unsigned char extruder_id : m_extruder_ids) { + ::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]); + append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_id], { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} }); + i++; + } + break; + } + case EViewType::ColorPrint: + { + //BBS: replace model custom gcode with current plate custom gcode + const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; + size_t total_items = 1; + // BBS: no ColorChange type, use ToolChange + //for (size_t extruder_id : m_extruder_ids) { + // total_items += color_print_ranges(extruder_id, custom_gcode_per_print_z).size(); + //} - const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; - // add scrollable region, if needed - if (need_scrollable) - ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); - // shows only extruders actually used - size_t i = 0; - for (auto extruder_idx : m_extruder_ids) { - const bool filament_visible = m_tools.m_tool_visibles[extruder_idx]; - if (i < model_used_filaments_m.size() && i < model_used_filaments_g.size()) { - std::vector> columns_offsets; - columns_offsets.push_back({ std::to_string(extruder_idx + 1), color_print_offsets[_u8L("Filament")]}); + // shows only extruders actually used + size_t i = 0; + for (auto extruder_idx : m_extruder_ids) { + const bool filament_visible = m_tools.m_tool_visibles[extruder_idx]; + if (i < model_used_filaments_m.size() && i < model_used_filaments_g.size()) { + std::vector> columns_offsets; + columns_offsets.push_back({ std::to_string(extruder_idx + 1), color_print_offsets[_u8L("Filament")] }); - char buf[64]; - float column_sum_m = 0.0f; - float column_sum_g = 0.0f; - if (displayed_columns & ColumnData::Model) { - if ((displayed_columns & ~ColumnData::Model) > 0) - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); - else - ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); - column_sum_m += model_used_filaments_m[i]; - column_sum_g += model_used_filaments_g[i]; - } - if (displayed_columns & ColumnData::Support) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); - column_sum_m += support_used_filaments_m[i]; - column_sum_g += support_used_filaments_g[i]; - } - if (displayed_columns & ColumnData::Flushed) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")]}); - column_sum_m += flushed_filaments_m[i]; - column_sum_g += flushed_filaments_g[i]; - } - if (displayed_columns & ColumnData::WipeTower) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); - column_sum_m += wipe_tower_used_filaments_m[i]; - column_sum_g += wipe_tower_used_filaments_g[i]; - } - if ((displayed_columns & ~ColumnData::Model) > 0) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); - } + char buf[64]; + float column_sum_m = 0.0f; + float column_sum_g = 0.0f; + if (displayed_columns & ColumnData::Model) { + if ((displayed_columns & ~ColumnData::Model) > 0) + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); + else + ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); + column_sum_m += model_used_filaments_m[i]; + column_sum_g += model_used_filaments_g[i]; + } + if (displayed_columns & ColumnData::Support) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); + column_sum_m += support_used_filaments_m[i]; + column_sum_g += support_used_filaments_g[i]; + } + if (displayed_columns & ColumnData::Flushed) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); + column_sum_m += flushed_filaments_m[i]; + column_sum_g += flushed_filaments_g[i]; + } + if (displayed_columns & ColumnData::WipeTower) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); + column_sum_m += wipe_tower_used_filaments_m[i]; + column_sum_g += wipe_tower_used_filaments_g[i]; + } + if ((displayed_columns & ~ColumnData::Model) > 0) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); + } - append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_idx], columns_offsets, false, filament_visible, [this, extruder_idx]() { + append_item(EItemType::Rect, m_tools.m_tool_colors[extruder_idx], columns_offsets, false, filament_visible, [this, extruder_idx]() { m_tools.m_tool_visibles[extruder_idx] = !m_tools.m_tool_visibles[extruder_idx]; // update buffers' render paths refresh_render_paths(false, false); update_moves_slider(); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - }); - } - i++; - } - - if (need_scrollable) - ImGui::EndChild(); - - // Sum of all rows - char buf[64]; - if (m_extruder_ids.size() > 1) { - // Separator - ImGuiWindow* window = ImGui::GetCurrentWindow(); - const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f)); - ImGui::ItemSize(ImVec2(0.0f, 0.0f)); - const bool item_visible = ImGui::ItemAdd(separator, 0); - window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator)); - - std::vector> columns_offsets; - columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")]}); - if (displayed_columns & ColumnData::Model) { - if ((displayed_columns & ~ColumnData::Model) > 0) - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); - else - ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); - } - if (displayed_columns & ColumnData::Support) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); - } - if (displayed_columns & ColumnData::Flushed) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); - } - if (displayed_columns & ColumnData::WipeTower) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); - } - if ((displayed_columns & ~ColumnData::Model) > 0) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, - (total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver); - columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); - } - append_item(EItemType::None, m_tools.m_tool_colors[0], columns_offsets); - } - - //BBS display filament change times - ImGui::Dummy({window_padding, window_padding}); - ImGui::SameLine(); - imgui.text(_u8L("Filament change times") + ":"); - ImGui::SameLine(); - ::sprintf(buf, "%d", m_print_statistics.total_filamentchanges); - imgui.text(buf); - - //BBS display cost - ImGui::Dummy({ window_padding, window_padding }); - ImGui::SameLine(); - imgui.text(_u8L("Cost")+":"); - ImGui::SameLine(); - ::sprintf(buf, "%.2f", ps.total_cost); - imgui.text(buf); - - break; - } - default: { break; } - } - - // partial estimated printing time section - if (m_view_type == EViewType::ColorPrint) { - using Times = std::pair; - using TimesList = std::vector>; - - // helper structure containig the data needed to render the time items - struct PartialTime - { - enum class EType : unsigned char - { - Print, - ColorChange, - Pause - }; - EType type; - int extruder_id; - Color color1; - Color color2; - Times times; - std::pair used_filament {0.0f, 0.0f}; - }; - using PartialTimes = std::vector; - - auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { - PartialTimes items; - - //BBS: replace model custom gcode with current plate custom gcode - std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; - std::vector last_color(m_extruders_count); - for (size_t i = 0; i < m_extruders_count; ++i) { - last_color[i] = m_tools.m_tool_colors[i]; - } - int last_extruder_id = 1; - int color_change_idx = 0; - for (const auto& time_rec : times) { - switch (time_rec.first) - { - case CustomGCode::PausePrint: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); - items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); - custom_gcode_per_print_z.erase(it); - } - break; + }); } - case CustomGCode::ColorChange: { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); - items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); - last_color[it->extruder - 1] = decode_color(it->color); - last_extruder_id = it->extruder; - custom_gcode_per_print_z.erase(it); - } + i++; + } + + if (need_scrollable) + ImGui::EndChild(); + + // Sum of all rows + char buf[64]; + if (m_extruder_ids.size() > 1) { + // Separator + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImRect separator(ImVec2(window->Pos.x + window_padding * 3, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x - window_padding * 3, window->DC.CursorPos.y + 1.0f)); + ImGui::ItemSize(ImVec2(0.0f, 0.0f)); + const bool item_visible = ImGui::ItemAdd(separator, 0); + window->DrawList->AddLine(separator.Min, ImVec2(separator.Max.x, separator.Min.y), ImGui::GetColorU32(ImGuiCol_Separator)); + + std::vector> columns_offsets; + columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")] }); + if (displayed_columns & ColumnData::Model) { + if ((displayed_columns & ~ColumnData::Model) > 0) + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); - - break; + ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); } - default: { break; } + if (displayed_columns & ColumnData::Support) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); } + if (displayed_columns & ColumnData::Flushed) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); + } + if (displayed_columns & ColumnData::WipeTower) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); + } + if ((displayed_columns & ~ColumnData::Model) > 0) { + ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, + (total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver); + columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); + } + append_item(EItemType::None, m_tools.m_tool_colors[0], columns_offsets); } - return items; - }; - - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { - imgui.text(_u8L("Color change")); + //BBS display filament change times + ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); - pos.x += icon_size; - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); - - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second - times.first))); - }; - - auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { - imgui.text(_u8L("Print")); + imgui.text(_u8L("Filament change times") + ":"); ImGui::SameLine(); + ::sprintf(buf, "%d", m_print_statistics.total_filamentchanges); + imgui.text(buf); - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + //BBS display cost + ImGui::Dummy({ window_padding, window_padding }); + ImGui::SameLine(); + imgui.text(_u8L("Cost") + ":"); + ImGui::SameLine(); + ::sprintf(buf, "%.2f", ps.total_cost); + imgui.text(buf); - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; + } + default: { break; } + } - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(times.first))); - if (used_filament.first > 0.0f) { - char buffer[64]; - ImGui::SameLine(offsets[2]); - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); - imgui.text(buffer); + // partial estimated printing time section + if (m_view_type == EViewType::ColorPrint) { + using Times = std::pair; + using TimesList = std::vector>; - ImGui::SameLine(offsets[3]); - ::sprintf(buffer, "%.2f g", used_filament.second); - imgui.text(buffer); - } - }; - - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); - if (!partial_times.empty()) { - labels.clear(); - times.clear(); - - for (const PartialTime& item : partial_times) { - switch (item.type) + // helper structure containig the data needed to render the time items + struct PartialTime + { + enum class EType : unsigned char { - case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } - case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } - case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } - } - times.push_back(short_time(get_time_dhms(item.times.second))); - } + Print, + ColorChange, + Pause + }; + EType type; + int extruder_id; + Color color1; + Color color2; + Times times; + std::pair used_filament{ 0.0f, 0.0f }; + }; + using PartialTimes = std::vector; - std::string longest_used_filament_string; - for (const PartialTime& item : partial_times) { - if (item.used_filament.first > 0.0f) { + auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { + PartialTimes items; + + //BBS: replace model custom gcode with current plate custom gcode + std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z; + std::vector last_color(m_extruders_count); + for (size_t i = 0; i < m_extruders_count; ++i) { + last_color[i] = m_tools.m_tool_colors[i]; + } + int last_extruder_id = 1; + int color_change_idx = 0; + for (const auto& time_rec : times) { + switch (time_rec.first) + { + case CustomGCode::PausePrint: { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); + custom_gcode_per_print_z.erase(it); + } + break; + } + case CustomGCode::ColorChange: { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) }); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); + last_color[it->extruder - 1] = decode_color(it->color); + last_extruder_id = it->extruder; + custom_gcode_per_print_z.erase(it); + } + else + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) }); + + break; + } + default: { break; } + } + } + + return items; + }; + + auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + imgui.text(_u8L("Color change")); + ImGui::SameLine(); + + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); + pos.x += icon_size; + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); + + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(times.second - times.first))); + }; + + auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { + imgui.text(_u8L("Print")); + ImGui::SameLine(); + + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(times.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(times.first))); + if (used_filament.first > 0.0f) { char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); - if (::strlen(buffer) > longest_used_filament_string.length()) - longest_used_filament_string = buffer; + ImGui::SameLine(offsets[2]); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); + imgui.text(buffer); + + ImGui::SameLine(offsets[3]); + ::sprintf(buffer, "%.2f g", used_filament.second); + imgui.text(buffer); } + }; + + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); + if (!partial_times.empty()) { + labels.clear(); + times.clear(); + + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } + case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } + case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } + } + times.push_back(short_time(get_time_dhms(item.times.second))); + } + + std::string longest_used_filament_string; + for (const PartialTime& item : partial_times) { + if (item.used_filament.first > 0.0f) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + } + + //offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); + + //ImGui::Spacing(); + //append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); + //const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + //if (need_scrollable) + // // add scrollable region + // ImGui::BeginChild("events", { -1.0f, child_height }, false); + + //for (const PartialTime& item : partial_times) { + // switch (item.type) + // { + // case PartialTime::EType::Print: { + // append_print(item.color1, offsets, item.times, item.used_filament); + // break; + // } + // case PartialTime::EType::Pause: { + // imgui.text(_u8L("Pause")); + // ImGui::SameLine(offsets[0]); + // imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); + // break; + // } + // case PartialTime::EType::ColorChange: { + // append_color_change(item.color1, item.color2, offsets, item.times); + // break; + // } + // } + //} + + //if (need_scrollable) + // ImGui::EndChild(); } - - //offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); - - //ImGui::Spacing(); - //append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); - //const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; - //if (need_scrollable) - // // add scrollable region - // ImGui::BeginChild("events", { -1.0f, child_height }, false); - - //for (const PartialTime& item : partial_times) { - // switch (item.type) - // { - // case PartialTime::EType::Print: { - // append_print(item.color1, offsets, item.times, item.used_filament); - // break; - // } - // case PartialTime::EType::Pause: { - // imgui.text(_u8L("Pause")); - // ImGui::SameLine(offsets[0]); - // imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - // break; - // } - // case PartialTime::EType::ColorChange: { - // append_color_change(item.color1, item.color2, offsets, item.times); - // break; - // } - // } - //} - - //if (need_scrollable) - // ImGui::EndChild(); } - } - // travel paths section - if (m_buffers[buffer_id(EMoveType::Travel)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { - break; + // travel paths section + if (m_buffers[buffer_id(EMoveType::Travel)].visible) { + switch (m_view_type) + { + case EViewType::Feedrate: + case EViewType::Tool: + case EViewType::ColorPrint: { + break; + } + default: { + // BBS GUI:refactor + // title + //ImGui::Spacing(); + //imgui.title(_u8L("Travel")); + //// items + //append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); + //append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); + //append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); + + break; + } + } } - default: { - // BBS GUI:refactor - // title - //ImGui::Spacing(); - //imgui.title(_u8L("Travel")); - //// items - //append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); - //append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); - //append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); - break; + // wipe paths section + //if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { + // switch (m_view_type) + // { + // case EViewType::Feedrate: + // case EViewType::Tool: + // case EViewType::ColorPrint: { break; } + // default: { + // // title + // ImGui::Spacing(); + // ImGui::Dummy({ window_padding, window_padding }); + // ImGui::SameLine(); + // imgui.title(_u8L("Wipe")); + + // // items + // append_item(EItemType::Line, Wipe_Color, { {_u8L("Wipe"), 0} }); + + // break; + // } + // } + //} + + auto any_option_available = [this]() { + auto available = [this](EMoveType type) { + const TBuffer& buffer = m_buffers[buffer_id(type)]; + return buffer.visible && buffer.has_data(); + }; + + return available(EMoveType::Color_change) || + available(EMoveType::Custom_GCode) || + available(EMoveType::Pause_Print) || + available(EMoveType::Retract) || + available(EMoveType::Tool_change) || + available(EMoveType::Unretract) || + available(EMoveType::Seam); + }; + + //auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { + // const TBuffer& buffer = m_buffers[buffer_id(move_type)]; + // if (buffer.visible && buffer.has_data()) + // append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); + //}; + + /* BBS GUI refactor */ + // options section + //if (any_option_available()) { + // // title + // ImGui::Spacing(); + // imgui.title(_u8L("Options")); + + // // items + // add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); + // add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); + // add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); + // add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); + // add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); + // add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); + // add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); + //} + + + // settings section + bool has_settings = false; + has_settings |= !m_settings_ids.print.empty(); + has_settings |= !m_settings_ids.printer.empty(); + bool has_filament_settings = true; + has_filament_settings &= !m_settings_ids.filament.empty(); + for (const std::string& fs : m_settings_ids.filament) { + has_filament_settings &= !fs.empty(); } - } - } + has_settings |= has_filament_settings; + //BBS: add only gcode mode + bool show_settings = m_only_gcode_in_preview; //wxGetApp().is_gcode_viewer(); + show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); + show_settings &= has_settings; + if (show_settings) { + auto calc_offset = [this]() { + float ret = 0.0f; + if (!m_settings_ids.printer.empty()) + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); + if (!m_settings_ids.print.empty()) + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); + if (!m_settings_ids.filament.empty()) { + for (unsigned char i : m_extruder_ids) { + ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); + } + } + if (ret > 0.0f) + ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; + return ret; + }; - // wipe paths section - //if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { - // switch (m_view_type) - // { - // case EViewType::Feedrate: - // case EViewType::Tool: - // case EViewType::ColorPrint: { break; } - // default: { - // // title - // ImGui::Spacing(); - // ImGui::Dummy({ window_padding, window_padding }); - // ImGui::SameLine(); - // imgui.title(_u8L("Wipe")); + ImGui::Spacing(); + imgui.title(_u8L("Settings")); - // // items - // append_item(EItemType::Line, Wipe_Color, { {_u8L("Wipe"), 0} }); + float offset = calc_offset(); - // break; - // } - // } - //} - - auto any_option_available = [this]() { - auto available = [this](EMoveType type) { - const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.has_data(); - }; - - return available(EMoveType::Color_change) || - available(EMoveType::Custom_GCode) || - available(EMoveType::Pause_Print) || - available(EMoveType::Retract) || - available(EMoveType::Tool_change) || - available(EMoveType::Unretract) || - available(EMoveType::Seam); - }; - - //auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { - // const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - // if (buffer.visible && buffer.has_data()) - // append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); - //}; - - /* BBS GUI refactor */ - // options section - //if (any_option_available()) { - // // title - // ImGui::Spacing(); - // imgui.title(_u8L("Options")); - - // // items - // add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); - // add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); - // add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); - // add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); - // add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); - // add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); - // add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); - //} - - - // settings section - bool has_settings = false; - has_settings |= !m_settings_ids.print.empty(); - has_settings |= !m_settings_ids.printer.empty(); - bool has_filament_settings = true; - has_filament_settings &= !m_settings_ids.filament.empty(); - for (const std::string& fs : m_settings_ids.filament) { - has_filament_settings &= !fs.empty(); - } - has_settings |= has_filament_settings; - //BBS: add only gcode mode - bool show_settings = m_only_gcode_in_preview; //wxGetApp().is_gcode_viewer(); - show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); - show_settings &= has_settings; - if (show_settings) { - auto calc_offset = [this]() { - float ret = 0.0f; - if (!m_settings_ids.printer.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); - if (!m_settings_ids.print.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); + if (!m_settings_ids.printer.empty()) { + imgui.text(_u8L("Printer") + ":"); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.printer); + } + if (!m_settings_ids.print.empty()) { + imgui.text(_u8L("Print settings") + ":"); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.print); + } if (!m_settings_ids.filament.empty()) { for (unsigned char i : m_extruder_ids) { - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); - } - } - if (ret > 0.0f) - ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; - return ret; - }; - - ImGui::Spacing(); - imgui.title(_u8L("Settings")); - - float offset = calc_offset(); - - if (!m_settings_ids.printer.empty()) { - imgui.text(_u8L("Printer") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.printer); - } - if (!m_settings_ids.print.empty()) { - imgui.text(_u8L("Print settings") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.print); - } - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { - std::string txt = _u8L("Filament"); - txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); - imgui.text(txt); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.filament[i]); + if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { + std::string txt = _u8L("Filament"); + txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); + imgui.text(txt); + ImGui::SameLine(offset); + imgui.text(m_settings_ids.filament[i]); + } } } } } - // total estimated printing time section if (show_estimated) { + ImGui::Dummy({ window_padding, window_padding }); ImGui::Spacing(); std::string time_title = m_view_type == EViewType::FeatureType ? _u8L("Total Estimation") : _u8L("Time Estimation"); auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) { @@ -5713,6 +5826,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.title(time_title); + //auto timecText = new wxStaticText(s_panel, wxID_ANY, wxString::FromUTF8(time_title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + //auto timecText = new wxStaticText(s_panel, wxID_ANY, time_title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); std::string total_filament_str = _u8L("Total Filament"); std::string model_filament_str = _u8L("Model Filament"); std::string cost_str = _u8L("Cost"); @@ -5796,6 +5911,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.text(total_str + ":"); ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time))); + //imgui.text(get_time_dhms(time_mode.time)); //xiamian+ ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); @@ -5818,6 +5934,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv //oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M:%S"); oss << std::put_time(new_local_tm, "%Y-%m-%d %H:%M"); imgui.text(oss.str()); + ImGui::SameLine(max_len); + std::string abc = " "; + imgui.text(abc); + //ImGui::Spacing(); //shangmian+ auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { if (can_show_mode_button(mode)) { @@ -5844,6 +5964,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv } default : { assert(false); break; } } + ImGui::Dummy({ window_padding, window_padding }); } if (m_view_type == EViewType::ColorPrint) { diff --git a/src/slic3r/GUI/ParamsPanel.cpp b/src/slic3r/GUI/ParamsPanel.cpp index 8cbef04c8..ac23cfac2 100644 --- a/src/slic3r/GUI/ParamsPanel.cpp +++ b/src/slic3r/GUI/ParamsPanel.cpp @@ -414,6 +414,9 @@ void ParamsPanel::create_layout() // m_left_sizer->Add( m_filament_sizer, 1, wxEXPAND, 5 ); m_left_sizer->Add( m_tab_filament, 0, wxEXPAND ); } + if (m_tab_config) { + m_left_sizer->Add(m_tab_config, 0, wxEXPAND); + } if (m_tab_printer) { //m_printer_sizer = new wxBoxSizer( wxVERTICAL ); @@ -481,6 +484,10 @@ void ParamsPanel::refresh_tabs() case Preset::TYPE_PRINTER: m_tab_printer = tab; break; + case Preset::TYPE_CONFIG: + m_tab_config = tab; + //m_tab_config = NULL; + break; default: break; } @@ -509,7 +516,7 @@ void ParamsPanel::OnActivate() BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print"); // BBS: open/close tab //m_current_tab = m_tab_print; - set_active_tab(m_tab_print ? m_tab_print : m_tab_filament); + set_active_tab(m_tab_print ? m_tab_print : m_tab_filament ? m_tab_filament : m_tab_config); } Tab* cur_tab = dynamic_cast (m_current_tab); if (cur_tab) @@ -587,7 +594,8 @@ void ParamsPanel::set_active_tab(wxPanel* tab) {m_tab_print_layer, nullptr}, {m_tab_print_plate, nullptr}, {m_tab_filament, m_staticline_filament}, - {m_tab_printer, m_staticline_printer}})) { + {m_tab_printer, m_staticline_printer}, + {m_tab_config, m_staticline_config}})) { if (!t.first) continue; t.first->Show(tab == t.first); if (!t.second) continue; @@ -658,7 +666,7 @@ void ParamsPanel::msw_rescale() ((SwitchButton* )m_mode_region)->Rescale(); if (m_mode_view) ((SwitchButton* )m_mode_view)->Rescale(); - for (auto tab : {m_tab_print, m_tab_print_plate, m_tab_print_object, m_tab_print_part, m_tab_print_layer, m_tab_filament, m_tab_printer}) { + for (auto tab : {m_tab_print, m_tab_print_plate, m_tab_print_object, m_tab_print_part, m_tab_print_layer, m_tab_filament, m_tab_printer, m_tab_config}) { if (tab) dynamic_cast(tab)->msw_rescale(); } //((Button*)m_export_to_file)->Rescale(); @@ -799,6 +807,12 @@ void ParamsPanel::delete_subwindows() m_staticline_printer = nullptr; } + if (m_staticline_config) + { + delete m_staticline_config; + m_staticline_config = nullptr; + } + if (m_export_to_file) { delete m_export_to_file; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4585b7e16..7c7fe618b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1382,6 +1382,17 @@ void Sidebar::update_presets(Preset::Type preset_type) } break; } + case Preset::TYPE_CONFIG: + //wxGetApp().mainframe->m_param_panel; + //p->combo_print->update(); + { + /* Tab* config_tab = wxGetApp().get_tab(Preset::TYPE_CONFIG); + if (config_tab) { + config_tab->get_combo_box()->update(); + }*/ + p->combo_config->update(); + break; + } case Preset::TYPE_SLA_PRINT: ;// p->combo_sla_print->update(); break; @@ -1538,6 +1549,7 @@ void Sidebar::msw_rescale() // //p->combo_printer // } ) // combo->msw_rescale(); + p->combo_config->msw_rescale(); p->combo_printer->msw_rescale(); for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9f1942059..225c7ccb9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -966,7 +966,8 @@ void Tab::update_changed_tree_ui() } } if (page->title() == "Dependencies") { - if (m_type == Slic3r::Preset::TYPE_PRINTER) { + //xiamian+ + if (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_CONFIG) { sys_page = m_presets->get_selected_preset_parent() != nullptr; modified_page = false; } else { @@ -1923,304 +1924,271 @@ void TabPrint::build() m_presets = &m_preset_bundle->prints; load_initial_data(); - - auto page = add_options_page(L("Quality"), "empty"); - //xiamian- - //auto page = add_options_page(L("Quality"), "empty"); - //auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); - auto optgroup = page->new_optgroup("", L"param_layer_height"); - //optgroup->append_single_option_line("layer_height", "layer-height"); - //optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); - //xiamian- - //optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); - //optgroup->append_single_option_line("line_width","parameter/line-width"); - //optgroup->append_single_option_line("initial_layer_line_width","parameter/line-width"); - //optgroup->append_single_option_line("outer_wall_line_width","parameter/line-width"); - //optgroup->append_single_option_line("inner_wall_line_width","parameter/line-width"); - //optgroup->append_single_option_line("top_surface_line_width","parameter/line-width"); - //optgroup->append_single_option_line("sparse_infill_line_width","parameter/line-width"); - //optgroup->append_single_option_line("internal_solid_infill_line_width","parameter/line-width"); - //optgroup->append_single_option_line("support_line_width","parameter/line-width"); - //xiamian- - //optgroup = page->new_optgroup(L("Seam"), L"param_seam"); - //optgroup->append_single_option_line("seam_position", "Seam"); - optgroup->append_single_option_line("seam_gap", "Seam"); - //optgroup->append_single_option_line("seam_slope_type"); - optgroup->append_single_option_line("seam_slope_conditional"); - optgroup->append_single_option_line("scarf_angle_threshold"); - optgroup->append_single_option_line("seam_slope_start_height"); - optgroup->append_single_option_line("seam_slope_entire_loop"); - optgroup->append_single_option_line("seam_slope_min_length"); - optgroup->append_single_option_line("seam_slope_steps"); - optgroup->append_single_option_line("seam_slope_inner_walls"); - optgroup->append_single_option_line("wipe_speed", "Seam"); - //xiamian- - //optgroup = page->new_optgroup(L("Precision"), L"param_precision"); - //optgroup->append_single_option_line("slice_closing_radius"); - //optgroup->append_single_option_line("resolution","acr-move"); - //optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); - //optgroup->append_single_option_line("xy_hole_compensation", "xy-hole-contour-compensation"); - //optgroup->append_single_option_line("xy_contour_compensation", "xy-hole-contour-compensation"); - //optgroup->append_single_option_line("elefant_foot_compensation", "parameter/elephant-foot"); - //optgroup->append_single_option_line("precise_z_height"); - //xiamian- - //optgroup = page->new_optgroup(L("Ironing"), L"param_ironing"); - //optgroup->append_single_option_line("ironing_type", "parameter/ironing"); - optgroup->append_single_option_line("ironing_pattern"); - optgroup->append_single_option_line("ironing_speed"); - optgroup->append_single_option_line("ironing_flow"); - optgroup->append_single_option_line("ironing_spacing"); - optgroup->append_single_option_line("ironing_direction"); - //xiamian- - //optgroup = page->new_optgroup(L("Wall generator"), L"param_wall"); - optgroup->append_single_option_line("wall_generator", "wall-generator"); - optgroup->append_single_option_line("wall_transition_angle"); - optgroup->append_single_option_line("wall_transition_filter_deviation"); - optgroup->append_single_option_line("wall_transition_length"); - optgroup->append_single_option_line("wall_distribution_count"); - optgroup->append_single_option_line("min_bead_width"); - optgroup->append_single_option_line("min_feature_size"); - //xiamian- - //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("wall_sequence"); - //optgroup->append_single_option_line("is_infill_first"); - //optgroup->append_single_option_line("bridge_flow","parameter/bridge"); - //optgroup->append_single_option_line("thick_bridges","parameter/bridge"); - //optgroup->append_single_option_line("top_solid_infill_flow_ratio"); - //optgroup->append_single_option_line("initial_layer_flow_ratio"); - //optgroup->append_single_option_line("top_one_wall_type"); - //optgroup->append_single_option_line("top_area_threshold"); - //optgroup->append_single_option_line("only_one_wall_first_layer"); - //optgroup->append_single_option_line("detect_overhang_wall"); - //optgroup->append_single_option_line("smooth_speed_discontinuity_area"); - //optgroup->append_single_option_line("smooth_coefficient"); - //optgroup->append_single_option_line("reduce_crossing_wall"); - //optgroup->append_single_option_line("max_travel_detour_distance"); + auto page = add_options_page(L("Quality"), "empty"); + auto optgroup = page->new_optgroup("", L"param_layer_height"); + optgroup->append_single_option_line("layer_height", "layer-height"); + //optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); + + //optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); + //optgroup->append_single_option_line("line_width", "parameter/line-width"); + //optgroup->append_single_option_line("initial_layer_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("outer_wall_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("inner_wall_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("top_surface_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("sparse_infill_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("internal_solid_infill_line_width", "parameter/line-width"); + //optgroup->append_single_option_line("support_line_width", "parameter/line-width"); + + //optgroup = page->new_optgroup(L("Seam"), L"param_seam"); + //optgroup->append_single_option_line("seam_position", "Seam"); + //optgroup->append_single_option_line("seam_gap", "Seam"); + //optgroup->append_single_option_line("seam_slope_type"); + //optgroup->append_single_option_line("seam_slope_conditional"); + //optgroup->append_single_option_line("scarf_angle_threshold"); + //optgroup->append_single_option_line("seam_slope_start_height"); + //optgroup->append_single_option_line("seam_slope_entire_loop"); + //optgroup->append_single_option_line("seam_slope_min_length"); + //optgroup->append_single_option_line("seam_slope_steps"); + //optgroup->append_single_option_line("seam_slope_inner_walls"); + //optgroup->append_single_option_line("wipe_speed", "Seam"); + + //optgroup = page->new_optgroup(L("Precision"), L"param_precision"); + //optgroup->append_single_option_line("slice_closing_radius"); + //optgroup->append_single_option_line("resolution", "acr-move"); + //optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); + //optgroup->append_single_option_line("xy_hole_compensation", "xy-hole-contour-compensation"); + //optgroup->append_single_option_line("xy_contour_compensation", "xy-hole-contour-compensation"); + //optgroup->append_single_option_line("elefant_foot_compensation", "parameter/elephant-foot"); + //optgroup->append_single_option_line("precise_z_height"); + + //optgroup = page->new_optgroup(L("Ironing"), L"param_ironing"); + //optgroup->append_single_option_line("ironing_type", "parameter/ironing"); + //optgroup->append_single_option_line("ironing_pattern"); + //optgroup->append_single_option_line("ironing_speed"); + //optgroup->append_single_option_line("ironing_flow"); + //optgroup->append_single_option_line("ironing_spacing"); + //optgroup->append_single_option_line("ironing_direction"); + + //optgroup = page->new_optgroup(L("Wall generator"), L"param_wall"); + //optgroup->append_single_option_line("wall_generator", "wall-generator"); + optgroup->append_single_option_line("wall_transition_angle"); + optgroup->append_single_option_line("wall_transition_filter_deviation"); + optgroup->append_single_option_line("wall_transition_length"); + optgroup->append_single_option_line("wall_distribution_count"); + optgroup->append_single_option_line("min_bead_width"); + optgroup->append_single_option_line("min_feature_size"); + + //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + //optgroup->append_single_option_line("wall_sequence"); + //optgroup->append_single_option_line("is_infill_first"); + //optgroup->append_single_option_line("bridge_flow", "parameter/bridge"); + //optgroup->append_single_option_line("thick_bridges", "parameter/bridge"); + //optgroup->append_single_option_line("top_solid_infill_flow_ratio"); + //optgroup->append_single_option_line("initial_layer_flow_ratio"); + optgroup->append_single_option_line("top_one_wall_type"); + optgroup->append_single_option_line("top_area_threshold"); + optgroup->append_single_option_line("only_one_wall_first_layer"); + optgroup->append_single_option_line("detect_overhang_wall"); + //optgroup->append_single_option_line("smooth_speed_discontinuity_area"); + //optgroup->append_single_option_line("smooth_coefficient"); + //optgroup->append_single_option_line("reduce_crossing_wall"); + optgroup->append_single_option_line("max_travel_detour_distance"); - optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); - page = add_options_page(L("Strength"), "empty"); - //xiamian+ - optgroup = page->new_optgroup("", L"param_wall"); - //xiamian- - //optgroup = page->new_optgroup(L("Walls"), L"param_wall"); - //optgroup->append_single_option_line("wall_loops","wall-generator"); - optgroup->append_single_option_line("detect_thin_wall","wall-generator"); - - //optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); - optgroup->append_single_option_line("interface_shells"); - optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); - //optgroup->append_single_option_line("top_shell_layers"); - //optgroup->append_single_option_line("top_shell_thickness"); - optgroup->append_single_option_line("bottom_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); - //optgroup->append_single_option_line("bottom_shell_layers"); - //optgroup->append_single_option_line("bottom_shell_thickness"); - optgroup->append_single_option_line("internal_solid_infill_pattern"); - - //optgroup = page->new_optgroup(L("Sparse infill"), L"param_infill"); - //optgroup->append_single_option_line("sparse_infill_density"); - optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); - //optgroup->append_single_option_line("sparse_infill_anchor"); - //optgroup->append_single_option_line("sparse_infill_anchor_max"); - optgroup->append_single_option_line("filter_out_gap_fill"); + optgroup = page->new_optgroup("", L"param_wall"); + optgroup->append_single_option_line("wall_loops", "wall-generator"); + //optgroup->append_single_option_line("detect_thin_wall", "wall-generator"); - //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - //optgroup->append_single_option_line("infill_wall_overlap","parameter/strength-advance-settings"); - //optgroup->append_single_option_line("infill_direction","parameter/strength-advance-settings"); - optgroup->append_single_option_line("bridge_angle","parameter/strength-advance-settings"); - optgroup->append_single_option_line("minimum_sparse_infill_area","parameter/strength-advance-settings"); - optgroup->append_single_option_line("infill_combination","parameter/strength-advance-settings"); - //optgroup->append_single_option_line("detect_narrow_internal_solid_infill","parameter/strength-advance-settings"); - //optgroup->append_single_option_line("ensure_vertical_shell_thickness","parameter/strength-advance-settings"); - //optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); + //optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); + optgroup->append_single_option_line("interface_shells"); + //optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("top_shell_layers"); + optgroup->append_single_option_line("top_shell_thickness"); + //optgroup->append_single_option_line("bottom_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("bottom_shell_layers"); + optgroup->append_single_option_line("bottom_shell_thickness"); + //optgroup->append_single_option_line("internal_solid_infill_pattern"); + + //optgroup = page->new_optgroup(L("Sparse infill"), L"param_infill"); + optgroup->append_single_option_line("sparse_infill_density"); + //optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); + //optgroup->append_single_option_line("sparse_infill_anchor"); + //optgroup->append_single_option_line("sparse_infill_anchor_max"); + //optgroup->append_single_option_line("filter_out_gap_fill"); + + //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + //optgroup->append_single_option_line("infill_wall_overlap", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("infill_direction", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("bridge_angle", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("minimum_sparse_infill_area", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("infill_combination", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("detect_narrow_internal_solid_infill", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("ensure_vertical_shell_thickness", "parameter/strength-advance-settings"); + //optgroup->append_single_option_line("internal_bridge_support_thickness","parameter/strength-advance-settings"); page = add_options_page(L("Speed"), "empty"); - //xiamian+ - optgroup = page->new_optgroup("", L"param_speed",15); - //xiamian- - //optgroup = page->new_optgroup(L("Initial layer speed"), L"param_speed_first", 15); - optgroup->append_single_option_line("default_print_speed"); - optgroup->append_single_option_line("initial_layer_speed"); - optgroup->append_single_option_line("initial_layer_infill_speed"); - //optgroup = page->new_optgroup(L("Other layers speed"), L"param_speed", 15); - optgroup->append_single_option_line("outer_wall_speed"); - optgroup->append_single_option_line("inner_wall_speed"); - //optgroup->append_single_option_line("small_perimeter_speed"); - //optgroup->append_single_option_line("small_perimeter_threshold"); - optgroup->append_single_option_line("sparse_infill_speed"); - optgroup->append_single_option_line("internal_solid_infill_speed"); - optgroup->append_single_option_line("top_surface_speed"); - //optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang"); - //Line line = { L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used") }; - //line.label_path = "slow-down-for-overhang"; - //line.append_option(optgroup->get_option("overhang_1_4_speed")); - //line.append_option(optgroup->get_option("overhang_2_4_speed")); - //line.append_option(optgroup->get_option("overhang_3_4_speed")); - //line.append_option(optgroup->get_option("overhang_4_4_speed")); - //optgroup->append_line(line); - //optgroup->append_single_option_line("overhang_totally_speed"); - optgroup->append_single_option_line("bridge_speed"); - optgroup->append_single_option_line("gap_infill_speed"); - optgroup->append_single_option_line("support_speed"); - optgroup->append_single_option_line("support_interface_speed"); + optgroup = page->new_optgroup("", L"param_speed_first", 15); + //optgroup->append_single_option_line("initial_layer_speed"); + //optgroup->append_single_option_line("initial_layer_infill_speed"); + + //optgroup = page->new_optgroup(L("Other layers speed"), L"param_speed", 15); + //optgroup->append_single_option_line("outer_wall_speed"); + //optgroup->append_single_option_line("inner_wall_speed"); + //optgroup->append_single_option_line("small_perimeter_speed"); + //optgroup->append_single_option_line("small_perimeter_threshold"); + //optgroup->append_single_option_line("sparse_infill_speed"); + //optgroup->append_single_option_line("internal_solid_infill_speed"); + //optgroup->append_single_option_line("top_surface_speed"); + //optgroup->append_single_option_line("enable_overhang_speed", "slow-down-for-overhang"); + //Line line = { L("Overhang speed"), L("This is the speed for various overhang degrees. Overhang degrees are expressed as a percentage of line width. 0 speed means no slowing down for the overhang degree range and wall speed is used") }; + //line.label_path = "slow-down-for-overhang"; + //line.append_option(optgroup->get_option("overhang_1_4_speed")); + //line.append_option(optgroup->get_option("overhang_2_4_speed")); + //line.append_option(optgroup->get_option("overhang_3_4_speed")); + //line.append_option(optgroup->get_option("overhang_4_4_speed")); + //optgroup->append_line(line); + //optgroup->append_single_option_line("overhang_totally_speed"); + //optgroup->append_single_option_line("bridge_speed"); + //optgroup->append_single_option_line("gap_infill_speed"); + //optgroup->append_single_option_line("support_speed"); + //optgroup->append_single_option_line("support_interface_speed"); - //optgroup = page->new_optgroup(L("Travel speed"), L"param_travel_speed", 15); - optgroup->append_single_option_line("travel_speed"); + //optgroup = page->new_optgroup(L("Travel speed"), L"param_travel_speed", 15); + //optgroup->append_single_option_line("travel_speed"); - //optgroup = page->new_optgroup(L("Acceleration"), L"param_acceleration", 15); - optgroup->append_single_option_line("default_acceleration"); - //optgroup->append_single_option_line("initial_layer_acceleration"); - //optgroup->append_single_option_line("outer_wall_acceleration"); - //optgroup->append_single_option_line("inner_wall_acceleration"); - //optgroup->append_single_option_line("top_surface_acceleration"); - //optgroup->append_single_option_line("sparse_infill_acceleration"); - //optgroup->append_single_option_line("accel_to_decel_enable"); - //optgroup->append_single_option_line("accel_to_decel_factor"); + //optgroup = page->new_optgroup(L("Acceleration"), L"param_acceleration", 15); + //optgroup->append_single_option_line("default_acceleration"); + //optgroup->append_single_option_line("initial_layer_acceleration"); + //optgroup->append_single_option_line("outer_wall_acceleration"); + //optgroup->append_single_option_line("inner_wall_acceleration"); + //optgroup->append_single_option_line("top_surface_acceleration"); + //optgroup->append_single_option_line("sparse_infill_acceleration"); + //optgroup->append_single_option_line("accel_to_decel_enable"); + //optgroup->append_single_option_line("accel_to_decel_factor"); - //optgroup = page->new_optgroup(L("Jerk(XY)"), L"param_acceleration", 15); - optgroup->append_single_option_line("default_jerk"); - optgroup->append_single_option_line("outer_wall_jerk"); - optgroup->append_single_option_line("inner_wall_jerk"); - optgroup->append_single_option_line("infill_jerk"); - optgroup->append_single_option_line("top_surface_jerk"); - optgroup->append_single_option_line("initial_layer_jerk"); - optgroup->append_single_option_line("travel_jerk"); + //optgroup = page->new_optgroup(L("Jerk(XY)"), L"param_acceleration", 15); + optgroup->append_single_option_line("default_jerk"); + optgroup->append_single_option_line("outer_wall_jerk"); + optgroup->append_single_option_line("inner_wall_jerk"); + optgroup->append_single_option_line("infill_jerk"); + optgroup->append_single_option_line("top_surface_jerk"); + optgroup->append_single_option_line("initial_layer_jerk"); + optgroup->append_single_option_line("travel_jerk"); -#ifdef HAS_PRESSURE_EQUALIZER - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); -#endif /* HAS_PRESSURE_EQUALIZER */ +//#ifdef HAS_PRESSURE_EQUALIZER +// optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); +// optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); +//#endif /* HAS_PRESSURE_EQUALIZER */ page = add_options_page(L("Support"), "support"); - //xiamian+ optgroup = page->new_optgroup("", L"param_support"); - //ximian- - //optgroup = page->new_optgroup(L("Support"), L"param_support"); - //optgroup->append_single_option_line("enable_support", "support"); - optgroup->append_single_option_line("support_type", "support#support-types"); - optgroup->append_single_option_line("support_style", "support#support-styles"); - optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle"); - optgroup->append_single_option_line("support_on_build_plate_only"); - optgroup->append_single_option_line("support_critical_regions_only"); - optgroup->append_single_option_line("support_remove_small_overhang"); - //optgroup->append_single_option_line("enforce_support_layers"); + optgroup->append_single_option_line("enable_support", "support"); + optgroup->append_single_option_line("support_type", "support#support-types"); + optgroup->append_single_option_line("support_style", "support#support-styles"); + optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle"); + optgroup->append_single_option_line("support_on_build_plate_only"); + optgroup->append_single_option_line("support_critical_regions_only"); + optgroup->append_single_option_line("support_remove_small_overhang"); + //optgroup->append_single_option_line("enforce_support_layers"); - //optgroup = page->new_optgroup(L("Raft"), L"param_raft"); - optgroup->append_single_option_line("raft_layers"); - optgroup->append_single_option_line("raft_contact_distance"); - optgroup->append_single_option_line("raft_first_layer_density"); + //optgroup = page->new_optgroup(L("Raft"), L"param_raft"); + //optgroup->append_single_option_line("raft_layers"); + //optgroup->append_single_option_line("raft_contact_distance"); + //optgroup->append_single_option_line("raft_first_layer_density"); - //optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); - optgroup->append_single_option_line("support_filament", "support#support-filament"); - optgroup->append_single_option_line("support_interface_filament", "support#support-filament"); - optgroup->append_single_option_line("support_interface_not_for_body", "support#support-filament"); + //optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); + //optgroup->append_single_option_line("support_filament", "support#support-filament"); + //optgroup->append_single_option_line("support_interface_filament", "support#support-filament"); + //optgroup->append_single_option_line("support_interface_not_for_body", "support#support-filament"); - //optgroup = page->new_optgroup(L("Options for support material and raft")); + //optgroup = page->new_optgroup(L("Options for support material and raft")); - //BBS - //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too - optgroup->append_single_option_line("tree_support_wall_count"); - //optgroup->append_single_option_line("support_top_z_distance", "support#top-z-distance"); - //optgroup->append_single_option_line("support_bottom_z_distance", "support#bottom-z-distance"); - optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); - //optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); - //optgroup->append_single_option_line("support_angle"); - //optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); - //optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); - optgroup->append_single_option_line("support_interface_pattern", "support#base-pattern"); - //optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); - optgroup->append_single_option_line("support_bottom_interface_spacing"); - //optgroup->append_single_option_line("support_expansion", "support#base-pattern"); - //optgroup->append_single_option_line("support_interface_loop_pattern"); + //BBS + //optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + //optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too + //optgroup->append_single_option_line("tree_support_wall_count"); + //optgroup->append_single_option_line("support_top_z_distance", "support#top-z-distance"); + //optgroup->append_single_option_line("support_bottom_z_distance", "support#bottom-z-distance"); + //optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); + //optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); + //optgroup->append_single_option_line("support_angle"); + //optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); + //optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); + //optgroup->append_single_option_line("support_interface_pattern", "support#base-pattern"); + //optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); + //optgroup->append_single_option_line("support_bottom_interface_spacing"); + //optgroup->append_single_option_line("support_expansion", "support#base-pattern"); + //optgroup->append_single_option_line("support_interface_loop_pattern"); - optgroup->append_single_option_line("support_object_xy_distance", "support"); - //optgroup->append_single_option_line("support_object_first_layer_gap", "support"); - optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); - optgroup->append_single_option_line("max_bridge_length", "support#base-pattern"); - optgroup->append_single_option_line("independent_support_layer_height", "support"); + //optgroup->append_single_option_line("support_object_xy_distance", "support"); + //optgroup->append_single_option_line("support_object_first_layer_gap", "support"); + //optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); + //optgroup->append_single_option_line("max_bridge_length", "support#base-pattern"); + //optgroup->append_single_option_line("independent_support_layer_height", "support"); - //optgroup = page->new_optgroup(L("Tree Support"), L"param_advanced"); - optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options"); - optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options"); - optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); - //xiamian+ - page = add_options_page(L("Configuration"), "empty"); - optgroup = page->new_optgroup("", L"param_layer_height"); - optgroup->append_single_option_line("wall_loops", "wall-generator"); - optgroup->append_single_option_line("layer_height", "layer-height"); - optgroup->append_single_option_line("seam_position", "Seam"); - optgroup->append_single_option_line("top_shell_layers"); - optgroup->append_single_option_line("top_shell_thickness"); - optgroup->append_single_option_line("bottom_shell_layers"); - optgroup->append_single_option_line("bottom_shell_thickness"); - optgroup->append_single_option_line("sparse_infill_density"); - optgroup->append_single_option_line("enable_support", "support"); + //optgroup = page->new_optgroup(L("Tree Support"), L"param_advanced"); + //optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options"); + //optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options"); + //optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); page = add_options_page(L("Others"), "advanced"); - //xiamian+ optgroup = page->new_optgroup("", L"param_adhension"); - //xiamian- - //optgroup = page->new_optgroup(L("Bed adhension"), L"param_adhension"); - //optgroup->append_single_option_line("skirt_loops"); - //optgroup->append_single_option_line("skirt_height"); - //optgroup->append_single_option_line("skirt_distance"); - //optgroup->append_single_option_line("draft_shield"); - //optgroup->append_single_option_line("brim_type", "auto-brim"); - //optgroup->append_single_option_line("brim_width", "auto-brim#manual"); - //optgroup->append_single_option_line("brim_object_gap", "auto-brim#brim-object-gap"); + //optgroup->append_single_option_line("skirt_loops"); + //optgroup->append_single_option_line("skirt_height"); + //optgroup->append_single_option_line("skirt_distance"); + //optgroup->append_single_option_line("draft_shield"); + //optgroup->append_single_option_line("brim_type", "auto-brim"); + //optgroup->append_single_option_line("brim_width", "auto-brim#manual"); + //optgroup->append_single_option_line("brim_object_gap", "auto-brim#brim-object-gap"); - //optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); - //optgroup->append_single_option_line("enable_prime_tower","parameter/prime-tower"); - //optgroup->append_single_option_line("prime_tower_width","parameter/prime-tower"); - //optgroup->append_single_option_line("prime_volume","parameter/prime-tower"); - //optgroup->append_single_option_line("prime_tower_brim_width","parameter/prime-tower"); + //optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); + //optgroup->append_single_option_line("enable_prime_tower", "parameter/prime-tower"); + //optgroup->append_single_option_line("prime_tower_width", "parameter/prime-tower"); + //optgroup->append_single_option_line("prime_volume", "parameter/prime-tower"); + //optgroup->append_single_option_line("prime_tower_brim_width", "parameter/prime-tower"); - //optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); - //optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill"); - //optgroup->append_single_option_line("flush_into_objects", "reduce-wasting-during-filament-change#wipe-into-object"); - //optgroup->append_single_option_line("flush_into_support", "reduce-wasting-during-filament-change#wipe-into-support-enabled-by-default"); + //optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); + //optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill"); + //optgroup->append_single_option_line("flush_into_objects", "reduce-wasting-during-filament-change#wipe-into-object"); + //optgroup->append_single_option_line("flush_into_support", "reduce-wasting-during-filament-change#wipe-into-support-enabled-by-default"); - //optgroup = page->new_optgroup(L("Special mode"), L"param_special"); - optgroup->append_single_option_line("slicing_mode"); - optgroup->append_single_option_line("print_sequence", "sequent-print"); - optgroup->append_single_option_line("spiral_mode", "spiral-vase"); - optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth"); - optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing"); - optgroup->append_single_option_line("timelapse_type", "Timelapse"); + //optgroup = page->new_optgroup(L("Special mode"), L"param_special"); + //optgroup->append_single_option_line("slicing_mode"); + //optgroup->append_single_option_line("print_sequence", "sequent-print"); + //optgroup->append_single_option_line("spiral_mode", "spiral-vase"); + //optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth"); + //optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing"); + //optgroup->append_single_option_line("timelapse_type", "Timelapse"); - optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); - optgroup->append_single_option_line("fuzzy_skin_point_distance"); - optgroup->append_single_option_line("fuzzy_skin_thickness"); + //optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); + //optgroup->append_single_option_line("fuzzy_skin_point_distance"); + //optgroup->append_single_option_line("fuzzy_skin_thickness"); - //optgroup = page->new_optgroup(L("Advanced"), L"advanced"); - // optgroup->append_single_option_line("mmu_segmented_region_max_width"); - optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth"); + //optgroup = page->new_optgroup(L("Advanced"), L"advanced"); + // optgroup->append_single_option_line("mmu_segmented_region_max_width"); + optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth"); - //optgroup = page->new_optgroup(L("G-code output"), L"param_gcode"); - optgroup->append_single_option_line("reduce_infill_retraction"); - optgroup->append_single_option_line("gcode_add_line_number"); - optgroup->append_single_option_line("exclude_object"); - Option option = optgroup->get_option("filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("G-code output"), L"param_gcode"); + optgroup->append_single_option_line("reduce_infill_retraction"); + optgroup->append_single_option_line("gcode_add_line_number"); + optgroup->append_single_option_line("exclude_object"); + Option option = optgroup->get_option("filename_format"); + option.opt.full_width = true; + 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; - 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; + option.opt.is_code = true; + option.opt.height = 15; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Notes"),"note"); - optgroup->label_width = 0; - option = optgroup->get_option("process_notes"); - option.opt.full_width = true; - option.opt.height = 25; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Notes"), "note"); + optgroup->label_width = 0; + option = optgroup->get_option("process_notes"); + option.opt.full_width = true; + option.opt.height = 25; + optgroup->append_single_option_line(option); #if 0 //page = add_options_page(L("Dependencies"), "advanced.png"); @@ -3184,7 +3152,7 @@ void TabFilament::build() line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); optgroup->append_line(line); //BBS - add_filament_overrides_page(); + // add_filament_overrides_page(); #if 0 //page = add_options_page(L("Advanced"), "advanced"); // optgroup = page->new_optgroup(L("Wipe tower parameters")); @@ -4281,48 +4249,152 @@ void TabPrinter::update_fff() void TabPrinter::update_sla() { ; } + void TabConfig::build() { m_presets = &m_preset_bundle->configs; load_initial_data(); + auto page = add_options_page(L("Quality"), "spool"); + auto optgroup = page->new_optgroup("", L"param_layer_height"); + optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); - //auto page = add_options_page(L("Qualityttttttt"), "empty"); - //auto optgroup = page->new_optgroup("", L"param_layer_height"); - //optgroup->append_single_option_line("initial_layer_print_height", "layer-height"); - //optgroup->append_single_option_line("initial_layer_line_width"); - //optgroup->append_single_option_line("outer_wall_line_width", "parameter/line-width"); - //optgroup->append_single_option_line("inner_wall_line_width", "parameter/line-width"); - //optgroup->append_single_option_line("top_surface_line_width", "parameter/line-width"); - //optgroup->append_single_option_line("sparse_infill_line_width", "parameter/line-width"); - //optgroup->append_single_option_line("support_line_width", "parameter/line-width"); - //optgroup->append_single_option_line("resolution", "acr-move"); - //optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); - //optgroup->append_single_option_line("wall_generator", "wall-generator"); - //optgroup->append_single_option_line("wall_sequence"); + optgroup->append_single_option_line("initial_layer_line_width"); + optgroup->append_single_option_line("outer_wall_line_width", "parameter/line-width"); + optgroup->append_single_option_line("inner_wall_line_width", "parameter/line-width"); + optgroup->append_single_option_line("top_surface_line_width", "parameter/line-width"); + optgroup->append_single_option_line("sparse_infill_line_width", "parameter/line-width"); + optgroup->append_single_option_line("support_line_width", "parameter/line-width"); - /*page = add_options_page(L("Strength"), "empty"); - optgroup = page->new_optgroup("", L"param_wall"); - optgroup->append_single_option_line("detect_thin_wall", "wall-generator");*/ + optgroup->append_single_option_line("seam_gap", "Seam"); + optgroup->append_single_option_line("seam_slope_conditional"); + optgroup->append_single_option_line("scarf_angle_threshold"); + optgroup->append_single_option_line("seam_slope_start_height"); + optgroup->append_single_option_line("seam_slope_entire_loop"); + optgroup->append_single_option_line("seam_slope_min_length"); + optgroup->append_single_option_line("seam_slope_steps"); + optgroup->append_single_option_line("seam_slope_inner_walls"); + optgroup->append_single_option_line("wipe_speed", "Seam"); - auto page = add_options_page(L("Filament"), "spool"); - //BBS - auto optgroup = page->new_optgroup(L("Basic information"), L"param_information"); - // Set size as all another fields for a better alignment - //Option option = optgroup->get_option("filament_type"); + optgroup->append_single_option_line("resolution", "acr-move"); + optgroup->append_single_option_line("enable_arc_fitting", "acr-move"); - page = add_options_page(L("Cooling"), "empty"); + optgroup->append_single_option_line("ironing_pattern"); + optgroup->append_single_option_line("ironing_speed"); + optgroup->append_single_option_line("ironing_flow"); + optgroup->append_single_option_line("ironing_spacing"); + optgroup->append_single_option_line("ironing_direction"); - //line = { "", "" }; - //line.full_width = 1; - //line.widget = [this](wxWindow* parent) { - // return description_line_widget(parent, &m_cooling_description_line); - //}; - //optgroup->append_line(line); - optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling"); + optgroup->append_single_option_line("wall_generator", "wall-generator"); + optgroup->append_single_option_line("wall_transition_angle"); + optgroup->append_single_option_line("wall_transition_filter_deviation"); + optgroup->append_single_option_line("wall_transition_length"); + optgroup->append_single_option_line("wall_distribution_count"); + optgroup->append_single_option_line("min_bead_width"); + optgroup->append_single_option_line("min_feature_size"); - page = add_options_page(L("Advanced"), "advanced"); - optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); + optgroup->append_single_option_line("wall_sequence"); + optgroup->append_single_option_line("top_one_wall_type"); + optgroup->append_single_option_line("top_area_threshold"); + optgroup->append_single_option_line("only_one_wall_first_layer"); + optgroup->append_single_option_line("detect_overhang_wall"); + optgroup->append_single_option_line("max_travel_detour_distance"); + optgroup->append_single_option_line("support_base_pattern_spacing", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_top_layers", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_bottom_layers", "support#base-pattern"); + //you cuo wu -- zan ding + //optgroup->append_single_option_line("support_interface_spacing", "support#base-pattern"); + + + page = add_options_page(L("Strength"), "empty"); + optgroup = page->new_optgroup("", L"param_wall"); + optgroup->append_single_option_line("top_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("bottom_surface_pattern", "fill-patterns#Infill of the top surface and bottom surface"); + optgroup->append_single_option_line("internal_solid_infill_pattern"); + + optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); + optgroup->append_single_option_line("filter_out_gap_fill"); + optgroup->append_single_option_line("bridge_angle", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("minimum_sparse_infill_area", "parameter/strength-advance-settings"); + optgroup->append_single_option_line("infill_combination", "parameter/strength-advance-settings"); + + page = add_options_page(L("Speed"), "empty"); + optgroup = page->new_optgroup("", L"param_speed_first", 15); + optgroup->append_single_option_line("default_print_speed"); + optgroup->append_single_option_line("initial_layer_speed"); + optgroup->append_single_option_line("initial_layer_infill_speed"); + + optgroup->append_single_option_line("outer_wall_speed"); + optgroup->append_single_option_line("inner_wall_speed"); + optgroup->append_single_option_line("sparse_infill_speed"); + optgroup->append_single_option_line("internal_solid_infill_speed"); + optgroup->append_single_option_line("top_surface_speed"); + optgroup->append_single_option_line("bridge_speed"); + optgroup->append_single_option_line("gap_infill_speed"); + optgroup->append_single_option_line("support_speed"); + optgroup->append_single_option_line("support_interface_speed"); + + optgroup->append_single_option_line("travel_speed"); + + optgroup->append_single_option_line("default_acceleration"); + optgroup->append_single_option_line("accel_to_decel_enable"); + optgroup->append_single_option_line("accel_to_decel_factor"); + + /* optgroup->append_single_option_line("default_jerk"); + optgroup->append_single_option_line("outer_wall_jerk"); + optgroup->append_single_option_line("inner_wall_jerk"); + optgroup->append_single_option_line("infill_jerk"); + optgroup->append_single_option_line("top_surface_jerk"); + optgroup->append_single_option_line("initial_layer_jerk"); + optgroup->append_single_option_line("travel_jerk");*/ + + page = add_options_page(L("Support"), "support"); + optgroup = page->new_optgroup("", L"param_support"); + optgroup->append_single_option_line("support_type", "support#support-types"); + optgroup->append_single_option_line("support_style", "support#support-styles"); + optgroup->append_single_option_line("support_threshold_angle", "support#threshold-angle"); + optgroup->append_single_option_line("support_on_build_plate_only"); + optgroup->append_single_option_line("support_critical_regions_only"); + optgroup->append_single_option_line("support_remove_small_overhang"); + + optgroup->append_single_option_line("raft_layers"); + optgroup->append_single_option_line("raft_contact_distance"); + optgroup->append_single_option_line("raft_first_layer_density"); + + optgroup->append_single_option_line("support_filament", "support#support-filament"); + optgroup->append_single_option_line("support_interface_filament", "support#support-filament"); + optgroup->append_single_option_line("support_interface_not_for_body", "support#support-filament"); + + optgroup->append_single_option_line("raft_first_layer_expansion"); // not only for raft, but for support too + optgroup->append_single_option_line("tree_support_wall_count"); + optgroup->append_single_option_line("support_base_pattern", "support#base-pattern"); + optgroup->append_single_option_line("support_interface_pattern", "support#base-pattern"); + optgroup->append_single_option_line("support_bottom_interface_spacing"); + optgroup->append_single_option_line("support_object_xy_distance", "support"); + optgroup->append_single_option_line("bridge_no_support", "support#base-pattern"); + optgroup->append_single_option_line("max_bridge_length", "support#base-pattern"); + optgroup->append_single_option_line("independent_support_layer_height", "support"); + + optgroup->append_single_option_line("tree_support_branch_distance", "support#tree-support-only-options"); + optgroup->append_single_option_line("tree_support_branch_diameter", "support#tree-support-only-options"); + optgroup->append_single_option_line("tree_support_branch_angle", "support#tree-support-only-options"); + + page = add_options_page(L("Others"), "advanced"); + optgroup = page->new_optgroup("", L"param_adhension"); + optgroup->append_single_option_line("slicing_mode"); + optgroup->append_single_option_line("fibre_feed_rate"); + optgroup->append_single_option_line("print_sequence", "sequent-print"); + optgroup->append_single_option_line("spiral_mode", "spiral-vase"); + optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase#smooth"); + optgroup->append_single_option_line("spiral_mode_max_xy_smoothing", "spiral-vase#max-xy-smoothing"); + optgroup->append_single_option_line("timelapse_type", "Timelapse"); + + optgroup->append_single_option_line("fuzzy_skin", "parameter/fuzzy-skin"); + optgroup->append_single_option_line("fuzzy_skin_point_distance"); + optgroup->append_single_option_line("fuzzy_skin_thickness"); + } + + void TabConfig::reload_config() { //this->compatible_widget_reload(m_compatible_printers); @@ -4333,8 +4405,8 @@ void TabConfig::update_description_lines() { Tab::update_description_lines(); - //if (!m_active_page) - // return; + if (!m_active_page) + return; //if (m_active_page->title() == "Cooling" && m_cooling_description_line) // m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); @@ -4348,15 +4420,16 @@ void TabConfig::toggle_options() { bool is_BBL_printer = false; if (m_preset_bundle) { - is_BBL_printer = - m_preset_bundle->configs.get_edited_preset().is_bbl_vendor_preset( - m_preset_bundle); + is_BBL_printer = m_preset_bundle->configs.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); } - if (m_active_page->title() == "Quality") { + + /* const Preset& preset = m_preset_bundle->configs.get_edited_preset(); + bool is_BBL = preset.is_system;*/ + //if (m_active_page->title() == "Quality") { //toggle_line("printable_area", !is_configed_by_BBL);//all printer can entry and view data //toggle_option("single_extruder_multi_material", have_multiple_extruders); //BBS: gcode_flavore of BBL printer can't be edited and changed - toggle_option("gcode_flavor", !is_BBL_printer); + //toggle_option("gcode_flavor", !is_BBL_printer); //toggle_option("thumbnail_size", !is_BBL_printer); //toggle_option("printer_structure", !is_BBL_printer); //toggle_option("use_relative_e_distances", !is_BBL_printer); @@ -4370,21 +4443,37 @@ void TabConfig::toggle_options() { ////BBS: extruder clearance of BBL printer can't be edited. //for (auto el : { "extruder_clearance_max_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) // toggle_option(el, !is_BBL_printer); - } + //} - if (m_active_page->title() == "Strength") { - //toggle_line("time_lapse_gcode", m_preset_bundle->configs.get_edited_preset().config.opt_enum("printer_structure") == PrinterStructure::psI3); + /* if (m_active_page->title() == "Strength") { + toggle_line("time_lapse_gcode", m_preset_bundle->configs.get_edited_preset().config.opt_enum("printer_structure") == PrinterStructure::psI3); toggle_option("thumbnail_size", !is_BBL_printer); - } + }*/ } void TabConfig::update() { + //m_update_cnt++; + //update_description_lines(); + ////BBS: GUI refactor + ////Layout(); + //m_parent->Layout(); + + m_update_cnt++; + update_description_lines(); //BBS: GUI refactor //Layout(); m_parent->Layout(); + + toggle_options(); + + m_update_cnt--; + + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); } void TabConfig::clear_pages() { + Tab::clear_pages(); } void Tab::update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent) { @@ -4816,7 +4905,7 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (current_dirty || delete_current || print_tab || printer_tab) m_preset_bundle->update_compatible( update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_PRINT))->m_show_incompatible_presets), - update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets)); + update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets)); // Initialize the UI from the current preset. if (printer_tab) static_cast(this)->update_pages(); diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp new file mode 100644 index 000000000..aa817f509 --- /dev/null +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -0,0 +1,336 @@ +#include "Button.hpp" +#include "Label.hpp" + +#include +#include + +BEGIN_EVENT_TABLE(Button, StaticBox) + +EVT_LEFT_DOWN(Button::mouseDown) +EVT_LEFT_UP(Button::mouseReleased) +EVT_MOUSE_CAPTURE_LOST(Button::mouseCaptureLost) +EVT_KEY_DOWN(Button::keyDownUp) +EVT_KEY_UP(Button::keyDownUp) + +// catch paint events +EVT_PAINT(Button::paintEvent) + +END_EVENT_TABLE() + +/* + * Called by the system of by wxWidgets when the panel needs + * to be redrawn. You can also trigger this call by + * calling Refresh()/Update(). + */ + +Button::Button() + : paddingSize(10, 8) +{ + background_color = StateColor( + std::make_pair(0xF0F0F1, (int) StateColor::Disabled), + std::make_pair(0x37EE7C, (int) StateColor::Hovered | StateColor::Checked), + std::make_pair(0x00AE42, (int) StateColor::Checked), + //std::make_pair(0x009FF3, (int) StateColor::Checked), + std::make_pair(*wxLIGHT_GREY, (int) StateColor::Hovered), + std::make_pair(*wxWHITE, (int) StateColor::Normal)); + text_color = StateColor( + std::make_pair(*wxLIGHT_GREY, (int) StateColor::Disabled), + std::make_pair(*wxBLACK, (int) StateColor::Normal)); +} + +Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) + : Button() +{ + Create(parent, text, icon, style, iconSize, btn_id); +} + +bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) +{ + StaticBox::Create(parent, btn_id, wxDefaultPosition, wxDefaultSize, style); + state_handler.attach({&text_color}); + state_handler.update_binds(); + //BBS set default font + SetFont(Label::Body_14); + wxWindow::SetLabel(text); + if (!icon.IsEmpty()) { + //BBS set button icon default size to 20 + this->active_icon = ScalableBitmap(this, icon.ToStdString(), iconSize > 0 ? iconSize : 20); + } + messureSize(); + return true; +} + +void Button::SetLabel(const wxString& label) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +bool Button::SetFont(const wxFont& font) +{ + wxWindow::SetFont(font); + messureSize(); + Refresh(); + return true; +} + +void Button::SetIcon(const wxString& icon) +{ + if (!icon.IsEmpty()) { + //BBS set button icon default size to 20 + this->active_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + } + else + { + this->active_icon = ScalableBitmap(); + } + Refresh(); +} + +void Button::SetInactiveIcon(const wxString &icon) +{ + if (!icon.IsEmpty()) { + // BBS set button icon default size to 20 + this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + } else { + this->inactive_icon = ScalableBitmap(); + } + Refresh(); +} + +void Button::SetMinSize(const wxSize& size) +{ + minSize = size; + messureSize(); +} + +void Button::SetPaddingSize(const wxSize& size) +{ + paddingSize = size; + messureSize(); +} + +void Button::SetTextColor(StateColor const& color) +{ + text_color = color; + state_handler.update_binds(); + Refresh(); +} + +void Button::SetTextColorNormal(wxColor const &color) +{ + text_color.setColorForStates(color, 0); + Refresh(); +} + +bool Button::Enable(bool enable) +{ + bool result = wxWindow::Enable(enable); + if (result) { + wxCommandEvent e(EVT_ENABLE_CHANGED); + e.SetEventObject(this); + GetEventHandler()->ProcessEvent(e); + } + return result; +} + +void Button::SetCanFocus(bool canFocus) { this->canFocus = canFocus; } + +void Button::SetValue(bool state) +{ + if (GetValue() == state) return; + state_handler.set_state(state ? StateHandler::Checked : 0, StateHandler::Checked); +} + +bool Button::GetValue() const { return state_handler.states() & StateHandler::Checked; } + +void Button::SetCenter(bool isCenter) +{ + this->isCenter = isCenter; +} + +void Button::Rescale() +{ + if (this->active_icon.bmp().IsOk()) + this->active_icon.msw_rescale(); + + if (this->inactive_icon.bmp().IsOk()) + this->inactive_icon.msw_rescale(); + + messureSize(); +} + +void Button::paintEvent(wxPaintEvent& evt) +{ + // depending on your system you may need to look at double-buffered dcs + wxPaintDC dc(this); + render(dc); +} + +/* + * Here we do the actual rendering. I put it in a separate + * method so that it can work no matter what type of DC + * (e.g. wxPaintDC or wxClientDC) is used. + */ +void Button::render(wxDC& dc) +{ + StaticBox::render(dc); + int states = state_handler.states(); + wxSize size = GetSize(); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + // calc content size + wxSize szIcon; + wxSize szContent = textSize.GetSize(); + + ScalableBitmap icon; + if (m_selected || ((states & (int)StateColor::State::Hovered) != 0)) + icon = active_icon; + else + icon = inactive_icon; + int padding = 5; + if (icon.bmp().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += padding; + } + szIcon = icon.GetBmpSize(); + szContent.x += szIcon.x; + if (szIcon.y > szContent.y) + szContent.y = szIcon.y; + if (szContent.x > size.x) { + int d = std::min(padding, szContent.x - size.x); + padding -= d; + szContent.x -= d; + } + } + // move to center + wxRect rcContent = { {0, 0}, size }; + if (isCenter) { + wxSize offset = (size - szContent) / 2; + if (offset.x < 0) offset.x = 0; + rcContent.Deflate(offset.x, offset.y); + } + // start draw + wxPoint pt = rcContent.GetLeftTop(); + if (icon.bmp().IsOk()) { + pt.y += (rcContent.height - szIcon.y) / 2; + dc.DrawBitmap(icon.bmp(), pt); + //BBS norrow size between text and icon + pt.x += szIcon.x + padding; + pt.y = rcContent.y; + } + auto text = GetLabel(); + if (!text.IsEmpty()) { + if (pt.x + textSize.width > size.x) + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - pt.x); + pt.y += (rcContent.height - textSize.height) / 2; + dc.SetTextForeground(text_color.colorForStates(states)); +#if 0 + dc.SetBrush(*wxLIGHT_GREY); + dc.SetPen(wxPen(*wxLIGHT_GREY)); + dc.DrawRectangle(pt, textSize.GetSize()); +#endif +#ifdef __WXOSX__ + pt.y -= textSize.x / 2; +#endif + dc.DrawText(text, pt); + } +} + +void Button::messureSize() +{ + wxClientDC dc(this); + dc.GetTextExtent(GetLabel(), &textSize.width, &textSize.height, &textSize.x, &textSize.y); + wxSize szContent = textSize.GetSize(); + if (this->active_icon.bmp().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += 5; + } + wxSize szIcon = this->active_icon.GetBmpSize(); + szContent.x += szIcon.x; + if (szIcon.y > szContent.y) + szContent.y = szIcon.y; + } + wxSize size = szContent + paddingSize * 2; + if (minSize.GetHeight() > 0) + size.SetHeight(minSize.GetHeight()); + + if (minSize.GetWidth() > size.GetWidth()) + wxWindow::SetMinSize(minSize); + else + wxWindow::SetMinSize(size); +} + +void Button::mouseDown(wxMouseEvent& event) +{ + event.Skip(); + pressedDown = true; + if (canFocus) + SetFocus(); + if (!HasCapture()) + CaptureMouse(); +} + +void Button::mouseReleased(wxMouseEvent& event) +{ + event.Skip(); + if (pressedDown) { + pressedDown = false; + if (HasCapture()) + ReleaseMouse(); + if (wxRect({0, 0}, GetSize()).Contains(event.GetPosition())) + sendButtonEvent(); + } +} + +void Button::mouseCaptureLost(wxMouseCaptureLostEvent &event) +{ + wxMouseEvent evt; + mouseReleased(evt); +} + +void Button::keyDownUp(wxKeyEvent &event) +{ + if (event.GetKeyCode() == WXK_SPACE || event.GetKeyCode() == WXK_RETURN) { + wxMouseEvent evt(event.GetEventType() == wxEVT_KEY_UP ? wxEVT_LEFT_UP : wxEVT_LEFT_DOWN); + event.SetEventObject(this); + GetEventHandler()->ProcessEvent(evt); + return; + } + if (event.GetEventType() == wxEVT_KEY_DOWN && + (event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT + || event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)) + HandleAsNavigationKey(event); + else + event.Skip(); +} + +void Button::sendButtonEvent() +{ + wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId()); + event.SetEventObject(this); + GetEventHandler()->ProcessEvent(event); +} + +#ifdef __WIN32__ + +WXLRESULT Button::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_GETDLGCODE) { return DLGC_WANTMESSAGE; } + if (nMsg == WM_KEYDOWN) { + wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, wParam, lParam)); + switch (wParam) { + case WXK_RETURN: { // WXK_RETURN key is handled by default button + GetEventHandler()->ProcessEvent(event); + return 0; + } + } + } + return wxWindow::MSWWindowProc(nMsg, wParam, lParam); +} + +#endif + +bool Button::AcceptsFocus() const { return canFocus; } diff --git a/src/slic3r/GUI/Widgets/SwitchButton.cpp b/src/slic3r/GUI/Widgets/SwitchButton.cpp new file mode 100644 index 000000000..f5656062e --- /dev/null +++ b/src/slic3r/GUI/Widgets/SwitchButton.cpp @@ -0,0 +1,157 @@ +#include "SwitchButton.hpp" +#include "Label.hpp" +#include "StaticBox.hpp" + +#include "../wxExtensions.hpp" +#include "../Utils/MacDarkMode.hpp" + +#include +#include +#include + +SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id) + : wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_EXACTFIT) + , m_on(this, "toggle_on", 16) + , m_off(this, "toggle_off", 16) + , text_color(std::pair{0xfffffe, (int) StateColor::Checked}, std::pair{0x6B6B6B, (int) StateColor::Normal}) + , track_color(0xD9D9D9) + , thumb_color(std::pair{0x009FF3, (int) StateColor::Checked}, std::pair{0xD9D9D9, (int) StateColor::Normal}) +{ + SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent)); + Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); }); + SetFont(Label::Body_12); + Rescale(); +} + +void SwitchButton::SetLabels(wxString const& lbl_on, wxString const& lbl_off) +{ + labels[0] = lbl_on; + labels[1] = lbl_off; + Rescale(); +} + +void SwitchButton::SetTextColor(StateColor const& color) +{ + text_color = color; +} + +void SwitchButton::SetTextColor2(StateColor const &color) +{ + text_color2 = color; +} + +void SwitchButton::SetTrackColor(StateColor const& color) +{ + track_color = color; +} + +void SwitchButton::SetThumbColor(StateColor const& color) +{ + thumb_color = color; +} + +void SwitchButton::SetValue(bool value) +{ + if (value != GetValue()) + wxBitmapToggleButton::SetValue(value); + update(); +} + +void SwitchButton::Rescale() +{ + if (labels[0].IsEmpty()) { + m_on.msw_rescale(); + m_off.msw_rescale(); + } + else { + SetBackgroundColour(StaticBox::GetParentBackgroundColor(GetParent())); +#ifdef __WXOSX__ + auto scale = Slic3r::GUI::mac_max_scaling_factor(); + int BS = (int) scale; +#else + constexpr int BS = 1; +#endif + wxSize thumbSize; + wxSize trackSize; + wxClientDC dc(this); +#ifdef __WXOSX__ + dc.SetFont(dc.GetFont().Scaled(scale)); +#endif + wxSize textSize[2]; + { + textSize[0] = dc.GetTextExtent(labels[0]); + textSize[1] = dc.GetTextExtent(labels[1]); + } + float fontScale = 0; + { + thumbSize = textSize[0]; + auto size = textSize[1]; + if (size.x > thumbSize.x) thumbSize.x = size.x; + else size.x = thumbSize.x; + thumbSize.x += BS * 12; + thumbSize.y += BS * 6; + trackSize.x = thumbSize.x + size.x + BS * 10; + trackSize.y = thumbSize.y + BS * 2; + auto maxWidth = GetMaxWidth(); +#ifdef __WXOSX__ + maxWidth *= scale; +#endif + if (trackSize.x > maxWidth) { + fontScale = float(maxWidth) / trackSize.x; + thumbSize.x -= (trackSize.x - maxWidth) / 2; + trackSize.x = maxWidth; + } + } + for (int i = 0; i < 2; ++i) { + wxMemoryDC memdc(&dc); +#ifdef __WXMSW__ + wxBitmap bmp(trackSize.x, trackSize.y); + memdc.SelectObject(bmp); + memdc.SetBackground(wxBrush(GetBackgroundColour())); + memdc.Clear(); +#else + wxImage image(trackSize); + image.InitAlpha(); + memset(image.GetAlpha(), 0, trackSize.GetWidth() * trackSize.GetHeight()); + wxBitmap bmp(std::move(image)); + memdc.SelectObject(bmp); +#endif + memdc.SetFont(dc.GetFont()); + if (fontScale) { + memdc.SetFont(dc.GetFont().Scaled(fontScale)); + textSize[0] = memdc.GetTextExtent(labels[0]); + textSize[1] = memdc.GetTextExtent(labels[1]); + } + auto state = i == 0 ? StateColor::Enabled : (StateColor::Checked | StateColor::Enabled); + { +#ifdef __WXMSW__ + wxGCDC dc2(memdc); +#else + wxDC &dc2(memdc); +#endif + dc2.SetBrush(wxBrush(track_color.colorForStates(state))); + dc2.SetPen(wxPen(track_color.colorForStates(state))); + dc2.DrawRoundedRectangle(wxRect({0, 0}, trackSize), trackSize.y / 2); + dc2.SetBrush(wxBrush(thumb_color.colorForStates(StateColor::Checked | StateColor::Enabled))); + dc2.SetPen(wxPen(thumb_color.colorForStates(StateColor::Checked | StateColor::Enabled))); + dc2.DrawRoundedRectangle(wxRect({ i == 0 ? BS : (trackSize.x - thumbSize.x - BS), BS}, thumbSize), thumbSize.y / 2); + } + memdc.SetTextForeground(text_color.colorForStates(state ^ StateColor::Checked)); + memdc.DrawText(labels[0], {BS + (thumbSize.x - textSize[0].x) / 2, BS + (thumbSize.y - textSize[0].y) / 2}); + memdc.SetTextForeground(text_color2.count() == 0 ? text_color.colorForStates(state) : text_color2.colorForStates(state)); + memdc.DrawText(labels[1], {trackSize.x - thumbSize.x - BS + (thumbSize.x - textSize[1].x) / 2, BS + (thumbSize.y - textSize[1].y) / 2}); + memdc.SelectObject(wxNullBitmap); +#ifdef __WXOSX__ + bmp = wxBitmap(bmp.ConvertToImage(), -1, scale); +#endif + (i == 0 ? m_off : m_on).bmp() = bmp; + } + } + SetSize(m_on.GetBmpSize()); + update(); +} + +void SwitchButton::update() +{ + SetBitmap((GetValue() ? m_on : m_off).bmp()); +}