Merge branch 'tm_sla_printer_mirror'

This commit is contained in:
tamasmeszaros 2019-06-18 17:58:23 +02:00
commit 967dba2b90
14 changed files with 486 additions and 478 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
@ -175,6 +172,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

@ -2258,6 +2258,20 @@ void PrintConfigDef::init_sla_params()
def->min = 100;
def->set_default_value(new ConfigOptionInt(1440));
def = this->add("display_mirror_x", coBool);
def->full_label = L("Display horizontal mirroring");
def->label = L("Mirror horizontally");
def->tooltip = L("Enable horizontal mirroring of output images");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("display_mirror_y", coBool);
def->full_label = L("Display vertical mirroring");
def->label = L("Mirror vertically");
def->tooltip = L("Enable vertical mirroring of output images");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("display_orientation", coEnum);
def->label = L("Display orientation");
def->tooltip = L("Set the actual LCD display orientation inside the SLA printer."

View file

@ -1083,6 +1083,8 @@ public:
ConfigOptionInt display_pixels_x;
ConfigOptionInt display_pixels_y;
ConfigOptionEnum<SLADisplayOrientation> display_orientation;
ConfigOptionBool display_mirror_x;
ConfigOptionBool display_mirror_y;
ConfigOptionFloats relative_correction;
ConfigOptionFloat absolute_correction;
ConfigOptionFloat gamma_correction;
@ -1099,6 +1101,8 @@ protected:
OPT_PTR(display_height);
OPT_PTR(display_pixels_x);
OPT_PTR(display_pixels_y);
OPT_PTR(display_mirror_x);
OPT_PTR(display_mirror_y);
OPT_PTR(display_orientation);
OPT_PTR(relative_correction);
OPT_PTR(absolute_correction);

View file

@ -1,327 +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)
{
}
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,5 +1,10 @@
#include "Rasterizer.hpp"
#include <ExPolygon.hpp>
#ifndef SLARASTER_CPP
#define SLARASTER_CPP
#include <functional>
#include "SLARaster.hpp"
#include "libslic3r/ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing
@ -19,11 +24,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 +46,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 +59,21 @@ private:
TRendererAA m_renderer;
std::function<double(double)> m_gammafn;
Origin m_o;
std::array<bool, 2> m_mirror;
Format m_fmt = Format::PNG;
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 +84,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 +93,19 @@ 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;
}
m_fmt = fmt;
}
template<class P> void draw(const P &poly) {
agg::rasterizer_scanline_aa<> ras;
@ -89,14 +114,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);
}
@ -108,11 +135,11 @@ public:
}
inline TBuffer& buffer() { return m_buf; }
inline Format format() const { return m_fmt; }
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 +181,30 @@ 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)) {}
template<> Raster::Raster() { reset(); };
Raster::~Raster() = default;
Raster::Raster() {}
// Raster::Raster(Raster &&m) = default;
// Raster& Raster::operator=(Raster&&) = default;
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)
{
// 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);
// FIXME: remove after migrating to higher version of windows compiler
Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {}
Raster& Raster::operator=(Raster &&m) {
m_impl = std::move(m.m_impl); return *this;
}
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
Raster::Origin o, double gamma)
Format fmt, double gamma)
{
m_impl.reset();
m_impl.reset(new Impl(r, pd, o, gamma));
m_impl.reset(new Impl(r, pd, fmt, gamma));
}
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
const std::array<bool, 2>& mirror, double gamma)
{
m_impl.reset();
m_impl.reset(new Impl(r, pd, mirror, gamma));
}
void Raster::reset()
@ -208,13 +235,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 +258,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 +271,19 @@ void Raster::save(std::ostream& stream, Compression comp)
}
}
RawBytes Raster::save(Raster::Compression comp)
void Raster::save(std::ostream &stream)
{
save(stream, m_impl->format());
}
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 +297,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 ";
@ -286,4 +318,12 @@ RawBytes Raster::save(Raster::Compression comp)
return {std::move(data)};
}
RawBytes Raster::save()
{
return save(m_impl->format());
}
}
}
#endif // SLARASTER_CPP

View file

@ -1,17 +1,21 @@
#ifndef RASTERIZER_HPP
#define RASTERIZER_HPP
#ifndef SLARASTER_HPP
#define SLARASTER_HPP
#include <ostream>
#include <memory>
#include <vector>
#include <array>
#include <utility>
#include <cstdint>
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,15 +27,18 @@ public:
size_t size() const { return m_buffer.size(); }
const uint8_t * data() { return m_buffer.data(); }
RawBytes(const RawBytes&) = delete;
RawBytes& operator=(const RawBytes&) = delete;
// /////////////////////////////////////////////////////////////////////////
// FIXME: the following is needed for MSVC2013 compatibility
// /////////////////////////////////////////////////////////////////////////
RawBytes(const RawBytes&) = delete;
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
// RawBytes(RawBytes&&) = default;
// RawBytes& operator=(RawBytes&&) = default;
RawBytes& operator=(const RawBytes&) = delete;
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
RawBytes& operator=(RawBytes&& mv) {
m_buffer = std::move(mv.m_buffer);
return *this;
@ -54,28 +61,19 @@ 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;
unsigned height_px;
inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {}
inline Resolution(unsigned w = 0, unsigned h = 0):
width_px(w), height_px(h) {}
inline unsigned pixels() const /*noexcept*/ {
return width_px * height_px;
}
@ -85,24 +83,34 @@ public:
struct PixelDim {
double w_mm;
double h_mm;
inline PixelDim(double px_width_mm, double px_height_mm ):
inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0):
w_mm(px_width_mm), h_mm(px_height_mm) {}
};
/// 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);
/// The third parameter is either the X, Y mirroring or a supported format
/// for which the correct mirroring will be configured.
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.
@ -119,11 +127,24 @@ public:
void draw(const ExPolygon& poly);
void draw(const ClipperLib::Polygon& poly);
// Saving the raster:
// It is possible to override the format given in the constructor but
// be aware that the mirroring will not be modified.
/// Save the raster on the specified stream.
void save(std::ostream& stream, Compression comp = Compression::RAW);
void save(std::ostream& stream, Format);
void save(std::ostream& stream);
RawBytes save(Compression comp = Compression::RAW);
/// Save into a continuous byte stream which is returned.
RawBytes save(Format fmt);
RawBytes save();
};
}
#endif // RASTERIZER_HPP
// This prevents the duplicate default constructor warning on MSVC2013
template<> Raster::Raster();
} // 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 = Point(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 = Point(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,167 @@
#ifndef SLARASTERWRITER_HPP
#define SLARASTERWRITER_HPP
// For png export of the sliced model
#include <fstream>
#include <sstream>
#include <vector>
#include <array>
#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;
// /////////////////////////////////////////////////////////////////////
// FIXME: the following is needed for MSVC2013 compatibility
// /////////////////////////////////////////////////////////////////////
// Layer(Layer&& m) = default;
// Layer& operator=(Layer&&) = default;
Layer(Layer &&m):
raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {}
Layer& operator=(Layer &&m) {
raster = std::move(m.raster); rawbytes = std::move(m.rawbytes);
return *this;
}
};
// 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;
// /////////////////////////////////////////////////////////////////////////
// FIXME: the following is needed for MSVC2013 compatibility
// /////////////////////////////////////////////////////////////////////////
// SLARasterWriter(SLARasterWriter&& m) = default;
// SLARasterWriter& operator=(SLARasterWriter&&) = default;
SLARasterWriter(SLARasterWriter&& m):
m_layers_rst(std::move(m.m_layers_rst)),
m_res(m.m_res),
m_pxdim(m.m_pxdim),
m_exp_time_s(m.m_exp_time_s),
m_exp_time_first_s(m.m_exp_time_first_s),
m_layer_height(m.m_layer_height),
m_o(m.m_o),
m_mirror(std::move(m.m_mirror)),
m_gamma(m.m_gamma),
m_used_material(m.m_used_material),
m_cnt_fade_layers(m.m_cnt_fade_layers),
m_cnt_slow_layers(m.m_cnt_slow_layers),
m_cnt_fast_layers(m.m_cnt_fast_layers)
{}
// /////////////////////////////////////////////////////////////////////////
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

@ -747,8 +747,8 @@ void SLAPrint::process()
{
// We apply the printer correction offset here.
if(clpr_offs != 0)
po.m_model_slices[id] =
offset_ex(po.m_model_slices[id], float(clpr_offs));
po.m_model_slices[id] =
offset_ex(po.m_model_slices[id], float(clpr_offs));
mit->set_model_slice_idx(po, id); ++mit;
}
@ -1014,7 +1014,7 @@ void SLAPrint::process()
namespace sl = libnest2d::shapelike; // For algorithms
// If the raster has vertical orientation, we will flip the coordinates
bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
// Set up custom union and diff functions for clipper polygons
auto polyunion = [] (const ClipperPolygons& subjects)
@ -1072,9 +1072,9 @@ void SLAPrint::process()
// get polygons for all instances in the object
auto get_all_polygons =
[flpXY](const ExPolygons& input_polygons,
const std::vector<SLAPrintObject::Instance>& instances,
bool is_lefthanded)
[](const ExPolygons& input_polygons,
const std::vector<SLAPrintObject::Instance>& instances,
bool is_lefthanded)
{
ClipperPolygons polygons;
polygons.reserve(input_polygons.size() * instances.size());
@ -1088,7 +1088,7 @@ void SLAPrint::process()
// We need to reverse if flpXY OR is_lefthanded is true but
// not if both are true which is a logical inequality (XOR)
bool needreverse = flpXY != is_lefthanded;
bool needreverse = /*flpXY !=*/ is_lefthanded;
// should be a move
poly.Contour.reserve(polygon.contour.size() + 1);
@ -1123,10 +1123,10 @@ void SLAPrint::process()
sl::translate(poly, ClipperPoint{instances[i].shift(X),
instances[i].shift(Y)});
if (flpXY) {
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
}
// if (flpXY) {
// for(auto& p : poly.Contour) std::swap(p.X, p.Y);
// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y);
// }
polygons.emplace_back(std::move(poly));
}
@ -1295,35 +1295,11 @@ void SLAPrint::process()
auto rasterize = [this]() {
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));
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
@ -1511,6 +1487,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
"display_height",
"display_pixels_x",
"display_pixels_y",
"display_mirror_x",
"display_mirror_y",
"display_orientation"
};

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 {
@ -326,37 +326,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.
*
@ -389,11 +358,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; }
@ -454,7 +422,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

View file

@ -9,6 +9,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>

View file

@ -509,6 +509,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
"printer_technology",
"bed_shape", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y",
"display_orientation",
"fast_tilt_time", "slow_tilt_time", "area_fill",
"relative_correction",

View file

@ -2087,6 +2087,10 @@ void TabPrinter::build_sla()
line.append_option(optgroup->get_option("display_pixels_y"));
optgroup->append_line(line);
optgroup->append_single_option_line("display_orientation");
// FIXME: This should be on one line in the UI
optgroup->append_single_option_line("display_mirror_x");
optgroup->append_single_option_line("display_mirror_y");
optgroup = page->new_optgroup(_(L("Tilt")));
line = { _(L("Tilt time")), "" };