diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e0492cc124..643afe69ec 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -98,6 +98,8 @@ set(SLIC3R_SOURCES Format/SLAArchiveWriter.cpp Format/SLAArchiveReader.hpp Format/SLAArchiveReader.cpp + Format/ZipperArchiveImport.hpp + Format/ZipperArchiveImport.cpp Format/SL1.hpp Format/SL1.cpp Format/SL1_SVG.hpp diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index accca4b775..a29dcc100a 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -16,6 +16,20 @@ #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" +#include "SLAArchiveReader.hpp" +#include "ZipperArchiveImport.hpp" + +#include "libslic3r/MarchingSquares.hpp" +#include "libslic3r/PNGReadWrite.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + +#include "libslic3r/SLA/RasterBase.hpp" + +#include +#include +#include + namespace Slic3r { using ConfMap = std::map; @@ -222,3 +236,238 @@ void SL1Archive::export_print(const std::string fname, } } // namespace Slic3r + +// ///////////////////////////////////////////////////////////////////////////// +// Reader implementation +// ///////////////////////////////////////////////////////////////////////////// + +namespace marchsq { + +template<> struct _RasterTraits { + using Rst = Slic3r::png::ImageGreyscale; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) + { + return rst.get(row, col); + } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.rows; } + static size_t cols(const Rst &rst) { return rst.cols; } +}; + +} // namespace marchsq + +namespace Slic3r { + +namespace { + +ExPolygons rings_to_expolygons(const std::vector &rings, + double px_w, double px_h) +{ + auto polys = reserve_vector(rings.size()); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); + + polys.emplace_back(poly); + } + + // TODO: Is a union necessary? + return union_ex(polys); +} + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +void invert_raster_trafo(ExPolygons & expolys, + const sla::RasterBase::Trafo &trafo, + coord_t width, + coord_t height) +{ + if (trafo.flipXY) std::swap(height, width); + + for (auto &expoly : expolys) { + if (trafo.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (trafo.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-trafo.center_x, -trafo.center_y); + + if (trafo.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } +} + +struct RasterParams { + sla::RasterBase::Trafo trafo; // Raster transformations + coord_t width, height; // scaled raster dimensions (not resolution) + double px_h, px_w; // pixel dimesions + marchsq::Coord win; // marching squares window size +}; + +RasterParams get_raster_params(const DynamicPrintConfig &cfg) +{ + auto *opt_disp_cols = cfg.option("display_pixels_x"); + auto *opt_disp_rows = cfg.option("display_pixels_y"); + auto *opt_disp_w = cfg.option("display_width"); + auto *opt_disp_h = cfg.option("display_height"); + auto *opt_mirror_x = cfg.option("display_mirror_x"); + auto *opt_mirror_y = cfg.option("display_mirror_y"); + auto *opt_orient = cfg.option>("display_orientation"); + + if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || + !opt_mirror_x || !opt_mirror_y || !opt_orient) + throw MissingProfileError("Invalid SL1 / SL1S file"); + + RasterParams rstp; + + rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); + rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); + + rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ? + sla::RasterBase::roLandscape : + sla::RasterBase::roPortrait, + {opt_mirror_x->value, opt_mirror_y->value}}; + + rstp.height = scaled(opt_disp_h->value); + rstp.width = scaled(opt_disp_w->value); + + return rstp; +} + +std::vector extract_slices_from_sla_archive( + ZipperArchive &arch, + const RasterParams &rstp, + std::function progr) +{ + std::vector slices(arch.entries.size()); + + struct Status + { + double incr, val, prev; + bool stop = false; + execution::SpinningMutex mutex = {}; + } st{100. / slices.size(), 0., 0.}; + + execution::for_each( + ex_tbb, size_t(0), arch.entries.size(), + [&arch, &slices, &st, &rstp, progr](size_t i) { + // Status indication guarded with the spinlock + { + std::lock_guard lck(st.mutex); + if (st.stop) return; + + st.val += st.incr; + double curr = std::round(st.val); + if (curr > st.prev) { + st.prev = curr; + st.stop = !progr(int(curr)); + } + } + + png::ImageGreyscale img; + png::ReadBuf rb{arch.entries[i].buf.data(), + arch.entries[i].buf.size()}; + if (!png::decode_png(rb, img)) return; + + constexpr uint8_t isoval = 128; + auto rings = marchsq::execute(img, isoval, rstp.win); + ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, + rstp.px_h); + + // Invert the raster transformations indicated in the profile metadata + invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); + + slices[i] = std::move(expolys); + }, + execution::max_concurrency(ex_tbb)); + + if (st.stop) slices = {}; + + return slices; +} + +} // namespace + +ConfigSubstitutions SL1Reader::read(std::vector &slices, + DynamicPrintConfig &profile_out) +{ + Vec2i windowsize; + + switch(m_quality) + { + case SLAImportQuality::Fast: windowsize = {8, 8}; break; + case SLAImportQuality::Balanced: windowsize = {4, 4}; break; + default: + case SLAImportQuality::Accurate: + windowsize = {2, 2}; break; + }; + + // Ensure minimum window size for marching squares + windowsize.x() = std::max(2, windowsize.x()); + windowsize.y() = std::max(2, windowsize.y()); + + std::vector includes = { "ini", "png"}; + std::vector excludes = { "thumbnail" }; + ZipperArchive arch = read_zipper_archive(m_fname, includes, excludes); + + DynamicPrintConfig profile_in, profile_use; + ConfigSubstitutions config_substitutions = + profile_in.load(arch.profile, + ForwardCompatibilitySubstitutionRule::Enable); + + if (profile_in.empty()) { // missing profile... do guess work + // try to recover the layer height from the config.ini which was + // present in all versions of sl1 files. + if (auto lh_opt = arch.config.find("layerHeight"); + lh_opt != arch.config.not_found()) { + auto lh_str = lh_opt->second.data(); + + size_t pos; + double lh = string_to_double_decimal_point(lh_str, &pos); + if (pos) { // TODO: verify that pos is 0 when parsing fails + profile_out.set("layer_height", lh); + profile_out.set("initial_layer_height", lh); + } + } + } + + // If the archive contains an empty profile, use the one that was passed as output argument + // then replace it with the readed profile to report that it was empty. + profile_use = profile_in.empty() ? profile_out : profile_in; + profile_out = profile_in; + + RasterParams rstp = get_raster_params(profile_use); + rstp.win = {windowsize.y(), windowsize.x()}; + slices = extract_slices_from_sla_archive(arch, rstp, m_progr); + + return config_substitutions; +} + +ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out) +{ + ZipperArchive arch = read_zipper_archive(m_fname, {}, {"png"}); + return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 7ba00d7c8d..8511ef5fd3 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -4,6 +4,7 @@ #include #include "SLAArchiveWriter.hpp" +#include "SLAArchiveReader.hpp" #include "libslic3r/Zipper.hpp" #include "libslic3r/PrintConfig.hpp" @@ -37,6 +38,28 @@ public: const std::string &projectname = "") override; }; +class SL1Reader: public SLAArchiveReader { + SLAImportQuality m_quality = SLAImportQuality::Balanced; + std::function m_progr; + std::string m_fname; + +public: + // If the profile is missing from the archive (older PS versions did not have + // it), profile_out's initial value will be used as fallback. profile_out will be empty on + // function return if the archive did not contain any profile. + ConfigSubstitutions read(std::vector &slices, + DynamicPrintConfig &profile_out) override; + + ConfigSubstitutions read(DynamicPrintConfig &profile) override; + + SL1Reader() = default; + SL1Reader(const std::string &fname, + SLAImportQuality quality, + std::function progr) + : m_quality(quality), m_progr(progr), m_fname(fname) + {} +}; + } // namespace Slic3r::sla #endif // ARCHIVETRAITS_HPP diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index d138a72ba8..96cc4f2131 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -3,6 +3,10 @@ #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Format/ZipperArchiveImport.hpp" + +#define NANOSVG_IMPLEMENTATION +#include "nanosvg/nanosvg.h" #include #include @@ -234,4 +238,66 @@ void SL1_SVGArchive::export_print(const std::string fname, SL1Archive::export_print(zipper, print, thumbnails, projectname); } +ConfigSubstitutions SL1_SVGReader::read(std::vector &slices, + DynamicPrintConfig &profile_out) +{ + std::vector includes = { "config.ini", "prusaslicer.ini", "svg"}; + ZipperArchive arch = read_zipper_archive(m_fname, includes, {}); + + DynamicPrintConfig profile_in, profile_use; + ConfigSubstitutions config_substitutions = + profile_in.load(arch.profile, + ForwardCompatibilitySubstitutionRule::Enable); + + if (profile_in.empty()) { // missing profile... do guess work + // try to recover the layer height from the config.ini which was + // present in all versions of sl1 files. + if (auto lh_opt = arch.config.find("layerHeight"); + lh_opt != arch.config.not_found()) { + auto lh_str = lh_opt->second.data(); + + size_t pos; + double lh = string_to_double_decimal_point(lh_str, &pos); + if (pos) { // TODO: verify that pos is 0 when parsing fails + profile_out.set("layer_height", lh); + profile_out.set("initial_layer_height", lh); + } + } + } + + // If the archive contains an empty profile, use the one that was passed as + // output argument then replace it with the readed profile to report that + // it was empty. + profile_use = profile_in.empty() ? profile_out : profile_in; + profile_out = profile_in; + + for (const EntryBuffer &entry : arch.entries) { + NSVGimage* image; + auto svgtxt = reserve_vector(entry.buf.size()); + std::copy(entry.buf.begin(), entry.buf.end(), std::back_inserter(svgtxt)); + image = nsvgParse(svgtxt.data(), "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != nullptr; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != nullptr; path = path->next) { + + } + } + // Delete + nsvgDelete(image); + } + +// RasterParams rstp = get_raster_params(profile_use); +// rstp.win = {windowsize.y(), windowsize.x()}; +// slices = extract_slices_from_sla_archive(arch, rstp, m_progr); + + return config_substitutions; +} + +ConfigSubstitutions SL1_SVGReader::read(DynamicPrintConfig &out) +{ + ZipperArchive arch = read_zipper_archive(m_fname, {"prusaslicer.ini"}, {}); + return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); +} + } // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp index a764f1a4c0..56e3b36c30 100644 --- a/src/libslic3r/Format/SL1_SVG.hpp +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -22,6 +22,28 @@ public: using SL1Archive::SL1Archive; }; +class SL1_SVGReader: public SLAArchiveReader { + SLAImportQuality m_quality = SLAImportQuality::Balanced; + std::function m_progr; + std::string m_fname; + +public: + // If the profile is missing from the archive (older PS versions did not have + // it), profile_out's initial value will be used as fallback. profile_out will be empty on + // function return if the archive did not contain any profile. + ConfigSubstitutions read(std::vector &slices, + DynamicPrintConfig &profile_out) override; + + ConfigSubstitutions read(DynamicPrintConfig &profile) override; + + SL1_SVGReader() = default; + SL1_SVGReader(const std::string &fname, + SLAImportQuality quality, + std::function progr) + : m_quality(quality), m_progr(progr), m_fname(fname) + {} +}; + } // namespace Slic3r #endif // SL1_SVG_HPP diff --git a/src/libslic3r/Format/SLAArchiveReader.cpp b/src/libslic3r/Format/SLAArchiveReader.cpp index 0c36031e6a..2c1bef8f1e 100644 --- a/src/libslic3r/Format/SLAArchiveReader.cpp +++ b/src/libslic3r/Format/SLAArchiveReader.cpp @@ -1,223 +1,57 @@ #include "SLAArchiveReader.hpp" +#include "SL1.hpp" +#include "SL1_SVG.hpp" -#include "libslic3r/MarchingSquares.hpp" #include "libslic3r/SlicesToTriangleMesh.hpp" -#include "libslic3r/PNGReadWrite.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Execution/ExecutionTBB.hpp" -#include "libslic3r/miniz_extension.hpp" -#include "libslic3r/SLA/RasterBase.hpp" - -#include #include #include -#include - -namespace marchsq { - -template<> struct _RasterTraits { - using Rst = Slic3r::png::ImageGreyscale; - - // The type of pixel cell in the raster - using ValueType = uint8_t; - - // Value at a given position - static uint8_t get(const Rst &rst, size_t row, size_t col) - { - return rst.get(row, col); - } - - // Number of rows and cols of the raster - static size_t rows(const Rst &rst) { return rst.rows; } - static size_t cols(const Rst &rst) { return rst.cols; } -}; - -} // namespace marchsq +#include namespace Slic3r { -struct PNGBuffer { std::vector buf; std::string fname; }; -struct ArchiveData { - boost::property_tree::ptree profile, config; - std::vector images; -}; - -static const constexpr char *CONFIG_FNAME = "config.ini"; -static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; - -namespace { - -boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip) +std::unique_ptr SLAArchiveReader::create( + const std::string &fname, + SLAImportQuality quality, + std::function progr) { - std::string buf(size_t(entry.m_uncomp_size), '\0'); + std::string ext = boost::filesystem::path(fname).extension().string(); + boost::algorithm::to_lower(ext); - if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, - buf.data(), buf.size(), 0)) - throw Slic3r::FileIOError(zip.get_errorstr()); + std::unique_ptr ret; - boost::property_tree::ptree tree; - std::stringstream ss(buf); - boost::property_tree::read_ini(ss, tree); - return tree; -} + const char *SL1_ext[] = { + SLAArchiveWriter::get_extension("SL1"), + "sl1s", + // ... + }; -PNGBuffer read_png(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip, - const std::string & name) -{ - std::vector buf(entry.m_uncomp_size); + const char *SL2_ext[] = { + SLAArchiveWriter::get_extension("SL2"), + "sl1_svg", + // ... + }; - if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, - buf.data(), buf.size(), 0)) - throw Slic3r::FileIOError(zip.get_errorstr()); + if (!ext.empty()) { + if (ext.front() == '.') + ext.erase(ext.begin()); - return {std::move(buf), (name.empty() ? entry.m_filename : name)}; -} + auto extcmp = [&ext](const auto &e) { return e == ext; }; -ArchiveData extract_sla_archive(const std::string &zipfname, - const std::string &exclude) -{ - ArchiveData arch; - - // Little RAII - struct Arch: public MZ_Archive { - Arch(const std::string &fname) { - if (!open_zip_reader(&arch, fname)) - throw Slic3r::FileIOError(get_errorstr()); - } - - ~Arch() { close_zip_reader(&arch); } - } zip (zipfname); - - mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); - - for (mz_uint i = 0; i < num_entries; ++i) - { - mz_zip_archive_file_stat entry; - - if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) - { - std::string name = entry.m_filename; - boost::algorithm::to_lower(name); - - if (boost::algorithm::contains(name, exclude)) continue; - - if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); - if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); - - std::string ext = boost::filesystem::path(name).extension().string(); - boost::algorithm::to_lower(ext); - - if (ext == ".png") { - auto it = std::lower_bound( - arch.images.begin(), arch.images.end(), PNGBuffer{{}, name}, - [](const PNGBuffer &r1, const PNGBuffer &r2) { - return std::less()(r1.fname, r2.fname); - }); - - arch.images.insert(it, read_png(entry, zip, name)); - } + if (std::any_of(std::begin(SL1_ext), std::end(SL1_ext), extcmp)) { + ret = std::make_unique(fname, quality, progr); + } else if (std::any_of(std::begin(SL2_ext), std::end(SL2_ext), extcmp)) { + ret = std::make_unique(fname, quality, progr); } } - return arch; -} - -ExPolygons rings_to_expolygons(const std::vector &rings, - double px_w, double px_h) -{ - auto polys = reserve_vector(rings.size()); - - for (const marchsq::Ring &ring : rings) { - Polygon poly; Points &pts = poly.points; - pts.reserve(ring.size()); - - for (const marchsq::Coord &crd : ring) - pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); - - polys.emplace_back(poly); - } - - // TODO: Is a union necessary? - return union_ex(polys); -} - -template void foreach_vertex(ExPolygon &poly, Fn &&fn) -{ - for (auto &p : poly.contour.points) fn(p); - for (auto &h : poly.holes) - for (auto &p : h.points) fn(p); -} - -void invert_raster_trafo(ExPolygons & expolys, - const sla::RasterBase::Trafo &trafo, - coord_t width, - coord_t height) -{ - if (trafo.flipXY) std::swap(height, width); - - for (auto &expoly : expolys) { - if (trafo.mirror_y) - foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); - - if (trafo.mirror_x) - foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); - - expoly.translate(-trafo.center_x, -trafo.center_y); - - if (trafo.flipXY) - foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); - - if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { - expoly.contour.reverse(); - for (auto &h : expoly.holes) h.reverse(); - } - } -} - -struct RasterParams { - sla::RasterBase::Trafo trafo; // Raster transformations - coord_t width, height; // scaled raster dimensions (not resolution) - double px_h, px_w; // pixel dimesions - marchsq::Coord win; // marching squares window size -}; - -RasterParams get_raster_params(const DynamicPrintConfig &cfg) -{ - auto *opt_disp_cols = cfg.option("display_pixels_x"); - auto *opt_disp_rows = cfg.option("display_pixels_y"); - auto *opt_disp_w = cfg.option("display_width"); - auto *opt_disp_h = cfg.option("display_height"); - auto *opt_mirror_x = cfg.option("display_mirror_x"); - auto *opt_mirror_y = cfg.option("display_mirror_y"); - auto *opt_orient = cfg.option>("display_orientation"); - - if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || - !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw MissingProfileError("Invalid SL1 / SL1S file"); - - RasterParams rstp; - - rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); - rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); - - rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ? - sla::RasterBase::roLandscape : - sla::RasterBase::roPortrait, - {opt_mirror_x->value, opt_mirror_y->value}}; - - rstp.height = scaled(opt_disp_h->value); - rstp.width = scaled(opt_disp_w->value); - - return rstp; + return ret; } struct SliceParams { double layerh = 0., initial_layerh = 0.; }; -SliceParams get_slice_params(const DynamicPrintConfig &cfg) +static SliceParams get_slice_params(const DynamicPrintConfig &cfg) { auto *opt_layerh = cfg.option("layer_height"); auto *opt_init_layerh = cfg.option("initial_layer_height"); @@ -228,153 +62,42 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg) return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; } -std::vector extract_slices_from_sla_archive( - ArchiveData & arch, - const RasterParams & rstp, - std::function progr) -{ - auto jobdir = arch.config.get("jobDir"); - for (auto &c : jobdir) c = std::tolower(c); - - std::vector slices(arch.images.size()); - - struct Status - { - double incr, val, prev; - bool stop = false; - execution::SpinningMutex mutex = {}; - } st{100. / slices.size(), 0., 0.}; - - execution::for_each( - ex_tbb, size_t(0), arch.images.size(), - [&arch, &slices, &st, &rstp, progr](size_t i) { - // Status indication guarded with the spinlock - { - std::lock_guard lck(st.mutex); - if (st.stop) return; - - st.val += st.incr; - double curr = std::round(st.val); - if (curr > st.prev) { - st.prev = curr; - st.stop = !progr(int(curr)); - } - } - - png::ImageGreyscale img; - png::ReadBuf rb{arch.images[i].buf.data(), - arch.images[i].buf.size()}; - if (!png::decode_png(rb, img)) return; - - constexpr uint8_t isoval = 128; - auto rings = marchsq::execute(img, isoval, rstp.win); - ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, - rstp.px_h); - - // Invert the raster transformations indicated in the profile metadata - invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); - - slices[i] = std::move(expolys); - }, - execution::max_concurrency(ex_tbb)); - - if (st.stop) slices = {}; - - return slices; -} - -// If the profile is missing from the archive (older PS versions did not have -// it), profile_out's initial value will be used as fallback. profile_out will be empty on -// function return if the archive did not contain any profile. -ConfigSubstitutions import_sla_archive(const std::string &zipfname, - Vec2i windowsize, - indexed_triangle_set &out, - DynamicPrintConfig &profile_out, - std::function progr) -{ - // Ensure minimum window size for marching squares - windowsize.x() = std::max(2, windowsize.x()); - windowsize.y() = std::max(2, windowsize.y()); - - std::string exclude_entries{"thumbnail"}; - ArchiveData arch = extract_sla_archive(zipfname, exclude_entries); - DynamicPrintConfig profile_in, profile_use; - ConfigSubstitutions config_substitutions = - profile_in.load(arch.profile, - ForwardCompatibilitySubstitutionRule::Enable); - - if (profile_in.empty()) { // missing profile... do guess work - // try to recover the layer height from the config.ini which was - // present in all versions of sl1 files. - if (auto lh_opt = arch.config.find("layerHeight"); - lh_opt != arch.config.not_found()) - { - auto lh_str = lh_opt->second.data(); - - size_t pos; - double lh = string_to_double_decimal_point(lh_str, &pos); - if (pos) { // TODO: verify that pos is 0 when parsing fails - profile_out.set("layer_height", lh); - profile_out.set("initial_layer_height", lh); - } - } - } - - // If the archive contains an empty profile, use the one that was passed as output argument - // then replace it with the readed profile to report that it was empty. - profile_use = profile_in.empty() ? profile_out : profile_in; - profile_out = profile_in; - - RasterParams rstp = get_raster_params(profile_use); - rstp.win = {windowsize.y(), windowsize.x()}; - - SliceParams slicp = get_slice_params(profile_use); - - std::vector slices = - extract_slices_from_sla_archive(arch, rstp, progr); - - if (!slices.empty()) - out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); - - return config_substitutions; -} - -} // namespace - -//inline ConfigSubstitutions import_sla_archive( -// const std::string & zipfname, -// Vec2i windowsize, -// indexed_triangle_set & out, -// std::function progr = [](int) { return true; }) -//{ -// DynamicPrintConfig profile; -// return import_sla_archive(zipfname, windowsize, out, profile, progr); -//} - -ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) -{ - ArchiveData arch = extract_sla_archive(zipfname, "png"); - return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); -} - ConfigSubstitutions import_sla_archive(const std::string &zipfname, indexed_triangle_set &out, DynamicPrintConfig &profile, SLAImportQuality quality, std::function progr) { - Vec2i window; + ConfigSubstitutions ret; - switch(quality) - { - case SLAImportQuality::Fast: window = {8, 8}; break; - case SLAImportQuality:: Balanced: window = {4, 4}; break; - default: - case SLAImportQuality::Accurate: - window = {2, 2}; - }; + if (auto reader = SLAArchiveReader::create(zipfname, quality, progr)) { + std::vector slices; + ret = reader->read(slices, profile); - return import_sla_archive(zipfname, window, out, profile, progr); + SliceParams slicp = get_slice_params(profile); + + if (!slices.empty()) + out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); + + } else { + throw ReaderUnimplementedError("Reader unimplemented"); + } + + return ret; +} + +ConfigSubstitutions import_sla_archive(const std::string &zipfname, + DynamicPrintConfig &out) +{ + ConfigSubstitutions ret; + + if (auto reader = SLAArchiveReader::create(zipfname)) { + ret = reader->read(out); + } else { + throw ReaderUnimplementedError("Reader unimplemented"); + } + + return ret; } } // namespace Slic3r diff --git a/src/libslic3r/Format/SLAArchiveReader.hpp b/src/libslic3r/Format/SLAArchiveReader.hpp index f092a9a09e..ffd9f84185 100644 --- a/src/libslic3r/Format/SLAArchiveReader.hpp +++ b/src/libslic3r/Format/SLAArchiveReader.hpp @@ -2,15 +2,37 @@ #define SLAARCHIVEREADER_HPP #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/ExPolygon.hpp" struct indexed_triangle_set; namespace Slic3r { -ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); - enum class SLAImportQuality { Accurate, Balanced, Fast }; +class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; }; + +class SLAArchiveReader { +public: + + virtual ~SLAArchiveReader() = default; + + virtual ConfigSubstitutions read(std::vector &slices, + DynamicPrintConfig &profile) = 0; + + virtual ConfigSubstitutions read(DynamicPrintConfig &profile) = 0; + + static std::unique_ptr create( + const std::string &fname, + SLAImportQuality quality = SLAImportQuality::Balanced, + std::function progr = [](int){ return false; }); +}; + +class ReaderUnimplementedError : public RuntimeError { using RuntimeError::RuntimeError; }; + +ConfigSubstitutions import_sla_archive(const std::string &zipfname, + DynamicPrintConfig &out); + ConfigSubstitutions import_sla_archive( const std::string &zipfname, indexed_triangle_set &out, @@ -18,8 +40,6 @@ ConfigSubstitutions import_sla_archive( SLAImportQuality quality = SLAImportQuality::Balanced, std::function progr = [](int) { return true; }); -class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; }; - } // namespace Slic3r #endif // SLAARCHIVEREADER_HPP diff --git a/src/libslic3r/Format/SLAArchiveWriter.cpp b/src/libslic3r/Format/SLAArchiveWriter.cpp index 02df9b5b3a..babf92d0db 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.cpp +++ b/src/libslic3r/Format/SLAArchiveWriter.cpp @@ -62,7 +62,7 @@ const std::vector& SLAArchiveWriter::registered_archives() const char *SLAArchiveWriter::get_extension(const char *archtype) { - static const char* DEFAULT_EXT = "zip"; + constexpr const char* DEFAULT_EXT = "zip"; auto entry = REGISTERED_ARCHIVES.find(archtype); if (entry != REGISTERED_ARCHIVES.end()) diff --git a/src/libslic3r/Format/SLAArchiveWriter.hpp b/src/libslic3r/Format/SLAArchiveWriter.hpp index 1e591ef704..86132cceba 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.hpp +++ b/src/libslic3r/Format/SLAArchiveWriter.hpp @@ -44,14 +44,15 @@ public: execution::max_concurrency(ep)); } - // Export the print into an archive using the provided filename. + // Export the print into an archive using the provided filename. virtual void export_print(const std::string fname, const SLAPrint &print, const ThumbnailsList &thumbnails, const std::string &projectname = "") = 0; // Factory method to create an archiver instance - static std::unique_ptr create(const std::string &archtype, const SLAPrinterConfig&); + static std::unique_ptr create( + const std::string &archtype, const SLAPrinterConfig &); // Get the names of currently known archiver implementations static const std::vector & registered_archives(); diff --git a/src/libslic3r/Format/ZipperArchiveImport.cpp b/src/libslic3r/Format/ZipperArchiveImport.cpp new file mode 100644 index 0000000000..d5d5f82727 --- /dev/null +++ b/src/libslic3r/Format/ZipperArchiveImport.cpp @@ -0,0 +1,103 @@ +#include "ZipperArchiveImport.hpp" + +#include "libslic3r/miniz_extension.hpp" +#include "libslic3r/Exception.hpp" + +#include +#include +#include + +namespace Slic3r { + +static const constexpr char *CONFIG_FNAME = "config.ini"; +static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; + +namespace { + +boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, + MZ_Archive &zip) +{ + std::string buf(size_t(entry.m_uncomp_size), '\0'); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw Slic3r::FileIOError(zip.get_errorstr()); + + boost::property_tree::ptree tree; + std::stringstream ss(buf); + boost::property_tree::read_ini(ss, tree); + return tree; +} + +EntryBuffer read_entry(const mz_zip_archive_file_stat &entry, + MZ_Archive &zip, + const std::string &name) +{ + std::vector buf(entry.m_uncomp_size); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw Slic3r::FileIOError(zip.get_errorstr()); + + return {std::move(buf), (name.empty() ? entry.m_filename : name)}; +} + +} // namespace + +ZipperArchive read_zipper_archive(const std::string &zipfname, + const std::vector &includes, + const std::vector &excludes) +{ + ZipperArchive arch; + + // Little RAII + struct Arch : public MZ_Archive + { + Arch(const std::string &fname) + { + if (!open_zip_reader(&arch, fname)) + throw Slic3r::FileIOError(get_errorstr()); + } + + ~Arch() { close_zip_reader(&arch); } + } zip(zipfname); + + mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); + + for (mz_uint i = 0; i < num_entries; ++i) { + mz_zip_archive_file_stat entry; + + if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) { + std::string name = entry.m_filename; + boost::algorithm::to_lower(name); + + if (!std::any_of(includes.begin(), includes.end(), + [&name](const std::string &incl) { + return boost::algorithm::contains(name, incl); + })) + continue; + + if (std::any_of(excludes.begin(), excludes.end(), + [&name](const std::string &excl) { + return boost::algorithm::contains(name, excl); + })) + continue; + + if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); + if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); + + auto it = std::lower_bound( + arch.entries.begin(), arch.entries.end(), + EntryBuffer{{}, name}, + [](const EntryBuffer &r1, const EntryBuffer &r2) { + return std::less()(r1.fname, r2.fname); + }); + + arch.entries.insert(it, read_entry(entry, zip, name)); + } + } + + return arch; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/ZipperArchiveImport.hpp b/src/libslic3r/Format/ZipperArchiveImport.hpp new file mode 100644 index 0000000000..5802c128cd --- /dev/null +++ b/src/libslic3r/Format/ZipperArchiveImport.hpp @@ -0,0 +1,30 @@ +#ifndef ZIPPERARCHIVEIMPORT_HPP +#define ZIPPERARCHIVEIMPORT_HPP + +#include +#include +#include + +#include + +namespace Slic3r { + +struct EntryBuffer +{ + std::vector buf; + std::string fname; +}; + +struct ZipperArchive +{ + boost::property_tree::ptree profile, config; + std::vector entries; +}; + +ZipperArchive read_zipper_archive(const std::string &zipfname, + const std::vector &includes, + const std::vector &excludes); + +} // namespace Slic3r + +#endif // ZIPPERARCHIVEIMPORT_HPP diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index e23591fb62..b585798d32 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -6,6 +6,8 @@ #include "GUI_Utils.hpp" #include +#include +#include #ifdef __WXGTK2__ // Broken alpha workaround @@ -13,7 +15,7 @@ #include #endif /* __WXGTK2__ */ -#define NANOSVG_IMPLEMENTATION +//#define NANOSVG_IMPLEMENTATION #include "nanosvg/nanosvg.h" #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp index 70adb2c272..7d6d1da6dd 100644 --- a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -34,7 +34,7 @@ public: m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), - "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", + "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP|SL2 archive files (*.sl2)|*.sl2", wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 36c1f09145..5aa936be1f 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -56,6 +56,7 @@ void SLAImportJob::process(Ctl &ctl) if (p->path.empty()) return; std::string path = p->path.ToUTF8().data(); + try { switch (p->sel) { case Sel::modelAndProfile: @@ -69,9 +70,12 @@ void SLAImportJob::process(Ctl &ctl) break; } } catch (MissingProfileError &) { - p->err = _L("The SLA archive doesn't contain any presets. " - "Please activate some SLA printer preset first before importing that SLA archive.").ToStdString(); - } catch (std::exception &ex) { + p->err = _u8L("The SLA archive doesn't contain any presets. " + "Please activate some SLA printer preset first before " + "importing that SLA archive."); + } catch (ReaderUnimplementedError &) { + p->err = _u8L("Import is unavailable for this archive format."); + }catch (std::exception &ex) { p->err = ex.what(); }