Async profile load on printer/filament selection screen (#9118)

The selection screen shows a loading indicator instead of frozen during
the page loading process, during that time you will able to close the
window if you want, instead of been stuck at this screen until it
loaded:


![profile-select-async](https://github.com/user-attachments/assets/ff6c810e-72f8-4398-b86f-2ca5b516fbe2)

Ported from BambuStudio, huge thanks to BambuLab!
This commit is contained in:
Noisyfox
2025-04-13 17:30:37 +08:00
committed by GitHub
7 changed files with 183 additions and 33 deletions

View File

@@ -48,7 +48,7 @@ static wxString update_custom_filaments()
json m_CustomFilaments = json::array();
PresetBundle * preset_bundle = wxGetApp().preset_bundle;
std::map<std::string, std::vector<Preset const *>> temp_filament_id_to_presets = preset_bundle->filaments.get_filament_presets();
std::vector<std::pair<std::string, std::string>> need_sort;
bool need_delete_some_filament = false;
for (std::pair<std::string, std::vector<Preset const *>> filament_id_to_presets : temp_filament_id_to_presets) {
@@ -72,7 +72,7 @@ static wxString update_custom_filaments()
auto filament_vendor = dynamic_cast<ConfigOptionStrings *>(const_cast<Preset *>(preset)->config.option("filament_vendor", false));
if (filament_vendor && filament_vendor->values.size() && filament_vendor->values[0] == "Generic") not_need_show = true;
}
if (filament_name.empty()) {
std::string preset_name = preset->name;
size_t index_at = preset_name.find(" @");
@@ -132,7 +132,7 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
}
m_browser->Hide();
m_browser->SetSize(0, 0);
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
@@ -178,12 +178,6 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
// Bind(wxEVT_IDLE, &GuideFrame::OnIdle, this);
// Bind(wxEVT_CLOSE_WINDOW, &GuideFrame::OnClose, this);
auto start = std::chrono::high_resolution_clock::now();
LoadProfile();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": LoadProfile() took " << duration.count() << " milliseconds";
// UI
SetStartPage(BBL_REGION);
@@ -193,6 +187,12 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
GuideFrame::~GuideFrame()
{
m_destroy = true;
if (m_load_task && m_load_task->joinable()) {
m_load_task->join();
delete m_load_task;
m_load_task = nullptr;
}
if (m_browser) {
delete m_browser;
m_browser = nullptr;
@@ -214,43 +214,43 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
m_page = startpage;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/1/index.html").make_preferred().string() );
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
if (startpage == BBL_WELCOME){
SetTitle(_L("Setup Wizard"));
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/1/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
} else if (startpage == BBL_REGION) {
SetTitle(_L("Setup Wizard"));
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/11/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
} else if (startpage == BBL_MODELS) {
SetTitle(_L("Setup Wizard"));
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/21/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENTS) {
SetTitle(_L("Setup Wizard"));
int nSize = m_ProfileJson["model"].size();
if (nSize>0)
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/22/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
else
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/21/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENT_ONLY) {
SetTitle("");
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/23/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
} else if (startpage == BBL_MODELS_ONLY) {
SetTitle("");
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/24/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
}
else {
SetTitle(_L("Setup Wizard"));
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/21/index.html").make_preferred().string());
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
}
wxString strlang = wxGetApp().current_language_code_safe();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(", strlang=%1%") % into_u8(strlang);
if (strlang != "")
TargetUrl = wxString::Format("%s?lang=%s", w2s(TargetUrl), strlang);
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
TargetUrl = "file://" + TargetUrl;
if (load)
@@ -301,9 +301,17 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
{
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
if (!bFirstComplete) {
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
//LoadProfileThread.detach();
bFirstComplete = true;
}
m_browser->Show();
Layout();
wxString NewUrl = evt.GetURL();
UpdateState();
@@ -509,7 +517,7 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
BOOST_LOG_TRIVIAL(trace) << "GuideFrame::OnScriptMessage;Error:" << e.what();
}
wxString strAll = m_ProfileJson.dump(-1,' ',false, json::error_handler_t::ignore);
//wxString strAll = m_ProfileJson.dump(-1,' ',false, json::error_handler_t::ignore);
}
void GuideFrame::RunScript(const wxString &javascript)
@@ -928,9 +936,9 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
if (jLocal.contains("inherits")) {
std::string FName = jLocal["inherits"];
if (!pFilaList.contains(FName)) {
if (!pFilaList.contains(FName)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "pFilaList - Not Contains inherits filaments: " << FName;
return -1;
return -1;
}
std::string FPath = pFilaList[FName]["sub_path"];
@@ -976,8 +984,7 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
return 0;
}
int GuideFrame::LoadProfile()
int GuideFrame::LoadProfileData()
{
try {
m_ProfileJson = json::parse("{}");
@@ -986,7 +993,7 @@ int GuideFrame::LoadProfile()
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR ).make_preferred();
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
// Orca: add custom as default
@@ -1028,6 +1035,8 @@ int GuideFrame::LoadProfile()
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (m_destroy)
return 0;
}
boost::filesystem::directory_iterator others_endIter;
@@ -1043,10 +1052,37 @@ int GuideFrame::LoadProfile()
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (m_destroy)
return 0;
}
//sync to web
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
json m_Res = json::object();
m_Res["command"] = "userguide_profile_load_finish";
m_Res["sequence_id"] = "10001";
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
if (!m_destroy)
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
//sync to appconfig
if (!m_destroy)
wxGetApp().CallAfter([this] { SaveProfileData(); });
} catch (std::exception& e) {
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
// wxMessageBox(e.what(), "", MB_OK);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
}
return 0;
}
int GuideFrame::SaveProfileData()
{
try {
const auto enabled_filaments = wxGetApp().app_config->has_section(AppConfig::SECTION_FILAMENTS) ? wxGetApp().app_config->get_section(AppConfig::SECTION_FILAMENTS) : std::map<std::string, std::string>();
m_appconfig_new.set_vendors(*wxGetApp().app_config);
m_appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, enabled_filaments);
@@ -1119,9 +1155,6 @@ int GuideFrame::LoadProfile()
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: "<< e.what() <<std::endl;
}
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: "<< std::endl<<strAll;
return 0;
}
@@ -1169,6 +1202,8 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
std::string s2 = OneModel["sub_path"];
boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred();
if (!boost::filesystem::exists(sub_path)) continue;
std::string sub_file = sub_path.string();
// wxLogMessage("GUIDE: json_path2 %s", w2s(ModelFilePath));
@@ -1211,6 +1246,8 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
// wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2);
boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred();
if (!boost::filesystem::exists(sub_path)) continue;
std::string sub_file = sub_path.string();
LoadFile(sub_file, contents);
json pm = json::parse(contents);
@@ -1252,10 +1289,12 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
if (!m_ProfileJson["filament"].contains(s1)) {
// wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2);
boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred();
if (!boost::filesystem::exists(sub_path)) continue;
std::string sub_file = sub_path.string();
LoadFile(sub_file, contents);
json pm = json::parse(contents);
std::string strInstant = pm["instantiation"];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",Path:" << sub_file << ",instantiation?" << strInstant;
@@ -1264,9 +1303,9 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
std::string sT;
int nRet = GetFilamentInfo(vendor_dir.string(),tFilaList, sub_file, sV, sT);
if (nRet != 0) {
if (nRet != 0) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",GetFilamentInfo Failed, Vendor:" << sV << ",Type:"<< sT;
continue;
continue;
}
OneFF["vendor"] = sV;
@@ -1312,6 +1351,8 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
std::string s2 = OneProcess["sub_path"];
// wxString ModelFilePath = wxString::Format("%s\\%s\\%s", strFolder, strVendor, s2);
boost::filesystem::path sub_path = boost::filesystem::absolute(vendor_dir / s2).make_preferred();
if (!boost::filesystem::exists(sub_path)) continue;
std::string sub_file = sub_path.string();
LoadFile(sub_file, contents);
json pm = json::parse(contents);

View File

@@ -73,7 +73,8 @@ public:
bool IsFirstUse();
//Model - Machine - Filaments
int LoadProfile();
int LoadProfileData();
int SaveProfileData();
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
int SaveProfile();
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
@@ -107,6 +108,11 @@ private:
boost::filesystem::path vendor_dir;
boost::filesystem::path rsrc_vendor_dir;
//First Load
bool bFirstComplete{false};
bool m_destroy{false};
boost::thread* m_load_task{ nullptr };
// User Config
bool PrivacyUse;
bool StealthMode;