diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 5fc2737288..a947119962 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -297,34 +297,6 @@ void invert_raster_trafo(ExPolygons & expolys, } } -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); -} - -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"); @@ -355,9 +327,31 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg) return rstp; } +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); +} + std::vector extract_slices_from_sla_archive( ZipperArchive &arch, const RasterParams &rstp, + const marchsq::Coord &win, std::function progr) { std::vector slices(arch.entries.size()); @@ -371,7 +365,7 @@ std::vector extract_slices_from_sla_archive( execution::for_each( ex_tbb, size_t(0), arch.entries.size(), - [&arch, &slices, &st, &rstp, progr](size_t i) { + [&arch, &slices, &st, &rstp, &win, progr](size_t i) { // Status indication guarded with the spinlock { std::lock_guard lck(st.mutex); @@ -391,7 +385,7 @@ std::vector extract_slices_from_sla_archive( if (!png::decode_png(rb, img)) return; constexpr uint8_t isoval = 128; - auto rings = marchsq::execute(img, isoval, rstp.win); + auto rings = marchsq::execute(img, isoval, win); ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); @@ -430,38 +424,13 @@ ConfigSubstitutions SL1Reader::read(std::vector &slices, std::vector includes = { "ini", "png"}; std::vector excludes = { "thumbnail" }; ZipperArchive arch = read_zipper_archive(m_fname, includes, excludes); + auto [profile_use, config_substitutions] = extract_profile(arch, profile_out); - DynamicPrintConfig profile_in, profile_use; - ConfigSubstitutions config_substitutions = - profile_in.load(arch.profile, - ForwardCompatibilitySubstitutionRule::Enable); + RasterParams rstp = get_raster_params(profile_use); + marchsq::Coord win = {windowsize.y(), windowsize.x()}; + slices = extract_slices_from_sla_archive(arch, rstp, win, m_progr); - 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; + return std::move(config_substitutions); } ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out) diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index b5d214e80f..6c7a903a63 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -60,6 +60,14 @@ public: {} }; +struct RasterParams { + sla::RasterBase::Trafo trafo; // Raster transformations + coord_t width, height; // scaled raster dimensions (not resolution) + double px_h, px_w; // pixel dimesions +}; + +RasterParams get_raster_params(const DynamicPrintConfig &cfg); + void invert_raster_trafo(ExPolygons & expolys, const sla::RasterBase::Trafo &trafo, coord_t width, diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 9dd9e16fec..252c1f47f7 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -77,21 +77,6 @@ void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBo void append_svg(std::string &buf, const Polygon &poly) { -// if (poly.points.empty()) -// return; - -// char intbuf[coord_t_bufsize]; -// buf += " SL1_SVGArchive::create_raster() const auto h = cfg().display_height.getFloat(); float precision_nm = scaled(cfg().sla_output_precision.getFloat()); - size_t res_x = std::round(scaled(w) / precision_nm); - size_t res_y = std::round(scaled(h) / precision_nm); + auto res_x = size_t(std::round(scaled(w) / precision_nm)); + auto res_y = size_t(std::round(scaled(h) / precision_nm)); - std::array mirror; + std::array mirror; mirror[X] = cfg().display_mirror_x.getBool(); mirror[Y] = cfg().display_mirror_y.getBool(); @@ -249,43 +234,11 @@ void SL1_SVGArchive::export_print(const std::string fname, SL1Archive::export_print(zipper, print, thumbnails, projectname); } -struct RasterParams { - sla::RasterBase::Trafo trafo; // Raster transformations - coord_t width, height; // scaled raster dimensions (not resolution) -}; - -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.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; -} - struct NanoSVGParser { NSVGimage *image; static constexpr const char *Units = "mm"; // Denotes user coordinate system static constexpr float Dpi = 1.f; // Not needed - NanoSVGParser(char* input): image{nsvgParse(input, Units, Dpi)} {} + explicit NanoSVGParser(char* input): image{nsvgParse(input, Units, Dpi)} {} ~NanoSVGParser() { nsvgDelete(image); } }; @@ -294,33 +247,7 @@ ConfigSubstitutions SL1_SVGReader::read(std::vector &slices, { std::vector includes = { CONFIG_FNAME, PROFILE_FNAME, "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; + auto [profile_use, config_substitutions] = extract_profile(arch, profile_out); RasterParams rstp = get_raster_params(profile_use); @@ -361,7 +288,8 @@ ConfigSubstitutions SL1_SVGReader::read(std::vector &slices, slices.emplace_back(expolys); } - return config_substitutions; + // Compile error without the move + return std::move(config_substitutions); } ConfigSubstitutions SL1_SVGReader::read(DynamicPrintConfig &out) diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp index 56e3b36c30..d94a5153a8 100644 --- a/src/libslic3r/Format/SL1_SVG.hpp +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -23,7 +23,6 @@ public: }; class SL1_SVGReader: public SLAArchiveReader { - SLAImportQuality m_quality = SLAImportQuality::Balanced; std::function m_progr; std::string m_fname; @@ -38,9 +37,9 @@ public: SL1_SVGReader() = default; SL1_SVGReader(const std::string &fname, - SLAImportQuality quality, - std::function progr) - : m_quality(quality), m_progr(progr), m_fname(fname) + SLAImportQuality /*quality*/, + const ProgrFn & progr) + : m_progr(progr), m_fname(fname) {} }; diff --git a/src/libslic3r/Format/SLAArchiveReader.cpp b/src/libslic3r/Format/SLAArchiveReader.cpp index 2c1bef8f1e..cddc9ec312 100644 --- a/src/libslic3r/Format/SLAArchiveReader.cpp +++ b/src/libslic3r/Format/SLAArchiveReader.cpp @@ -7,48 +7,101 @@ #include #include +constexpr const char * L(const char * str) { return str; } + #include +#include namespace Slic3r { +namespace { + +using ArchiveFactory = std::function< + std::unique_ptr(const std::string &fname, + SLAImportQuality quality, + const ProgrFn & progr)>; + +struct ArchiveEntry { + const char *descr; + std::vector extensions; + ArchiveFactory factoryfn; +}; + +static const std::map REGISTERED_ARCHIVES { + { + "SL1", + { L("SL1 / SL1S archive files"), {"sl1", "sl1s", "zip"}, + [] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique(fname, quality, progr); } } + }, + { + "SL2", + { L("SL2 archive files"), {"sl2", "sl1_svg", "zip"}, + [] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique(fname, quality, progr); }} + }, + // TODO: pwmx +}; + +} // namespace + std::unique_ptr SLAArchiveReader::create( const std::string &fname, SLAImportQuality quality, - std::function progr) + const ProgrFn & progr) { std::string ext = boost::filesystem::path(fname).extension().string(); boost::algorithm::to_lower(ext); std::unique_ptr ret; - const char *SL1_ext[] = { - SLAArchiveWriter::get_extension("SL1"), - "sl1s", - // ... - }; - - const char *SL2_ext[] = { - SLAArchiveWriter::get_extension("SL2"), - "sl1_svg", - // ... - }; - if (!ext.empty()) { if (ext.front() == '.') ext.erase(ext.begin()); auto extcmp = [&ext](const auto &e) { return e == ext; }; - 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); + for (const auto &[format_id, entry] : REGISTERED_ARCHIVES) { + if (std::any_of(entry.extensions.begin(), entry.extensions.end(), extcmp)) + ret = entry.factoryfn(fname, quality, progr); } } return ret; } +const std::vector &SLAArchiveReader::registered_archives() +{ + static std::vector archnames; + + if (archnames.empty()) { + archnames.reserve(REGISTERED_ARCHIVES.size()); + + for (auto &[name, _] : REGISTERED_ARCHIVES) + archnames.emplace_back(name.c_str()); + } + + return archnames; +} + +std::vector SLAArchiveReader::get_extensions(const char *archtype) +{ + auto it = REGISTERED_ARCHIVES.find(archtype); + + if (it != REGISTERED_ARCHIVES.end()) + return it->second.extensions; + + return {}; +} + +const char *SLAArchiveReader::get_description(const char *archtype) +{ + auto it = REGISTERED_ARCHIVES.find(archtype); + + if (it != REGISTERED_ARCHIVES.end()) + return it->second.descr; + + return nullptr; +} + struct SliceParams { double layerh = 0., initial_layerh = 0.; }; static SliceParams get_slice_params(const DynamicPrintConfig &cfg) @@ -66,7 +119,7 @@ ConfigSubstitutions import_sla_archive(const std::string &zipfname, indexed_triangle_set &out, DynamicPrintConfig &profile, SLAImportQuality quality, - std::function progr) + const ProgrFn & progr) { ConfigSubstitutions ret; diff --git a/src/libslic3r/Format/SLAArchiveReader.hpp b/src/libslic3r/Format/SLAArchiveReader.hpp index ffd9f84185..3ece8bd369 100644 --- a/src/libslic3r/Format/SLAArchiveReader.hpp +++ b/src/libslic3r/Format/SLAArchiveReader.hpp @@ -12,6 +12,8 @@ enum class SLAImportQuality { Accurate, Balanced, Fast }; class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; }; +using ProgrFn = std::function; + class SLAArchiveReader { public: @@ -23,9 +25,17 @@ public: 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; }); + const std::string &fname, + SLAImportQuality quality = SLAImportQuality::Balanced, + const ProgrFn &progr = [](int) { return false; }); + + // Get the names of currently known archive reader implementations + static const std::vector & registered_archives(); + + // Get the default file extensions belonging to an archive format + static std::vector get_extensions(const char *archtype); + + static const char * get_description(const char *archtype); }; class ReaderUnimplementedError : public RuntimeError { using RuntimeError::RuntimeError; }; @@ -38,7 +48,7 @@ ConfigSubstitutions import_sla_archive( indexed_triangle_set &out, DynamicPrintConfig &profile, SLAImportQuality quality = SLAImportQuality::Balanced, - std::function progr = [](int) { return true; }); + const ProgrFn &progr = [](int) { return true; }); } // namespace Slic3r diff --git a/src/libslic3r/Format/ZipperArchiveImport.cpp b/src/libslic3r/Format/ZipperArchiveImport.cpp index f02de23231..526e89df3b 100644 --- a/src/libslic3r/Format/ZipperArchiveImport.cpp +++ b/src/libslic3r/Format/ZipperArchiveImport.cpp @@ -2,6 +2,7 @@ #include "libslic3r/miniz_extension.hpp" #include "libslic3r/Exception.hpp" +#include "libslic3r/PrintConfig.hpp" #include #include @@ -97,4 +98,36 @@ ZipperArchive read_zipper_archive(const std::string &zipfname, return arch; } +std::pair extract_profile( + const ZipperArchive &arch, DynamicPrintConfig &profile_out) +{ + 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 = 0; + 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; + + return {profile_use, std::move(config_substitutions)}; +} + } // namespace Slic3r diff --git a/src/libslic3r/Format/ZipperArchiveImport.hpp b/src/libslic3r/Format/ZipperArchiveImport.hpp index 1b1273a4bf..105de4753c 100644 --- a/src/libslic3r/Format/ZipperArchiveImport.hpp +++ b/src/libslic3r/Format/ZipperArchiveImport.hpp @@ -7,6 +7,8 @@ #include +#include "libslic3r/PrintConfig.hpp" + namespace Slic3r { struct EntryBuffer @@ -28,6 +30,14 @@ ZipperArchive read_zipper_archive(const std::string &zipfname, const std::vector &includes, const std::vector &excludes); +// Extract the print profile form the archive onto 'out'. +// Returns a profile that has correct parameters to use for model reconstruction +// even if the needed parameters were not fully found in the archive's metadata. +// The inout argument shall be a usable fallback profile if the archive +// has totally corrupted metadata. +std::pair extract_profile( + const ZipperArchive &arch, DynamicPrintConfig &inout); + } // namespace Slic3r #endif // ZIPPERARCHIVEIMPORT_HPP diff --git a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp index 7d6d1da6dd..e5a8070117 100644 --- a/src/slic3r/GUI/Jobs/SLAImportDialog.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportDialog.hpp @@ -10,6 +10,8 @@ #include #include "libslic3r/AppConfig.hpp" +#include "libslic3r/Format/SLAArchiveReader.hpp" + #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GUI.hpp" @@ -21,6 +23,52 @@ namespace Slic3r { namespace GUI { +std::string get_readers_wildcard() +{ + std::string ret; + + for (const char *archtype : SLAArchiveReader::registered_archives()) { + ret += _utf8(SLAArchiveReader::get_description(archtype)); + ret += " ("; + auto extensions = SLAArchiveReader::get_extensions(archtype); + for (const char * ext : extensions) { + ret += "*."; + ret += ext; + ret += ", "; + } + // remove last ", " + if (!extensions.empty()) { + ret.pop_back(); + ret.pop_back(); + } + + ret += ")|"; + + for (std::string ext : extensions) { + boost::algorithm::to_lower(ext); + ret += "*."; + ret += ext; + ret += ";"; + + boost::algorithm::to_upper(ext); + ret += "*."; + ret += ext; + ret += ";"; + } + + // remove last ';' + if (!extensions.empty()) + ret.pop_back(); + + ret += "|"; + } + + if (ret.back() == '|') + ret.pop_back(); + + return ret; +} + class SLAImportDialog: public wxDialog, public SLAImportJobView { wxFilePickerCtrl *m_filepicker; wxComboBox *m_import_dropdown, *m_quality_dropdown; @@ -34,7 +82,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|SL2 archive files (*.sl2)|*.sl2", + get_readers_wildcard(), 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/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index af285e46e2..570877716f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1212,7 +1212,7 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); - append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S Archive") + dots, _L("Load an SL1 / Sl1S archive"), + append_menu_item(import_menu, wxID_ANY, _L("Import SLA Archive") + dots, _L("Load an SLA archive"), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, [this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this);