From 904b1228782d9e172822a841425edfe902930f89 Mon Sep 17 00:00:00 2001 From: "chunmao.guo" Date: Sun, 23 Apr 2023 09:18:10 +0800 Subject: [PATCH] ENH: [STUDIO-1868] fetch printer model files and parse Change-Id: If8da0072be5856cc179da3e62a06982de0ce2ecb --- src/libslic3r/Format/bbs_3mf.cpp | 185 ++++++++++++++- src/libslic3r/Format/bbs_3mf.hpp | 4 + src/slic3r/GUI/Printer/PrinterFileSystem.cpp | 226 +++++++++++++++---- src/slic3r/GUI/Printer/PrinterFileSystem.h | 32 +-- 4 files changed, 391 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 37616d138b..96e437e0c3 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -951,6 +951,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string m_start_part_path; std::string m_thumbnail_path; + std::string m_thumbnail_middle; + std::string m_thumbnail_small; std::vector m_sub_model_paths; std::vector m_object_importers; @@ -975,6 +977,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0); bool get_thumbnail(const std::string &filename, std::string &data); + bool load_gcode_3mf_from_stream(std::istream & data, Model& model, PlateDataPtrs& plate_data_list, DynamicPrintConfig& config, Semver& file_version); unsigned int version() const { return m_version; } private: @@ -1263,8 +1266,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // BBS: load relationships if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element)) return false; - if (!m_thumbnail_path.empty()) { - return _extract_from_archive(archive, m_thumbnail_path, [&data](auto &archive, auto const &stat) -> bool { + if (m_thumbnail_small.empty()) m_thumbnail_small = m_thumbnail_path; + if (!m_thumbnail_small.empty()) { + return _extract_from_archive(archive, m_thumbnail_small, [&data](auto &archive, auto const &stat) -> bool { data.resize(stat.m_uncomp_size); return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0); }); @@ -1275,6 +1279,170 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) }); } + static size_t mz_zip_read_istream(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) + { + auto & is = *reinterpret_cast(pOpaque); + is.seekg(file_ofs, std::istream::beg); + if (!is) + return 0; + is.read((char *)pBuf, n); + return is.gcount(); + } + + bool _BBS_3MF_Importer::load_gcode_3mf_from_stream(std::istream &data, Model &model, PlateDataPtrs &plate_data_list, DynamicPrintConfig &config, Semver &file_version) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + archive.m_pRead = mz_zip_read_istream; + archive.m_pIO_opaque = &data; + + data.seekg(0, std::istream::end); + mz_uint64 size = data.tellg(); + data.seekg(0, std::istream::beg); + if (!mz_zip_reader_init(&archive, size, 0)) + return false; + + struct close_lock + { + mz_zip_archive *archive; + void close() + { + if (archive) { + mz_zip_reader_end(archive); + archive = nullptr; + } + } + ~close_lock() { close(); } + } lock{&archive}; + + // BBS: load relationships + if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element)) + return false; + if (m_start_part_path.empty()) + return false; + + //extract model files + if (!_extract_from_archive(archive, m_start_part_path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { + return _extract_model_from_archive(archive, stat); + })) { + add_error("Archive does not contain a valid model"); + return false; + } + + m_model = &model; + if (!m_designer.empty()) { + m_model->design_info = std::make_shared(); + m_model->design_info->DesignerUserId = m_designer_user_id; + m_model->design_info->Designer = m_designer; + } + + m_model->profile_info = std::make_shared(); + m_model->profile_info->ProfileTile = m_profile_title; + m_model->profile_info->ProfileCover = m_profile_cover; + m_model->profile_info->ProfileDescription = m_Profile_description; + m_model->profile_info->ProfileUserId = m_profile_user_id; + m_model->profile_info->ProfileUserName = m_profile_user_name; + + m_model->model_info = std::make_shared(); + m_model->model_info->load(model_info); + + if (m_thumbnail_middle.empty()) m_thumbnail_middle = m_thumbnail_path; + if (m_thumbnail_small.empty()) m_thumbnail_small = m_thumbnail_path; + m_model->model_info->metadata_items.emplace("Thumbnail", m_thumbnail_small); + m_model->model_info->metadata_items.emplace("Poster", m_thumbnail_middle); + + //BBS: version check + bool dont_load_config = !m_load_config; + if (m_bambuslicer_generator_version) { + Semver app_version = *(Semver::parse(SLIC3R_VERSION)); + Semver file_version = *m_bambuslicer_generator_version; + if (file_version.maj() != app_version.maj()) + dont_load_config = true; + } + else { + m_bambuslicer_generator_version = Semver::parse("0.0.0.0"); + dont_load_config = true; + } + + // we then loop again the entries to read other files stored in the archive + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + mz_zip_archive_file_stat stat; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract %1%th file %2%, total=%3%\n")%(i+1)%name%num_entries; + + if (!dont_load_config && boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) { + // extract slic3r print config file + ConfigSubstitutionContext config_substitutions(ForwardCompatibilitySubstitutionRule::Disable); + _extract_project_config_from_archive(archive, stat, config, config_substitutions, model); + } + else if (boost::algorithm::iequals(name, BBS_MODEL_CONFIG_FILE)) { + // extract slic3r model config file + if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) { + add_error("Archive does not contain a valid model config"); + return false; + } + } + else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { + m_parsing_slice_info = true; + //extract slice info from archive + _extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element); + m_parsing_slice_info = false; + } + } + } + + //BBS: load the plate info into plate_data_list + std::map::iterator it = m_plater_data.begin(); + plate_data_list.clear(); + plate_data_list.reserve(m_plater_data.size()); + for (unsigned int i = 0; i < m_plater_data.size(); i++) + { + PlateData* plate = new PlateData(); + plate_data_list.push_back(plate); + } + while (it != m_plater_data.end()) + { + if (it->first > m_plater_data.size()) + { + add_error("invalid plate index"); + return false; + } + plate_data_list[it->first-1]->locked = it->second->locked; + plate_data_list[it->first-1]->plate_index = it->second->plate_index-1; + plate_data_list[it->first-1]->obj_inst_map = it->second->obj_inst_map; + plate_data_list[it->first-1]->gcode_file = (m_load_restore || it->second->gcode_file.empty()) ? it->second->gcode_file : m_backup_path + "/" + it->second->gcode_file; + plate_data_list[it->first-1]->gcode_prediction = it->second->gcode_prediction; + plate_data_list[it->first-1]->gcode_weight = it->second->gcode_weight; + plate_data_list[it->first-1]->toolpath_outside = it->second->toolpath_outside; + plate_data_list[it->first-1]->is_support_used = it->second->is_support_used; + plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info; + plate_data_list[it->first-1]->warnings = it->second->warnings; + plate_data_list[it->first-1]->thumbnail_file = it->second->thumbnail_file; + //plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file; + plate_data_list[it->first-1]->top_file = it->second->top_file; + plate_data_list[it->first-1]->pick_file = it->second->pick_file.empty(); + plate_data_list[it->first-1]->pattern_bbox_file = it->second->pattern_bbox_file.empty(); + plate_data_list[it->first-1]->config = it->second->config; + + _extract_from_archive(archive, m_thumbnail_path, [&pixels = plate_data_list[it->first - 1]->plate_thumbnail.pixels](auto &archive, auto const &stat) -> bool { + pixels.resize(stat.m_uncomp_size); + return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, pixels.data(), pixels.size(), 0); + }); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%")%it->first %plate_data_list[it->first-1]->thumbnail_file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate_data_list[it->first-1]->top_file %plate_data_list[it->first-1]->pick_file; + it++; + } + + lock.close(); + + return true; + } + void _BBS_3MF_Importer::_destroy_xml_parser() { if (m_xml_parser != nullptr) { @@ -4002,6 +4170,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) else m_sub_model_paths.push_back(path); } else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) { m_thumbnail_path = path; + } else if (boost::starts_with(type, "http://schemas.bambulab.com/") && boost::ends_with(type, "cover-thumbnail-middle")) { + m_thumbnail_middle = path; + } else if (boost::starts_with(type, "http://schemas.bambulab.com/") && boost::ends_with(type, "cover-thumbnail-small")) { + m_thumbnail_small = path; } return true; } @@ -7652,6 +7824,15 @@ std::string bbs_3mf_get_thumbnail(const char *path) return data; } +bool load_gcode_3mf_from_stream(std::istream &data, DynamicPrintConfig *config, Model *model, PlateDataPtrs *plate_data_list, Semver *file_version) +{ + CNumericLocalesSetter locales_setter; + _BBS_3MF_Importer importer; + bool res = importer.load_gcode_3mf_from_stream(data, *model, *plate_data_list, *config, *file_version); + importer.log_errors(); + return res; +} + bool store_bbs_3mf(StoreParams& store_params) { // All export should use "C" locales for number formatting. diff --git a/src/libslic3r/Format/bbs_3mf.hpp b/src/libslic3r/Format/bbs_3mf.hpp index a60228d27e..d089019087 100644 --- a/src/libslic3r/Format/bbs_3mf.hpp +++ b/src/libslic3r/Format/bbs_3mf.hpp @@ -222,6 +222,10 @@ extern bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSub extern std::string bbs_3mf_get_thumbnail(const char * path); +extern bool load_gcode_3mf_from_stream(std::istream & data, DynamicPrintConfig* config, Model* model, PlateDataPtrs* plate_data_list, + Semver* file_version); + + //BBS: add plate data list related logic // add backup logic // Save the given model and the config data contained in the given Print into a 3mf file. diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index 739d510a07..e4199ffa76 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp @@ -1,11 +1,14 @@ #include "PrinterFileSystem.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Format/bbs_3mf.hpp" +#include "libslic3r/Model.hpp" #include "../../Utils/NetworkAgent.hpp" #include "../BitmapCache.hpp" #include #include +#include #include "nlohmann/json.hpp" @@ -43,14 +46,25 @@ PrinterFileSystem::PrinterFileSystem() m_session.owner = this; #ifdef PRINTER_FILE_SYSTEM_TEST auto time = wxDateTime::Now(); + wxString path = "D:\\work\\pic\\"; + for (int i = 0; i < 10; ++i) { + auto name = wxString::Format(L"gcode-%02d.3mf", i + 1); + m_file_list.push_back({name.ToUTF8().data(), "", time.GetTicks(), 26937, i < 5 ? FF_DOWNLOAD : 0, default_thumbnail, i * 34 - 35}); + std::ifstream ifs((path + name).ToUTF8().data(), std::ios::binary); + if (ifs) + ParseThumbnail(m_file_list.back(), ifs); + time.Add(wxDateSpan::Days(-1)); + } + m_file_list.swap(m_file_list_cache[{F_MODEL, ""}]); + time = wxDateTime::Now(); for (int i = 0; i < 100; ++i) { auto name = wxString::Format(L"img-%03d.jpg", i + 1); - wxImage im(L"D:\\work\\pic\\" + name); - m_file_list.push_back({name.ToUTF8().data(), "", time.GetTicks(), 26937, i > 3 ? im : default_thumbnail, i < 20 ? FF_DOWNLOAD : 0, i * 10 - 40 - 1}); + wxImage im(path + name); + m_file_list.push_back({name.ToUTF8().data(), "", time.GetTicks(), 26937, i < 20 ? FF_DOWNLOAD : 0, i > 3 ? im : default_thumbnail, i * 10 - 40 - 1}); time.Add(wxDateSpan::Days(-1)); } m_file_list[0].thumbnail = default_thumbnail; - BuildGroups(); + m_file_list.swap(m_file_list_cache[{F_TIMELAPSE, ""}]); #endif } @@ -59,14 +73,20 @@ PrinterFileSystem::~PrinterFileSystem() m_recv_thread.detach(); } -void PrinterFileSystem::SetFileType(FileType type) +void PrinterFileSystem::SetFileType(FileType type, std::string const &storage) { - if (this->m_file_type == type) + if (m_file_type == type && m_file_storage == storage) return; - this->m_file_type = type; - m_file_list.swap(m_file_list2); + assert(m_file_list_cache[std::make_pair(m_file_type, m_file_storage)].empty()); + m_file_list.swap(m_file_list_cache[{m_file_type, m_file_storage}]); + std::swap(m_file_type, type); + m_file_storage = storage; + m_file_list.swap(m_file_list_cache[{m_file_type, m_file_storage}]); m_lock_start = m_lock_end = 0; + BuildGroups(); SendChangedEvent(EVT_FILE_CHANGED); + if (type == F_INVALID_TYPE) + return; m_status = Status::ListSyncing; SendChangedEvent(EVT_STATUS_CHANGED, m_status); ListAllFiles(); @@ -94,7 +114,10 @@ size_t PrinterFileSystem::EnterSubGroup(size_t index) void PrinterFileSystem::ListAllFiles() { json req; - req["type"] = m_file_type == F_VIDEO ? "video" : "timelapse"; + char const * types[] {"timelapse","video", "model" }; + req["type"] = types[m_file_type]; + if (!m_file_storage.empty()) + req["storage"] = m_file_storage; req["notify"] = "DETAIL"; SendRequest(LIST_INFO, req, [this](json const& resp, FileList & list, auto) { json files = resp["file_lists"]; @@ -103,7 +126,7 @@ void PrinterFileSystem::ListAllFiles() std::string path = f.value("path", ""); time_t time = f.value("time", 0); boost::uint64_t size = f["size"]; - File ff = {name, path, time, size, default_thumbnail}; + File ff = {name, path, time, size, 0, default_thumbnail}; list.push_back(ff); } return 0; @@ -235,6 +258,38 @@ void PrinterFileSystem::DownloadCancel(size_t index) file.flags &= ~FF_DOWNLOAD; } +void PrinterFileSystem::FetchModel(size_t index, std::function callback) +{ + json req; + json arr; + if (index == (size_t) -1) return; + if (index >= m_file_list.size()) return; + auto &file = m_file_list[index]; + arr.push_back(file.path + "#_rel/.rels"); + arr.push_back(file.path + "#3D/3dmodel.model"); + arr.push_back(file.path + "#Metadata/slice_info.config"); + arr.push_back(file.path + "#Metadata/project_settings.config"); + for (auto & meta : file.metadata) { + if (boost::algorithm::starts_with(meta.first, "plate_thumbnail_")) + arr.push_back(file.path + "#" + meta.second); + } + req["paths"] = arr; + SendRequest( + SUB_FILE, req, + [](json const &resp, File &file, unsigned char const *data) -> int { + // in work thread, continue recv + // receive data + boost::uint32_t size = resp["size"]; + if (size > 0) { + file.local_path = std::string((char *) data, size); + } + return 0; + }, + [callback](int, File const &file) { + callback(file.local_path); + }); +} + size_t PrinterFileSystem::GetCount() const { if (m_group_mode == G_NONE) @@ -242,6 +297,12 @@ size_t PrinterFileSystem::GetCount() const return m_group_mode == G_YEAR ? m_group_year.size() : m_group_month.size(); } +std::string PrinterFileSystem::File::Metadata(std::string const &key, std::string const & dflt) const +{ + auto iter = metadata.find(key); + return iter == metadata.end() || iter->second.empty() ? dflt : iter->second; +} + size_t PrinterFileSystem::GetIndexAtTime(boost::uint32_t time) { auto iter = std::upper_bound(m_file_list.begin(), m_file_list.end(), File{"", "", time}); @@ -513,7 +574,7 @@ void PrinterFileSystem::DownloadNextFile() m_task_flags |= FF_DOWNLOAD; m_download_seq = SendRequest( FILE_DOWNLOAD, req, - [this, download](json const &resp, Progress &prog, unsigned char const *data) -> int { + [download](json const &resp, Progress &prog, unsigned char const *data) -> int { // in work thread, continue recv size_t size = resp["size"]; prog.size = resp["offset"]; @@ -572,65 +633,148 @@ void PrinterFileSystem::UpdateFocusThumbnail() return; size_t start = m_lock_start; size_t end = std::min(m_lock_end, GetCount()); - std::vector names; - std::vector paths; + std::vector names; + std::vector paths; for (; start < end; ++start) { auto &file = GetFile(start); if ((file.flags & FF_THUMNAIL) == 0) { if (file.path.empty()) - names.push_back(file.name); + names.push_back({file.name, "", 0, 0, FF_THUMNAIL}); else - paths.push_back(file.path + "#thumbnail"); + paths.push_back({"", file.path}); if (names.size() >= 5 || paths.size() >= 5) break; } } if (names.empty() && paths.empty()) return; + m_task_flags |= FF_THUMNAIL; + UpdateFocusThumbnail2(std::make_shared>(paths), paths.empty() ? 0 : m_file_type == F_MODEL ? 2 : 1); +} + +bool PrinterFileSystem::ParseThumbnail(File &file) +{ + std::istringstream iss(file.local_path, std::ios::binary); + return ParseThumbnail(file, iss); +} + +static std::string durationString(long duration) +{ + static boost::regex rx("^0d(0h)?"); + auto time = boost::format("%1%d%2%h%3%m") % (duration / 86400) % ((duration % 86400) / 3600) % ((duration % 3600) / 60); + return boost::regex_replace(time.str(), rx, ""); +} + +bool PrinterFileSystem::ParseThumbnail(File &file, std::istream &is) +{ + Slic3r::DynamicPrintConfig config; + Slic3r::Model model; + Slic3r::PlateDataPtrs plate_data_list; + Slic3r::Semver file_version; + if (!Slic3r::load_gcode_3mf_from_stream(is, &config, &model, &plate_data_list, &file_version)) + return false; + float time = 0.f; + float weight = 0.f; + for (auto &plate : plate_data_list) { + time += atof(plate->gcode_prediction.c_str()); + weight += atof(plate->gcode_weight.c_str()); + file.metadata.emplace("plate_thumbnail_" + std::to_string(plate->plate_index), plate->thumbnail_file); + } + file.metadata.emplace("Title", model.model_info->model_name); + file.metadata.emplace("Time", durationString(round(time))); + file.metadata.emplace("Weight", std::to_string(int(round(weight)))); + auto thumbnail = model.model_info->metadata_items["Thumbnail"]; + if (thumbnail.empty() && !plate_data_list.empty()) { + thumbnail = plate_data_list.front()->thumbnail_file; + if (thumbnail.empty()) { + thumbnail = plate_data_list.front()->gcode_file; + boost::algorithm::replace_all(thumbnail, ".gcode", ".png"); + } + } + file.metadata.emplace("Thumbnail", thumbnail); + return true; +} + +void PrinterFileSystem::UpdateFocusThumbnail2(std::shared_ptr> files, int type) +{ json req; json arr; - if (paths.empty()) { - for (auto &name : names) arr.push_back(name); + if (type == 0) { + for (auto &file : *files) arr.push_back(file.name); req["files"] = arr; + if (m_file_type == F_MODEL) { + for (auto &file : *files) arr.push_back(file.path); + } + for (auto &file : *files) arr.push_back(file.path); } else { - for (auto &path : paths) arr.push_back(path); + if (type == 1) { + for (auto &file : *files) arr.push_back(file.path + "#thumbnail"); + } else if (type == 2) { + for (auto &file : *files) { + arr.push_back(file.path + "#_rel/.rels"); + arr.push_back(file.path + "#3D/3dmodel.model"); + arr.push_back(file.path + "#Metadata/slice_info.config"); + arr.push_back(file.path + "#Metadata/project_settings.config"); + } + req["zip"] = true; + } else { + for (auto &file : *files) arr.push_back(file.path + "#" + file.metadata["Thumbnail"]); + } req["paths"] = arr; } - m_task_flags |= FF_THUMNAIL; - SendRequest( - THUMBNAIL, req, - [this](json const &resp, Thumbnail &thumb, unsigned char const *data) -> int { + SendRequest( + SUB_FILE, req, [type](json const &resp, File &file, unsigned char const *data) -> int { // in work thread, continue recv // receive data - wxString mimetype = resp.value("mimetype", "image/jpeg"); - std::string thumbnail = resp.value("thumbnail", ""); - std::string path = resp.value("path", ""); - boost::uint32_t size = resp["size"]; - thumb.name = thumbnail; - thumb.path = path; + wxString mimetype = resp.value("mimetype", "image/jpeg"); + std::string thumbnail = resp.value("thumbnail", ""); + std::string path = resp.value("path", ""); + boost::uint32_t size = resp["size"]; + file.name = thumbnail; + file.path = path; if (size > 0) { - wxMemoryInputStream mis(data, size); - thumb.thumbnail = wxImage(mis, mimetype); + if (type != 2) { + wxMemoryInputStream mis(data, size); + file.thumbnail = wxImage(mis, mimetype); + } else { + file.local_path = std::string((char *) data, size); + ParseThumbnail(file); + } } return 0; }, - [this](int result, Thumbnail const &thumb) { - auto n = thumb.name.find_last_of('.'); - auto name = n == std::string::npos ? thumb.name : thumb.name.substr(0, n); - n = thumb.path.find_last_of('#'); - auto path = n == std::string::npos ? thumb.path : thumb.path.substr(0, n); + [this, files, type](int result, File const &file) { + auto n = file.name.find_last_of('.'); + auto name = n == std::string::npos ? file.name : file.name.substr(0, n); + n = file.path.find_last_of('#'); + auto path = n == std::string::npos ? file.path : file.path.substr(0, n); auto iter = path.empty() ? std::find_if(m_file_list.begin(), m_file_list.end(), [&name](auto &f) { return boost::algorithm::starts_with(f.name, name); }) : std::find_if(m_file_list.begin(), m_file_list.end(), [&path](auto &f) { return f.path == path; }); if (iter != m_file_list.end()) { - iter->flags |= FF_THUMNAIL; // DOTO: retry on fail - if (thumb.thumbnail.IsOk()) { - iter->thumbnail = thumb.thumbnail; - int index = iter - m_file_list.begin(); - SendChangedEvent(EVT_THUMBNAIL, index, thumb.name); + if (type == 2) { + if (!file.metadata.empty()) { + iter->metadata = file.metadata; + int index = iter - m_file_list.begin(); + SendChangedEvent(EVT_THUMBNAIL, index, file.name); + auto iter = std::find_if(files->begin(), files->end(), [&path](auto &f) { return f.path == path; }); + if (iter != files->end()) + iter->metadata = file.metadata; + } + } else { + iter->flags |= FF_THUMNAIL; // DOTO: retry on fail + if (file.thumbnail.IsOk()) { + iter->thumbnail = file.thumbnail; + int index = iter - m_file_list.begin(); + SendChangedEvent(EVT_THUMBNAIL, index, file.name); + } } } - if (result == 0) - UpdateFocusThumbnail(); + if (result == 0) { + if (type == 2) + UpdateFocusThumbnail2(files, 3); + else + UpdateFocusThumbnail(); + } }); } diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.h b/src/slic3r/GUI/Printer/PrinterFileSystem.h index c40ae2d25e..f6dfe0c1ce 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.h +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.h @@ -28,7 +28,7 @@ class PrinterFileSystem : public wxEvtHandler, public boost::enable_shared_from_ enum { LIST_INFO = 0x0001, - THUMBNAIL = 0x0002, + SUB_FILE = 0x0002, FILE_DEL = 0x0003, FILE_DOWNLOAD = 0X0004, NOTIFY_FIRST = 0x0100, @@ -61,6 +61,8 @@ public: enum FileType { F_TIMELAPSE, F_VIDEO, + F_MODEL, + F_INVALID_TYPE, }; enum GroupMode { @@ -69,13 +71,13 @@ public: G_YEAR, }; - void SetFileType(FileType type); + void SetFileType(FileType type, std::string const & storage = {}); void SetGroupMode(GroupMode mode); size_t EnterSubGroup(size_t index); - GroupMode GetFileType() const { return m_group_mode; } + FileType GetFileType() const { return m_file_type; } GroupMode GetGroupMode() const { return m_group_mode; } @@ -94,13 +96,15 @@ public: std::string path; time_t time = 0; boost::uint64_t size = 0; - wxBitmap thumbnail; int flags = 0; + wxBitmap thumbnail; int progress = -1; // -1: waiting std::string local_path; + std::map metadata; bool IsSelect() const { return flags & FF_SELECT; } bool IsDownload() const { return flags & FF_DOWNLOAD; } + std::string Metadata(std::string const & key, std::string const & dflt) const; friend bool operator<(File const & l, File const & r) { return l.time > r.time; } }; @@ -109,13 +113,6 @@ public: typedef std::vector FileList; - struct Thumbnail - { - std::string name; - std::string path; - wxBitmap thumbnail; - }; - struct Progress { wxInt64 size; @@ -134,6 +131,8 @@ public: void DownloadCancel(size_t index); + void FetchModel(size_t index, std::function callback); + size_t GetCount() const; size_t GetIndexAtTime(boost::uint32_t time); @@ -182,6 +181,12 @@ private: void UpdateFocusThumbnail(); + static bool ParseThumbnail(File &file); + + static bool ParseThumbnail(File &file, std::istream &is); + + void UpdateFocusThumbnail2(std::shared_ptr> files, int type); + void FileRemoved(size_t index, std::string const &name, bool by_path); size_t FindFile(size_t index, std::string const &name, bool by_path); @@ -266,10 +271,11 @@ private: void PostCallback(std::function const & callback); protected: - FileType m_file_type = F_TIMELAPSE; + FileType m_file_type = F_INVALID_TYPE; + std::string m_file_storage; GroupMode m_group_mode = G_NONE; FileList m_file_list; - FileList m_file_list2; + std::map, FileList> m_file_list_cache; std::vector m_group_year; std::vector m_group_month; std::vector m_group_flags;