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
commit 697fc5b8d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 183 additions and 33 deletions

View File

@ -105,6 +105,7 @@ var LangText = {
t111: "Create New",
t112: "Join the Program",
t113: "You may change your choice in preference anytime.",
t126: "Loading……",
orca1: "Edit Project Info",
orca2: "no model information",
orca3: "Stealth Mode",
@ -326,6 +327,7 @@ var LangText = {
t111: "Crear nuevo",
t112: "Unirse al programa",
t113: "Puede cambiar su elección en preferencias en cualquier momento.",
t126: "Carga en progreso……",
orca1: "Editar información del proyecto",
orca2: "No hay información sobre el modelo",
orca3: "Modo Invisible",
@ -543,6 +545,7 @@ var LangText = {
t104: "Profilname",
t105: "Profilautor",
t106: "Profilbeschreibung",
t126: "Laden……",
orca1: "Edit Project Info",
orca2: "no model information",
},
@ -645,6 +648,7 @@ var LangText = {
t104: "Název profilu",
t105: "Autor profilu",
t106: "Popis profilu",
t126: "Načtení probíhá……",
orca1: "Edit Project Info",
orca2: "no model information",
},
@ -750,6 +754,7 @@ var LangText = {
t109: "Filaments du système",
t110: "Filaments personnalisés",
t111: "Créer un nouveau filament",
t126: "Chargement en cours……",
orca1: "Modifier les informations du projet",
orca2: "pas d'information sur le modèle",
wk1: "Démarrage rapide",
@ -875,6 +880,7 @@ var LangText = {
t111: "新建",
t112: "加入该计划",
t113: "您可以随时更改您的偏好。",
t126: "正在加载……",
wk1: "快速入门指南",
wk2: "本文介绍了Orca Slicer的最基本用法。它指导用户配置软件创建项目并逐步完成第一个打印任务。",
wk3: "基于项目的工作流",
@ -1105,6 +1111,7 @@ var LangText = {
t111: "Создать новый",
t112: "Присоединяйтесь к программе",
t113: "Вы можете изменить свой выбор в любое время.",
t126: "Загрузка идёт……",
orca1: "Редактировать информацию о проекте",
orca2: "Информации о модели отсутствует",
orca3: "Режим конфиденциальности",
@ -1199,6 +1206,7 @@ var LangText = {
t92: "Bambu Christmas Cabin",
t93: "프린터 연결",
t94: "장치를 보려면 프린터 연결을 설정하세요.",
t126: "로딩 중……",
orca1: "Edit Project Info",
orca2: "no model information",
},
@ -1308,6 +1316,7 @@ var LangText = {
t111: "Yeni Oluştur",
t112: "Programa Katılın",
t113: "Tercihinizi istediğiniz zaman değiştirebilirsiniz.",
t126: "Yükleme devam ediyor……",
orca1: "Proje Bilgilerini Düzenle",
orca2: "model bilgisi yok",
},
@ -1417,6 +1426,7 @@ var LangText = {
t111: "Utwórz nowy",
t112: "Dołącz do programu",
t113: "Możesz zmienić swój wybór w preferencjach w dowolnym momencie.",
t126: "Ładowanie trwa……",
orca1: "Edytuj informacje o projekcie",
orca2: "brak informacji o modelu",
orca3: "Tryb «Niewidzialny»",
@ -1529,6 +1539,7 @@ var LangText = {
t111: "Criar Novo",
t112: "Junte-se ao Programa",
t113: "Você pode alterar sua escolha nas Preferências a qualquer momento",
t126: "Carregamento em andamento……",
orca1: "Editar Info do Projeto",
orca2: "Sem informação do modelo",
orca3: "Modo Furtivo",

View File

@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=7200" />
<title>loading</title>
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="load.css" />
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script type="text/javascript" src="load.js"></script>
</head>
<body onLoad="OnInit()">
<div id="LoadBlock">
<img id="LoadingSvg" src="loading.svg" />
<div id="LoadTip" class="trans" tid="t126">Loading……</div>
</div>
</body>
</html>

View File

@ -0,0 +1,36 @@
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
body
{
display:flex;
align-content: center;
justify-content: center;
}
#LoadBlock
{
display:flex;
align-items: center;
justify-content: center;
}
#LoadingSvg
{
animation: rotate 1.2s infinite linear;
margin-right: 8px;
height: 120%;
}
#LoadTip
{
font-size: 15px;
font-weight: 700;
color: #262E30;
}

View File

@ -0,0 +1,24 @@
var TargetPage=null;
function OnInit()
{
TargetPage=GetQueryString("target");
//setTimeout("JumpToTarget()",20*1000);
}
function HandleStudio( pVal )
{
let strCmd=pVal['command'];
if(strCmd=='userguide_profile_load_finish')
{
JumpToTarget();
}
}
function JumpToTarget()
{
window.open('../'+TargetPage+'/index.html','_self');
}

View File

@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 19.2742C11 19.675 11.3555 20 11.7941 20C16.3261 20 20 16.6421 20 12.5C20 8.35786 16.3261 5 11.7941 5C11.3555 5 11 5.32496 11 5.72581C11 6.12666 11.3555 6.45161 11.7941 6.45161C15.4489 6.45161 18.4118 9.15957 18.4118 12.5C18.4118 15.8404 15.4489 18.5484 11.7941 18.5484C11.3555 18.5484 11 18.8733 11 19.2742Z" fill="url(#paint0_linear_2417_1599)"/>
<defs>
<linearGradient id="paint0_linear_2417_1599" x1="11.9338" y1="19.2801" x2="11" y2="5.79458" gradientUnits="userSpaceOnUse">
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 747 B

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;