sla::Raster interface clarified and covered with tests.
Also renamed sla::SupportTreeAlgorithm to SupportTreeBuildsteps.
This commit is contained in:
parent
705e82ec8e
commit
be7428d66e
@ -37,7 +37,7 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
|
||||
|
||||
# Proposal for C++ unit tests and sandboxes
|
||||
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
||||
option(SLIC3R_BUILD_TESTS "Build unit tests" OFF)
|
||||
option(SLIC3R_BUILD_TESTS "Build unit tests" ON)
|
||||
|
||||
# Print out the SLIC3R_* cache options
|
||||
get_cmake_property(_cache_vars CACHE_VARIABLES)
|
||||
|
@ -156,7 +156,7 @@ namespace agg
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
template<class VertexSource>
|
||||
void add_path(VertexSource& vs, unsigned path_id=0)
|
||||
void add_path(VertexSource &&vs, unsigned path_id=0)
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
|
@ -179,8 +179,8 @@ add_library(libslic3r STATIC
|
||||
SLA/SLAPad.hpp
|
||||
SLA/SLAPad.cpp
|
||||
SLA/SLASupportTreeBuilder.hpp
|
||||
SLA/SLASupportTreeAlgorithm.hpp
|
||||
SLA/SLASupportTreeAlgorithm.cpp
|
||||
SLA/SLASupportTreeBuildsteps.hpp
|
||||
SLA/SLASupportTreeBuildsteps.cpp
|
||||
SLA/SLASupportTreeBuilder.cpp
|
||||
SLA/SLAConcurrency.hpp
|
||||
SLA/SLASupportTree.hpp
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
// For rasterizing
|
||||
@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H
|
||||
|
||||
namespace sla {
|
||||
|
||||
const Raster::TMirroring Raster::NoMirror = {false, false};
|
||||
const Raster::TMirroring Raster::MirrorX = {true, false};
|
||||
const Raster::TMirroring Raster::MirrorY = {false, true};
|
||||
const Raster::TMirroring Raster::MirrorXY = {true, true};
|
||||
|
||||
|
||||
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
|
||||
using TRawRenderer = agg::renderer_base<TPixelRenderer>;
|
||||
using TPixel = TPixelRenderer::color_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
||||
|
||||
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
||||
|
||||
class Raster::Impl {
|
||||
public:
|
||||
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
|
||||
using TRawRenderer = agg::renderer_base<TPixelRenderer>;
|
||||
using TPixel = TPixelRenderer::color_type;
|
||||
using TRawBuffer = agg::rendering_buffer;
|
||||
|
||||
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
||||
|
||||
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
||||
|
||||
static const TPixel ColorWhite;
|
||||
static const TPixel ColorBlack;
|
||||
|
||||
using Format = Raster::Format;
|
||||
using Format = Raster::RawData;
|
||||
|
||||
private:
|
||||
Raster::Resolution m_resolution;
|
||||
// Raster::PixelDim m_pxdim;
|
||||
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
||||
TBuffer m_buf;
|
||||
TRawBuffer m_rbuf;
|
||||
@ -59,74 +65,49 @@ private:
|
||||
TRendererAA m_renderer;
|
||||
|
||||
std::function<double(double)> m_gammafn;
|
||||
std::array<bool, 2> m_mirror;
|
||||
Format m_fmt = Format::PNG;
|
||||
Trafo m_trafo;
|
||||
|
||||
inline void flipy(agg::path_storage& path) const {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
path.flip_y(0, double(m_resolution.height_px));
|
||||
}
|
||||
|
||||
inline void flipx(agg::path_storage& path) const {
|
||||
path.flip_x(0, m_resolution.width_px);
|
||||
path.flip_x(0, double(m_resolution.width_px));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
||||
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),
|
||||
m_buf(res.pixels()),
|
||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
||||
res.width_px, res.height_px,
|
||||
int(res.width_px*TPixelRenderer::num_components)),
|
||||
m_pixfmt(m_rbuf),
|
||||
m_raw_renderer(m_pixfmt),
|
||||
m_renderer(m_raw_renderer),
|
||||
m_mirror(mirror)
|
||||
inline Impl(const Raster::Resolution & res,
|
||||
const Raster::PixelDim & pd,
|
||||
const Trafo &trafo)
|
||||
: m_resolution(res)
|
||||
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
|
||||
, m_buf(res.pixels())
|
||||
, m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()),
|
||||
unsigned(res.width_px),
|
||||
unsigned(res.height_px),
|
||||
int(res.width_px * TPixelRenderer::num_components))
|
||||
, m_pixfmt(m_rbuf)
|
||||
, m_raw_renderer(m_pixfmt)
|
||||
, m_renderer(m_raw_renderer)
|
||||
, m_trafo(trafo)
|
||||
{
|
||||
m_renderer.color(ColorWhite);
|
||||
|
||||
if(gamma > 0) m_gammafn = agg::gamma_power(gamma);
|
||||
if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma);
|
||||
else m_gammafn = agg::gamma_threshold(0.5);
|
||||
|
||||
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;
|
||||
agg::scanline_p8 scanlines;
|
||||
|
||||
ras.gamma(m_gammafn);
|
||||
|
||||
auto&& path = to_path(contour(poly));
|
||||
|
||||
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_mirror[X]) flipx(holepath);
|
||||
if(m_mirror[Y]) flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
ras.add_path(to_path(contour(poly)));
|
||||
for(auto& h : holes(poly)) ras.add_path(to_path(h));
|
||||
|
||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||
}
|
||||
|
||||
@ -135,11 +116,16 @@ public:
|
||||
}
|
||||
|
||||
inline TBuffer& buffer() { return m_buf; }
|
||||
inline const TBuffer& buffer() const { return m_buf; }
|
||||
|
||||
inline Format format() const { return m_fmt; }
|
||||
|
||||
inline const Raster::Resolution resolution() { return m_resolution; }
|
||||
|
||||
inline const Raster::PixelDim pixdim()
|
||||
{
|
||||
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
|
||||
SCALING_FACTOR / m_pxdim_scaled.h_mm};
|
||||
}
|
||||
|
||||
private:
|
||||
inline double getPx(const Point& p) {
|
||||
return p(0) * m_pxdim_scaled.w_mm;
|
||||
@ -162,49 +148,67 @@ private:
|
||||
return p.Y * m_pxdim_scaled.h_mm;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage to_path(const PointVec& poly)
|
||||
template<class PointVec> agg::path_storage _to_path(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = poly.begin();
|
||||
auto it = v.begin();
|
||||
path.move_to(getPx(*it), getPy(*it));
|
||||
while(++it != v.end()) path.line_to(getPx(*it), getPy(*it));
|
||||
path.line_to(getPx(v.front()), getPy(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage _to_path_flpxy(const PointVec& v)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = v.begin();
|
||||
path.move_to(getPy(*it), getPx(*it));
|
||||
while(++it != v.end()) path.line_to(getPy(*it), getPx(*it));
|
||||
path.line_to(getPy(v.front()), getPx(v.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template<class PointVec> agg::path_storage to_path(const PointVec &v)
|
||||
{
|
||||
auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v);
|
||||
|
||||
path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm,
|
||||
m_trafo.origin_y * m_pxdim_scaled.h_mm);
|
||||
|
||||
if(m_trafo.mirror_x) flipx(path);
|
||||
if(m_trafo.mirror_y) flipy(path);
|
||||
|
||||
while(++it != poly.end())
|
||||
path.line_to(getPx(*it), getPy(*it));
|
||||
|
||||
path.line_to(getPx(poly.front()), getPy(poly.front()));
|
||||
return path;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
||||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
||||
const TPixel Raster::Impl::ColorWhite = TPixel(255);
|
||||
const TPixel Raster::Impl::ColorBlack = TPixel(0);
|
||||
|
||||
Raster::Raster() { reset(); }
|
||||
|
||||
Raster::Raster(const Raster::Resolution &r,
|
||||
const Raster::PixelDim & pd,
|
||||
const Raster::Trafo & tr)
|
||||
{
|
||||
reset(r, pd, tr);
|
||||
}
|
||||
|
||||
template<> Raster::Raster() { reset(); };
|
||||
Raster::~Raster() = default;
|
||||
|
||||
// Raster::Raster(Raster &&m) = default;
|
||||
// Raster& Raster::operator=(Raster&&) = default;
|
||||
|
||||
// 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;
|
||||
}
|
||||
Raster::Raster(Raster &&m) = default;
|
||||
Raster &Raster::operator=(Raster &&) = default;
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
Format fmt, double gamma)
|
||||
const Trafo &trafo)
|
||||
{
|
||||
m_impl.reset();
|
||||
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));
|
||||
m_impl.reset(new Impl(r, pd, trafo));
|
||||
}
|
||||
|
||||
void Raster::reset()
|
||||
@ -214,9 +218,16 @@ void Raster::reset()
|
||||
|
||||
Raster::Resolution Raster::resolution() const
|
||||
{
|
||||
if(m_impl) return m_impl->resolution();
|
||||
if (m_impl) return m_impl->resolution();
|
||||
|
||||
return Resolution{0, 0};
|
||||
}
|
||||
|
||||
return Resolution(0, 0);
|
||||
Raster::PixelDim Raster::pixel_dimensions() const
|
||||
{
|
||||
if (m_impl) return m_impl->pixdim();
|
||||
|
||||
return PixelDim{0., 0.};
|
||||
}
|
||||
|
||||
void Raster::clear()
|
||||
@ -227,103 +238,83 @@ void Raster::clear()
|
||||
|
||||
void Raster::draw(const ExPolygon &expoly)
|
||||
{
|
||||
assert(m_impl);
|
||||
m_impl->draw(expoly);
|
||||
}
|
||||
|
||||
void Raster::draw(const ClipperLib::Polygon &poly)
|
||||
{
|
||||
assert(m_impl);
|
||||
m_impl->draw(poly);
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream& stream, Format fmt)
|
||||
uint8_t Raster::read_pixel(size_t x, size_t y) const
|
||||
{
|
||||
assert(m_impl);
|
||||
if(!stream.good()) return;
|
||||
|
||||
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(
|
||||
b.data(),
|
||||
int(resolution().width_px),
|
||||
int(resolution().height_px), 1, &out_len);
|
||||
|
||||
if(rawdata == nullptr) break;
|
||||
|
||||
stream.write(static_cast<const char*>(rawdata),
|
||||
std::streamsize(out_len));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
|
||||
break;
|
||||
}
|
||||
case Format::RAW: {
|
||||
stream << "P5 "
|
||||
<< m_impl->resolution().width_px << " "
|
||||
<< m_impl->resolution().height_px << " "
|
||||
<< "255 ";
|
||||
|
||||
auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type);
|
||||
stream.write(reinterpret_cast<const char*>(m_impl->buffer().data()),
|
||||
std::streamsize(sz));
|
||||
}
|
||||
}
|
||||
assert (m_impl);
|
||||
TPixel::value_type px;
|
||||
m_impl->buffer()[y * resolution().width_px + x].get(px);
|
||||
return px;
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream &stream)
|
||||
PNGImage & PNGImage::serialize(const Raster &raster)
|
||||
{
|
||||
save(stream, m_impl->format());
|
||||
size_t s = 0;
|
||||
m_buffer.clear();
|
||||
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
get_internals(raster).buffer().data(),
|
||||
int(raster.resolution().width_px),
|
||||
int(raster.resolution().height_px), 1, &s);
|
||||
|
||||
// On error, data() will return an empty vector. No other info can be
|
||||
// retrieved from miniz anyway...
|
||||
if (rawdata == nullptr) return *this;
|
||||
|
||||
auto ptr = static_cast<std::uint8_t*>(rawdata);
|
||||
|
||||
m_buffer.reserve(s);
|
||||
std::copy(ptr, ptr + s, std::back_inserter(m_buffer));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RawBytes Raster::save(Format fmt)
|
||||
std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes)
|
||||
{
|
||||
assert(m_impl);
|
||||
|
||||
std::vector<std::uint8_t> data; size_t s = 0;
|
||||
|
||||
switch(fmt) {
|
||||
case Format::PNG: {
|
||||
void *rawdata = tdefl_write_image_to_png_file_in_memory(
|
||||
m_impl->buffer().data(),
|
||||
int(resolution().width_px),
|
||||
int(resolution().height_px), 1, &s);
|
||||
|
||||
if(rawdata == nullptr) break;
|
||||
auto ptr = static_cast<std::uint8_t*>(rawdata);
|
||||
|
||||
data.reserve(s); std::copy(ptr, ptr + s, std::back_inserter(data));
|
||||
|
||||
MZ_FREE(rawdata);
|
||||
break;
|
||||
}
|
||||
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 ";
|
||||
|
||||
auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type);
|
||||
s = sz + header.size();
|
||||
|
||||
data.reserve(s);
|
||||
|
||||
auto buff = reinterpret_cast<std::uint8_t*>(m_impl->buffer().data());
|
||||
std::copy(header.begin(), header.end(), std::back_inserter(data));
|
||||
std::copy(buff, buff+sz, std::back_inserter(data));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {std::move(data)};
|
||||
stream.write(reinterpret_cast<const char *>(bytes.data()),
|
||||
std::streamsize(bytes.size()));
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
RawBytes Raster::save()
|
||||
Raster::RawData::~RawData() = default;
|
||||
|
||||
PPMImage & PPMImage::serialize(const Raster &raster)
|
||||
{
|
||||
return save(m_impl->format());
|
||||
auto header = std::string("P5 ") +
|
||||
std::to_string(raster.resolution().width_px) + " " +
|
||||
std::to_string(raster.resolution().height_px) + " " + "255 ";
|
||||
|
||||
const auto &impl = get_internals(raster);
|
||||
auto sz = impl.buffer().size() * sizeof(TBuffer::value_type);
|
||||
size_t s = sz + header.size();
|
||||
|
||||
m_buffer.clear();
|
||||
m_buffer.reserve(s);
|
||||
|
||||
auto buff = reinterpret_cast<const std::uint8_t*>(impl.buffer().data());
|
||||
std::copy(header.begin(), header.end(), std::back_inserter(m_buffer));
|
||||
std::copy(buff, buff+sz, std::back_inserter(m_buffer));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Raster::Impl &Raster::RawData::get_internals(const Raster &raster)
|
||||
{
|
||||
return *raster.m_impl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // SLARASTER_CPP
|
||||
|
@ -8,44 +8,14 @@
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace ClipperLib { struct Polygon; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
|
||||
class RawBytes {
|
||||
|
||||
std::vector<std::uint8_t> m_buffer;
|
||||
public:
|
||||
|
||||
RawBytes() = default;
|
||||
RawBytes(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {}
|
||||
|
||||
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(RawBytes&&) = default;
|
||||
// RawBytes& operator=(RawBytes&&) = default;
|
||||
|
||||
RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {}
|
||||
RawBytes& operator=(RawBytes&& mv) {
|
||||
m_buffer = std::move(mv.m_buffer);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
class Raster;
|
||||
|
||||
/**
|
||||
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
||||
@ -59,11 +29,29 @@ class Raster {
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
/// Supported compression types
|
||||
enum class Format {
|
||||
RAW, //!> Uncompressed pixel data
|
||||
PNG //!> PNG compression
|
||||
|
||||
// Raw byte buffer paired with its size. Suitable for compressed image data.
|
||||
class RawData
|
||||
{
|
||||
protected:
|
||||
std::vector<std::uint8_t> m_buffer;
|
||||
const Impl& get_internals(const Raster& raster);
|
||||
public:
|
||||
RawData() = default;
|
||||
RawData(std::vector<std::uint8_t>&& data): m_buffer(std::move(data)) {}
|
||||
virtual ~RawData();
|
||||
|
||||
RawData(const RawData &) = delete;
|
||||
RawData &operator=(const RawData &) = delete;
|
||||
|
||||
RawData(RawData &&) = default;
|
||||
RawData &operator=(RawData &&) = default;
|
||||
|
||||
size_t size() const { return m_buffer.size(); }
|
||||
const uint8_t * data() const { return m_buffer.data(); }
|
||||
|
||||
virtual RawData& serialize(const Raster &/*raster*/) { return *this; }
|
||||
virtual std::string get_file_extension() const = 0;
|
||||
};
|
||||
|
||||
/// Type that represents a resolution in pixels.
|
||||
@ -85,11 +73,36 @@ public:
|
||||
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.
|
||||
template <class...Args> Raster(Args...args) {
|
||||
reset(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
enum Orientation { roLandscape, roPortrait };
|
||||
|
||||
using TMirroring = std::array<bool, 2>;
|
||||
static const TMirroring NoMirror;
|
||||
static const TMirroring MirrorX;
|
||||
static const TMirroring MirrorY;
|
||||
static const TMirroring MirrorXY;
|
||||
|
||||
struct Trafo {
|
||||
bool mirror_x = false, mirror_y = false, flipXY = false;
|
||||
coord_t origin_x = 0, origin_y = .0;
|
||||
|
||||
// If gamma is zero, thresholding will be performed which disables AA.
|
||||
double gamma = 1.;
|
||||
|
||||
// Portrait orientation will make sure the drawed polygons are rotated
|
||||
// by 90 degrees.
|
||||
Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror)
|
||||
// XY flipping implicitly does an X mirror
|
||||
: mirror_x(o == roPortrait ? !mirror[0] : mirror[0])
|
||||
, mirror_y(!mirror[1]) // Makes raster origin to be top left corner
|
||||
, flipXY(o == roPortrait)
|
||||
{}
|
||||
};
|
||||
|
||||
Raster();
|
||||
Raster(const Resolution &r,
|
||||
const PixelDim & pd,
|
||||
const Trafo & tr = {});
|
||||
|
||||
Raster(const Raster& cpy) = delete;
|
||||
Raster& operator=(const Raster& cpy) = delete;
|
||||
@ -98,17 +111,9 @@ public:
|
||||
~Raster();
|
||||
|
||||
/// Reallocated everything for the given resolution and pixel dimension.
|
||||
/// 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);
|
||||
const PixelDim& pd,
|
||||
const Trafo &tr = {});
|
||||
|
||||
/**
|
||||
* Release the allocated resources. Drawing in this state ends in
|
||||
@ -118,6 +123,7 @@ public:
|
||||
|
||||
/// Get the resolution of the raster.
|
||||
Resolution resolution() const;
|
||||
PixelDim pixel_dimensions() const;
|
||||
|
||||
/// Clear the raster with black color.
|
||||
void clear();
|
||||
@ -125,25 +131,29 @@ public:
|
||||
/// Draw a polygon with holes.
|
||||
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.
|
||||
|
||||
uint8_t read_pixel(size_t w, size_t h) const;
|
||||
|
||||
/// Save the raster on the specified stream.
|
||||
void save(std::ostream& stream, Format);
|
||||
void save(std::ostream& stream);
|
||||
|
||||
/// Save into a continuous byte stream which is returned.
|
||||
RawBytes save(Format fmt);
|
||||
RawBytes save();
|
||||
inline bool empty() const { return ! bool(m_impl); }
|
||||
|
||||
};
|
||||
|
||||
// This prevents the duplicate default constructor warning on MSVC2013
|
||||
template<> Raster::Raster();
|
||||
class PNGImage: public Raster::RawData {
|
||||
public:
|
||||
PNGImage& serialize(const Raster &raster) override;
|
||||
std::string get_file_extension() const override { return "png"; }
|
||||
};
|
||||
|
||||
class PPMImage: public Raster::RawData {
|
||||
public:
|
||||
PPMImage& serialize(const Raster &raster) override;
|
||||
std::string get_file_extension() const override { return "ppm"; }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes);
|
||||
|
||||
} // sla
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif // SLARASTER_HPP
|
||||
|
@ -21,37 +21,12 @@ std::string RasterWriter::createIniContent(const std::string& projectname) const
|
||||
return out;
|
||||
}
|
||||
|
||||
void RasterWriter::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 RasterWriter::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());
|
||||
}
|
||||
}
|
||||
|
||||
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim &pixdim,
|
||||
const std::array<bool, 2> &mirror,
|
||||
double gamma)
|
||||
: m_res(res), m_pxdim(pixdim), m_mirror(mirror), m_gamma(gamma)
|
||||
{
|
||||
// PNG raster will implicitly do an Y mirror
|
||||
m_mirror[1] = !m_mirror[1];
|
||||
}
|
||||
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim & pixdim,
|
||||
const Raster::Trafo & trafo,
|
||||
double gamma)
|
||||
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
|
||||
{}
|
||||
|
||||
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||
{
|
||||
|
@ -15,7 +15,8 @@
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// Implementation for PNG raster output
|
||||
// API to write the zipped sla output layers and metadata.
|
||||
// Implementation uses 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
|
||||
@ -25,10 +26,6 @@ namespace Slic3r { namespace sla {
|
||||
class RasterWriter
|
||||
{
|
||||
public:
|
||||
enum Orientation {
|
||||
roLandscape,
|
||||
roPortrait
|
||||
};
|
||||
|
||||
// Used for addressing parameters of set_statistics()
|
||||
struct PrintStatistics
|
||||
@ -45,7 +42,7 @@ private:
|
||||
// A struct to bind the raster image data and its compressed bytes together.
|
||||
struct Layer {
|
||||
Raster raster;
|
||||
RawBytes rawbytes;
|
||||
PNGImage rawbytes;
|
||||
|
||||
Layer() = default;
|
||||
|
||||
@ -61,22 +58,21 @@ private:
|
||||
// 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;
|
||||
std::array<bool, 2> m_mirror;
|
||||
double m_gamma;
|
||||
|
||||
Raster::PixelDim m_pxdim;
|
||||
Raster::Trafo m_trafo;
|
||||
double m_gamma;
|
||||
|
||||
std::map<std::string, std::string> m_config;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) const;
|
||||
|
||||
static void flpXY(ClipperLib::Polygon& poly);
|
||||
static void flpXY(ExPolygon& poly);
|
||||
|
||||
public:
|
||||
RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim &pixdim,
|
||||
const std::array<bool, 2> &mirror,
|
||||
double gamma = 1.);
|
||||
|
||||
// SLARasterWriter is using Raster in custom mirroring mode
|
||||
RasterWriter(const Raster::Resolution &res,
|
||||
const Raster::PixelDim & pixdim,
|
||||
const Raster::Trafo & trafo,
|
||||
double gamma = 1.);
|
||||
|
||||
RasterWriter(const RasterWriter& ) = delete;
|
||||
RasterWriter& operator=(const RasterWriter&) = delete;
|
||||
@ -86,45 +82,31 @@ public:
|
||||
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,
|
||||
Orientation o = roPortrait)
|
||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr)
|
||||
{
|
||||
assert(lyr < m_layers_rst.size());
|
||||
|
||||
switch (o) {
|
||||
case roPortrait: {
|
||||
Poly poly(p);
|
||||
flpXY(poly);
|
||||
m_layers_rst[lyr].raster.draw(poly);
|
||||
break;
|
||||
}
|
||||
case roLandscape:
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo);
|
||||
}
|
||||
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
|
||||
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo);
|
||||
}
|
||||
|
||||
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].rawbytes.serialize(m_layers_rst[lyr_id].raster);
|
||||
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().rawbytes.serialize(m_layers_rst.back().raster);
|
||||
m_layers_rst.back().raster.reset();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include "SLASupportTreeAlgorithm.hpp"
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include "SLASupportTreeAlgorithm.hpp"
|
||||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
@ -455,7 +455,7 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
|
||||
bool SupportTreeBuilder::build(const SupportableMesh &sm)
|
||||
{
|
||||
ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
|
||||
return SupportTreeAlgorithm::execute(*this, sm);
|
||||
return SupportTreeBuildsteps::execute(*this, sm);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "SLASupportTreeAlgorithm.hpp"
|
||||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
@ -7,7 +7,7 @@
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
SupportTreeAlgorithm::SupportTreeAlgorithm(SupportTreeBuilder & builder,
|
||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
: m_cfg(sm.cfg)
|
||||
, m_mesh(sm.emesh)
|
||||
@ -29,12 +29,12 @@ SupportTreeAlgorithm::SupportTreeAlgorithm(SupportTreeBuilder & builder,
|
||||
}
|
||||
}
|
||||
|
||||
bool SupportTreeAlgorithm::execute(SupportTreeBuilder & builder,
|
||||
bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
{
|
||||
if(sm.pts.empty()) return false;
|
||||
|
||||
SupportTreeAlgorithm alg(builder, sm);
|
||||
SupportTreeBuildsteps alg(builder, sm);
|
||||
|
||||
// Let's define the individual steps of the processing. We can experiment
|
||||
// later with the ordering and the dependencies between them.
|
||||
@ -61,21 +61,21 @@ bool SupportTreeAlgorithm::execute(SupportTreeBuilder & builder,
|
||||
// Potentially clear up the shared data (not needed for now)
|
||||
},
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::filter, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::filter, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::add_pinheads, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::add_pinheads, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::classify, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::classify, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::routing_to_ground, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::routing_to_model, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::routing_to_model, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::interconnect_pillars, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::routing_headless, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::routing_headless, &alg),
|
||||
|
||||
std::bind(&SupportTreeAlgorithm::merge_result, &alg),
|
||||
std::bind(&SupportTreeBuildsteps::merge_result, &alg),
|
||||
|
||||
[] () {
|
||||
// Done
|
||||
@ -158,7 +158,7 @@ bool SupportTreeAlgorithm::execute(SupportTreeBuilder & builder,
|
||||
return pc == ABORT;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result SupportTreeAlgorithm::pinhead_mesh_intersect(
|
||||
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
||||
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
||||
{
|
||||
static const size_t SAMPLES = 8;
|
||||
@ -275,7 +275,7 @@ EigenMesh3D::hit_result SupportTreeAlgorithm::pinhead_mesh_intersect(
|
||||
return *mit;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result SupportTreeAlgorithm::bridge_mesh_intersect(
|
||||
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
||||
const Vec3d &s, const Vec3d &dir, double r, bool ins_check)
|
||||
{
|
||||
static const size_t SAMPLES = 8;
|
||||
@ -344,7 +344,7 @@ EigenMesh3D::hit_result SupportTreeAlgorithm::bridge_mesh_intersect(
|
||||
return *mit;
|
||||
}
|
||||
|
||||
bool SupportTreeAlgorithm::interconnect(const Pillar &pillar,
|
||||
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
||||
const Pillar &nextpillar)
|
||||
{
|
||||
// We need to get the starting point of the zig-zag pattern. We have to
|
||||
@ -437,7 +437,7 @@ bool SupportTreeAlgorithm::interconnect(const Pillar &pillar,
|
||||
return was_connected;
|
||||
}
|
||||
|
||||
bool SupportTreeAlgorithm::connect_to_nearpillar(const Head &head,
|
||||
bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
||||
long nearpillar_id)
|
||||
{
|
||||
auto nearpillar = [this, nearpillar_id]() {
|
||||
@ -514,7 +514,7 @@ bool SupportTreeAlgorithm::connect_to_nearpillar(const Head &head,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SupportTreeAlgorithm::search_pillar_and_connect(const Head &head)
|
||||
bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head)
|
||||
{
|
||||
PointIndex spindex = m_pillar_index.guarded_clone();
|
||||
|
||||
@ -549,7 +549,7 @@ bool SupportTreeAlgorithm::search_pillar_and_connect(const Head &head)
|
||||
return nearest_id >= 0;
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::create_ground_pillar(const Vec3d &jp,
|
||||
void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id)
|
||||
@ -661,7 +661,7 @@ void SupportTreeAlgorithm::create_ground_pillar(const Vec3d &jp,
|
||||
m_pillar_index.guarded_insert(endp, unsigned(pillar_id));
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::filter()
|
||||
void SupportTreeBuildsteps::filter()
|
||||
{
|
||||
// Get the points that are too close to each other and keep only the
|
||||
// first one
|
||||
@ -806,7 +806,7 @@ void SupportTreeAlgorithm::filter()
|
||||
m_thr();
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::add_pinheads()
|
||||
void SupportTreeBuildsteps::add_pinheads()
|
||||
{
|
||||
for (unsigned i : m_iheads) {
|
||||
m_thr();
|
||||
@ -822,7 +822,7 @@ void SupportTreeAlgorithm::add_pinheads()
|
||||
}
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::classify()
|
||||
void SupportTreeBuildsteps::classify()
|
||||
{
|
||||
// We should first get the heads that reach the ground directly
|
||||
PtIndices ground_head_indices;
|
||||
@ -872,7 +872,7 @@ void SupportTreeAlgorithm::classify()
|
||||
m_cfg.max_bridges_on_pillar);
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::routing_to_ground()
|
||||
void SupportTreeBuildsteps::routing_to_ground()
|
||||
{
|
||||
const double pradius = m_cfg.head_back_radius_mm;
|
||||
|
||||
@ -946,7 +946,7 @@ void SupportTreeAlgorithm::routing_to_ground()
|
||||
}
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::routing_to_model()
|
||||
void SupportTreeBuildsteps::routing_to_model()
|
||||
{
|
||||
// We need to check if there is an easy way out to the bed surface.
|
||||
// If it can be routed there with a bridge shorter than
|
||||
@ -1131,7 +1131,7 @@ void SupportTreeAlgorithm::routing_to_model()
|
||||
}
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::interconnect_pillars()
|
||||
void SupportTreeBuildsteps::interconnect_pillars()
|
||||
{
|
||||
// Now comes the algorithm that connects pillars with each other.
|
||||
// Ideally every pillar should be connected with at least one of its
|
||||
@ -1337,7 +1337,7 @@ void SupportTreeAlgorithm::interconnect_pillars()
|
||||
}
|
||||
}
|
||||
|
||||
void SupportTreeAlgorithm::routing_headless()
|
||||
void SupportTreeBuildsteps::routing_headless()
|
||||
{
|
||||
// For now we will just generate smaller headless sticks with a sharp
|
||||
// ending point that connects to the mesh surface.
|
@ -142,7 +142,7 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||
return (DoubleI(g) << shift) + l;
|
||||
}
|
||||
|
||||
class SupportTreeAlgorithm {
|
||||
class SupportTreeBuildsteps {
|
||||
const SupportConfig& m_cfg;
|
||||
const EigenMesh3D& m_mesh;
|
||||
const std::vector<SupportPoint>& m_support_pts;
|
||||
@ -227,15 +227,9 @@ class SupportTreeAlgorithm {
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = ID_UNSET);
|
||||
|
||||
SupportTreeAlgorithm(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
||||
long head_id = ID_UNSET);
|
||||
public:
|
||||
SupportTreeAlgorithm(const SupportTreeAlgorithm &) = delete;
|
||||
SupportTreeAlgorithm(SupportTreeAlgorithm &&) = delete;
|
||||
SupportTreeAlgorithm& operator=(const SupportTreeAlgorithm &) = delete;
|
||||
SupportTreeAlgorithm& operator=(SupportTreeAlgorithm &&) = delete;
|
||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
||||
// Now let's define the individual steps of the support generation algorithm
|
||||
|
@ -1176,9 +1176,8 @@ void SLAPrint::process()
|
||||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
// 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;
|
||||
// We need to reverse if is_lefthanded is true but
|
||||
bool needreverse = is_lefthanded;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
@ -1401,11 +1400,9 @@ void SLAPrint::process()
|
||||
|
||||
SpinMutex slck;
|
||||
|
||||
auto orientation = get_printer_orientation();
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &printer, increment, &dstatus, &pst, orientation]
|
||||
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||
(unsigned level_id)
|
||||
{
|
||||
if(canceled()) return;
|
||||
@ -1416,7 +1413,7 @@ void SLAPrint::process()
|
||||
printer.begin_layer(level_id);
|
||||
|
||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
printer.draw_polygon(poly, level_id, orientation);
|
||||
printer.draw_polygon(poly, level_id);
|
||||
|
||||
// Finish the layer for later saving it.
|
||||
printer.finish_layer(level_id);
|
||||
@ -1644,7 +1641,6 @@ sla::RasterWriter & SLAPrint::init_printer()
|
||||
sla::Raster::Resolution res;
|
||||
sla::Raster::PixelDim pxdim;
|
||||
std::array<bool, 2> mirror;
|
||||
double gamma;
|
||||
|
||||
double w = m_printer_config.display_width.getFloat();
|
||||
double h = m_printer_config.display_height.getFloat();
|
||||
@ -1654,20 +1650,18 @@ sla::RasterWriter & SLAPrint::init_printer()
|
||||
mirror[X] = m_printer_config.display_mirror_x.getBool();
|
||||
mirror[Y] = m_printer_config.display_mirror_y.getBool();
|
||||
|
||||
if (get_printer_orientation() == sla::RasterWriter::roPortrait) {
|
||||
auto orientation = get_printer_orientation();
|
||||
if (orientation == sla::Raster::roPortrait) {
|
||||
std::swap(w, h);
|
||||
std::swap(pw, ph);
|
||||
|
||||
// XY flipping implicitly does an X mirror
|
||||
mirror[X] = !mirror[X];
|
||||
}
|
||||
|
||||
res = sla::Raster::Resolution{pw, ph};
|
||||
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
||||
|
||||
gamma = m_printer_config.gamma_correction.getFloat();
|
||||
|
||||
m_printer.reset(new sla::RasterWriter(res, pxdim, mirror, gamma));
|
||||
sla::Raster::Trafo tr{orientation, mirror};
|
||||
tr.gamma = m_printer_config.gamma_correction.getFloat();
|
||||
|
||||
m_printer.reset(new sla::RasterWriter(res, pxdim, tr));
|
||||
m_printer->set_config(m_full_print_config);
|
||||
return *m_printer;
|
||||
}
|
||||
|
@ -461,12 +461,11 @@ private:
|
||||
|
||||
sla::RasterWriter &init_printer();
|
||||
|
||||
inline sla::RasterWriter::Orientation get_printer_orientation() const
|
||||
inline sla::Raster::Orientation get_printer_orientation() const
|
||||
{
|
||||
auto ro = m_printer_config.display_orientation.getInt();
|
||||
return ro == sla::RasterWriter::roPortrait ?
|
||||
sla::RasterWriter::roPortrait :
|
||||
sla::RasterWriter::roLandscape;
|
||||
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
|
||||
sla::Raster::roLandscape;
|
||||
}
|
||||
|
||||
friend SLAPrintObject;
|
||||
|
@ -1,4 +1,5 @@
|
||||
add_executable(sla_print_tests sla_print_tests_main.cpp)
|
||||
target_link_libraries(sla_print_tests test_common libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES})
|
||||
|
||||
gtest_discover_tests(sla_print_tests TEST_PREFIX sla_print.)
|
||||
add_test(sla_print_tests sla_print_tests)
|
||||
#gtest_discover_tests(sla_print_tests TEST_PREFIX sla_print.)
|
||||
|
@ -1,5 +1,8 @@
|
||||
#include <map>
|
||||
|
||||
// Debug
|
||||
#include <fstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
@ -8,8 +11,9 @@
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/SLA/SLAPad.hpp"
|
||||
#include "libslic3r/SLA/SLASupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/SLASupportTreeAlgorithm.hpp"
|
||||
#include "libslic3r/SLA/SLASupportTreeBuildsteps.hpp"
|
||||
#include "libslic3r/SLA/SLAAutoSupports.hpp"
|
||||
#include "libslic3r/SLA/SLARaster.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
#include "libslic3r/SVG.hpp"
|
||||
@ -86,7 +90,7 @@ void test_pad(const std::string & obj_filename,
|
||||
|
||||
ASSERT_FALSE(out.model_contours.empty());
|
||||
|
||||
// Create the pad geometry the model contours only
|
||||
// Create the pad geometry for the model contours only
|
||||
Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
|
||||
|
||||
check_validity(out.mesh);
|
||||
@ -369,6 +373,180 @@ TEST(SLASupportGeneration, SupportsDoNotPierceModel) {
|
||||
test_support_model_collision(fname, supportcfg);
|
||||
}
|
||||
|
||||
TEST(SLARasterOutput, DefaultRasterShouldBeEmpty) {
|
||||
sla::Raster raster;
|
||||
ASSERT_TRUE(raster.empty());
|
||||
}
|
||||
|
||||
TEST(SLARasterOutput, InitializedRasterShouldBeNONEmpty) {
|
||||
// Default Prusa SL1 display parameters
|
||||
sla::Raster::Resolution res{2560, 1440};
|
||||
sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
|
||||
|
||||
sla::Raster raster;
|
||||
raster.reset(res, pixdim);
|
||||
ASSERT_FALSE(raster.empty());
|
||||
ASSERT_EQ(raster.resolution().width_px, res.width_px);
|
||||
ASSERT_EQ(raster.resolution().height_px, res.height_px);
|
||||
ASSERT_DOUBLE_EQ(raster.pixel_dimensions().w_mm, pixdim.w_mm);
|
||||
ASSERT_DOUBLE_EQ(raster.pixel_dimensions().h_mm, pixdim.h_mm);
|
||||
}
|
||||
|
||||
using TPixel = uint8_t;
|
||||
static constexpr const TPixel FullWhite = 255;
|
||||
static constexpr const TPixel FullBlack = 0;
|
||||
|
||||
template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; }
|
||||
|
||||
static void check_raster_transformations(sla::Raster::Orientation o,
|
||||
sla::Raster::TMirroring mirroring)
|
||||
{
|
||||
double disp_w = 120., disp_h = 68.;
|
||||
sla::Raster::Resolution res{2560, 1440};
|
||||
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
|
||||
|
||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||
sla::Raster::Trafo trafo{o, mirroring};
|
||||
trafo.origin_x = bb.center().x();
|
||||
trafo.origin_y = bb.center().y();
|
||||
|
||||
sla::Raster raster{res, pixdim, trafo};
|
||||
|
||||
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
|
||||
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
|
||||
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
|
||||
ExPolygon box;
|
||||
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
|
||||
|
||||
double tr_x = scaled<double>(20.), tr_y = tr_x;
|
||||
|
||||
box.translate(tr_x, tr_y);
|
||||
ExPolygon expected_box = box;
|
||||
|
||||
// Now calculate the position of the translated box according to output
|
||||
// trafo.
|
||||
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
|
||||
|
||||
if (mirroring[X])
|
||||
for (auto &p : expected_box.contour.points) p.x() = -p.x();
|
||||
|
||||
if (mirroring[Y])
|
||||
for (auto &p : expected_box.contour.points) p.y() = -p.y();
|
||||
|
||||
raster.draw(box);
|
||||
|
||||
Point expected_coords = expected_box.contour.bounding_box().center();
|
||||
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
|
||||
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
|
||||
auto w = size_t(std::floor(rx));
|
||||
auto h = res.height_px - size_t(std::floor(ry));
|
||||
|
||||
ASSERT_TRUE(w < res.width_px && h < res.height_px);
|
||||
|
||||
auto px = raster.read_pixel(w, h);
|
||||
|
||||
if (px != FullWhite) {
|
||||
sla::PNGImage img;
|
||||
std::fstream outf("out.png", std::ios::out);
|
||||
|
||||
outf << img.serialize(raster);
|
||||
}
|
||||
|
||||
ASSERT_EQ(px, FullWhite);
|
||||
}
|
||||
|
||||
TEST(SLARasterOutput, MirroringShouldBeCorrect) {
|
||||
sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror,
|
||||
sla::Raster::MirrorX,
|
||||
sla::Raster::MirrorY,
|
||||
sla::Raster::MirrorXY};
|
||||
|
||||
sla::Raster::Orientation orientations[] = {sla::Raster::roLandscape,
|
||||
sla::Raster::roPortrait};
|
||||
for (auto orientation : orientations)
|
||||
for (auto &mirror : mirrorings)
|
||||
check_raster_transformations(orientation, mirror);
|
||||
}
|
||||
|
||||
static ExPolygon square_with_hole(double v)
|
||||
{
|
||||
ExPolygon poly;
|
||||
coord_t V = scaled(v / 2.);
|
||||
|
||||
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
|
||||
poly.holes.emplace_back();
|
||||
V = V / 2;
|
||||
poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}};
|
||||
return poly;
|
||||
}
|
||||
|
||||
static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim)
|
||||
{
|
||||
return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack);
|
||||
}
|
||||
|
||||
static double raster_white_area(const sla::Raster &raster)
|
||||
{
|
||||
if (raster.empty()) return std::nan("");
|
||||
|
||||
auto res = raster.resolution();
|
||||
double a = 0;
|
||||
|
||||
for (size_t x = 0; x < res.width_px; ++x)
|
||||
for (size_t y = 0; y < res.height_px; ++y) {
|
||||
auto px = raster.read_pixel(x, y);
|
||||
a += pixel_area(px, raster.pixel_dimensions());
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd)
|
||||
{
|
||||
auto lines = p.lines();
|
||||
double pix_err = pixel_area(FullWhite, pd) / 2.;
|
||||
|
||||
// Worst case is when a line is parallel to the shorter axis of one pixel,
|
||||
// when the line will be composed of the max number of pixels
|
||||
double pix_l = std::min(pd.h_mm, pd.w_mm);
|
||||
|
||||
double error = 0.;
|
||||
for (auto &l : lines)
|
||||
error += (unscaled(l.length()) / pix_l) * pix_err;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
TEST(SLARasterOutput, RasterizedPolygonAreaShouldMatch) {
|
||||
double disp_w = 120., disp_h = 68.;
|
||||
sla::Raster::Resolution res{2560, 1440};
|
||||
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
|
||||
|
||||
sla::Raster raster{res, pixdim};
|
||||
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
|
||||
|
||||
ExPolygon poly = square_with_hole(10.);
|
||||
poly.translate(bb.center().x(), bb.center().y());
|
||||
raster.draw(poly);
|
||||
|
||||
double a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||
double ra = raster_white_area(raster);
|
||||
double diff = std::abs(a - ra);
|
||||
|
||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||
|
||||
raster.clear();
|
||||
poly = square_with_hole(60.);
|
||||
poly.translate(bb.center().x(), bb.center().y());
|
||||
raster.draw(poly);
|
||||
|
||||
a = poly.area() / (scaled<double>(1.) * scaled(1.));
|
||||
ra = raster_white_area(raster);
|
||||
diff = std::abs(a - ra);
|
||||
|
||||
ASSERT_LE(diff, predict_error(poly, pixdim));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
Loading…
Reference in New Issue
Block a user