mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 06:57:36 -06:00
ENH: Open Prinables.com Links and Zip Archives (#3823)
* Enable ability to open `prusaslicer://` links * Add needed function to miniz * Import Zip Functionality Allows zip file to be drag and dropped or imported via the menu option Based on prusa3d/PrusaSlicer@ce38e57 and current master branch files * Update dialog style to match Orca * Ensure link is from printables * add toggle option in preferences doesn't actually control anything yet * Add Downloader classes As-is from PS master * Create Orca Styled Variant of Icons * Add Icons to ImGui * Use PS's Downloader impl for `prusaslicer://` links * Implement URL Registering on Windows * Implement prusaslicer:// link on macOS * Remove unnecessary class name qualifier in Plater.hpp * Add downloader desktop integration registration and undo * Revert Info.plist --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
parent
0dbf610226
commit
a764d836e1
39 changed files with 3109 additions and 41 deletions
|
@ -156,6 +156,7 @@
|
|||
|
||||
#include <libslic3r/CutUtils.hpp>
|
||||
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
|
||||
#include <libslic3r/miniz_extension.hpp>
|
||||
#include "WipeTowerDialog.hpp"
|
||||
#include "ObjColorDialog.hpp"
|
||||
|
||||
|
@ -168,6 +169,7 @@
|
|||
#include "PlateSettingsDialog.hpp"
|
||||
#include "DailyTips.hpp"
|
||||
#include "CreatePresetsDialog.hpp"
|
||||
#include "FileArchiveDialog.hpp"
|
||||
|
||||
using boost::optional;
|
||||
namespace fs = boost::filesystem;
|
||||
|
@ -8996,8 +8998,9 @@ void Plater::import_model_id(wxString download_info)
|
|||
/* prepare project and profile */
|
||||
boost::thread import_thread = Slic3r::create_thread([&percent, &cont, &cancel, &retry_count, max_retries, &msg, &target_path, &download_ok, download_url, &filename] {
|
||||
|
||||
NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||
if (!m_agent) return;
|
||||
// Orca: NetworkAgent is not needed and only prevents this from running
|
||||
// NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||
// if (!m_agent) return;
|
||||
|
||||
int res = 0;
|
||||
unsigned int http_code;
|
||||
|
@ -9148,7 +9151,11 @@ void Plater::import_model_id(wxString download_info)
|
|||
if (download_ok) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "import_model_id: target_path = " << target_path.string();
|
||||
/* load project */
|
||||
this->load_project(target_path.wstring());
|
||||
// Orca: If download is a zip file, treat it as if file has been drag and dropped on the plater
|
||||
if (target_path.extension() == ".zip")
|
||||
this->load_files(wxArrayString(1, target_path.string()));
|
||||
else
|
||||
this->load_project(target_path.wstring());
|
||||
/*BBS set project info after load project, project info is reset in load project */
|
||||
//p->project.project_model_id = model_id;
|
||||
//p->project.project_design_id = design_id;
|
||||
|
@ -9818,6 +9825,19 @@ void Plater::calib_VFA(const Calib_Params& params)
|
|||
p->background_process.fff_print()->set_calib_params(params);
|
||||
}
|
||||
BuildVolume_Type Plater::get_build_volume_type() const { return p->bed.get_build_volume_type(); }
|
||||
|
||||
void Plater::import_zip_archive()
|
||||
{
|
||||
wxString input_file;
|
||||
wxGetApp().import_zip(this, input_file);
|
||||
if (input_file.empty())
|
||||
return;
|
||||
|
||||
wxArrayString arr;
|
||||
arr.Add(input_file);
|
||||
load_files(arr);
|
||||
}
|
||||
|
||||
void Plater::import_sl1_archive()
|
||||
{
|
||||
auto &w = get_ui_job_worker();
|
||||
|
@ -10003,6 +10023,186 @@ std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_fil
|
|||
return p->load_files(paths, strategy, ask_multi);
|
||||
}
|
||||
|
||||
bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
|
||||
{
|
||||
//std::vector<fs::path> unzipped_paths;
|
||||
std::vector<fs::path> non_project_paths;
|
||||
std::vector<fs::path> project_paths;
|
||||
try
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
||||
if (!open_zip_reader(&archive, archive_path.string())) {
|
||||
// TRN %1% is archive path
|
||||
std::string err_msg = GUI::format(_u8L("Loading of a ZIP archive on path %1% has failed."), archive_path.string());
|
||||
throw Slic3r::FileIOError(err_msg);
|
||||
}
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
mz_zip_archive_file_stat stat;
|
||||
// selected_paths contains paths and its uncompressed size. The size is used to distinguish between files with same path.
|
||||
std::vector<std::pair<fs::path, size_t>> selected_paths;
|
||||
FileArchiveDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe), &archive, selected_paths);
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
{
|
||||
std::string archive_path_string = archive_path.string();
|
||||
archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4);
|
||||
fs::path archive_dir(wxStandardPaths::Get().GetTempDir().utf8_str().data());
|
||||
|
||||
for (auto& path_w_size : selected_paths) {
|
||||
const fs::path& path = path_w_size.first;
|
||||
size_t size = path_w_size.second;
|
||||
// find path in zip archive
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
if (size != stat.m_uncomp_size) // size must fit
|
||||
continue;
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
fs::path archive_path(name);
|
||||
|
||||
std::string extra(1024, 0);
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(&archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
archive_path = fs::path(extra.substr(0, extra_size));
|
||||
name = archive_path.string();
|
||||
}
|
||||
|
||||
if (archive_path.empty())
|
||||
continue;
|
||||
if (path != archive_path)
|
||||
continue;
|
||||
// decompressing
|
||||
try
|
||||
{
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
// rename if file exists
|
||||
std::string filename = path.filename().string();
|
||||
std::string extension = boost::filesystem::extension(path);
|
||||
std::string just_filename = filename.substr(0, filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
|
||||
size_t version = 0;
|
||||
while (fs::exists(archive_dir / (final_filename + extension)))
|
||||
{
|
||||
++version;
|
||||
final_filename = just_filename + "(" + std::to_string(version) + ")";
|
||||
}
|
||||
filename = final_filename + extension;
|
||||
fs::path final_path = archive_dir / filename;
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
// Decompress action. We already has correct file index in stat structure.
|
||||
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
// TRN: First argument = path to file, second argument = error description
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2%"), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive)));
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
// write buffer to file
|
||||
fs::fstream file(final_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(buffer.c_str(), buffer.size());
|
||||
file.close();
|
||||
if (!fs::exists(final_path)) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to find unzipped file at %1%. Unzipping of file has failed."), final_path.string());
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Unzipped " << final_path;
|
||||
if (!boost::algorithm::iends_with(filename, ".3mf") && !boost::algorithm::iends_with(filename, ".amf")) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
// if 3mf - read archive headers to find project file
|
||||
if (/*(boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||*/
|
||||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
|
||||
project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close_zip_reader(&archive);
|
||||
if (non_project_paths.size() + project_paths.size() != selected_paths.size())
|
||||
BOOST_LOG_TRIVIAL(error) << "Decompresing of archive did not retrieve all files. Expected files: "
|
||||
<< selected_paths.size()
|
||||
<< " Decopressed files: "
|
||||
<< non_project_paths.size() + project_paths.size();
|
||||
} else {
|
||||
close_zip_reader(&archive);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
catch (const Slic3r::FileIOError& e) {
|
||||
// zip reader should be already closed or not even opened
|
||||
GUI::show_error(this, e.what());
|
||||
return false;
|
||||
}
|
||||
// none selected
|
||||
if (project_paths.empty() && non_project_paths.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1 project file and some models - behave like drag n drop of 3mf and then load models
|
||||
if (project_paths.size() == 1)
|
||||
{
|
||||
wxArrayString aux;
|
||||
aux.Add(from_u8(project_paths.front().string()));
|
||||
bool loaded3mf = load_files(aux);
|
||||
load_files(non_project_paths, LoadStrategy::LoadModel);
|
||||
boost::system::error_code ec;
|
||||
if (loaded3mf) {
|
||||
fs::remove(project_paths.front(), ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
for (const fs::path& path : non_project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// load all projects and all models as geometry
|
||||
load_files(project_paths, LoadStrategy::LoadModel);
|
||||
load_files(non_project_paths, LoadStrategy::LoadModel);
|
||||
|
||||
|
||||
for (const fs::path& path : project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
for (const fs::path& path : non_project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class RadioBox;
|
||||
class RadioSelector
|
||||
{
|
||||
|
@ -10341,7 +10541,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
|
|||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
bool Plater::load_files(const wxArrayString& filenames)
|
||||
{
|
||||
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg)", std::regex::icase);
|
||||
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg|zip)", std::regex::icase);
|
||||
const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase);
|
||||
|
||||
std::vector<fs::path> normal_paths;
|
||||
|
@ -10431,6 +10631,21 @@ bool Plater::load_files(const wxArrayString& filenames)
|
|||
}
|
||||
}
|
||||
|
||||
// Orca: Iters through given paths and imports files from zip then remove zip from paths
|
||||
// returns true if zip files were found
|
||||
auto handle_zips = [this](vector<fs::path>& paths) { // NOLINT(*-no-recursion) - Recursion is intended and should be managed properly
|
||||
bool res = false;
|
||||
for (auto it = paths.begin(); it != paths.end();) {
|
||||
if (boost::algorithm::iends_with(it->string(), ".zip")) {
|
||||
res = true;
|
||||
preview_zip_archive(*it);
|
||||
it = paths.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
switch (loadfiles_type) {
|
||||
case LoadFilesType::Single3MF:
|
||||
open_3mf_file(normal_paths[0]);
|
||||
|
@ -10438,6 +10653,7 @@ bool Plater::load_files(const wxArrayString& filenames)
|
|||
|
||||
case LoadFilesType::SingleOther: {
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
if (handle_zips(normal_paths)) return true;
|
||||
if (load_files(normal_paths, LoadStrategy::LoadModel, false).empty()) { res = false; }
|
||||
break;
|
||||
}
|
||||
|
@ -10453,6 +10669,9 @@ bool Plater::load_files(const wxArrayString& filenames)
|
|||
|
||||
case LoadFilesType::MultipleOther: {
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
if (handle_zips(normal_paths)) {
|
||||
if (normal_paths.empty()) return true;
|
||||
}
|
||||
if (load_files(normal_paths, LoadStrategy::LoadModel, true).empty()) { res = false; }
|
||||
break;
|
||||
}
|
||||
|
@ -10471,6 +10690,9 @@ bool Plater::load_files(const wxArrayString& filenames)
|
|||
|
||||
open_3mf_file(first_file[0]);
|
||||
if (load_files(tmf_file, LoadStrategy::LoadModel).empty()) { res = false; }
|
||||
if (res && handle_zips(other_file)) {
|
||||
if (normal_paths.empty()) return true;
|
||||
}
|
||||
if (load_files(other_file, LoadStrategy::LoadModel, false).empty()) { res = false; }
|
||||
break;
|
||||
default: break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue