Mirroring refactored.

This commit is contained in:
tamasmeszaros 2019-05-18 22:45:24 +02:00
parent baab5e49f1
commit bb73b59aa6
10 changed files with 380 additions and 680 deletions

View file

@ -130,13 +130,10 @@ add_library(libslic3r STATIC
Print.hpp
PrintBase.cpp
PrintBase.hpp
PrintExport.hpp
PrintConfig.cpp
PrintConfig.hpp
PrintObject.cpp
PrintRegion.cpp
Rasterizer/Rasterizer.hpp
Rasterizer/Rasterizer.cpp
SLAPrint.cpp
SLAPrint.hpp
SLA/SLAAutoSupports.hpp
@ -173,6 +170,10 @@ add_library(libslic3r STATIC
SLA/SLARotfinder.cpp
SLA/SLABoostAdapter.hpp
SLA/SLASpatIndex.hpp
SLA/SLARaster.hpp
SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp
)
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)

View file

@ -2262,13 +2262,13 @@ void PrintConfigDef::init_sla_params()
def->full_label = L("Display mirroring in X axis");
def->label = L("Mirror X");
def->tooltip = L("Enable mirroring of output images in the X axis");
def->set_default_value(new ConfigOptionBool(false));
def->set_default_value(new ConfigOptionBool(true));
def = this->add("display_mirror_y", coBool);
def->full_label = L("Display mirroring in Y axis");
def->label = L("Mirror Y");
def->tooltip = L("Enable mirroring of output images in the Y axis");
def->set_default_value(new ConfigOptionBool(true));
def->set_default_value(new ConfigOptionBool(false));
def = this->add("display_orientation", coEnum);
def->label = L("Display orientation");

View file

@ -1,349 +0,0 @@
#ifndef PRINTEXPORT_HPP
#define PRINTEXPORT_HPP
// For png export of the sliced model
#include <fstream>
#include <sstream>
#include <vector>
#include <boost/log/trivial.hpp>
#include <boost/filesystem/path.hpp>
#include "Rasterizer/Rasterizer.hpp"
//#include <tbb/parallel_for.h>
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
namespace Slic3r {
// Used for addressing parameters of FilePrinter::set_statistics()
enum ePrintStatistics
{
psUsedMaterial = 0,
psNumFade,
psNumSlow,
psNumFast,
psCnt
};
enum class FilePrinterFormat {
SLA_PNGZIP,
SVG
};
/*
* Interface for a file printer of the slices. Implementation can be an SVG
* or PNG printer or any other format.
*
* The format argument specifies the output format of the printer and it enables
* different implementations of this class template for each supported format.
*
*/
template<FilePrinterFormat format>
class FilePrinter {
public:
// Draw a polygon which is a polygon inside a slice on the specified layer.
void draw_polygon(const ExPolygon& p, unsigned lyr);
void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr);
// Tell the printer how many layers should it consider.
void layers(unsigned layernum);
// Get the number of layers in the print.
unsigned layers() const;
/* Switch to a particular layer. If there where less layers then the
* specified layer number than an appropriate number of layers will be
* allocated in the printer.
*/
void begin_layer(unsigned layer);
// Allocate a new layer on top of the last and switch to it.
void begin_layer();
/*
* Finish the selected layer. It means that no drawing is allowed on that
* layer anymore. This fact can be used to prepare the file system output
* data like png comprimation and so on.
*/
void finish_layer(unsigned layer);
// Finish the top layer.
void finish_layer();
// Save all the layers into the file (or dir) specified in the path argument
// An optional project name can be added to be used for the layer file names
void save(const std::string& path, const std::string& projectname = "");
// Save only the selected layer to the file specified in path argument.
void save_layer(unsigned lyr, const std::string& path);
};
// Provokes static_assert in the right way.
template<class T = void> struct VeryFalse { static const bool value = false; };
// This can be explicitly implemented in the gui layer or the default Zipper
// API in libslic3r with minz.
template<class Fmt> class LayerWriter {
public:
LayerWriter(const std::string& /*zipfile_path*/)
{
static_assert(VeryFalse<Fmt>::value,
"No layer writer implementation provided!");
}
// Should create a new file within the zip with the given filename. It
// should also finish any previous entry.
void next_entry(const std::string& /*fname*/) {}
// Should create a new file within the archive and write the provided data.
void binary_entry(const std::string& /*fname*/,
const std::uint8_t* buf, size_t len);
// Test whether the object can still be used for writing.
bool is_ok() { return false; }
// Write some data (text) into the current file (entry) within the archive.
template<class T> LayerWriter& operator<<(T&& /*arg*/) {
return *this;
}
// Flush the current entry into the archive.
void finalize() {}
};
// Implementation for PNG raster output
// Be aware that if a large number of layers are allocated, it can very well
// exhaust the available memory especially on 32 bit platform.
template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
{
struct Layer {
Raster raster;
RawBytes rawbytes;
Layer() {}
Layer(const Layer&) = delete;
Layer(Layer&& m):
raster(std::move(m.raster)) {}
};
// We will save the compressed PNG data into stringstreams which can be done
// in parallel. Later we can write every layer to the disk sequentially.
std::vector<Layer> m_layers_rst;
Raster::Resolution m_res;
Raster::PixelDim m_pxdim;
double m_exp_time_s = .0, m_exp_time_first_s = .0;
double m_layer_height = .0;
Raster::Origin m_o = Raster::Origin::TOP_LEFT;
double m_gamma;
double m_used_material = 0.0;
int m_cnt_fade_layers = 0;
int m_cnt_slow_layers = 0;
int m_cnt_fast_layers = 0;
std::string createIniContent(const std::string& projectname) {
using std::string;
using std::to_string;
auto expt_str = to_string(m_exp_time_s);
auto expt_first_str = to_string(m_exp_time_first_s);
auto layerh_str = to_string(m_layer_height);
const std::string cnt_fade_layers = to_string(m_cnt_fade_layers);
const std::string cnt_slow_layers = to_string(m_cnt_slow_layers);
const std::string cnt_fast_layers = to_string(m_cnt_fast_layers);
const std::string used_material = to_string(m_used_material);
return string(
"action = print\n"
"jobDir = ") + projectname + "\n" +
"expTime = " + expt_str + "\n"
"expTimeFirst = " + expt_first_str + "\n"
"numFade = " + cnt_fade_layers + "\n"
"layerHeight = " + layerh_str + "\n"
"usedMaterial = " + used_material + "\n"
"numSlow = " + cnt_slow_layers + "\n"
"numFast = " + cnt_fast_layers + "\n";
}
public:
enum RasterOrientation {
RO_LANDSCAPE,
RO_PORTRAIT
};
// We will play with the raster's coordinate origin parameter. When the
// printer should print in landscape mode it should have the Y axis flipped
// because the layers should be displayed upside down. PNG has its
// coordinate origin in the top-left corner so normally the Raster objects
// should be instantiated with the TOP_LEFT flag. However, in landscape mode
// we do want the pictures to be upside down so we will make BOTTOM_LEFT
// type rasters and the PNG format will do the flipping automatically.
// In case of portrait images, we have to rotate the image by a 90 degrees
// and flip the y axis. To get the correct upside-down orientation of the
// slice images, we can flip the x and y coordinates of the input polygons
// and do the Y flipping of the image. This will generate the correct
// orientation in portrait mode.
inline FilePrinter(double width_mm, double height_mm,
unsigned width_px, unsigned height_px,
double layer_height,
double exp_time, double exp_time_first,
RasterOrientation ro = RO_PORTRAIT,
double gamma = 1.0):
m_res(width_px, height_px),
m_pxdim(width_mm/width_px, height_mm/height_px),
m_exp_time_s(exp_time),
m_exp_time_first_s(exp_time_first),
m_layer_height(layer_height),
// Here is the trick with the orientation.
m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT :
Raster::Origin::TOP_LEFT ),
m_gamma(gamma)
{
}
inline FilePrinter(const SLAPrinterConfig& cfg, const SLAMaterialConfig& mcfg, double layer_height)
{
double w = cfg.display_width.getFloat();
double h = cfg.display_height.getFloat();
auto pw = unsigned(cfg.display_pixels_x.getInt());
auto ph = unsigned(cfg.display_pixels_y.getInt());
m_res = Raster::Resolution(pw, ph);
m_pxdim = Raster::PixelDim(w/pw, h/ph);
m_exp_time_s = mcfg.exposure_time.getFloat();
m_exp_time_first_s = mcfg.initial_exposure_time.getFloat();
m_layer_height = layer_height;
auto ro = cfg.display_orientation.getInt();
// Here is the trick with the orientation.
m_o = ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT :
Raster::Origin::TOP_LEFT;
m_gamma = cfg.gamma_correction.getFloat();
}
FilePrinter(const FilePrinter& ) = delete;
FilePrinter(FilePrinter&& m):
m_layers_rst(std::move(m.m_layers_rst)),
m_res(m.m_res),
m_pxdim(m.m_pxdim) {}
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
inline void draw_polygon(const ExPolygon& p, unsigned lyr) {
assert(lyr < m_layers_rst.size());
m_layers_rst[lyr].raster.draw(p);
}
inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) {
assert(lyr < m_layers_rst.size());
m_layers_rst[lyr].raster.draw(p);
}
inline void begin_layer(unsigned lyr) {
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma);
}
inline void begin_layer() {
m_layers_rst.emplace_back();
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma);
}
inline void finish_layer(unsigned lyr_id) {
assert(lyr_id < m_layers_rst.size());
m_layers_rst[lyr_id].rawbytes =
m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG);
m_layers_rst[lyr_id].raster.reset();
}
inline void finish_layer() {
if(!m_layers_rst.empty()) {
m_layers_rst.back().rawbytes =
m_layers_rst.back().raster.save(Raster::Compression::PNG);
m_layers_rst.back().raster.reset();
}
}
template<class LyrFmt>
inline void save(const std::string& fpath, const std::string& prjname = "")
{
try {
LayerWriter<LyrFmt> writer(fpath);
if(!writer.is_ok()) return;
std::string project = prjname.empty()?
boost::filesystem::path(fpath).stem().string() : prjname;
writer.next_entry("config.ini");
if(!writer.is_ok()) return;
writer << createIniContent(project);
for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++)
{
if(m_layers_rst[i].rawbytes.size() > 0) {
char lyrnum[6];
std::sprintf(lyrnum, "%.5d", i);
auto zfilename = project + lyrnum + ".png";
if(!writer.is_ok()) break;
writer.binary_entry(zfilename,
m_layers_rst[i].rawbytes.data(),
m_layers_rst[i].rawbytes.size());
}
}
writer.finalize();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception
throw;
}
}
void save_layer(unsigned lyr, const std::string& path) {
unsigned i = lyr;
assert(i < m_layers_rst.size());
char lyrnum[6];
std::sprintf(lyrnum, "%.5d", lyr);
std::string loc = path + "layer" + lyrnum + ".png";
std::fstream out(loc, std::fstream::out | std::fstream::binary);
if(out.good()) {
m_layers_rst[i].raster.save(out, Raster::Compression::PNG);
} else {
BOOST_LOG_TRIVIAL(error) << "Can't create file for layer";
}
out.close();
m_layers_rst[i].raster.reset();
}
void set_statistics(const std::vector<double> statistics)
{
if (statistics.size() != psCnt)
return;
m_used_material = statistics[psUsedMaterial];
m_cnt_fade_layers = int(statistics[psNumFade]);
m_cnt_slow_layers = int(statistics[psNumSlow]);
m_cnt_fast_layers = int(statistics[psNumFast]);
}
};
}
#endif // PRINTEXPORT_HPP

View file

@ -1,186 +0,0 @@
#ifndef BICUBIC_HPP
#define BICUBIC_HPP
#include <algorithm>
#include <vector>
#include <cmath>
#include <Eigen/Dense>
namespace Slic3r {
namespace BicubicInternal {
// Linear kernel, to be able to test cubic methods with hat kernels.
template<typename T>
struct LinearKernel
{
typedef T FloatType;
static T a00() { return T(0.); }
static T a01() { return T(0.); }
static T a02() { return T(0.); }
static T a03() { return T(0.); }
static T a10() { return T(1.); }
static T a11() { return T(-1.); }
static T a12() { return T(0.); }
static T a13() { return T(0.); }
static T a20() { return T(0.); }
static T a21() { return T(1.); }
static T a22() { return T(0.); }
static T a23() { return T(0.); }
static T a30() { return T(0.); }
static T a31() { return T(0.); }
static T a32() { return T(0.); }
static T a33() { return T(0.); }
};
// Interpolation kernel aka Catmul-Rom aka Keyes kernel.
template<typename T>
struct CubicCatmulRomKernel
{
typedef T FloatType;
static T a00() { return 0; }
static T a01() { return (T)-0.5; }
static T a02() { return (T) 1.; }
static T a03() { return (T)-0.5; }
static T a10() { return (T) 1.; }
static T a11() { return 0; }
static T a12() { return (T)-5./2.; }
static T a13() { return (T) 3./2.; }
static T a20() { return 0; }
static T a21() { return (T) 0.5; }
static T a22() { return (T) 2.; }
static T a23() { return (T)-3./2.; }
static T a30() { return 0; }
static T a31() { return 0; }
static T a32() { return (T)-0.5; }
static T a33() { return (T) 0.5; }
};
// B-spline kernel
template<typename T>
struct CubicBSplineKernel
{
typedef T FloatType;
static T a00() { return (T) 1./6.; }
static T a01() { return (T) -3./6.; }
static T a02() { return (T) 3./6.; }
static T a03() { return (T) -1./6.; }
static T a10() { return (T) 4./6.; }
static T a11() { return 0; }
static T a12() { return (T) -6./6.; }
static T a13() { return (T) 3./6.; }
static T a20() { return (T) 1./6.; }
static T a21() { return (T) 3./6.; }
static T a22() { return (T) 3./6.; }
static T a23() { return (T)- 3./6.; }
static T a30() { return 0; }
static T a31() { return 0; }
static T a32() { return 0; }
static T a33() { return (T) 1./6.; }
};
template<class T>
inline T clamp(T a, T lower, T upper)
{
return (a < lower) ? lower :
(a > upper) ? upper : a;
}
}
template<typename KERNEL>
struct CubicKernel
{
typedef typename KERNEL KernelInternal;
typedef typename KERNEL::FloatType FloatType;
static FloatType kernel(FloatType x)
{
x = fabs(x);
if (x >= (FloatType)2.)
return 0.0f;
if (x <= (FloatType)1.) {
FloatType x2 = x * x;
FloatType x3 = x2 * x;
return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3;
}
assert(x > (FloatType)1. && x < (FloatType)2.);
x -= (FloatType)1.;
FloatType x2 = x * x;
FloatType x3 = x2 * x;
return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3;
}
static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x)
{
const FloatType x2 = x*x;
const FloatType x3 = x*x*x;
return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) +
f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) +
f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) +
f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3);
}
};
// Linear splines
typedef CubicKernel<BicubicInternal::LinearKernel<float>> LinearKernelf;
typedef CubicKernel<BicubicInternal::LinearKernel<double>> LinearKerneld;
// Catmul-Rom splines
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<float>> CubicCatmulRomKernelf;
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<double>> CubicCatmulRomKerneld;
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<float>> CubicInterpolationKernelf;
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<double>> CubicInterpolationKerneld;
// Cubic B-splines
typedef CubicKernel<BicubicInternal::CubicBSplineKernel<float>> CubicBSplineKernelf;
typedef CubicKernel<BicubicInternal::CubicBSplineKernel<double>> CubicBSplineKerneld;
template<typename KERNEL, typename Derived>
static float cubic_interpolate(const Eigen::ArrayBase<Derived> &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx)
{
typedef typename KERNEL::FloatType T;
const int w = int(F.size());
const int ix = (int)floor(pt);
const T s = pt - (T)ix;
if (ix > 1 && ix + 2 < w) {
// Inside the fully interpolated region.
return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s);
}
// Transition region. Extend with a constant function.
auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; }
return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s);
}
template<typename KERNEL, typename Derived>
static float bicubic_interpolate(const Eigen::MatrixBase<Derived> &F, const Eigen::Matrix<typename KERNEL::FloatType, 2, 1, Eigen::DontAlign> &pt, const typename KERNEL::FloatType dx)
{
typedef typename KERNEL::FloatType T;
const int w = F.cols();
const int h = F.rows();
const int ix = (int)floor(pt[0]);
const int iy = (int)floor(pt[1]);
const T s = pt[0] - (T)ix;
const T t = pt[1] - (T)iy;
if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) {
// Inside the fully interpolated region.
return KERNEL::interpolate(
KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s),
KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s),
KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s),
KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t);
}
// Transition region. Extend with a constant function.
auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); }
return KERNEL::interpolate(
KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s),
KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s),
KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s),
KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t);
}
} // namespace Slic3r
#endif /* BICUBIC_HPP */

View file

@ -1,5 +1,8 @@
#include "Rasterizer.hpp"
#include <ExPolygon.hpp>
#ifndef SLARASTER_CPP
#define SLARASTER_CPP
#include "SLARaster.hpp"
#include "libslic3r/ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing
@ -19,11 +22,13 @@
namespace Slic3r {
const Polygon& contour(const ExPolygon& p) { return p.contour; }
const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; }
inline const Polygon& contour(const ExPolygon& p) { return p.contour; }
inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; }
const Polygons& holes(const ExPolygon& p) { return p.holes; }
const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
inline const Polygons& holes(const ExPolygon& p) { return p.holes; }
inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; }
namespace sla {
class Raster::Impl {
public:
@ -39,7 +44,7 @@ public:
static const TPixel ColorWhite;
static const TPixel ColorBlack;
using Origin = Raster::Origin;
using Format = Raster::Format;
private:
Raster::Resolution m_resolution;
@ -52,16 +57,20 @@ private:
TRendererAA m_renderer;
std::function<double(double)> m_gammafn;
Origin m_o;
std::array<bool, 2> m_mirror;
inline void flipy(agg::path_storage& path) const {
path.flip_y(0, m_resolution.height_px);
}
inline void flipx(agg::path_storage& path) const {
path.flip_x(0, m_resolution.width_px);
}
public:
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
Origin o, double gamma = 1.0):
const std::array<bool, 2>& mirror, double gamma = 1.0):
m_resolution(res),
// m_pxdim(pd),
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
@ -72,7 +81,7 @@ public:
m_pixfmt(m_rbuf),
m_raw_renderer(m_pixfmt),
m_renderer(m_raw_renderer),
m_o(o)
m_mirror(mirror)
{
m_renderer.color(ColorWhite);
@ -81,6 +90,18 @@ public:
clear();
}
inline Impl(const Raster::Resolution& res,
const Raster::PixelDim &pd,
Format fmt,
double gamma = 1.0):
Impl(res, pd, {false, false}, gamma)
{
switch (fmt) {
case Format::PNG: m_mirror = {false, true}; break;
case Format::RAW: m_mirror = {false, false}; break;
}
}
template<class P> void draw(const P &poly) {
agg::rasterizer_scanline_aa<> ras;
@ -89,14 +110,16 @@ public:
ras.gamma(m_gammafn);
auto&& path = to_path(contour(poly));
if(m_o == Origin::TOP_LEFT) flipy(path);
if(m_mirror[X]) flipx(path);
if(m_mirror[Y]) flipy(path);
ras.add_path(path);
for(auto& h : holes(poly)) {
auto&& holepath = to_path(h);
if(m_o == Origin::TOP_LEFT) flipy(holepath);
if(m_mirror[X]) flipx(holepath);
if(m_mirror[Y]) flipy(holepath);
ras.add_path(holepath);
}
@ -110,9 +133,7 @@ public:
inline TBuffer& buffer() { return m_buf; }
inline const Raster::Resolution resolution() { return m_resolution; }
inline Origin origin() const /*noexcept*/ { return m_o; }
private:
inline double getPx(const Point& p) {
return p(0) * m_pxdim_scaled.w_mm;
@ -154,30 +175,23 @@ private:
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g):
m_impl(new Impl(r, pd, o, g)) {}
Raster::Raster() = default;
Raster::~Raster() = default;
Raster::Raster(Raster &&m) = default;
Raster& Raster::operator=(Raster&&) = default;
Raster::Raster() {}
Raster::~Raster() {}
Raster::Raster(Raster &&m):
m_impl(std::move(m.m_impl)) {}
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
double g)
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
Format fmt, double gamma)
{
// Free up the unnecessary memory and make sure it stays clear after
// an exception
auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT;
reset(r, pd, o, g);
m_impl.reset();
m_impl.reset(new Impl(r, pd, fmt, gamma));
}
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
Raster::Origin o, double gamma)
const std::array<bool, 2>& mirror, double gamma)
{
m_impl.reset();
m_impl.reset(new Impl(r, pd, o, gamma));
m_impl.reset(new Impl(r, pd, mirror, gamma));
}
void Raster::reset()
@ -208,13 +222,13 @@ void Raster::draw(const ClipperLib::Polygon &poly)
m_impl->draw(poly);
}
void Raster::save(std::ostream& stream, Compression comp)
void Raster::save(std::ostream& stream, Format fmt)
{
assert(m_impl);
if(!stream.good()) return;
switch(comp) {
case Compression::PNG: {
switch(fmt) {
case Format::PNG: {
auto& b = m_impl->buffer();
size_t out_len = 0;
void * rawdata = tdefl_write_image_to_png_file_in_memory(
@ -231,7 +245,7 @@ void Raster::save(std::ostream& stream, Compression comp)
break;
}
case Compression::RAW: {
case Format::RAW: {
stream << "P5 "
<< m_impl->resolution().width_px << " "
<< m_impl->resolution().height_px << " "
@ -244,14 +258,14 @@ void Raster::save(std::ostream& stream, Compression comp)
}
}
RawBytes Raster::save(Raster::Compression comp)
RawBytes Raster::save(Format fmt)
{
assert(m_impl);
std::vector<std::uint8_t> data; size_t s = 0;
switch(comp) {
case Compression::PNG: {
switch(fmt) {
case Format::PNG: {
void *rawdata = tdefl_write_image_to_png_file_in_memory(
m_impl->buffer().data(),
int(resolution().width_px),
@ -265,7 +279,7 @@ RawBytes Raster::save(Raster::Compression comp)
MZ_FREE(rawdata);
break;
}
case Compression::RAW: {
case Format::RAW: {
auto header = std::string("P5 ") +
std::to_string(m_impl->resolution().width_px) + " " +
std::to_string(m_impl->resolution().height_px) + " " + "255 ";
@ -287,3 +301,6 @@ RawBytes Raster::save(Raster::Compression comp)
}
}
}
#endif // SLARASTER_CPP

View file

@ -1,5 +1,5 @@
#ifndef RASTERIZER_HPP
#define RASTERIZER_HPP
#ifndef SLARASTER_HPP
#define SLARASTER_HPP
#include <ostream>
#include <memory>
@ -8,10 +8,12 @@
namespace ClipperLib { struct Polygon; }
namespace Slic3r {
namespace Slic3r {
class ExPolygon;
namespace sla {
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
class RawBytes {
@ -23,19 +25,24 @@ public:
size_t size() const { return m_buffer.size(); }
const uint8_t * data() { return m_buffer.data(); }
RawBytes(const RawBytes&) = delete;
RawBytes(RawBytes&&) = default;
RawBytes& operator=(const RawBytes&) = delete;
RawBytes& operator=(RawBytes&&) = default;
// /////////////////////////////////////////////////////////////////////////
// FIXME: the following is needed for MSVC2013 compatibility
// /////////////////////////////////////////////////////////////////////////
RawBytes(const RawBytes&) = delete;
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
// RawBytes(const RawBytes&) = delete;
// RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
RawBytes& operator=(const RawBytes&) = delete;
RawBytes& operator=(RawBytes&& mv) {
m_buffer = std::move(mv.m_buffer);
return *this;
}
// RawBytes& operator=(const RawBytes&) = delete;
// RawBytes& operator=(RawBytes&& mv) {
// m_buffer = std::move(mv.m_buffer);
// return *this;
// }
// /////////////////////////////////////////////////////////////////////////
};
@ -54,23 +61,11 @@ class Raster {
public:
/// Supported compression types
enum class Compression {
enum class Format {
RAW, //!> Uncompressed pixel data
PNG //!> PNG compression
};
/// The Rasterizer expects the input polygons to have their coordinate
/// system origin in the bottom left corner. If the raster is then
/// configured with the TOP_LEFT origin parameter (in the constructor) than
/// it will flip the Y axis in output to maintain the correct orientation.
/// This is the default case with PNG images. They have the origin in the
/// top left corner. Without the flipping, the image would be upside down
/// with the scaled (clipper) coordinate system of the input polygons.
enum class Origin {
TOP_LEFT,
BOTTOM_LEFT
};
/// Type that represents a resolution in pixels.
struct Resolution {
unsigned width_px;
@ -93,19 +88,21 @@ public:
};
/// Constructor taking the resolution and the pixel dimension.
Raster(const Resolution& r, const PixelDim& pd,
Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0);
template <class...Args> Raster(Args...args) {
reset(std::forward<Args>(args)...);
}
Raster();
Raster(const Raster& cpy) = delete;
Raster& operator=(const Raster& cpy) = delete;
Raster(Raster&& m);
Raster& operator=(Raster&&);
~Raster();
/// Reallocated everything for the given resolution and pixel dimension.
void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0);
void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma);
void reset(const Resolution&, const PixelDim&, const std::array<bool, 2>& mirror, double gamma = 1.0);
void reset(const Resolution& r, const PixelDim& pd, Format o, double gamma = 1.0);
/**
* Release the allocated resources. Drawing in this state ends in
* unspecified behavior.
@ -123,10 +120,13 @@ public:
void draw(const ClipperLib::Polygon& poly);
/// Save the raster on the specified stream.
void save(std::ostream& stream, Compression comp = Compression::RAW);
void save(std::ostream& stream, Format = Format::PNG);
RawBytes save(Compression comp = Compression::RAW);
/// Save into a continuous byte stream which is returned.
RawBytes save(Format fmt = Format::PNG);
};
}
#endif // RASTERIZER_HPP
} // sla
} // Slic3r
#endif // SLARASTER_HPP

View file

@ -0,0 +1,136 @@
#include "SLARasterWriter.hpp"
#include "libslic3r/Zipper.hpp"
#include "ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem/path.hpp>
namespace Slic3r { namespace sla {
std::string SLARasterWriter::createIniContent(const std::string& projectname) const
{
auto expt_str = std::to_string(m_exp_time_s);
auto expt_first_str = std::to_string(m_exp_time_first_s);
auto layerh_str = std::to_string(m_layer_height);
const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers);
const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers);
const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers);
const std::string used_material = std::to_string(m_used_material);
return std::string(
"action = print\n"
"jobDir = ") + projectname + "\n" +
"expTime = " + expt_str + "\n"
"expTimeFirst = " + expt_first_str + "\n"
"numFade = " + cnt_fade_layers + "\n"
"layerHeight = " + layerh_str + "\n"
"usedMaterial = " + used_material + "\n"
"numSlow = " + cnt_slow_layers + "\n"
"numFast = " + cnt_fast_layers + "\n";
}
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
{
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
std::reverse(poly.Contour.begin(), poly.Contour.end());
for(auto& h : poly.Holes) {
for(auto& p : h) std::swap(p.X, p.Y);
std::reverse(h.begin(), h.end());
}
}
void SLARasterWriter::flpXY(ExPolygon &poly)
{
for(auto& p : poly.contour.points) p = {p.y(), p.x()};
std::reverse(poly.contour.points.begin(), poly.contour.points.end());
for(auto& h : poly.holes) {
for(auto& p : h.points) p = {p.y(), p.x()};
std::reverse(h.points.begin(), h.points.end());
}
}
SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg,
const SLAMaterialConfig &mcfg,
double layer_height)
{
double w = cfg.display_width.getFloat();
double h = cfg.display_height.getFloat();
auto pw = unsigned(cfg.display_pixels_x.getInt());
auto ph = unsigned(cfg.display_pixels_y.getInt());
m_mirror[X] = cfg.display_mirror_x.getBool();
// PNG raster will implicitly do an Y mirror
m_mirror[Y] = ! cfg.display_mirror_y.getBool();
auto ro = cfg.display_orientation.getInt();
if(ro == roPortrait) {
std::swap(w, h);
std::swap(pw, ph);
m_o = roPortrait;
// XY flipping implicitly does an X mirror
m_mirror[X] = ! m_mirror[X];
} else m_o = roLandscape;
m_res = Raster::Resolution(pw, ph);
m_pxdim = Raster::PixelDim(w/pw, h/ph);
m_exp_time_s = mcfg.exposure_time.getFloat();
m_exp_time_first_s = mcfg.initial_exposure_time.getFloat();
m_layer_height = layer_height;
m_gamma = cfg.gamma_correction.getFloat();
}
void SLARasterWriter::save(const std::string &fpath, const std::string &prjname)
{
try {
Zipper zipper(fpath); // zipper with no compression
std::string project = prjname.empty()?
boost::filesystem::path(fpath).stem().string() : prjname;
zipper.add_entry("config.ini");
zipper << createIniContent(project);
for(unsigned i = 0; i < m_layers_rst.size(); i++)
{
if(m_layers_rst[i].rawbytes.size() > 0) {
char lyrnum[6];
std::sprintf(lyrnum, "%.5d", i);
auto zfilename = project + lyrnum + ".png";
// Add binary entry to the zipper
zipper.add_entry(zfilename,
m_layers_rst[i].rawbytes.data(),
m_layers_rst[i].rawbytes.size());
}
}
zipper.finalize();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception
throw;
}
}
void SLARasterWriter::set_statistics(const std::vector<double> statistics)
{
if (statistics.size() != psCnt)
return;
m_used_material = statistics[psUsedMaterial];
m_cnt_fade_layers = int(statistics[psNumFade]);
m_cnt_slow_layers = int(statistics[psNumSlow]);
m_cnt_fast_layers = int(statistics[psNumFast]);
}
} // namespace sla
} // namespace Slic3r

View file

@ -0,0 +1,139 @@
#ifndef SLARASTERWRITER_HPP
#define SLARASTERWRITER_HPP
// For png export of the sliced model
#include <fstream>
#include <sstream>
#include <vector>
#include "libslic3r/PrintConfig.hpp"
#include "SLARaster.hpp"
namespace Slic3r { namespace sla {
// Implementation for PNG raster output
// Be aware that if a large number of layers are allocated, it can very well
// exhaust the available memory especially on 32 bit platform.
// This class is designed to be used in parallel mode. Layers have an ID and
// each layer can be written and compressed independently (in parallel).
// At the end when all layers where written, the save method can be used to
// write out the result into a zipped archive.
class SLARasterWriter
{
public:
enum RasterOrientation {
roLandscape,
roPortrait
};
// Used for addressing parameters of set_statistics()
enum ePrintStatistics
{
psUsedMaterial = 0,
psNumFade,
psNumSlow,
psNumFast,
psCnt
};
private:
// A struct to bind the raster image data and its compressed bytes together.
struct Layer {
Raster raster;
RawBytes rawbytes;
Layer() = default;
Layer(const Layer&) = delete; // The image is big, do not copy by accident
Layer& operator=(const Layer&) = delete;
Layer(Layer&& m) = default;
Layer& operator=(Layer&&) = default;
};
// We will save the compressed PNG data into RawBytes type buffers in
// parallel. Later we can write every layer to the disk sequentially.
std::vector<Layer> m_layers_rst;
Raster::Resolution m_res;
Raster::PixelDim m_pxdim;
double m_exp_time_s = .0, m_exp_time_first_s = .0;
double m_layer_height = .0;
RasterOrientation m_o = roPortrait;
std::array<bool, 2> m_mirror;
double m_gamma;
double m_used_material = 0.0;
int m_cnt_fade_layers = 0;
int m_cnt_slow_layers = 0;
int m_cnt_fast_layers = 0;
std::string createIniContent(const std::string& projectname) const;
static void flpXY(ClipperLib::Polygon& poly);
static void flpXY(ExPolygon& poly);
public:
SLARasterWriter(const SLAPrinterConfig& cfg,
const SLAMaterialConfig& mcfg,
double layer_height);
SLARasterWriter(const SLARasterWriter& ) = delete;
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
SLARasterWriter(SLARasterWriter&& m) = default;
SLARasterWriter& operator=(SLARasterWriter&&) = default;
// SLARasterWriter(SLARasterWriter&& m) = default;
// SLARasterWriter(SLARasterWriter&& m):
// m_layers_rst(std::move(m.m_layers_rst)),
// m_res(m.m_res),
// m_pxdim(m.m_pxdim) {}
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr) {
assert(lyr < m_layers_rst.size());
if(m_o == roPortrait) {
Poly poly(p); flpXY(poly);
m_layers_rst[lyr].raster.draw(poly);
}
else m_layers_rst[lyr].raster.draw(p);
}
inline void begin_layer(unsigned lyr) {
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
}
inline void begin_layer() {
m_layers_rst.emplace_back();
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
}
inline void finish_layer(unsigned lyr_id) {
assert(lyr_id < m_layers_rst.size());
m_layers_rst[lyr_id].rawbytes =
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
m_layers_rst[lyr_id].raster.reset();
}
inline void finish_layer() {
if(!m_layers_rst.empty()) {
m_layers_rst.back().rawbytes =
m_layers_rst.back().raster.save(Raster::Format::PNG);
m_layers_rst.back().raster.reset();
}
}
void save(const std::string& fpath, const std::string& prjname = "");
void set_statistics(const std::vector<double> statistics);
};
} // namespace sla
} // namespace Slic3r
#endif // SLARASTERWRITER_HPP

View file

@ -1281,37 +1281,11 @@ void SLAPrint::process()
auto rasterize = [this, max_objstatus]() {
if(canceled()) return;
// collect all the keys
// If the raster has vertical orientation, we will flip the coordinates
bool flpXY = m_printer_config.display_orientation.getInt() ==
SLADisplayOrientation::sladoPortrait;
{ // create a raster printer for the current print parameters
// I don't know any better
// auto& ocfg = m_objects.front()->m_config;
// auto& matcfg = m_material_config;
// auto& printcfg = m_printer_config;
// double w = printcfg.display_width.getFloat();
// double h = printcfg.display_height.getFloat();
// auto pw = unsigned(printcfg.display_pixels_x.getInt());
// auto ph = unsigned(printcfg.display_pixels_y.getInt());
// double lh = ocfg.layer_height.getFloat();
// double exp_t = matcfg.exposure_time.getFloat();
// double iexp_t = matcfg.initial_exposure_time.getFloat();
// double gamma = m_printer_config.gamma_correction.getFloat();
// if(flpXY) { std::swap(w, h); std::swap(pw, ph); }
// m_printer.reset(
// new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t,
// flpXY? SLAPrinter::RO_PORTRAIT :
// SLAPrinter::RO_LANDSCAPE,
// gamma));
m_printer.reset(new SLAPrinter(m_printer_config, m_material_config, m_default_object_config.layer_height.getFloat()));
double layerh = m_default_object_config.layer_height.getFloat();
m_printer.reset(new SLAPrinter(m_printer_config,
m_material_config,
layerh));
}
// Allocate space for all the layers

View file

@ -3,11 +3,11 @@
#include <mutex>
#include "PrintBase.hpp"
#include "PrintExport.hpp"
//#include "PrintExport.hpp"
#include "SLA/SLARasterWriter.hpp"
#include "Point.hpp"
#include "MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
#include "Zipper.hpp"
namespace Slic3r {
@ -322,37 +322,6 @@ struct SLAPrintStatistics
}
};
// The implementation of creating zipped archives with wxWidgets
template<> class LayerWriter<Zipper> {
Zipper m_zip;
public:
LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {}
void next_entry(const std::string& fname) { m_zip.add_entry(fname); }
void binary_entry(const std::string& fname,
const std::uint8_t* buf,
size_t l)
{
m_zip.add_entry(fname, buf, l);
}
template<class T> inline LayerWriter& operator<<(T&& arg) {
m_zip << std::forward<T>(arg); return *this;
}
bool is_ok() const {
return true; // m_zip blows up if something goes wrong...
}
// After finalize, no writing to the archive will have an effect. The only
// valid operation is to dispose the object calling the destructor which
// should close the file. This method can throw and signal potential errors
// when flushing the archive. This is why its present.
void finalize() { m_zip.finalize(); }
};
/**
* @brief This class is the high level FSM for the SLA printing process.
*
@ -385,11 +354,10 @@ public:
// Returns true if the last step was finished with success.
bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); }
template<class Fmt = Zipper>
inline void export_raster(const std::string& fpath,
const std::string& projectname = "")
const std::string& projectname = "")
{
if(m_printer) m_printer->save<Fmt>(fpath, projectname);
if(m_printer) m_printer->save(fpath, projectname);
}
const PrintObjects& objects() const { return m_objects; }
@ -450,7 +418,7 @@ public:
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
private:
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
using SLAPrinter = sla::SLARasterWriter;
using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
// Implement same logic as in SLAPrintObject