Merge branch 'tm_read_svg_archive'
This commit is contained in:
commit
2377349e7f
@ -94,8 +94,12 @@ set(SLIC3R_SOURCES
|
|||||||
Format/objparser.hpp
|
Format/objparser.hpp
|
||||||
Format/STL.cpp
|
Format/STL.cpp
|
||||||
Format/STL.hpp
|
Format/STL.hpp
|
||||||
Format/SLAArchive.hpp
|
Format/SLAArchiveWriter.hpp
|
||||||
Format/SLAArchive.cpp
|
Format/SLAArchiveWriter.cpp
|
||||||
|
Format/SLAArchiveReader.hpp
|
||||||
|
Format/SLAArchiveReader.cpp
|
||||||
|
Format/ZipperArchiveImport.hpp
|
||||||
|
Format/ZipperArchiveImport.cpp
|
||||||
Format/SL1.hpp
|
Format/SL1.hpp
|
||||||
Format/SL1.cpp
|
Format/SL1.cpp
|
||||||
Format/SL1_SVG.hpp
|
Format/SL1_SVG.hpp
|
||||||
|
@ -1,359 +1,37 @@
|
|||||||
#include "SL1.hpp"
|
#include "SL1.hpp"
|
||||||
#include "GCode/ThumbnailData.hpp"
|
|
||||||
#include "libslic3r/Time.hpp"
|
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
#include "libslic3r/Zipper.hpp"
|
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "libslic3r/Time.hpp"
|
||||||
|
#include "libslic3r/Zipper.hpp"
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/Exception.hpp"
|
#include "libslic3r/Exception.hpp"
|
||||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
|
||||||
#include "libslic3r/MarchingSquares.hpp"
|
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
|
||||||
#include "libslic3r/MTUtils.hpp"
|
#include "libslic3r/MTUtils.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/SLA/RasterBase.hpp"
|
|
||||||
#include "libslic3r/miniz_extension.hpp"
|
#include "libslic3r/miniz_extension.hpp"
|
||||||
#include "libslic3r/PNGReadWrite.hpp"
|
|
||||||
#include "libslic3r/LocalesUtils.hpp"
|
#include "libslic3r/LocalesUtils.hpp"
|
||||||
#include "libslic3r/GCode/ThumbnailData.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 <boost/property_tree/ini_parser.hpp>
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include <miniz.h>
|
|
||||||
|
|
||||||
namespace marchsq {
|
|
||||||
|
|
||||||
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
|
||||||
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 Slic3r {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
|
|
||||||
struct ArchiveData {
|
|
||||||
boost::property_tree::ptree profile, config;
|
|
||||||
std::vector<PNGBuffer> images;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const constexpr char *CONFIG_FNAME = "config.ini";
|
|
||||||
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
|
||||||
MZ_Archive & zip,
|
|
||||||
const std::string & name)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> 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)};
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (boost::filesystem::path(name).extension().string() == ".png") {
|
|
||||||
auto it = std::lower_bound(
|
|
||||||
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
|
|
||||||
[](const PNGBuffer &r1, const PNGBuffer &r2) {
|
|
||||||
return std::less<std::string>()(r1.fname, r2.fname);
|
|
||||||
});
|
|
||||||
|
|
||||||
arch.images.insert(it, read_png(entry, zip, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arch;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
|
||||||
double px_w, double px_h)
|
|
||||||
{
|
|
||||||
auto polys = reserve_vector<ExPolygon>(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<class Fn> 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<ConfigOptionInt>("display_pixels_x");
|
|
||||||
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
|
||||||
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
|
||||||
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
|
||||||
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
|
||||||
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
|
||||||
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("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;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
|
||||||
|
|
||||||
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
|
||||||
{
|
|
||||||
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
|
||||||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
|
||||||
|
|
||||||
if (!opt_layerh || !opt_init_layerh)
|
|
||||||
throw MissingProfileError("Invalid SL1 / SL1S file");
|
|
||||||
|
|
||||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
|
||||||
ArchiveData & arch,
|
|
||||||
const RasterParams & rstp,
|
|
||||||
std::function<bool(int)> progr)
|
|
||||||
{
|
|
||||||
auto jobdir = arch.config.get<std::string>("jobDir");
|
|
||||||
for (auto &c : jobdir) c = std::tolower(c);
|
|
||||||
|
|
||||||
std::vector<ExPolygons> slices(arch.images.size());
|
|
||||||
|
|
||||||
struct Status
|
|
||||||
{
|
|
||||||
double incr, val, prev;
|
|
||||||
bool stop = false;
|
|
||||||
tbb::spin_mutex mutex = {};
|
|
||||||
} st {100. / slices.size(), 0., 0.};
|
|
||||||
|
|
||||||
tbb::parallel_for(size_t(0), arch.images.size(),
|
|
||||||
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
|
||||||
// Status indication guarded with the spinlock
|
|
||||||
{
|
|
||||||
std::lock_guard<tbb::spin_mutex> 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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (st.stop) slices = {};
|
|
||||||
|
|
||||||
return slices;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
|
||||||
{
|
|
||||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
|
||||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<bool(int)> 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<ExPolygons> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
using ConfMap = std::map<std::string, std::string>;
|
using ConfMap = std::map<std::string, std::string>;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -558,3 +236,207 @@ void SL1Archive::export_print(const std::string fname,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Reader implementation
|
||||||
|
// /////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace marchsq {
|
||||||
|
|
||||||
|
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
template<class Fn> static 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
||||||
|
{
|
||||||
|
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
||||||
|
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
||||||
|
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
||||||
|
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
||||||
|
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
||||||
|
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
||||||
|
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("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;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
||||||
|
double px_w, double px_h)
|
||||||
|
{
|
||||||
|
auto polys = reserve_vector<ExPolygon>(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<ExPolygons> extract_slices_from_sla_archive(
|
||||||
|
ZipperArchive &arch,
|
||||||
|
const RasterParams &rstp,
|
||||||
|
const marchsq::Coord &win,
|
||||||
|
std::function<bool(int)> progr)
|
||||||
|
{
|
||||||
|
std::vector<ExPolygons> slices(arch.entries.size());
|
||||||
|
|
||||||
|
struct Status
|
||||||
|
{
|
||||||
|
double incr, val, prev;
|
||||||
|
bool stop = false;
|
||||||
|
execution::SpinningMutex<ExecutionTBB> mutex = {};
|
||||||
|
} st{100. / slices.size(), 0., 0.};
|
||||||
|
|
||||||
|
execution::for_each(
|
||||||
|
ex_tbb, size_t(0), arch.entries.size(),
|
||||||
|
[&arch, &slices, &st, &rstp, &win, 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, 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<ExPolygons> &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<std::string> includes = { "ini", "png"};
|
||||||
|
std::vector<std::string> excludes = { "thumbnail" };
|
||||||
|
ZipperArchive arch = read_zipper_archive(m_fname, includes, excludes);
|
||||||
|
auto [profile_use, config_substitutions] = extract_profile(arch, profile_out);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return std::move(config_substitutions);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out)
|
||||||
|
{
|
||||||
|
ZipperArchive arch = read_zipper_archive(m_fname, {}, {"png"});
|
||||||
|
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
@ -3,16 +3,15 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "SLAArchive.hpp"
|
#include "SLAArchiveWriter.hpp"
|
||||||
|
#include "SLAArchiveReader.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Zipper.hpp"
|
#include "libslic3r/Zipper.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
struct indexed_triangle_set;
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class SL1Archive: public SLAArchive {
|
class SL1Archive: public SLAArchiveWriter {
|
||||||
SLAPrinterConfig m_cfg;
|
SLAPrinterConfig m_cfg;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -39,26 +38,40 @@ public:
|
|||||||
const std::string &projectname = "") override;
|
const std::string &projectname = "") override;
|
||||||
};
|
};
|
||||||
|
|
||||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
class SL1Reader: public SLAArchiveReader {
|
||||||
|
SLAImportQuality m_quality = SLAImportQuality::Balanced;
|
||||||
|
std::function<bool(int)> m_progr;
|
||||||
|
std::string m_fname;
|
||||||
|
|
||||||
ConfigSubstitutions import_sla_archive(
|
public:
|
||||||
const std::string & zipfname,
|
// If the profile is missing from the archive (older PS versions did not have
|
||||||
Vec2i windowsize,
|
// it), profile_out's initial value will be used as fallback. profile_out will be empty on
|
||||||
indexed_triangle_set & out,
|
// function return if the archive did not contain any profile.
|
||||||
DynamicPrintConfig & profile,
|
ConfigSubstitutions read(std::vector<ExPolygons> &slices,
|
||||||
std::function<bool(int)> progr = [](int) { return true; });
|
DynamicPrintConfig &profile_out) override;
|
||||||
|
|
||||||
inline ConfigSubstitutions import_sla_archive(
|
ConfigSubstitutions read(DynamicPrintConfig &profile) override;
|
||||||
const std::string & zipfname,
|
|
||||||
Vec2i windowsize,
|
|
||||||
indexed_triangle_set & out,
|
|
||||||
std::function<bool(int)> progr = [](int) { return true; })
|
|
||||||
{
|
|
||||||
DynamicPrintConfig profile;
|
|
||||||
return import_sla_archive(zipfname, windowsize, out, profile, progr);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; };
|
SL1Reader() = default;
|
||||||
|
SL1Reader(const std::string &fname,
|
||||||
|
SLAImportQuality quality,
|
||||||
|
std::function<bool(int)> progr)
|
||||||
|
: m_quality(quality), m_progr(progr), m_fname(fname)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
coord_t height);
|
||||||
|
|
||||||
} // namespace Slic3r::sla
|
} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
#include "libslic3r/LocalesUtils.hpp"
|
#include "libslic3r/LocalesUtils.hpp"
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
|
#include "libslic3r/Format/ZipperArchiveImport.hpp"
|
||||||
|
|
||||||
|
#define NANOSVG_IMPLEMENTATION
|
||||||
|
#include "nanosvg/nanosvg.h"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -16,6 +20,7 @@ namespace {
|
|||||||
|
|
||||||
size_t constexpr coord_t_bufsize = 40;
|
size_t constexpr coord_t_bufsize = 40;
|
||||||
|
|
||||||
|
// A fast and locale independent implementation of int=>str
|
||||||
char const* decimal_from(coord_t snumber, char* buffer)
|
char const* decimal_from(coord_t snumber, char* buffer)
|
||||||
{
|
{
|
||||||
std::make_unsigned_t<coord_t> number = 0;
|
std::make_unsigned_t<coord_t> number = 0;
|
||||||
@ -50,6 +55,7 @@ inline std::string coord2str(coord_t crd)
|
|||||||
return decimal_from(crd, buf);
|
return decimal_from(crd, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply the sla::RasterBase::Trafo onto an ExPolygon
|
||||||
void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb)
|
void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb)
|
||||||
{
|
{
|
||||||
if (tr.flipXY) {
|
if (tr.flipXY) {
|
||||||
@ -71,12 +77,13 @@ void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append the svg string representation of a Polygon to the input 'buf'
|
||||||
void append_svg(std::string &buf, const Polygon &poly)
|
void append_svg(std::string &buf, const Polygon &poly)
|
||||||
{
|
{
|
||||||
if (poly.points.empty())
|
if (poly.points.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto c = poly.points.front();
|
Point c = poly.points.front();
|
||||||
|
|
||||||
char intbuf[coord_t_bufsize];
|
char intbuf[coord_t_bufsize];
|
||||||
|
|
||||||
@ -91,7 +98,7 @@ void append_svg(std::string &buf, const Polygon &poly)
|
|||||||
if (d.x() == 0 && d.y() == 0)
|
if (d.x() == 0 && d.y() == 0)
|
||||||
continue;
|
continue;
|
||||||
buf += " "sv;
|
buf += " "sv;
|
||||||
buf += decimal_from(p.x() - c.x(), intbuf);
|
buf += decimal_from(d.x(), intbuf);
|
||||||
buf += " "sv;
|
buf += " "sv;
|
||||||
buf += decimal_from(d.y(), intbuf);
|
buf += decimal_from(d.y(), intbuf);
|
||||||
c = p;
|
c = p;
|
||||||
@ -139,9 +146,6 @@ public:
|
|||||||
"<svg height=\"" + hf + "mm" + "\" width=\"" + wf + "mm" + "\" viewBox=\"0 0 " + w + " " + h +
|
"<svg height=\"" + hf + "mm" + "\" width=\"" + wf + "mm" + "\" viewBox=\"0 0 " + w + " " + h +
|
||||||
"\" style=\"fill: white; stroke: none; fill-rule: nonzero\" "
|
"\" style=\"fill: white; stroke: none; fill-rule: nonzero\" "
|
||||||
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
|
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
|
||||||
|
|
||||||
// Add black background;
|
|
||||||
m_svg += "<rect fill='black' stroke='none' x='0' y='0' width='" + w + "' height='" + h + "'/>\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const ExPolygon& poly) override
|
void draw(const ExPolygon& poly) override
|
||||||
@ -171,6 +175,8 @@ public:
|
|||||||
|
|
||||||
Trafo trafo() const override { return m_trafo; }
|
Trafo trafo() const override { return m_trafo; }
|
||||||
|
|
||||||
|
// The encoder is ignored here, the svg text does not need any further
|
||||||
|
// encoding.
|
||||||
sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override
|
sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
@ -190,11 +196,9 @@ std::unique_ptr<sla::RasterBase> SL1_SVGArchive::create_raster() const
|
|||||||
auto w = cfg().display_width.getFloat();
|
auto w = cfg().display_width.getFloat();
|
||||||
auto h = cfg().display_height.getFloat();
|
auto h = cfg().display_height.getFloat();
|
||||||
|
|
||||||
// auto res_x = size_t(cfg().display_pixels_x.getInt());
|
|
||||||
// auto res_y = size_t(cfg().display_pixels_y.getInt());
|
|
||||||
float precision_nm = scaled<float>(cfg().sla_output_precision.getFloat());
|
float precision_nm = scaled<float>(cfg().sla_output_precision.getFloat());
|
||||||
size_t res_x = std::round(scaled(w) / precision_nm);
|
auto res_x = size_t(std::round(scaled(w) / precision_nm));
|
||||||
size_t res_y = std::round(scaled(h) / precision_nm);
|
auto res_y = size_t(std::round(scaled(h) / precision_nm));
|
||||||
|
|
||||||
std::array<bool, 2> mirror;
|
std::array<bool, 2> mirror;
|
||||||
|
|
||||||
@ -220,6 +224,7 @@ std::unique_ptr<sla::RasterBase> SL1_SVGArchive::create_raster() const
|
|||||||
return std::make_unique<SVGRaster>(svgarea, sla::Resolution{res_x, res_y}, tr);
|
return std::make_unique<SVGRaster>(svgarea, sla::Resolution{res_x, res_y}, tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SVG does not need additional binary encoding.
|
||||||
sla::RasterEncoder SL1_SVGArchive::get_encoder() const
|
sla::RasterEncoder SL1_SVGArchive::get_encoder() const
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -230,9 +235,83 @@ void SL1_SVGArchive::export_print(const std::string fname,
|
|||||||
const ThumbnailsList &thumbnails,
|
const ThumbnailsList &thumbnails,
|
||||||
const std::string &projectname)
|
const std::string &projectname)
|
||||||
{
|
{
|
||||||
|
// Export code is completely identical to SL1, only the compression level
|
||||||
|
// is elevated, as the SL1 has already compressed PNGs with deflate,
|
||||||
|
// but the svg is just text.
|
||||||
Zipper zipper{fname, Zipper::TIGHT_COMPRESSION};
|
Zipper zipper{fname, Zipper::TIGHT_COMPRESSION};
|
||||||
|
|
||||||
SL1Archive::export_print(zipper, print, thumbnails, projectname);
|
SL1Archive::export_print(zipper, print, thumbnails, projectname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NanoSVGParser {
|
||||||
|
NSVGimage *image;
|
||||||
|
static constexpr const char *Units = "mm"; // Denotes user coordinate system
|
||||||
|
static constexpr float Dpi = 1.f; // Not needed
|
||||||
|
explicit NanoSVGParser(char* input): image{nsvgParse(input, Units, Dpi)} {}
|
||||||
|
~NanoSVGParser() { nsvgDelete(image); }
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigSubstitutions SL1_SVGReader::read(std::vector<ExPolygons> &slices,
|
||||||
|
DynamicPrintConfig &profile_out)
|
||||||
|
{
|
||||||
|
std::vector<std::string> includes = { CONFIG_FNAME, PROFILE_FNAME, "svg"};
|
||||||
|
ZipperArchive arch = read_zipper_archive(m_fname, includes, {});
|
||||||
|
auto [profile_use, config_substitutions] = extract_profile(arch, profile_out);
|
||||||
|
|
||||||
|
RasterParams rstp = get_raster_params(profile_use);
|
||||||
|
|
||||||
|
struct Status
|
||||||
|
{
|
||||||
|
double incr, val, prev;
|
||||||
|
bool stop = false;
|
||||||
|
} st{100. / arch.entries.size(), 0., 0.};
|
||||||
|
|
||||||
|
for (const EntryBuffer &entry : arch.entries) {
|
||||||
|
if (st.stop) break;
|
||||||
|
|
||||||
|
st.val += st.incr;
|
||||||
|
double curr = std::round(st.val);
|
||||||
|
if (curr > st.prev) {
|
||||||
|
st.prev = curr;
|
||||||
|
st.stop = !m_progr(int(curr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't want to use dirty casts for the buffer to be usable in
|
||||||
|
// the NanoSVGParser until performance is not a bottleneck here.
|
||||||
|
auto svgtxt = reserve_vector<char>(entry.buf.size() + 1);
|
||||||
|
std::copy(entry.buf.begin(), entry.buf.end(), std::back_inserter(svgtxt));
|
||||||
|
svgtxt.emplace_back('\0');
|
||||||
|
NanoSVGParser svgp(svgtxt.data());
|
||||||
|
|
||||||
|
Polygons polys;
|
||||||
|
for (NSVGshape *shape = svgp.image->shapes; shape != nullptr; shape = shape->next) {
|
||||||
|
for (NSVGpath *path = shape->paths; path != nullptr; path = path->next) {
|
||||||
|
Polygon p;
|
||||||
|
for (int i = 0; i < path->npts; ++i) {
|
||||||
|
size_t c = 2 * i;
|
||||||
|
p.points.emplace_back(scaled(Vec2f(path->pts[c], path->pts[c + 1])));
|
||||||
|
}
|
||||||
|
polys.emplace_back(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the slice from the read polygons. Here, the fill rule has to
|
||||||
|
// be the same as stated in the svg file which is `nonzero` when exported
|
||||||
|
// using SL1_SVGArchive. Would be better to parse it from the svg file,
|
||||||
|
// but if it's different, the file is probably corrupted anyways.
|
||||||
|
ExPolygons expolys = union_ex(polys, ClipperLib::pftNonZero);
|
||||||
|
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
||||||
|
slices.emplace_back(expolys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile error without the move
|
||||||
|
return std::move(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
|
} // namespace Slic3r
|
||||||
|
@ -22,6 +22,27 @@ public:
|
|||||||
using SL1Archive::SL1Archive;
|
using SL1Archive::SL1Archive;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SL1_SVGReader: public SLAArchiveReader {
|
||||||
|
std::function<bool(int)> 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<ExPolygons> &slices,
|
||||||
|
DynamicPrintConfig &profile_out) override;
|
||||||
|
|
||||||
|
ConfigSubstitutions read(DynamicPrintConfig &profile) override;
|
||||||
|
|
||||||
|
SL1_SVGReader() = default;
|
||||||
|
SL1_SVGReader(const std::string &fname,
|
||||||
|
SLAImportQuality /*quality*/,
|
||||||
|
const ProgrFn & progr)
|
||||||
|
: m_progr(progr), m_fname(fname)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // SL1_SVG_HPP
|
#endif // SL1_SVG_HPP
|
||||||
|
179
src/libslic3r/Format/SLAArchiveReader.cpp
Normal file
179
src/libslic3r/Format/SLAArchiveReader.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#include "SLAArchiveReader.hpp"
|
||||||
|
#include "SL1.hpp"
|
||||||
|
#include "SL1_SVG.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
constexpr const char * L(const char * str) { return str; }
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Factory function that returns an implementation of SLAArchiveReader.
|
||||||
|
using ArchiveFactory = std::function<
|
||||||
|
std::unique_ptr<SLAArchiveReader>(const std::string &fname,
|
||||||
|
SLAImportQuality quality,
|
||||||
|
const ProgrFn & progr)>;
|
||||||
|
|
||||||
|
// Entry in the global registry of readable archive formats.
|
||||||
|
struct ArchiveEntry {
|
||||||
|
const char *descr;
|
||||||
|
std::vector<const char *> extensions;
|
||||||
|
ArchiveFactory factoryfn;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is where the readable archive formats are registered.
|
||||||
|
static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
|
||||||
|
{
|
||||||
|
"SL1",
|
||||||
|
{ L("SL1 / SL1S archive files"), {"sl1", "sl1s", "zip"},
|
||||||
|
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique<SL1Reader>(fname, quality, progr); } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SL2",
|
||||||
|
{ L("SL2 archive files"), {"sl2", "sl1_svg"/*, "zip"*/}, // also a zip but unnecessary hassle to implement single extension for multiple archives
|
||||||
|
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique<SL1_SVGReader>(fname, quality, progr); }}
|
||||||
|
},
|
||||||
|
// TODO: pwmx and future others.
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<SLAArchiveReader> SLAArchiveReader::create(
|
||||||
|
const std::string &fname,
|
||||||
|
const std::string &format_id,
|
||||||
|
SLAImportQuality quality,
|
||||||
|
const ProgrFn & progr)
|
||||||
|
{
|
||||||
|
// Create an instance of SLAArchiveReader using the registered archive
|
||||||
|
// reader implementations.
|
||||||
|
// If format_id is specified and valid, that archive format will be
|
||||||
|
// preferred. When format_id is emtpy, the file extension is compared
|
||||||
|
// with the advertised extensions of registered readers and the first
|
||||||
|
// match will be used.
|
||||||
|
|
||||||
|
std::string ext = boost::filesystem::path(fname).extension().string();
|
||||||
|
boost::algorithm::to_lower(ext);
|
||||||
|
|
||||||
|
std::unique_ptr<SLAArchiveReader> ret;
|
||||||
|
|
||||||
|
auto arch_from = REGISTERED_ARCHIVES.begin();
|
||||||
|
auto arch_to = REGISTERED_ARCHIVES.end();
|
||||||
|
|
||||||
|
auto arch_it = REGISTERED_ARCHIVES.find(format_id);
|
||||||
|
if (arch_it != REGISTERED_ARCHIVES.end()) {
|
||||||
|
arch_from = arch_it;
|
||||||
|
arch_to = arch_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.empty()) {
|
||||||
|
if (ext.front() == '.')
|
||||||
|
ext.erase(ext.begin());
|
||||||
|
|
||||||
|
auto extcmp = [&ext](const auto &e) { return e == ext; };
|
||||||
|
|
||||||
|
for (auto it = arch_from; it != arch_to; ++it) {
|
||||||
|
const auto &[format_id, entry] = *it;
|
||||||
|
if (std::any_of(entry.extensions.begin(), entry.extensions.end(), extcmp))
|
||||||
|
ret = entry.factoryfn(fname, quality, progr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<const char *> &SLAArchiveReader::registered_archives()
|
||||||
|
{
|
||||||
|
static std::vector<const char*> archnames;
|
||||||
|
|
||||||
|
if (archnames.empty()) {
|
||||||
|
archnames.reserve(REGISTERED_ARCHIVES.size());
|
||||||
|
|
||||||
|
for (auto &[name, _] : REGISTERED_ARCHIVES)
|
||||||
|
archnames.emplace_back(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return archnames;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const char *> 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)
|
||||||
|
{
|
||||||
|
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
||||||
|
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||||
|
|
||||||
|
if (!opt_layerh || !opt_init_layerh)
|
||||||
|
throw MissingProfileError("Invalid SL1 / SL1S file");
|
||||||
|
|
||||||
|
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSubstitutions import_sla_archive(const std::string &zipfname,
|
||||||
|
const std::string &format_id,
|
||||||
|
indexed_triangle_set &out,
|
||||||
|
DynamicPrintConfig &profile,
|
||||||
|
SLAImportQuality quality,
|
||||||
|
const ProgrFn & progr)
|
||||||
|
{
|
||||||
|
ConfigSubstitutions ret;
|
||||||
|
|
||||||
|
if (auto reader = SLAArchiveReader::create(zipfname, format_id, quality, progr)) {
|
||||||
|
std::vector<ExPolygons> slices;
|
||||||
|
ret = reader->read(slices, profile);
|
||||||
|
|
||||||
|
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,
|
||||||
|
const std::string &format_id,
|
||||||
|
DynamicPrintConfig &out)
|
||||||
|
{
|
||||||
|
ConfigSubstitutions ret;
|
||||||
|
|
||||||
|
if (auto reader = SLAArchiveReader::create(zipfname, format_id)) {
|
||||||
|
ret = reader->read(out);
|
||||||
|
} else {
|
||||||
|
throw ReaderUnimplementedError("Reader unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
85
src/libslic3r/Format/SLAArchiveReader.hpp
Normal file
85
src/libslic3r/Format/SLAArchiveReader.hpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#ifndef SLAARCHIVEREADER_HPP
|
||||||
|
#define SLAARCHIVEREADER_HPP
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
|
|
||||||
|
struct indexed_triangle_set;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// A generic indicator for the quality of an imported model. Obviously, the
|
||||||
|
// original cannot be fully reconstructed.
|
||||||
|
enum class SLAImportQuality { Accurate, Balanced, Fast };
|
||||||
|
|
||||||
|
// Raised when the needed metadata cannot be retrieved or guessed from an archive
|
||||||
|
class MissingProfileError : public RuntimeError
|
||||||
|
{
|
||||||
|
using RuntimeError::RuntimeError;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A shortname for status indication function.
|
||||||
|
// The argument is the status (from <0, 100>)
|
||||||
|
// Returns false if cancel was requested.
|
||||||
|
using ProgrFn = std::function<bool(int)>;
|
||||||
|
|
||||||
|
// Abstract interface for an archive reader. This needs to be implemented for
|
||||||
|
// every supported archive format.
|
||||||
|
class SLAArchiveReader {
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~SLAArchiveReader() = default;
|
||||||
|
|
||||||
|
// Read the profile and reconstruct the slices
|
||||||
|
virtual ConfigSubstitutions read(std::vector<ExPolygons> &slices,
|
||||||
|
DynamicPrintConfig &profile) = 0;
|
||||||
|
|
||||||
|
// Overload for reading only the profile contained in the archive (if present)
|
||||||
|
virtual ConfigSubstitutions read(DynamicPrintConfig &profile) = 0;
|
||||||
|
|
||||||
|
// Creates a reader instance based on the provided file path.
|
||||||
|
// format_id can be one of the archive type identifiers returned by
|
||||||
|
// registered_archives(). If left empty, only the file extension will
|
||||||
|
// be considered. If more archive types have the same extension (like *.zip)
|
||||||
|
// The first match is used.
|
||||||
|
static std::unique_ptr<SLAArchiveReader> create(
|
||||||
|
const std::string &fname,
|
||||||
|
const std::string &format_id,
|
||||||
|
SLAImportQuality quality = SLAImportQuality::Balanced,
|
||||||
|
const ProgrFn &progr = [](int) { return false; });
|
||||||
|
|
||||||
|
// Get the names of currently known archive reader implementations
|
||||||
|
static const std::vector<const char *> & registered_archives();
|
||||||
|
|
||||||
|
// Get the understood file extensions belonging to an archive format
|
||||||
|
static std::vector<const char *> get_extensions(const char *archtype);
|
||||||
|
|
||||||
|
// Generic description (usable in GUI) about an archive format
|
||||||
|
static const char * get_description(const char *archtype);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Raised in import_sla_archive when a nullptr reader is returned by
|
||||||
|
// SLAArchiveReader::create()
|
||||||
|
class ReaderUnimplementedError : public RuntimeError
|
||||||
|
{
|
||||||
|
using RuntimeError::RuntimeError;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper free functions to import an archive using the above interface.
|
||||||
|
// Can throw ReaderUnimplementedError or MissingProfileError
|
||||||
|
ConfigSubstitutions import_sla_archive(
|
||||||
|
const std::string &zipfname,
|
||||||
|
const std::string &format_id,
|
||||||
|
indexed_triangle_set &out,
|
||||||
|
DynamicPrintConfig &profile,
|
||||||
|
SLAImportQuality quality = SLAImportQuality::Balanced,
|
||||||
|
const ProgrFn &progr = [](int) { return true; });
|
||||||
|
|
||||||
|
// Only reads the profile, doesn't reconstruct the model.
|
||||||
|
ConfigSubstitutions import_sla_archive(const std::string &zipfname,
|
||||||
|
const std::string &format_id,
|
||||||
|
DynamicPrintConfig &out);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // SLAARCHIVEREADER_HPP
|
@ -1,4 +1,4 @@
|
|||||||
#include "SLAArchive.hpp"
|
#include "SLAArchiveWriter.hpp"
|
||||||
|
|
||||||
#include "SL1.hpp"
|
#include "SL1.hpp"
|
||||||
#include "SL1_SVG.hpp"
|
#include "SL1_SVG.hpp"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
using ArchiveFactory = std::function<std::unique_ptr<SLAArchive>(const SLAPrinterConfig&)>;
|
using ArchiveFactory = std::function<std::unique_ptr<SLAArchiveWriter>(const SLAPrinterConfig&)>;
|
||||||
|
|
||||||
struct ArchiveEntry {
|
struct ArchiveEntry {
|
||||||
const char *ext;
|
const char *ext;
|
||||||
@ -35,8 +35,8 @@ static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<SLAArchive>
|
std::unique_ptr<SLAArchiveWriter>
|
||||||
SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
|
SLAArchiveWriter::create(const std::string &archtype, const SLAPrinterConfig &cfg)
|
||||||
{
|
{
|
||||||
auto entry = REGISTERED_ARCHIVES.find(archtype);
|
auto entry = REGISTERED_ARCHIVES.find(archtype);
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<const char*>& SLAArchive::registered_archives()
|
const std::vector<const char*>& SLAArchiveWriter::registered_archives()
|
||||||
{
|
{
|
||||||
static std::vector<const char*> archnames;
|
static std::vector<const char*> archnames;
|
||||||
|
|
||||||
@ -60,9 +60,9 @@ const std::vector<const char*>& SLAArchive::registered_archives()
|
|||||||
return archnames;
|
return archnames;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *SLAArchive::get_extension(const char *archtype)
|
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);
|
auto entry = REGISTERED_ARCHIVES.find(archtype);
|
||||||
if (entry != REGISTERED_ARCHIVES.end())
|
if (entry != REGISTERED_ARCHIVES.end())
|
@ -12,7 +12,7 @@ namespace Slic3r {
|
|||||||
class SLAPrint;
|
class SLAPrint;
|
||||||
class SLAPrinterConfig;
|
class SLAPrinterConfig;
|
||||||
|
|
||||||
class SLAArchive {
|
class SLAArchiveWriter {
|
||||||
protected:
|
protected:
|
||||||
std::vector<sla::EncodedRaster> m_layers;
|
std::vector<sla::EncodedRaster> m_layers;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ protected:
|
|||||||
virtual sla::RasterEncoder get_encoder() const = 0;
|
virtual sla::RasterEncoder get_encoder() const = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~SLAArchive() = default;
|
virtual ~SLAArchiveWriter() = default;
|
||||||
|
|
||||||
// Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
|
// Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
|
||||||
template<class Fn, class CancelFn, class EP = ExecutionTBB>
|
template<class Fn, class CancelFn, class EP = ExecutionTBB>
|
||||||
@ -51,7 +51,8 @@ public:
|
|||||||
const std::string &projectname = "") = 0;
|
const std::string &projectname = "") = 0;
|
||||||
|
|
||||||
// Factory method to create an archiver instance
|
// Factory method to create an archiver instance
|
||||||
static std::unique_ptr<SLAArchive> create(const std::string &archtype, const SLAPrinterConfig&);
|
static std::unique_ptr<SLAArchiveWriter> create(
|
||||||
|
const std::string &archtype, const SLAPrinterConfig &);
|
||||||
|
|
||||||
// Get the names of currently known archiver implementations
|
// Get the names of currently known archiver implementations
|
||||||
static const std::vector<const char *> & registered_archives();
|
static const std::vector<const char *> & registered_archives();
|
136
src/libslic3r/Format/ZipperArchiveImport.cpp
Normal file
136
src/libslic3r/Format/ZipperArchiveImport.cpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#include "ZipperArchiveImport.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/miniz_extension.hpp"
|
||||||
|
#include "libslic3r/Exception.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Read an ini file into boost property tree
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read an arbitrary file into EntryBuffer
|
||||||
|
EntryBuffer read_entry(const mz_zip_archive_file_stat &entry,
|
||||||
|
MZ_Archive &zip,
|
||||||
|
const std::string &name)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> 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<std::string> &includes,
|
||||||
|
const std::vector<std::string> &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); continue; }
|
||||||
|
if (name == PROFILE_FNAME) { arch.profile = read_ini(entry, zip); continue; }
|
||||||
|
|
||||||
|
auto it = std::lower_bound(
|
||||||
|
arch.entries.begin(), arch.entries.end(),
|
||||||
|
EntryBuffer{{}, name},
|
||||||
|
[](const EntryBuffer &r1, const EntryBuffer &r2) {
|
||||||
|
return std::less<std::string>()(r1.fname, r2.fname);
|
||||||
|
});
|
||||||
|
|
||||||
|
arch.entries.insert(it, read_entry(entry, zip, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<DynamicPrintConfig, ConfigSubstitutions> 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
|
54
src/libslic3r/Format/ZipperArchiveImport.hpp
Normal file
54
src/libslic3r/Format/ZipperArchiveImport.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef ZIPPERARCHIVEIMPORT_HPP
|
||||||
|
#define ZIPPERARCHIVEIMPORT_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Buffer for arbitraryfiles inside a zipper archive.
|
||||||
|
struct EntryBuffer
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> buf;
|
||||||
|
std::string fname;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Structure holding the data read from a zipper archive.
|
||||||
|
struct ZipperArchive
|
||||||
|
{
|
||||||
|
boost::property_tree::ptree profile, config;
|
||||||
|
std::vector<EntryBuffer> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Names of the files containing metadata inside the archive.
|
||||||
|
const constexpr char *CONFIG_FNAME = "config.ini";
|
||||||
|
const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||||
|
|
||||||
|
// Read an archive that was written using the Zipper class.
|
||||||
|
// The includes parameter is a set of file name substrings that the entries
|
||||||
|
// must contain to be included in ZipperArchive.
|
||||||
|
// The excludes parameter may contain substrings that filenames must not
|
||||||
|
// contain.
|
||||||
|
// Every file in the archive is read into ZipperArchive::entries
|
||||||
|
// except the files CONFIG_FNAME, and PROFILE_FNAME which are read into
|
||||||
|
// ZipperArchive::config and ZipperArchive::profile structures.
|
||||||
|
ZipperArchive read_zipper_archive(const std::string &zipfname,
|
||||||
|
const std::vector<std::string> &includes,
|
||||||
|
const std::vector<std::string> &excludes);
|
||||||
|
|
||||||
|
// Extract the print profile form the archive into '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<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
|
||||||
|
const ZipperArchive &arch, DynamicPrintConfig &inout);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // ZIPPERARCHIVEIMPORT_HPP
|
@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "SLAArchive.hpp"
|
#include "SLAArchiveWriter.hpp"
|
||||||
|
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class PwmxArchive: public SLAArchive {
|
class PwmxArchive: public SLAArchiveWriter {
|
||||||
SLAPrinterConfig m_cfg;
|
SLAPrinterConfig m_cfg;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -246,7 +246,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
|
|||||||
m_default_object_config.apply_only(config, object_diff, true);
|
m_default_object_config.apply_only(config, object_diff, true);
|
||||||
|
|
||||||
if (!m_archiver || !printer_diff.empty())
|
if (!m_archiver || !printer_diff.empty())
|
||||||
m_archiver = SLAArchive::create(m_printer_config.sla_archive_format.value.c_str(), m_printer_config);
|
m_archiver = SLAArchiveWriter::create(m_printer_config.sla_archive_format.value.c_str(), m_printer_config);
|
||||||
|
|
||||||
struct ModelObjectStatus {
|
struct ModelObjectStatus {
|
||||||
enum Status {
|
enum Status {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "MTUtils.hpp"
|
#include "MTUtils.hpp"
|
||||||
#include "Zipper.hpp"
|
#include "Zipper.hpp"
|
||||||
#include "Format/SLAArchive.hpp"
|
#include "Format/SLAArchiveWriter.hpp"
|
||||||
#include "GCode/ThumbnailData.hpp"
|
#include "GCode/ThumbnailData.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||||
@ -524,7 +524,7 @@ private:
|
|||||||
std::vector<PrintLayer> m_printer_input;
|
std::vector<PrintLayer> m_printer_input;
|
||||||
|
|
||||||
// The archive object which collects the raster images after slicing
|
// The archive object which collects the raster images after slicing
|
||||||
std::unique_ptr<SLAArchive> m_archiver;
|
std::unique_ptr<SLAArchiveWriter> m_archiver;
|
||||||
|
|
||||||
// Estimated print time, material consumed.
|
// Estimated print time, material consumed.
|
||||||
SLAPrintStatistics m_print_statistics;
|
SLAPrintStatistics m_print_statistics;
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "GUI_Utils.hpp"
|
#include "GUI_Utils.hpp"
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/nowide/cstdio.hpp>
|
||||||
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
#ifdef __WXGTK2__
|
#ifdef __WXGTK2__
|
||||||
// Broken alpha workaround
|
// Broken alpha workaround
|
||||||
@ -13,7 +15,7 @@
|
|||||||
#include <wx/rawbmp.h>
|
#include <wx/rawbmp.h>
|
||||||
#endif /* __WXGTK2__ */
|
#endif /* __WXGTK2__ */
|
||||||
|
|
||||||
#define NANOSVG_IMPLEMENTATION
|
//#define NANOSVG_IMPLEMENTATION
|
||||||
#include "nanosvg/nanosvg.h"
|
#include "nanosvg/nanosvg.h"
|
||||||
#define NANOSVGRAST_IMPLEMENTATION
|
#define NANOSVGRAST_IMPLEMENTATION
|
||||||
#include "nanosvg/nanosvgrast.h"
|
#include "nanosvg/nanosvgrast.h"
|
||||||
|
@ -10,17 +10,67 @@
|
|||||||
#include <wx/filepicker.h>
|
#include <wx/filepicker.h>
|
||||||
|
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
||||||
|
|
||||||
#include "slic3r/GUI/I18N.hpp"
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
//#include "libslic3r/Model.hpp"
|
//#include "libslic3r/Model.hpp"
|
||||||
//#include "libslic3r/PresetBundle.hpp"
|
//#include "libslic3r/PresetBundle.hpp"
|
||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
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 {
|
class SLAImportDialog: public wxDialog, public SLAImportJobView {
|
||||||
wxFilePickerCtrl *m_filepicker;
|
wxFilePickerCtrl *m_filepicker;
|
||||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||||
@ -34,7 +84,7 @@ public:
|
|||||||
|
|
||||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
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",
|
get_readers_wildcard(),
|
||||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||||
|
|
||||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
||||||
@ -64,7 +114,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_quality_dropdown = new wxComboBox(
|
m_quality_dropdown = new wxComboBox(
|
||||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
this, wxID_ANY, qual_choices[1], wxDefaultPosition, wxDefaultSize,
|
||||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||||
szchoices->Add(m_quality_dropdown, 1);
|
szchoices->Add(m_quality_dropdown, 1);
|
||||||
|
|
||||||
@ -96,17 +146,15 @@ public:
|
|||||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2i get_marchsq_windowsize() const override
|
SLAImportQuality get_quality() const override
|
||||||
{
|
{
|
||||||
enum { Accurate, Balanced, Fast};
|
|
||||||
|
|
||||||
switch(m_quality_dropdown->GetSelection())
|
switch(m_quality_dropdown->GetSelection())
|
||||||
{
|
{
|
||||||
case Fast: return {8, 8};
|
case 2: return SLAImportQuality::Fast;
|
||||||
case Balanced: return {4, 4};
|
case 1: return SLAImportQuality::Balanced;
|
||||||
|
case 0: return SLAImportQuality::Accurate;
|
||||||
default:
|
default:
|
||||||
case Accurate:
|
return SLAImportQuality::Balanced;
|
||||||
return {2, 2};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +162,14 @@ public:
|
|||||||
{
|
{
|
||||||
return m_filepicker->GetPath().ToUTF8().data();
|
return m_filepicker->GetPath().ToUTF8().data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_archive_format() const override
|
||||||
|
{
|
||||||
|
// TODO: the choosen format is inside the file dialog which is not
|
||||||
|
// accessible from the file picker object. The file picker could be
|
||||||
|
// changed to a custom file dialog.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace Slic3r::GUI
|
}} // namespace Slic3r::GUI
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/Format/SL1.hpp"
|
#include "libslic3r/Format/SL1.hpp"
|
||||||
|
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
||||||
|
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
@ -24,7 +25,7 @@ public:
|
|||||||
indexed_triangle_set mesh;
|
indexed_triangle_set mesh;
|
||||||
DynamicPrintConfig profile;
|
DynamicPrintConfig profile;
|
||||||
wxString path;
|
wxString path;
|
||||||
Vec2i win = {2, 2};
|
Quality quality = Quality::Balanced;
|
||||||
std::string err;
|
std::string err;
|
||||||
ConfigSubstitutions config_substitutions;
|
ConfigSubstitutions config_substitutions;
|
||||||
|
|
||||||
@ -55,19 +56,29 @@ void SLAImportJob::process(Ctl &ctl)
|
|||||||
if (p->path.empty()) return;
|
if (p->path.empty()) return;
|
||||||
|
|
||||||
std::string path = p->path.ToUTF8().data();
|
std::string path = p->path.ToUTF8().data();
|
||||||
|
std::string format_id = p->import_dlg->get_archive_format();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (p->sel) {
|
switch (p->sel) {
|
||||||
case Sel::modelAndProfile:
|
case Sel::modelAndProfile:
|
||||||
case Sel::modelOnly:
|
case Sel::modelOnly:
|
||||||
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
p->config_substitutions = import_sla_archive(path,
|
||||||
|
format_id,
|
||||||
|
p->mesh,
|
||||||
|
p->profile,
|
||||||
|
p->quality, progr);
|
||||||
break;
|
break;
|
||||||
case Sel::profileOnly:
|
case Sel::profileOnly:
|
||||||
p->config_substitutions = import_sla_archive(path, p->profile);
|
p->config_substitutions = import_sla_archive(path, format_id,
|
||||||
|
p->profile);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (MissingProfileError &) {
|
} catch (MissingProfileError &) {
|
||||||
p->err = _L("The SLA archive doesn't contain any presets. "
|
p->err = _u8L("The SLA archive doesn't contain any presets. "
|
||||||
"Please activate some SLA printer preset first before importing that SLA archive.").ToStdString();
|
"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) {
|
}catch (std::exception &ex) {
|
||||||
p->err = ex.what();
|
p->err = ex.what();
|
||||||
}
|
}
|
||||||
@ -81,7 +92,7 @@ void SLAImportJob::reset()
|
|||||||
p->sel = Sel::modelAndProfile;
|
p->sel = Sel::modelAndProfile;
|
||||||
p->mesh = {};
|
p->mesh = {};
|
||||||
p->profile = p->plater->sla_print().full_print_config();
|
p->profile = p->plater->sla_print().full_print_config();
|
||||||
p->win = {2, 2};
|
p->quality = SLAImportQuality::Balanced;
|
||||||
p->path.Clear();
|
p->path.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +104,8 @@ void SLAImportJob::prepare()
|
|||||||
auto nm = wxFileName(path);
|
auto nm = wxFileName(path);
|
||||||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
||||||
p->sel = p->import_dlg->get_selection();
|
p->sel = p->import_dlg->get_selection();
|
||||||
p->win = p->import_dlg->get_marchsq_windowsize();
|
p->quality = p->import_dlg->get_quality();
|
||||||
|
|
||||||
p->config_substitutions.clear();
|
p->config_substitutions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +127,8 @@ void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr)
|
|||||||
p->plater->get_notification_manager()->push_notification(
|
p->plater->get_notification_manager()->push_notification(
|
||||||
NotificationType::CustomNotification,
|
NotificationType::CustomNotification,
|
||||||
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||||
_L("The imported SLA archive did not contain any presets. "
|
_u8L("The imported SLA archive did not contain any presets. "
|
||||||
"The current SLA presets were used as fallback.").ToStdString());
|
"The current SLA presets were used as fallback."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->sel != Sel::modelOnly) {
|
if (p->sel != Sel::modelOnly) {
|
||||||
@ -138,15 +150,27 @@ void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr)
|
|||||||
config.apply(SLAFullPrintConfig::defaults());
|
config.apply(SLAFullPrintConfig::defaults());
|
||||||
config += std::move(p->profile);
|
config += std::move(p->profile);
|
||||||
|
|
||||||
|
if (Preset::printer_technology(config) == ptSLA) {
|
||||||
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
|
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
|
||||||
p->plater->check_selected_presets_visibility(ptSLA);
|
p->plater->check_selected_presets_visibility(ptSLA);
|
||||||
wxGetApp().load_current_presets();
|
wxGetApp().load_current_presets();
|
||||||
|
} else {
|
||||||
|
p->plater->get_notification_manager()->push_notification(
|
||||||
|
NotificationType::CustomNotification,
|
||||||
|
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||||
|
_u8L("The profile in the imported archive is corrupt and will not be loaded."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!p->mesh.empty()) {
|
if (!p->mesh.empty()) {
|
||||||
bool is_centered = false;
|
bool is_centered = false;
|
||||||
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{std::move(p->mesh)},
|
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{std::move(p->mesh)},
|
||||||
name, is_centered);
|
name, is_centered);
|
||||||
|
} else if (p->sel == Sel::modelOnly || p->sel == Sel::modelAndProfile) {
|
||||||
|
p->plater->get_notification_manager()->push_notification(
|
||||||
|
NotificationType::CustomNotification,
|
||||||
|
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||||
|
_u8L("No object could be retrieved from the archive. The slices might be corrupted or missing."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! p->config_substitutions.empty())
|
if (! p->config_substitutions.empty())
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "Job.hpp"
|
#include "Job.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
@ -14,8 +14,9 @@ public:
|
|||||||
virtual ~SLAImportJobView() = default;
|
virtual ~SLAImportJobView() = default;
|
||||||
|
|
||||||
virtual Sel get_selection() const = 0;
|
virtual Sel get_selection() const = 0;
|
||||||
virtual Vec2i get_marchsq_windowsize() const = 0;
|
virtual SLAImportQuality get_quality() const = 0;
|
||||||
virtual std::string get_path() const = 0;
|
virtual std::string get_path() const = 0;
|
||||||
|
virtual std::string get_archive_format() const { return ""; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Plater;
|
class Plater;
|
||||||
@ -25,6 +26,7 @@ class SLAImportJob : public Job {
|
|||||||
|
|
||||||
std::unique_ptr<priv> p;
|
std::unique_ptr<priv> p;
|
||||||
using Sel = SLAImportJobView::Sel;
|
using Sel = SLAImportJobView::Sel;
|
||||||
|
using Quality = SLAImportQuality;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void prepare();
|
void prepare();
|
||||||
|
@ -1212,7 +1212,7 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
|
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
|
||||||
[this](){return m_plater != nullptr; }, this);
|
[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](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);
|
[this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
|||||||
sla_test_utils.hpp sla_test_utils.cpp
|
sla_test_utils.hpp sla_test_utils.cpp
|
||||||
sla_supptgen_tests.cpp
|
sla_supptgen_tests.cpp
|
||||||
sla_raycast_tests.cpp
|
sla_raycast_tests.cpp
|
||||||
sla_archive_export_tests.cpp)
|
sla_archive_readwrite_tests.cpp)
|
||||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||||
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
#include <catch2/catch.hpp>
|
|
||||||
#include <test_utils.hpp>
|
|
||||||
|
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
|
||||||
#include "libslic3r/Format/SLAArchive.hpp"
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
|
||||||
|
|
||||||
TEST_CASE("Archive export test", "[sla_archives]") {
|
|
||||||
constexpr const char *PNAME = "20mm_cube";
|
|
||||||
|
|
||||||
for (auto &archname : SLAArchive::registered_archives()) {
|
|
||||||
INFO(std::string("Testing archive type: ") + archname);
|
|
||||||
SLAPrint print;
|
|
||||||
SLAFullPrintConfig fullcfg;
|
|
||||||
|
|
||||||
auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(PNAME) + ".obj", nullptr);
|
|
||||||
|
|
||||||
fullcfg.set("sla_archive_format", archname);
|
|
||||||
fullcfg.set("supports_enable", false);
|
|
||||||
fullcfg.set("pad_enable", false);
|
|
||||||
|
|
||||||
DynamicPrintConfig cfg;
|
|
||||||
cfg.apply(fullcfg);
|
|
||||||
|
|
||||||
print.set_status_callback([](const PrintBase::SlicingStatus&) {});
|
|
||||||
print.apply(m, cfg);
|
|
||||||
print.process();
|
|
||||||
|
|
||||||
ThumbnailsList thumbnails;
|
|
||||||
auto outputfname = std::string("output.") + SLAArchive::get_extension(archname);
|
|
||||||
|
|
||||||
print.export_print(outputfname, thumbnails, PNAME);
|
|
||||||
|
|
||||||
// Not much can be checked about the archives...
|
|
||||||
REQUIRE(boost::filesystem::exists(outputfname));
|
|
||||||
}
|
|
||||||
}
|
|
71
tests/sla_print/sla_archive_readwrite_tests.cpp
Normal file
71
tests/sla_print/sla_archive_readwrite_tests.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <test_utils.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
#include "libslic3r/Format/SLAArchiveWriter.hpp"
|
||||||
|
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("Archive export test", "[sla_archives]") {
|
||||||
|
for (const char * pname : {"20mm_cube", "extruder_idler"})
|
||||||
|
for (auto &archname : SLAArchiveWriter::registered_archives()) {
|
||||||
|
INFO(std::string("Testing archive type: ") + archname + " -- writing...");
|
||||||
|
SLAPrint print;
|
||||||
|
SLAFullPrintConfig fullcfg;
|
||||||
|
|
||||||
|
auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(pname) + ".obj", nullptr);
|
||||||
|
|
||||||
|
fullcfg.printer_technology.setInt(ptSLA); // FIXME this should be ensured
|
||||||
|
fullcfg.set("sla_archive_format", archname);
|
||||||
|
fullcfg.set("supports_enable", false);
|
||||||
|
fullcfg.set("pad_enable", false);
|
||||||
|
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
cfg.apply(fullcfg);
|
||||||
|
|
||||||
|
print.set_status_callback([](const PrintBase::SlicingStatus&) {});
|
||||||
|
print.apply(m, cfg);
|
||||||
|
print.process();
|
||||||
|
|
||||||
|
ThumbnailsList thumbnails;
|
||||||
|
auto outputfname = std::string("output_") + pname + "." + SLAArchiveWriter::get_extension(archname);
|
||||||
|
|
||||||
|
print.export_print(outputfname, thumbnails, pname);
|
||||||
|
|
||||||
|
// Not much can be checked about the archives...
|
||||||
|
REQUIRE(boost::filesystem::exists(outputfname));
|
||||||
|
|
||||||
|
double vol_written = m.mesh().volume();
|
||||||
|
|
||||||
|
auto readable_formats = SLAArchiveReader::registered_archives();
|
||||||
|
if (std::any_of(readable_formats.begin(), readable_formats.end(),
|
||||||
|
[&archname](const std::string &a) { return a == archname; })) {
|
||||||
|
|
||||||
|
INFO(std::string("Testing archive type: ") + archname + " -- reading back...");
|
||||||
|
|
||||||
|
indexed_triangle_set its;
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Leave format_id deliberetaly empty, guessing should always
|
||||||
|
// work here.
|
||||||
|
import_sla_archive(outputfname, "", its, cfg);
|
||||||
|
} catch (...) {
|
||||||
|
REQUIRE(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// its_write_obj(its, (outputfname + ".obj").c_str());
|
||||||
|
|
||||||
|
REQUIRE(!cfg.empty());
|
||||||
|
REQUIRE(!its.empty());
|
||||||
|
|
||||||
|
double vol_read = its_volume(its);
|
||||||
|
double rel_err = std::abs(vol_written - vol_read) / vol_written;
|
||||||
|
REQUIRE(rel_err < 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user