Merge branch 'et_world_coordinates' into fs_emboss

This commit is contained in:
Filip Sykala 2022-05-02 10:11:47 +02:00
commit 3e9778b46b
94 changed files with 8223 additions and 7508 deletions

View file

@ -1,359 +1,37 @@
#include "SL1.hpp"
#include "GCode/ThumbnailData.hpp"
#include "libslic3r/Time.hpp"
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <sstream>
#include "libslic3r/Time.hpp"
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "SLAArchiveReader.hpp"
#include "ZipperArchiveImport.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.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 {
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>;
namespace {
@ -558,3 +236,207 @@ void SL1Archive::export_print(const std::string fname,
}
} // 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

View file

@ -3,16 +3,15 @@
#include <string>
#include "SLAArchive.hpp"
#include "SLAArchiveWriter.hpp"
#include "SLAArchiveReader.hpp"
#include "libslic3r/Zipper.hpp"
#include "libslic3r/PrintConfig.hpp"
struct indexed_triangle_set;
namespace Slic3r {
class SL1Archive: public SLAArchive {
class SL1Archive: public SLAArchiveWriter {
SLAPrinterConfig m_cfg;
protected:
@ -38,27 +37,41 @@ public:
const ThumbnailsList &thumbnails,
const std::string &projectname = "") override;
};
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
class SL1Reader: public SLAArchiveReader {
SLAImportQuality m_quality = SLAImportQuality::Balanced;
std::function<bool(int)> m_progr;
std::string m_fname;
inline ConfigSubstitutions import_sla_archive(
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);
}
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;
class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; };
ConfigSubstitutions read(DynamicPrintConfig &profile) override;
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

View file

@ -3,6 +3,10 @@
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Format/ZipperArchiveImport.hpp"
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
#include <limits>
#include <cstdint>
@ -16,6 +20,7 @@ namespace {
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)
{
std::make_unsigned_t<coord_t> number = 0;
@ -50,6 +55,7 @@ inline std::string coord2str(coord_t crd)
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)
{
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)
{
if (poly.points.empty())
return;
auto c = poly.points.front();
Point c = poly.points.front();
char intbuf[coord_t_bufsize];
@ -84,15 +91,16 @@ void append_svg(std::string &buf, const Polygon &poly)
buf += decimal_from(c.x(), intbuf);
buf += " "sv;
buf += decimal_from(c.y(), intbuf);
buf += " m"sv;
buf += " l "sv;
for (auto &p : poly) {
auto d = p - c;
if (d.squaredNorm() == 0) continue;
for (const Point &p : poly) {
Point d = p - c;
if (d.x() == 0 && d.y() == 0)
continue;
buf += " "sv;
buf += decimal_from(p.x() - c.x(), intbuf);
buf += decimal_from(d.x(), intbuf);
buf += " "sv;
buf += decimal_from(p.y() - c.y(), intbuf);
buf += decimal_from(d.y(), intbuf);
c = p;
}
buf += " z\""sv; // mark path as closed
@ -138,9 +146,6 @@ public:
"<svg height=\"" + hf + "mm" + "\" width=\"" + wf + "mm" + "\" viewBox=\"0 0 " + w + " " + h +
"\" 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";
// 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
@ -170,6 +175,8 @@ public:
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
{
std::vector<uint8_t> data;
@ -189,13 +196,11 @@ std::unique_ptr<sla::RasterBase> SL1_SVGArchive::create_raster() const
auto w = cfg().display_width.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());
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<bool, 2> mirror;
std::array<bool, 2> mirror;
mirror[X] = cfg().display_mirror_x.getBool();
mirror[Y] = cfg().display_mirror_y.getBool();
@ -219,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);
}
// SVG does not need additional binary encoding.
sla::RasterEncoder SL1_SVGArchive::get_encoder() const
{
return nullptr;
@ -229,9 +235,83 @@ void SL1_SVGArchive::export_print(const std::string fname,
const ThumbnailsList &thumbnails,
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};
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

View file

@ -22,6 +22,27 @@ public:
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
#endif // SL1_SVG_HPP

View 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

View 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

View file

@ -1,4 +1,4 @@
#include "SLAArchive.hpp"
#include "SLAArchiveWriter.hpp"
#include "SL1.hpp"
#include "SL1_SVG.hpp"
@ -13,7 +13,7 @@
namespace Slic3r {
using ArchiveFactory = std::function<std::unique_ptr<SLAArchive>(const SLAPrinterConfig&)>;
using ArchiveFactory = std::function<std::unique_ptr<SLAArchiveWriter>(const SLAPrinterConfig&)>;
struct ArchiveEntry {
const char *ext;
@ -35,8 +35,8 @@ static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
}
};
std::unique_ptr<SLAArchive>
SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
std::unique_ptr<SLAArchiveWriter>
SLAArchiveWriter::create(const std::string &archtype, const SLAPrinterConfig &cfg)
{
auto entry = REGISTERED_ARCHIVES.find(archtype);
@ -46,7 +46,7 @@ SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
return nullptr;
}
const std::vector<const char*>& SLAArchive::registered_archives()
const std::vector<const char*>& SLAArchiveWriter::registered_archives()
{
static std::vector<const char*> archnames;
@ -60,9 +60,9 @@ const std::vector<const char*>& SLAArchive::registered_archives()
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);
if (entry != REGISTERED_ARCHIVES.end())

View file

@ -12,7 +12,7 @@ namespace Slic3r {
class SLAPrint;
class SLAPrinterConfig;
class SLAArchive {
class SLAArchiveWriter {
protected:
std::vector<sla::EncodedRaster> m_layers;
@ -20,7 +20,7 @@ protected:
virtual sla::RasterEncoder get_encoder() const = 0;
public:
virtual ~SLAArchive() = default;
virtual ~SLAArchiveWriter() = default;
// Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
template<class Fn, class CancelFn, class EP = ExecutionTBB>
@ -44,14 +44,15 @@ public:
execution::max_concurrency(ep));
}
// Export the print into an archive using the provided filename.
// Export the print into an archive using the provided filename.
virtual void export_print(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &projectname = "") = 0;
// Factory method to create an archiver instance
static std::unique_ptr<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
static const std::vector<const char *> & registered_archives();

View 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

View 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

View file

@ -3,13 +3,13 @@
#include <string>
#include "SLAArchive.hpp"
#include "SLAArchiveWriter.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
class PwmxArchive: public SLAArchive {
class PwmxArchive: public SLAArchiveWriter {
SLAPrinterConfig m_cfg;
protected: