mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-15 02:37:51 -06:00
ENH: [STUDIO-1868] fetch printer model files and parse
Change-Id: If8da0072be5856cc179da3e62a06982de0ce2ecb
This commit is contained in:
parent
e8387b97ac
commit
904b122878
4 changed files with 391 additions and 56 deletions
|
@ -951,6 +951,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||||
|
|
||||||
std::string m_start_part_path;
|
std::string m_start_part_path;
|
||||||
std::string m_thumbnail_path;
|
std::string m_thumbnail_path;
|
||||||
|
std::string m_thumbnail_middle;
|
||||||
|
std::string m_thumbnail_small;
|
||||||
std::vector<std::string> m_sub_model_paths;
|
std::vector<std::string> m_sub_model_paths;
|
||||||
std::vector<ObjectImporter*> m_object_importers;
|
std::vector<ObjectImporter*> 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<Preset*>& project_presets, DynamicPrintConfig& config,
|
bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& 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);
|
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 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; }
|
unsigned int version() const { return m_version; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1263,8 +1266,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||||
// BBS: load relationships
|
// BBS: load relationships
|
||||||
if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element))
|
if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element))
|
||||||
return false;
|
return false;
|
||||||
if (!m_thumbnail_path.empty()) {
|
if (m_thumbnail_small.empty()) m_thumbnail_small = m_thumbnail_path;
|
||||||
return _extract_from_archive(archive, m_thumbnail_path, [&data](auto &archive, auto const &stat) -> bool {
|
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);
|
data.resize(stat.m_uncomp_size);
|
||||||
return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0);
|
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<std::istream*>(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<ModelDesignInfo>();
|
||||||
|
m_model->design_info->DesignerUserId = m_designer_user_id;
|
||||||
|
m_model->design_info->Designer = m_designer;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_model->profile_info = std::make_shared<ModelProfileInfo>();
|
||||||
|
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<ModelInfo>();
|
||||||
|
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<int, PlateData*>::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()
|
void _BBS_3MF_Importer::_destroy_xml_parser()
|
||||||
{
|
{
|
||||||
if (m_xml_parser != nullptr) {
|
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 m_sub_model_paths.push_back(path);
|
||||||
} else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) {
|
} else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) {
|
||||||
m_thumbnail_path = path;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -7652,6 +7824,15 @@ std::string bbs_3mf_get_thumbnail(const char *path)
|
||||||
return data;
|
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)
|
bool store_bbs_3mf(StoreParams& store_params)
|
||||||
{
|
{
|
||||||
// All export should use "C" locales for number formatting.
|
// All export should use "C" locales for number formatting.
|
||||||
|
|
|
@ -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 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
|
//BBS: add plate data list related logic
|
||||||
// add backup logic
|
// add backup logic
|
||||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
#include "PrinterFileSystem.h"
|
#include "PrinterFileSystem.h"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||||
|
#include "libslic3r/Model.hpp"
|
||||||
|
|
||||||
#include "../../Utils/NetworkAgent.hpp"
|
#include "../../Utils/NetworkAgent.hpp"
|
||||||
#include "../BitmapCache.hpp"
|
#include "../BitmapCache.hpp"
|
||||||
|
|
||||||
#include <boost/algorithm/hex.hpp>
|
#include <boost/algorithm/hex.hpp>
|
||||||
#include <boost/uuid/detail/md5.hpp>
|
#include <boost/uuid/detail/md5.hpp>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
|
@ -43,14 +46,25 @@ PrinterFileSystem::PrinterFileSystem()
|
||||||
m_session.owner = this;
|
m_session.owner = this;
|
||||||
#ifdef PRINTER_FILE_SYSTEM_TEST
|
#ifdef PRINTER_FILE_SYSTEM_TEST
|
||||||
auto time = wxDateTime::Now();
|
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) {
|
for (int i = 0; i < 100; ++i) {
|
||||||
auto name = wxString::Format(L"img-%03d.jpg", i + 1);
|
auto name = wxString::Format(L"img-%03d.jpg", i + 1);
|
||||||
wxImage im(L"D:\\work\\pic\\" + name);
|
wxImage im(path + 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});
|
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));
|
time.Add(wxDateSpan::Days(-1));
|
||||||
}
|
}
|
||||||
m_file_list[0].thumbnail = default_thumbnail;
|
m_file_list[0].thumbnail = default_thumbnail;
|
||||||
BuildGroups();
|
m_file_list.swap(m_file_list_cache[{F_TIMELAPSE, ""}]);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +73,20 @@ PrinterFileSystem::~PrinterFileSystem()
|
||||||
m_recv_thread.detach();
|
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;
|
return;
|
||||||
this->m_file_type = type;
|
assert(m_file_list_cache[std::make_pair(m_file_type, m_file_storage)].empty());
|
||||||
m_file_list.swap(m_file_list2);
|
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;
|
m_lock_start = m_lock_end = 0;
|
||||||
|
BuildGroups();
|
||||||
SendChangedEvent(EVT_FILE_CHANGED);
|
SendChangedEvent(EVT_FILE_CHANGED);
|
||||||
|
if (type == F_INVALID_TYPE)
|
||||||
|
return;
|
||||||
m_status = Status::ListSyncing;
|
m_status = Status::ListSyncing;
|
||||||
SendChangedEvent(EVT_STATUS_CHANGED, m_status);
|
SendChangedEvent(EVT_STATUS_CHANGED, m_status);
|
||||||
ListAllFiles();
|
ListAllFiles();
|
||||||
|
@ -94,7 +114,10 @@ size_t PrinterFileSystem::EnterSubGroup(size_t index)
|
||||||
void PrinterFileSystem::ListAllFiles()
|
void PrinterFileSystem::ListAllFiles()
|
||||||
{
|
{
|
||||||
json req;
|
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";
|
req["notify"] = "DETAIL";
|
||||||
SendRequest<FileList>(LIST_INFO, req, [this](json const& resp, FileList & list, auto) {
|
SendRequest<FileList>(LIST_INFO, req, [this](json const& resp, FileList & list, auto) {
|
||||||
json files = resp["file_lists"];
|
json files = resp["file_lists"];
|
||||||
|
@ -103,7 +126,7 @@ void PrinterFileSystem::ListAllFiles()
|
||||||
std::string path = f.value("path", "");
|
std::string path = f.value("path", "");
|
||||||
time_t time = f.value("time", 0);
|
time_t time = f.value("time", 0);
|
||||||
boost::uint64_t size = f["size"];
|
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);
|
list.push_back(ff);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -235,6 +258,38 @@ void PrinterFileSystem::DownloadCancel(size_t index)
|
||||||
file.flags &= ~FF_DOWNLOAD;
|
file.flags &= ~FF_DOWNLOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrinterFileSystem::FetchModel(size_t index, std::function<void(std::string const &)> 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<File>(
|
||||||
|
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
|
size_t PrinterFileSystem::GetCount() const
|
||||||
{
|
{
|
||||||
if (m_group_mode == G_NONE)
|
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();
|
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)
|
size_t PrinterFileSystem::GetIndexAtTime(boost::uint32_t time)
|
||||||
{
|
{
|
||||||
auto iter = std::upper_bound(m_file_list.begin(), m_file_list.end(), File{"", "", 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_task_flags |= FF_DOWNLOAD;
|
||||||
m_download_seq = SendRequest<Progress>(
|
m_download_seq = SendRequest<Progress>(
|
||||||
FILE_DOWNLOAD, req,
|
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
|
// in work thread, continue recv
|
||||||
size_t size = resp["size"];
|
size_t size = resp["size"];
|
||||||
prog.size = resp["offset"];
|
prog.size = resp["offset"];
|
||||||
|
@ -572,65 +633,148 @@ void PrinterFileSystem::UpdateFocusThumbnail()
|
||||||
return;
|
return;
|
||||||
size_t start = m_lock_start;
|
size_t start = m_lock_start;
|
||||||
size_t end = std::min(m_lock_end, GetCount());
|
size_t end = std::min(m_lock_end, GetCount());
|
||||||
std::vector<std::string> names;
|
std::vector<File> names;
|
||||||
std::vector<std::string> paths;
|
std::vector<File> paths;
|
||||||
for (; start < end; ++start) {
|
for (; start < end; ++start) {
|
||||||
auto &file = GetFile(start);
|
auto &file = GetFile(start);
|
||||||
if ((file.flags & FF_THUMNAIL) == 0) {
|
if ((file.flags & FF_THUMNAIL) == 0) {
|
||||||
if (file.path.empty())
|
if (file.path.empty())
|
||||||
names.push_back(file.name);
|
names.push_back({file.name, "", 0, 0, FF_THUMNAIL});
|
||||||
else
|
else
|
||||||
paths.push_back(file.path + "#thumbnail");
|
paths.push_back({"", file.path});
|
||||||
if (names.size() >= 5 || paths.size() >= 5)
|
if (names.size() >= 5 || paths.size() >= 5)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (names.empty() && paths.empty())
|
if (names.empty() && paths.empty())
|
||||||
return;
|
return;
|
||||||
|
m_task_flags |= FF_THUMNAIL;
|
||||||
|
UpdateFocusThumbnail2(std::make_shared<std::vector<File>>(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<std::vector<File>> files, int type)
|
||||||
|
{
|
||||||
json req;
|
json req;
|
||||||
json arr;
|
json arr;
|
||||||
if (paths.empty()) {
|
if (type == 0) {
|
||||||
for (auto &name : names) arr.push_back(name);
|
for (auto &file : *files) arr.push_back(file.name);
|
||||||
req["files"] = arr;
|
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 {
|
} 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;
|
req["paths"] = arr;
|
||||||
}
|
}
|
||||||
m_task_flags |= FF_THUMNAIL;
|
SendRequest<File>(
|
||||||
SendRequest<Thumbnail>(
|
SUB_FILE, req, [type](json const &resp, File &file, unsigned char const *data) -> int {
|
||||||
THUMBNAIL, req,
|
|
||||||
[this](json const &resp, Thumbnail &thumb, unsigned char const *data) -> int {
|
|
||||||
// in work thread, continue recv
|
// in work thread, continue recv
|
||||||
// receive data
|
// receive data
|
||||||
wxString mimetype = resp.value("mimetype", "image/jpeg");
|
wxString mimetype = resp.value("mimetype", "image/jpeg");
|
||||||
std::string thumbnail = resp.value("thumbnail", "");
|
std::string thumbnail = resp.value("thumbnail", "");
|
||||||
std::string path = resp.value("path", "");
|
std::string path = resp.value("path", "");
|
||||||
boost::uint32_t size = resp["size"];
|
boost::uint32_t size = resp["size"];
|
||||||
thumb.name = thumbnail;
|
file.name = thumbnail;
|
||||||
thumb.path = path;
|
file.path = path;
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
if (type != 2) {
|
||||||
wxMemoryInputStream mis(data, size);
|
wxMemoryInputStream mis(data, size);
|
||||||
thumb.thumbnail = wxImage(mis, mimetype);
|
file.thumbnail = wxImage(mis, mimetype);
|
||||||
|
} else {
|
||||||
|
file.local_path = std::string((char *) data, size);
|
||||||
|
ParseThumbnail(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
[this](int result, Thumbnail const &thumb) {
|
[this, files, type](int result, File const &file) {
|
||||||
auto n = thumb.name.find_last_of('.');
|
auto n = file.name.find_last_of('.');
|
||||||
auto name = n == std::string::npos ? thumb.name : thumb.name.substr(0, n);
|
auto name = n == std::string::npos ? file.name : file.name.substr(0, n);
|
||||||
n = thumb.path.find_last_of('#');
|
n = file.path.find_last_of('#');
|
||||||
auto path = n == std::string::npos ? thumb.path : thumb.path.substr(0, n);
|
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); }) :
|
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; });
|
std::find_if(m_file_list.begin(), m_file_list.end(), [&path](auto &f) { return f.path == path; });
|
||||||
if (iter != m_file_list.end()) {
|
if (iter != m_file_list.end()) {
|
||||||
iter->flags |= FF_THUMNAIL; // DOTO: retry on fail
|
if (type == 2) {
|
||||||
if (thumb.thumbnail.IsOk()) {
|
if (!file.metadata.empty()) {
|
||||||
iter->thumbnail = thumb.thumbnail;
|
iter->metadata = file.metadata;
|
||||||
int index = iter - m_file_list.begin();
|
int index = iter - m_file_list.begin();
|
||||||
SendChangedEvent(EVT_THUMBNAIL, index, thumb.name);
|
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)
|
}
|
||||||
|
if (result == 0) {
|
||||||
|
if (type == 2)
|
||||||
|
UpdateFocusThumbnail2(files, 3);
|
||||||
|
else
|
||||||
UpdateFocusThumbnail();
|
UpdateFocusThumbnail();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PrinterFileSystem : public wxEvtHandler, public boost::enable_shared_from_
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LIST_INFO = 0x0001,
|
LIST_INFO = 0x0001,
|
||||||
THUMBNAIL = 0x0002,
|
SUB_FILE = 0x0002,
|
||||||
FILE_DEL = 0x0003,
|
FILE_DEL = 0x0003,
|
||||||
FILE_DOWNLOAD = 0X0004,
|
FILE_DOWNLOAD = 0X0004,
|
||||||
NOTIFY_FIRST = 0x0100,
|
NOTIFY_FIRST = 0x0100,
|
||||||
|
@ -61,6 +61,8 @@ public:
|
||||||
enum FileType {
|
enum FileType {
|
||||||
F_TIMELAPSE,
|
F_TIMELAPSE,
|
||||||
F_VIDEO,
|
F_VIDEO,
|
||||||
|
F_MODEL,
|
||||||
|
F_INVALID_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GroupMode {
|
enum GroupMode {
|
||||||
|
@ -69,13 +71,13 @@ public:
|
||||||
G_YEAR,
|
G_YEAR,
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetFileType(FileType type);
|
void SetFileType(FileType type, std::string const & storage = {});
|
||||||
|
|
||||||
void SetGroupMode(GroupMode mode);
|
void SetGroupMode(GroupMode mode);
|
||||||
|
|
||||||
size_t EnterSubGroup(size_t index);
|
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; }
|
GroupMode GetGroupMode() const { return m_group_mode; }
|
||||||
|
|
||||||
|
@ -94,13 +96,15 @@ public:
|
||||||
std::string path;
|
std::string path;
|
||||||
time_t time = 0;
|
time_t time = 0;
|
||||||
boost::uint64_t size = 0;
|
boost::uint64_t size = 0;
|
||||||
wxBitmap thumbnail;
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
wxBitmap thumbnail;
|
||||||
int progress = -1; // -1: waiting
|
int progress = -1; // -1: waiting
|
||||||
std::string local_path;
|
std::string local_path;
|
||||||
|
std::map<std::string, std::string> metadata;
|
||||||
|
|
||||||
bool IsSelect() const { return flags & FF_SELECT; }
|
bool IsSelect() const { return flags & FF_SELECT; }
|
||||||
bool IsDownload() const { return flags & FF_DOWNLOAD; }
|
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; }
|
friend bool operator<(File const & l, File const & r) { return l.time > r.time; }
|
||||||
};
|
};
|
||||||
|
@ -109,13 +113,6 @@ public:
|
||||||
|
|
||||||
typedef std::vector<File> FileList;
|
typedef std::vector<File> FileList;
|
||||||
|
|
||||||
struct Thumbnail
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string path;
|
|
||||||
wxBitmap thumbnail;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Progress
|
struct Progress
|
||||||
{
|
{
|
||||||
wxInt64 size;
|
wxInt64 size;
|
||||||
|
@ -134,6 +131,8 @@ public:
|
||||||
|
|
||||||
void DownloadCancel(size_t index);
|
void DownloadCancel(size_t index);
|
||||||
|
|
||||||
|
void FetchModel(size_t index, std::function<void(std::string const &)> callback);
|
||||||
|
|
||||||
size_t GetCount() const;
|
size_t GetCount() const;
|
||||||
|
|
||||||
size_t GetIndexAtTime(boost::uint32_t time);
|
size_t GetIndexAtTime(boost::uint32_t time);
|
||||||
|
@ -182,6 +181,12 @@ private:
|
||||||
|
|
||||||
void UpdateFocusThumbnail();
|
void UpdateFocusThumbnail();
|
||||||
|
|
||||||
|
static bool ParseThumbnail(File &file);
|
||||||
|
|
||||||
|
static bool ParseThumbnail(File &file, std::istream &is);
|
||||||
|
|
||||||
|
void UpdateFocusThumbnail2(std::shared_ptr<std::vector<File>> files, int type);
|
||||||
|
|
||||||
void FileRemoved(size_t index, std::string const &name, bool by_path);
|
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);
|
size_t FindFile(size_t index, std::string const &name, bool by_path);
|
||||||
|
@ -266,10 +271,11 @@ private:
|
||||||
void PostCallback(std::function<void(void)> const & callback);
|
void PostCallback(std::function<void(void)> const & callback);
|
||||||
|
|
||||||
protected:
|
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;
|
GroupMode m_group_mode = G_NONE;
|
||||||
FileList m_file_list;
|
FileList m_file_list;
|
||||||
FileList m_file_list2;
|
std::map<std::pair<FileType, std::string>, FileList> m_file_list_cache;
|
||||||
std::vector<size_t> m_group_year;
|
std::vector<size_t> m_group_year;
|
||||||
std::vector<size_t> m_group_month;
|
std::vector<size_t> m_group_month;
|
||||||
std::vector<int> m_group_flags;
|
std::vector<int> m_group_flags;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue