diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3e0af50c2..5bd4ea2b6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -161,6 +161,8 @@ add_library(libslic3r STATIC utils.cpp Utils.hpp MTUtils.hpp + Zipper.hpp + Zipper.cpp SLA/SLABoilerPlate.hpp SLA/SLABasePool.hpp SLA/SLABasePool.cpp diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index df9446cf5..99a2110b3 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -97,7 +97,7 @@ public: bool is_ok() { return false; } - template<class T> LayerWriter& operator<<(const T& /*arg*/) { + template<class T> LayerWriter& operator<<(T&& /*arg*/) { return *this; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index c06e2fc77..6e2d7ae0d 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -6,6 +6,7 @@ #include "PrintExport.hpp" #include "Point.hpp" #include "MTUtils.hpp" +#include "Zipper.hpp" namespace Slic3r { @@ -200,6 +201,32 @@ struct SLAPrintStatistics } }; +struct SLAminzZipper {}; + +// The implementation of creating zipped archives with wxWidgets +template<> class LayerWriter<SLAminzZipper> { + Zipper m_zip; +public: + + inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} + + inline void next_entry(const std::string& fname) { m_zip.add_entry(fname); } + + inline std::string get_name() const { + return m_zip.get_name(); + } + + template<class T> inline LayerWriter& operator<<(T&& arg) { + m_zip << std::forward<T>(arg); return *this; + } + + bool is_ok() const { + return true; // m_zip blows up if something goes wrong... + } + + inline void close() { /* m_zip closes upon destruction */ } +}; + /** * @brief This class is the high level FSM for the SLA printing process. * @@ -231,9 +258,11 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposIndexSlices) && this->Inherited::is_step_done(slapsRasterize); } - template<class Fmt> void export_raster(const std::string& fname) { + template<class Fmt = SLAminzZipper> + void export_raster(const std::string& fname) { if(m_printer) m_printer->save<Fmt>(fname); } + const PrintObjects& objects() const { return m_objects; } std::string output_filename() const override; diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp new file mode 100644 index 000000000..941d9719d --- /dev/null +++ b/src/libslic3r/Zipper.cpp @@ -0,0 +1,177 @@ +#include <exception> +#include <sstream> +#include <iostream> + +#include "Zipper.hpp" +#include "miniz/miniz_zip.h" +#include <boost/filesystem/path.hpp> + +#include "I18N.hpp" + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + +#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L + #define SLIC3R_NORETURN +#elif __cplusplus >= 201103L + #define SLIC3R_NORETURN [[noreturn]] +#endif + +namespace Slic3r { + +class Zipper::Impl { +public: + mz_zip_archive arch; + std::string m_zipname; + + std::string get_errorstr(mz_zip_error mz_err) + { + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return L("undefined error"); + case MZ_ZIP_TOO_MANY_FILES: + return L("too many files"); + case MZ_ZIP_FILE_TOO_LARGE: + return L("file too large"); + case MZ_ZIP_UNSUPPORTED_METHOD: + return L("unsupported method"); + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return L("unsupported encryption"); + case MZ_ZIP_UNSUPPORTED_FEATURE: + return L("unsupported feature"); + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return L("failed finding central directory"); + case MZ_ZIP_NOT_AN_ARCHIVE: + return L("not a ZIP archive"); + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return L("invalid header or archive is corrupted"); + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return L("unsupported multidisk archive"); + case MZ_ZIP_DECOMPRESSION_FAILED: + return L("decompression failed or archive is corrupted"); + case MZ_ZIP_COMPRESSION_FAILED: + return L("compression failed"); + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return L("unexpected decompressed size"); + case MZ_ZIP_CRC_CHECK_FAILED: + return L("CRC-32 check failed"); + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return L("unsupported central directory size"); + case MZ_ZIP_ALLOC_FAILED: + return L("allocation failed"); + case MZ_ZIP_FILE_OPEN_FAILED: + return L("file open failed"); + case MZ_ZIP_FILE_CREATE_FAILED: + return L("file create failed"); + case MZ_ZIP_FILE_WRITE_FAILED: + return L("file write failed"); + case MZ_ZIP_FILE_READ_FAILED: + return L("file read failed"); + case MZ_ZIP_FILE_CLOSE_FAILED: + return L("file close failed"); + case MZ_ZIP_FILE_SEEK_FAILED: + return L("file seek failed"); + case MZ_ZIP_FILE_STAT_FAILED: + return L("file stat failed"); + case MZ_ZIP_INVALID_PARAMETER: + return L("invalid parameter"); + case MZ_ZIP_INVALID_FILENAME: + return L("invalid filename"); + case MZ_ZIP_BUF_TOO_SMALL: + return L("buffer too small"); + case MZ_ZIP_INTERNAL_ERROR: + return L("internal error"); + case MZ_ZIP_FILE_NOT_FOUND: + return L("file not found"); + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return L("archive is too large"); + case MZ_ZIP_VALIDATION_FAILED: + return L("validation failed"); + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return L("write calledback failed"); + default: + break; + } + + return "unknown error"; + } + + SLIC3R_NORETURN void blow_up() { + std::string prefix(L("Error with zip archive")); + throw std::runtime_error(prefix + " " + m_zipname + ": " + + get_errorstr(arch.m_last_error) + "!"); + } +}; + +Zipper::Zipper(const std::string &zipfname, e_compression compression) +{ + m_impl.reset(new Impl()); + + m_compression = compression; + m_impl->m_zipname = zipfname; + + memset(&m_impl->arch, 0, sizeof(m_impl->arch)); + + // Initialize the archive data + if(!mz_zip_writer_init_file(&m_impl->arch, zipfname.c_str(), 0)) + m_impl->blow_up(); +} + +Zipper::~Zipper() +{ + finish_entry(); + + if(!mz_zip_writer_finalize_archive(&m_impl->arch)) m_impl->blow_up(); + if(!mz_zip_writer_end(&m_impl->arch)) m_impl->blow_up(); +} + +Zipper::Zipper(Zipper &&m): + m_impl(std::move(m.m_impl)), + m_data(std::move(m.m_data)), + m_entry(std::move(m.m_entry)), + m_compression(m.m_compression) {} + +Zipper &Zipper::operator=(Zipper &&m) { + m_impl = std::move(m.m_impl); + m_data = std::move(m.m_data); + m_entry = std::move(m.m_entry); + m_compression = m.m_compression; + return *this; +} + +void Zipper::add_entry(const std::string &name) +{ + finish_entry(); // finish previous business + m_entry = name; +} + +void Zipper::finish_entry() +{ + if(!m_data.empty() > 0 && !m_entry.empty()) { + mz_uint compression = MZ_NO_COMPRESSION; + + switch (m_compression) { + case NO_COMPRESSION: compression = MZ_NO_COMPRESSION; break; + case FAST_COMPRESSION: compression = MZ_BEST_SPEED; break; + case TIGHT_COMPRESSION: compression = MZ_BEST_COMPRESSION; break; + } + + if(!mz_zip_writer_add_mem(&m_impl->arch, m_entry.c_str(), + m_data.c_str(), + m_data.size(), + compression)) m_impl->blow_up(); + } + + m_data.clear(); + m_entry.clear(); +} + +std::string Zipper::get_name() const { + return boost::filesystem::path(m_impl->m_zipname).stem().string(); +} + +} diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp new file mode 100644 index 000000000..526ffed2e --- /dev/null +++ b/src/libslic3r/Zipper.hpp @@ -0,0 +1,83 @@ +#ifndef ZIPPER_HPP +#define ZIPPER_HPP + +#include <string> +#include <memory> + +namespace Slic3r { + +// Class for creating zip archives. +class Zipper { +public: + // Three compression levels supported + enum e_compression { + NO_COMPRESSION, + FAST_COMPRESSION, + TIGHT_COMPRESSION + }; + +private: + class Impl; + std::unique_ptr<Impl> m_impl; + std::string m_data; + std::string m_entry; + e_compression m_compression; + +public: + + // Will blow up in a runtime exception if the file cannot be created. + explicit Zipper(const std::string& zipfname, + e_compression level = NO_COMPRESSION); + ~Zipper(); + + // No copies allwed, this is a file resource... + Zipper(const Zipper&) = delete; + Zipper& operator=(const Zipper&) = delete; + + // Moving is fine. + // Zipper(Zipper&&) = default; + // Zipper& operator=(Zipper&&) = default; + // All becouse of VS2013: + Zipper(Zipper &&m); + Zipper& operator=(Zipper &&m); + + /// Adding an entry means a file inside the new archive. Name param is the + /// name of the new file. To create directories, append a forward slash. + /// The previous entry is finished (see finish_entry) + void add_entry(const std::string& name); + + // Writing data to the archive works like with standard streams. The target + // within the zip file is the entry created with the add_entry method. + + // Template taking only arithmetic values, that std::to_string can handle. + template<class T> inline + typename std::enable_if<std::is_arithmetic<T>::value, Zipper&>::type + operator<<(T &&val) { + return this->operator<<(std::to_string(std::forward<T>(val))); + } + + // Template applied only for types that std::string can handle for append + // and copy. This includes c style strings... + template<class T> inline + typename std::enable_if<!std::is_arithmetic<T>::value, Zipper&>::type + operator<<(T &&val) { + if(m_data.empty()) m_data = std::forward<T>(val); + else m_data.append(val); + return *this; + } + + /// Finishing an entry means that subsequent writes will no longer be + /// appended to the previous entry. They will be written into the internal + /// buffer and ones an entry is added, the buffer will bind to the new entry + /// If the buffer was written, but no entry was added, the buffer will be + /// cleared after this call. + void finish_entry(); + + /// Gets the name of the archive without the path or extension. + std::string get_name() const; +}; + + +} + +#endif // ZIPPER_HPP diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 7c2f6c68c..2601842ef 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -92,64 +92,13 @@ void BackgroundSlicingProcess::process_fff() } } -// Pseudo type for specializing LayerWriter trait class -struct SLAZipFmt {}; - -// The implementation of creating zipped archives with wxWidgets -template<> class LayerWriter<SLAZipFmt> { - wxFileName fpath; - wxFFileOutputStream zipfile; - wxZipOutputStream zipstream; - wxStdOutputStream pngstream; - -public: - - inline LayerWriter(const std::string& zipfile_path): - fpath(zipfile_path), - zipfile(zipfile_path), - zipstream(zipfile), - pngstream(zipstream) - { - if(!is_ok()) - throw std::runtime_error("Cannot create zip file."); - } - - ~LayerWriter() { - // In case of an error (disk space full) zipstream destructor would - // crash. - pngstream.clear(); - zipstream.CloseEntry(); - } - - inline void next_entry(const std::string& fname) { - zipstream.PutNextEntry(fname); - } - - inline std::string get_name() const { - return fpath.GetName().ToUTF8().data(); - } - - template<class T> inline LayerWriter& operator<<(const T& arg) { - pngstream << arg; return *this; - } - - bool is_ok() const { - return pngstream.good() && zipstream.IsOk() && zipfile.IsOk(); - } - - inline void close() { - zipstream.Close(); - zipfile.Close(); - } -}; - void BackgroundSlicingProcess::process_sla() { assert(m_print == m_sla_print); m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { - m_sla_print->export_raster<SLAZipFmt>(m_export_path); + m_sla_print->export_raster(m_export_path); m_print->set_status(100, "Masked SLA file exported to " + m_export_path); } else if (! m_upload_job.empty()) { prepare_upload(); @@ -449,8 +398,8 @@ void BackgroundSlicingProcess::prepare_upload() } run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - } else { - m_sla_print->export_raster<SLAZipFmt>(source_path.string()); + } else { + m_sla_print->export_raster(source_path.string()); // TODO: Also finalize upload path like with FFF when there are statistics for SLA print }