#include "Tab.hpp" #include "Project.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Format/bbs_3mf.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wxExtensions.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "MainFrame.hpp" #include namespace Slic3r { namespace GUI { wxDEFINE_EVENT(EVT_PROJECT_RELOAD, wxCommandEvent); const std::vector license_list = { "BSD License", "Apache License", "GPL License", "LGPL License", "MIT License", "CC License" }; ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style) { m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir())); wxString strlang = wxGetApp().current_language_code_safe(); if (strlang != "") m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); m_browser = WebView::CreateWebView(this, m_project_home_url); if (m_browser == nullptr) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("load web view of project page failed"); return; } //m_browser->Hide(); main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this); m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId()); Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this); SetSizer(main_sizer); Layout(); Fit(); } ProjectPanel::~ProjectPanel() {} void ProjectPanel::onWebNavigating(wxWebViewEvent& evt) { wxString tmpUrl = evt.GetURL(); //wxString NowUrl = m_browser->GetCurrentURL(); if (boost::starts_with(tmpUrl, "http://") || boost::starts_with(tmpUrl, "https://")) { m_browser->Stop(); evt.Veto(); wxLaunchDefaultApplication(tmpUrl); } } void ProjectPanel::on_reload(wxCommandEvent& evt) { boost::thread reload = boost::thread([this] { std::string update_type; std::string license; std::string model_name; std::string model_author; std::string cover_file; std::string description; std::map> files; std::string p_name; std::string p_author; std::string p_description; std::string p_cover_file; std::string model_id; Model model = wxGetApp().plater()->model(); license = model.model_info->license; model_name = model.model_info->model_name; cover_file = model.model_info->cover_file; description = model.model_info->description; update_type = model.model_info->origin; if (model.design_info && !model.design_info->DesignerId.empty()) { if (m_model_id_map.count(model.design_info->DesignerId) > 0) { model_id = m_model_id_map[model.design_info->DesignerId]; } else { model_id = get_model_id(model.design_info->DesignerId); m_model_id_map[model.design_info->DesignerId] = model_id; } } try { if (!model.model_info->copyright.empty()) { json copy_right = json::parse(model.model_info->copyright); if (copy_right.is_array()) { for (auto it = copy_right.begin(); it != copy_right.end(); it++) { if ((*it).contains("author")) { model_author = (*it)["author"].get(); } } } } } catch (...) { ; } if (model_author.empty() && model.design_info != nullptr) model_author = model.design_info->Designer; if (model.profile_info != nullptr) { p_name = model.profile_info->ProfileTile; p_description = model.profile_info->ProfileDescription; p_cover_file = model.profile_info->ProfileCover; p_author = model.profile_info->ProfileUserName; } //file info std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str()); if (!file_path.empty()) { files = Reload(file_path); } else { clear_model_info(); return; } json j; j["model"]["license"] = license; j["model"]["name"] = wxGetApp().url_encode(model_name); j["model"]["author"] = wxGetApp().url_encode(model_author);; j["model"]["cover_img"] = wxGetApp().url_encode(cover_file); j["model"]["description"] = wxGetApp().url_encode(description); j["model"]["preview_img"] = files["Model Pictures"]; j["model"]["upload_type"] = update_type; j["model"]["model_id"] = model_id; j["file"]["BOM"] = files["Bill of Materials"]; j["file"]["Assembly"] = files["Assembly Guide"]; j["file"]["Other"] = files["Others"]; j["profile"]["name"] = wxGetApp().url_encode(p_name); j["profile"]["author"] = wxGetApp().url_encode(p_author); j["profile"]["description"] = wxGetApp().url_encode(p_description); j["profile"]["cover_img"] = wxGetApp().url_encode(p_cover_file); j["profile"]["preview_img"] = files["Profile Pictures"]; json m_Res = json::object(); m_Res["command"] = "show_3mf_info"; m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++); m_Res["model"] = j; wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore)); if (m_web_init_completed) { wxGetApp().CallAfter([this, strJS] { RunScript(strJS.ToStdString()); }); } }); } std::string ProjectPanel::get_model_id(std::string desgin_id) { std::string model_id; auto host = wxGetApp().get_http_url(wxGetApp().app_config->get_country_code(), "v1/design-service/model/" + desgin_id); Http http = Http::get(host); http.header("accept", "application/json") //.header("Authorization") .on_complete([this, &model_id](std::string body, unsigned status) { try { json j = json::parse(body); if (j.contains("id")) { int mid = j["id"].get(); if (mid > 0) { model_id = std::to_string(mid); } } } catch (...) { ; } }) .on_error([this](std::string body, std::string error, unsigned status) { }) .perform_sync(); return model_id; } void ProjectPanel::msw_rescale() { } void ProjectPanel::on_size(wxSizeEvent &event) { event.Skip(); } void ProjectPanel::on_navigated(wxWebViewEvent& event) { event.Skip(); } void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt) { try { wxString strInput = evt.GetString(); json j = json::parse(strInput); wxString strCmd = j["command"]; if (strCmd == "open_3mf_accessory") { wxString accessory_path = j["accessory_path"]; if (!accessory_path.empty()) { std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString()); fs::path path(decode_path); if (fs::exists(path)) { wxLaunchDefaultApplication(path.wstring(), 0); } } } else if (strCmd == "request_3mf_info") { m_web_init_completed = true; } else if (strCmd == "modelmall_model_open") { if (j.contains("data")) { json data = j["data"]; if (data.contains("id")) { wxString model_id = j["data"]["id"]; if (!model_id.empty()) { std::string h = wxGetApp().get_model_http_url(wxGetApp().app_config->get_country_code()); auto l = wxGetApp().current_language_code_safe(); if (auto n = l.find('_'); n != std::string::npos) l = l.substr(0, n); auto url = (boost::format("%1%%2%/models/%3%") % h % l % model_id).str(); wxLaunchDefaultBrowser(url); } } } } else if (strCmd == "debug_info") { //wxString msg = j["msg"]; //OutputDebugString(wxString::Format("Model_Web: msg = %s \r\n", msg)); //BOOST_LOG_TRIVIAL(info) << wxString::Format("Model_Web: msg = %s", msg); } } catch (std::exception& e) { // wxMessageBox(e.what(), "json Exception", MB_OK); } } void ProjectPanel::update_model_data() { Model model = wxGetApp().plater()->model(); clear_model_info(); //basics info if (model.model_info == nullptr) return; auto event = wxCommandEvent(EVT_PROJECT_RELOAD); event.SetEventObject(this); wxPostEvent(this, event); } void ProjectPanel::clear_model_info() { json m_Res = json::object(); m_Res["command"] = "clear_3mf_info"; m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++); wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore)); wxGetApp().CallAfter([this, strJS] { RunScript(strJS.ToStdString()); }); } std::map> ProjectPanel::Reload(wxString aux_path) { std::vector dir_cache; fs::directory_iterator iter_end; wxString m_root_dir; std::map> m_paths_list; const static std::array s_default_folders = { ("Model Pictures"), ("Bill of Materials"), ("Assembly Guide"), ("Others"), //(".thumbnails"), ("Profile Pictures"), }; for (auto folder : s_default_folders) m_paths_list[folder.ToStdString()] = std::vector{}; fs::path new_aux_path(aux_path.ToStdWstring()); try { fs::remove_all(fs::path(m_root_dir.ToStdWstring())); } catch (...) { BOOST_LOG_TRIVIAL(error) << "Failed removing the auxiliary directory" << m_root_dir.c_str(); } m_root_dir = aux_path; // Check new path. If not exist, create a new one. if (!fs::exists(new_aux_path)) { fs::create_directory(new_aux_path); // Create default folders if they are not loaded for (auto folder : s_default_folders) { wxString folder_path = aux_path + "/" + folder; if (fs::exists(folder_path.ToStdWstring())) continue; fs::create_directory(folder_path.ToStdWstring()); } return m_paths_list; } // Load from new path for (fs::directory_iterator iter(new_aux_path); iter != iter_end; iter++) { wxString path = iter->path().generic_wstring(); dir_cache.push_back(iter->path()); } for (auto dir : dir_cache) { for (fs::directory_iterator iter(dir); iter != iter_end; iter++) { if (fs::is_directory(iter->path())) continue; json pfile_obj; std::string file_path = iter->path().string(); fs::path file_path_obj = fs::path(iter->path().string()); for (auto folder : s_default_folders) { auto idx = file_path.find(folder.ToStdString()); if (idx != std::string::npos) { wxStructStat strucStat; wxString file_name = encode_path(file_path.c_str()); wxStat(file_name, &strucStat); wxFileOffset filelen = strucStat.st_size; pfile_obj["filename"] = wxGetApp().url_encode(file_path_obj.filename().string().c_str()); pfile_obj["size"] = formatBytes((unsigned long)filelen); std::string file_extension = file_path_obj.extension().string(); boost::algorithm::to_lower(file_extension); //image if (file_extension == ".jpg" || file_extension == ".jpeg" || file_extension == ".pjpeg" || file_extension == ".png" || file_extension == ".jfif" || file_extension == ".pjp" || file_extension == ".webp" || file_extension == ".bmp") { wxString base64_str = to_base64(file_path); pfile_obj["filepath"] = base64_str.ToStdString(); m_paths_list[folder.ToStdString()].push_back(pfile_obj); break; } else { pfile_obj["filepath"] = wxGetApp().url_encode(file_path); m_paths_list[folder.ToStdString()].push_back(pfile_obj); break; } } } } } return m_paths_list; } std::string ProjectPanel::formatBytes(unsigned long bytes) { double dValidData = round(double(bytes) / (1024 * 1024) * 1000) / 1000; return wxString::Format("%.2fMB", dValidData).ToStdString(); } wxString ProjectPanel::to_base64(std::string file_path) { std::ifstream imageFile(encode_path(file_path.c_str()), std::ios::binary); if (!imageFile) { return wxEmptyString; } std::ostringstream imageStream; imageStream << imageFile.rdbuf(); std::string binaryImageData = imageStream.str(); std::string extension; size_t last_dot = file_path.find_last_of("."); if (last_dot != std::string::npos) { extension = file_path.substr(last_dot + 1); } wxString bease64_head = wxString::Format("data:image/%s;base64,", extension); std::wstringstream wss; wss << bease64_head; wss << wxBase64Encode(binaryImageData.data(), binaryImageData.size()); wxString base64_str = wss.str(); return base64_str; } void ProjectPanel::RunScript(std::string content) { WebView::RunScript(m_browser, content); } bool ProjectPanel::Show(bool show) { if (show) update_model_data(); return wxPanel::Show(show); } }} // namespace Slic3r::GUI