sla::Raster interface clarified and covered with tests.
Also renamed sla::SupportTreeAlgorithm to SupportTreeBuildsteps.
This commit is contained in:
parent
705e82ec8e
commit
be7428d66e
15 changed files with 484 additions and 361 deletions
|
@ -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
|
# Proposal for C++ unit tests and sandboxes
|
||||||
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
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
|
# Print out the SLIC3R_* cache options
|
||||||
get_cmake_property(_cache_vars CACHE_VARIABLES)
|
get_cmake_property(_cache_vars CACHE_VARIABLES)
|
||||||
|
|
|
@ -156,7 +156,7 @@ namespace agg
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
template<class VertexSource>
|
template<class VertexSource>
|
||||||
void add_path(VertexSource& vs, unsigned path_id=0)
|
void add_path(VertexSource &&vs, unsigned path_id=0)
|
||||||
{
|
{
|
||||||
double x;
|
double x;
|
||||||
double y;
|
double y;
|
||||||
|
|
|
@ -179,8 +179,8 @@ add_library(libslic3r STATIC
|
||||||
SLA/SLAPad.hpp
|
SLA/SLAPad.hpp
|
||||||
SLA/SLAPad.cpp
|
SLA/SLAPad.cpp
|
||||||
SLA/SLASupportTreeBuilder.hpp
|
SLA/SLASupportTreeBuilder.hpp
|
||||||
SLA/SLASupportTreeAlgorithm.hpp
|
SLA/SLASupportTreeBuildsteps.hpp
|
||||||
SLA/SLASupportTreeAlgorithm.cpp
|
SLA/SLASupportTreeBuildsteps.cpp
|
||||||
SLA/SLASupportTreeBuilder.cpp
|
SLA/SLASupportTreeBuilder.cpp
|
||||||
SLA/SLAConcurrency.hpp
|
SLA/SLAConcurrency.hpp
|
||||||
SLA/SLASupportTree.hpp
|
SLA/SLASupportTree.hpp
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "SLARaster.hpp"
|
#include "SLARaster.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
|
#include "libslic3r/MTUtils.hpp"
|
||||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||||
|
|
||||||
// For rasterizing
|
// For rasterizing
|
||||||
|
@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H
|
||||||
|
|
||||||
namespace sla {
|
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 {
|
class Raster::Impl {
|
||||||
public:
|
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 ColorWhite;
|
||||||
static const TPixel ColorBlack;
|
static const TPixel ColorBlack;
|
||||||
|
|
||||||
using Format = Raster::Format;
|
using Format = Raster::RawData;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Raster::Resolution m_resolution;
|
Raster::Resolution m_resolution;
|
||||||
// Raster::PixelDim m_pxdim;
|
|
||||||
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
||||||
TBuffer m_buf;
|
TBuffer m_buf;
|
||||||
TRawBuffer m_rbuf;
|
TRawBuffer m_rbuf;
|
||||||
|
@ -59,74 +65,49 @@ private:
|
||||||
TRendererAA m_renderer;
|
TRendererAA m_renderer;
|
||||||
|
|
||||||
std::function<double(double)> m_gammafn;
|
std::function<double(double)> m_gammafn;
|
||||||
std::array<bool, 2> m_mirror;
|
Trafo m_trafo;
|
||||||
Format m_fmt = Format::PNG;
|
|
||||||
|
|
||||||
inline void flipy(agg::path_storage& path) const {
|
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 {
|
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:
|
public:
|
||||||
|
inline Impl(const Raster::Resolution & res,
|
||||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
const Raster::PixelDim & pd,
|
||||||
const std::array<bool, 2>& mirror, double gamma = 1.0):
|
const Trafo &trafo)
|
||||||
m_resolution(res),
|
: m_resolution(res)
|
||||||
// m_pxdim(pd),
|
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
|
||||||
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
|
, m_buf(res.pixels())
|
||||||
m_buf(res.pixels()),
|
, m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()),
|
||||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
unsigned(res.width_px),
|
||||||
res.width_px, res.height_px,
|
unsigned(res.height_px),
|
||||||
int(res.width_px*TPixelRenderer::num_components)),
|
int(res.width_px * TPixelRenderer::num_components))
|
||||||
m_pixfmt(m_rbuf),
|
, m_pixfmt(m_rbuf)
|
||||||
m_raw_renderer(m_pixfmt),
|
, m_raw_renderer(m_pixfmt)
|
||||||
m_renderer(m_raw_renderer),
|
, m_renderer(m_raw_renderer)
|
||||||
m_mirror(mirror)
|
, m_trafo(trafo)
|
||||||
{
|
{
|
||||||
m_renderer.color(ColorWhite);
|
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);
|
else m_gammafn = agg::gamma_threshold(0.5);
|
||||||
|
|
||||||
clear();
|
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) {
|
template<class P> void draw(const P &poly) {
|
||||||
agg::rasterizer_scanline_aa<> ras;
|
agg::rasterizer_scanline_aa<> ras;
|
||||||
agg::scanline_p8 scanlines;
|
agg::scanline_p8 scanlines;
|
||||||
|
|
||||||
ras.gamma(m_gammafn);
|
ras.gamma(m_gammafn);
|
||||||
|
|
||||||
auto&& path = to_path(contour(poly));
|
|
||||||
|
|
||||||
if(m_mirror[X]) flipx(path);
|
ras.add_path(to_path(contour(poly)));
|
||||||
if(m_mirror[Y]) flipy(path);
|
for(auto& h : holes(poly)) ras.add_path(to_path(h));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,11 +116,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TBuffer& buffer() { return m_buf; }
|
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::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:
|
private:
|
||||||
inline double getPx(const Point& p) {
|
inline double getPx(const Point& p) {
|
||||||
return p(0) * m_pxdim_scaled.w_mm;
|
return p(0) * m_pxdim_scaled.w_mm;
|
||||||
|
@ -162,49 +148,67 @@ private:
|
||||||
return p.Y * m_pxdim_scaled.h_mm;
|
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;
|
agg::path_storage path;
|
||||||
|
|
||||||
auto it = poly.begin();
|
auto it = v.begin();
|
||||||
path.move_to(getPx(*it), getPy(*it));
|
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;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
const TPixel Raster::Impl::ColorWhite = TPixel(255);
|
||||||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
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() = default;
|
||||||
|
|
||||||
// Raster::Raster(Raster &&m) = default;
|
Raster::Raster(Raster &&m) = default;
|
||||||
// Raster& Raster::operator=(Raster&&) = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||||
Format fmt, double gamma)
|
const Trafo &trafo)
|
||||||
{
|
{
|
||||||
m_impl.reset();
|
m_impl.reset();
|
||||||
m_impl.reset(new Impl(r, pd, fmt, gamma));
|
m_impl.reset(new Impl(r, pd, trafo));
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
void Raster::reset()
|
||||||
|
@ -214,9 +218,16 @@ void Raster::reset()
|
||||||
|
|
||||||
Raster::Resolution Raster::resolution() const
|
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()
|
void Raster::clear()
|
||||||
|
@ -227,103 +238,83 @@ void Raster::clear()
|
||||||
|
|
||||||
void Raster::draw(const ExPolygon &expoly)
|
void Raster::draw(const ExPolygon &expoly)
|
||||||
{
|
{
|
||||||
|
assert(m_impl);
|
||||||
m_impl->draw(expoly);
|
m_impl->draw(expoly);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Raster::draw(const ClipperLib::Polygon &poly)
|
void Raster::draw(const ClipperLib::Polygon &poly)
|
||||||
{
|
{
|
||||||
|
assert(m_impl);
|
||||||
m_impl->draw(poly);
|
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);
|
assert (m_impl);
|
||||||
if(!stream.good()) return;
|
TPixel::value_type px;
|
||||||
|
m_impl->buffer()[y * resolution().width_px + x].get(px);
|
||||||
switch(fmt) {
|
return px;
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
stream.write(reinterpret_cast<const char *>(bytes.data()),
|
||||||
|
std::streamsize(bytes.size()));
|
||||||
std::vector<std::uint8_t> data; size_t s = 0;
|
|
||||||
|
return stream;
|
||||||
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)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
#endif // SLARASTER_CPP
|
||||||
|
|
|
@ -8,44 +8,14 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
namespace ClipperLib { struct Polygon; }
|
namespace ClipperLib { struct Polygon; }
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ExPolygon;
|
|
||||||
|
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
|
class Raster;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
||||||
|
@ -59,11 +29,29 @@ class Raster {
|
||||||
class Impl;
|
class Impl;
|
||||||
std::unique_ptr<Impl> m_impl;
|
std::unique_ptr<Impl> m_impl;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Supported compression types
|
// Raw byte buffer paired with its size. Suitable for compressed image data.
|
||||||
enum class Format {
|
class RawData
|
||||||
RAW, //!> Uncompressed pixel data
|
{
|
||||||
PNG //!> PNG compression
|
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.
|
/// 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):
|
inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0):
|
||||||
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constructor taking the resolution and the pixel dimension.
|
enum Orientation { roLandscape, roPortrait };
|
||||||
template <class...Args> Raster(Args...args) {
|
|
||||||
reset(std::forward<Args>(args)...);
|
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(const Raster& cpy) = delete;
|
||||||
Raster& operator=(const Raster& cpy) = delete;
|
Raster& operator=(const Raster& cpy) = delete;
|
||||||
|
@ -98,17 +111,9 @@ public:
|
||||||
~Raster();
|
~Raster();
|
||||||
|
|
||||||
/// Reallocated everything for the given resolution and pixel dimension.
|
/// 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,
|
void reset(const Resolution& r,
|
||||||
const PixelDim& pd,
|
const PixelDim& pd,
|
||||||
Format o,
|
const Trafo &tr = {});
|
||||||
double gamma = 1.0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the allocated resources. Drawing in this state ends in
|
* Release the allocated resources. Drawing in this state ends in
|
||||||
|
@ -118,6 +123,7 @@ public:
|
||||||
|
|
||||||
/// Get the resolution of the raster.
|
/// Get the resolution of the raster.
|
||||||
Resolution resolution() const;
|
Resolution resolution() const;
|
||||||
|
PixelDim pixel_dimensions() const;
|
||||||
|
|
||||||
/// Clear the raster with black color.
|
/// Clear the raster with black color.
|
||||||
void clear();
|
void clear();
|
||||||
|
@ -125,25 +131,29 @@ public:
|
||||||
/// Draw a polygon with holes.
|
/// Draw a polygon with holes.
|
||||||
void draw(const ExPolygon& poly);
|
void draw(const ExPolygon& poly);
|
||||||
void draw(const ClipperLib::Polygon& poly);
|
void draw(const ClipperLib::Polygon& poly);
|
||||||
|
|
||||||
// Saving the raster:
|
uint8_t read_pixel(size_t w, size_t h) const;
|
||||||
// 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.
|
inline bool empty() const { return ! bool(m_impl); }
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This prevents the duplicate default constructor warning on MSVC2013
|
class PNGImage: public Raster::RawData {
|
||||||
template<> Raster::Raster();
|
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
|
} // sla
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
|
||||||
|
|
||||||
#endif // SLARASTER_HPP
|
#endif // SLARASTER_HPP
|
||||||
|
|
|
@ -21,37 +21,12 @@ std::string RasterWriter::createIniContent(const std::string& projectname) const
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterWriter::flpXY(ClipperLib::Polygon &poly)
|
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||||
{
|
const Raster::PixelDim & pixdim,
|
||||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
const Raster::Trafo & trafo,
|
||||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
double gamma)
|
||||||
|
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
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
|
// Be aware that if a large number of layers are allocated, it can very well
|
||||||
// exhaust the available memory especially on 32 bit platform.
|
// exhaust the available memory especially on 32 bit platform.
|
||||||
// This class is designed to be used in parallel mode. Layers have an ID and
|
// 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
|
class RasterWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Orientation {
|
|
||||||
roLandscape,
|
|
||||||
roPortrait
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for addressing parameters of set_statistics()
|
// Used for addressing parameters of set_statistics()
|
||||||
struct PrintStatistics
|
struct PrintStatistics
|
||||||
|
@ -45,7 +42,7 @@ private:
|
||||||
// A struct to bind the raster image data and its compressed bytes together.
|
// A struct to bind the raster image data and its compressed bytes together.
|
||||||
struct Layer {
|
struct Layer {
|
||||||
Raster raster;
|
Raster raster;
|
||||||
RawBytes rawbytes;
|
PNGImage rawbytes;
|
||||||
|
|
||||||
Layer() = default;
|
Layer() = default;
|
||||||
|
|
||||||
|
@ -61,22 +58,21 @@ private:
|
||||||
// parallel. Later we can write every layer to the disk sequentially.
|
// parallel. Later we can write every layer to the disk sequentially.
|
||||||
std::vector<Layer> m_layers_rst;
|
std::vector<Layer> m_layers_rst;
|
||||||
Raster::Resolution m_res;
|
Raster::Resolution m_res;
|
||||||
Raster::PixelDim m_pxdim;
|
Raster::PixelDim m_pxdim;
|
||||||
std::array<bool, 2> m_mirror;
|
Raster::Trafo m_trafo;
|
||||||
double m_gamma;
|
double m_gamma;
|
||||||
|
|
||||||
std::map<std::string, std::string> m_config;
|
std::map<std::string, std::string> m_config;
|
||||||
|
|
||||||
std::string createIniContent(const std::string& projectname) const;
|
std::string createIniContent(const std::string& projectname) const;
|
||||||
|
|
||||||
static void flpXY(ClipperLib::Polygon& poly);
|
|
||||||
static void flpXY(ExPolygon& poly);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RasterWriter(const Raster::Resolution &res,
|
|
||||||
const Raster::PixelDim &pixdim,
|
// SLARasterWriter is using Raster in custom mirroring mode
|
||||||
const std::array<bool, 2> &mirror,
|
RasterWriter(const Raster::Resolution &res,
|
||||||
double gamma = 1.);
|
const Raster::PixelDim & pixdim,
|
||||||
|
const Raster::Trafo & trafo,
|
||||||
|
double gamma = 1.);
|
||||||
|
|
||||||
RasterWriter(const RasterWriter& ) = delete;
|
RasterWriter(const RasterWriter& ) = delete;
|
||||||
RasterWriter& operator=(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 void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||||
|
|
||||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr,
|
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr)
|
||||||
Orientation o = roPortrait)
|
|
||||||
{
|
{
|
||||||
assert(lyr < m_layers_rst.size());
|
assert(lyr < m_layers_rst.size());
|
||||||
|
m_layers_rst[lyr].raster.draw(p);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void begin_layer(unsigned lyr) {
|
inline void begin_layer(unsigned lyr) {
|
||||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
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() {
|
inline void begin_layer() {
|
||||||
m_layers_rst.emplace_back();
|
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) {
|
inline void finish_layer(unsigned lyr_id) {
|
||||||
assert(lyr_id < m_layers_rst.size());
|
assert(lyr_id < m_layers_rst.size());
|
||||||
m_layers_rst[lyr_id].rawbytes =
|
m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster);
|
||||||
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
|
|
||||||
m_layers_rst[lyr_id].raster.reset();
|
m_layers_rst[lyr_id].raster.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void finish_layer() {
|
inline void finish_layer() {
|
||||||
if(!m_layers_rst.empty()) {
|
if(!m_layers_rst.empty()) {
|
||||||
m_layers_rst.back().rawbytes =
|
m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster);
|
||||||
m_layers_rst.back().raster.save(Raster::Format::PNG);
|
|
||||||
m_layers_rst.back().raster.reset();
|
m_layers_rst.back().raster.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "SLABoilerPlate.hpp"
|
#include "SLABoilerPlate.hpp"
|
||||||
#include "SLASpatIndex.hpp"
|
#include "SLASpatIndex.hpp"
|
||||||
#include "SLASupportTreeBuilder.hpp"
|
#include "SLASupportTreeBuilder.hpp"
|
||||||
#include "SLASupportTreeAlgorithm.hpp"
|
|
||||||
|
|
||||||
#include <libslic3r/MTUtils.hpp>
|
#include <libslic3r/MTUtils.hpp>
|
||||||
#include <libslic3r/ClipperUtils.hpp>
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "SLASupportTreeBuilder.hpp"
|
#include "SLASupportTreeBuilder.hpp"
|
||||||
#include "SLASupportTreeAlgorithm.hpp"
|
#include "SLASupportTreeBuildsteps.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
@ -455,7 +455,7 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
|
||||||
bool SupportTreeBuilder::build(const SupportableMesh &sm)
|
bool SupportTreeBuilder::build(const SupportableMesh &sm)
|
||||||
{
|
{
|
||||||
ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
|
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/genetic.hpp>
|
||||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
SupportTreeAlgorithm::SupportTreeAlgorithm(SupportTreeBuilder & builder,
|
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||||
const SupportableMesh &sm)
|
const SupportableMesh &sm)
|
||||||
: m_cfg(sm.cfg)
|
: m_cfg(sm.cfg)
|
||||||
, m_mesh(sm.emesh)
|
, 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)
|
const SupportableMesh &sm)
|
||||||
{
|
{
|
||||||
if(sm.pts.empty()) return false;
|
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
|
// Let's define the individual steps of the processing. We can experiment
|
||||||
// later with the ordering and the dependencies between them.
|
// 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)
|
// 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
|
// Done
|
||||||
|
@ -158,7 +158,7 @@ bool SupportTreeAlgorithm::execute(SupportTreeBuilder & builder,
|
||||||
return pc == ABORT;
|
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)
|
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
||||||
{
|
{
|
||||||
static const size_t SAMPLES = 8;
|
static const size_t SAMPLES = 8;
|
||||||
|
@ -275,7 +275,7 @@ EigenMesh3D::hit_result SupportTreeAlgorithm::pinhead_mesh_intersect(
|
||||||
return *mit;
|
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)
|
const Vec3d &s, const Vec3d &dir, double r, bool ins_check)
|
||||||
{
|
{
|
||||||
static const size_t SAMPLES = 8;
|
static const size_t SAMPLES = 8;
|
||||||
|
@ -344,7 +344,7 @@ EigenMesh3D::hit_result SupportTreeAlgorithm::bridge_mesh_intersect(
|
||||||
return *mit;
|
return *mit;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeAlgorithm::interconnect(const Pillar &pillar,
|
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
||||||
const Pillar &nextpillar)
|
const Pillar &nextpillar)
|
||||||
{
|
{
|
||||||
// We need to get the starting point of the zig-zag pattern. We have to
|
// 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;
|
return was_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SupportTreeAlgorithm::connect_to_nearpillar(const Head &head,
|
bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
||||||
long nearpillar_id)
|
long nearpillar_id)
|
||||||
{
|
{
|
||||||
auto nearpillar = [this, nearpillar_id]() {
|
auto nearpillar = [this, nearpillar_id]() {
|
||||||
|
@ -514,7 +514,7 @@ bool SupportTreeAlgorithm::connect_to_nearpillar(const Head &head,
|
||||||
return true;
|
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();
|
PointIndex spindex = m_pillar_index.guarded_clone();
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ bool SupportTreeAlgorithm::search_pillar_and_connect(const Head &head)
|
||||||
return nearest_id >= 0;
|
return nearest_id >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeAlgorithm::create_ground_pillar(const Vec3d &jp,
|
void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id)
|
long head_id)
|
||||||
|
@ -661,7 +661,7 @@ void SupportTreeAlgorithm::create_ground_pillar(const Vec3d &jp,
|
||||||
m_pillar_index.guarded_insert(endp, unsigned(pillar_id));
|
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
|
// Get the points that are too close to each other and keep only the
|
||||||
// first one
|
// first one
|
||||||
|
@ -806,7 +806,7 @@ void SupportTreeAlgorithm::filter()
|
||||||
m_thr();
|
m_thr();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeAlgorithm::add_pinheads()
|
void SupportTreeBuildsteps::add_pinheads()
|
||||||
{
|
{
|
||||||
for (unsigned i : m_iheads) {
|
for (unsigned i : m_iheads) {
|
||||||
m_thr();
|
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
|
// We should first get the heads that reach the ground directly
|
||||||
PtIndices ground_head_indices;
|
PtIndices ground_head_indices;
|
||||||
|
@ -872,7 +872,7 @@ void SupportTreeAlgorithm::classify()
|
||||||
m_cfg.max_bridges_on_pillar);
|
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;
|
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.
|
// 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
|
// 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.
|
// Now comes the algorithm that connects pillars with each other.
|
||||||
// Ideally every pillar should be connected with at least one of its
|
// 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
|
// For now we will just generate smaller headless sticks with a sharp
|
||||||
// ending point that connects to the mesh surface.
|
// ending point that connects to the mesh surface.
|
|
@ -142,7 +142,7 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||||
return (DoubleI(g) << shift) + l;
|
return (DoubleI(g) << shift) + l;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SupportTreeAlgorithm {
|
class SupportTreeBuildsteps {
|
||||||
const SupportConfig& m_cfg;
|
const SupportConfig& m_cfg;
|
||||||
const EigenMesh3D& m_mesh;
|
const EigenMesh3D& m_mesh;
|
||||||
const std::vector<SupportPoint>& m_support_pts;
|
const std::vector<SupportPoint>& m_support_pts;
|
||||||
|
@ -227,15 +227,9 @@ class SupportTreeAlgorithm {
|
||||||
void create_ground_pillar(const Vec3d &jp,
|
void create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id = ID_UNSET);
|
long head_id = ID_UNSET);
|
||||||
|
|
||||||
SupportTreeAlgorithm(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SupportTreeAlgorithm(const SupportTreeAlgorithm &) = delete;
|
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||||
SupportTreeAlgorithm(SupportTreeAlgorithm &&) = delete;
|
|
||||||
SupportTreeAlgorithm& operator=(const SupportTreeAlgorithm &) = delete;
|
|
||||||
SupportTreeAlgorithm& operator=(SupportTreeAlgorithm &&) = delete;
|
|
||||||
|
|
||||||
// Now let's define the individual steps of the support generation algorithm
|
// Now let's define the individual steps of the support generation algorithm
|
||||||
|
|
|
@ -1176,9 +1176,8 @@ void SLAPrint::process()
|
||||||
{
|
{
|
||||||
ClipperPolygon poly;
|
ClipperPolygon poly;
|
||||||
|
|
||||||
// We need to reverse if flpXY OR is_lefthanded is true but
|
// We need to reverse if is_lefthanded is true but
|
||||||
// not if both are true which is a logical inequality (XOR)
|
bool needreverse = is_lefthanded;
|
||||||
bool needreverse = /*flpXY !=*/ is_lefthanded;
|
|
||||||
|
|
||||||
// should be a move
|
// should be a move
|
||||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||||
|
@ -1401,11 +1400,9 @@ void SLAPrint::process()
|
||||||
|
|
||||||
SpinMutex slck;
|
SpinMutex slck;
|
||||||
|
|
||||||
auto orientation = get_printer_orientation();
|
|
||||||
|
|
||||||
// procedure to process one height level. This will run in parallel
|
// procedure to process one height level. This will run in parallel
|
||||||
auto lvlfn =
|
auto lvlfn =
|
||||||
[this, &slck, &printer, increment, &dstatus, &pst, orientation]
|
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||||
(unsigned level_id)
|
(unsigned level_id)
|
||||||
{
|
{
|
||||||
if(canceled()) return;
|
if(canceled()) return;
|
||||||
|
@ -1416,7 +1413,7 @@ void SLAPrint::process()
|
||||||
printer.begin_layer(level_id);
|
printer.begin_layer(level_id);
|
||||||
|
|
||||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
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.
|
// Finish the layer for later saving it.
|
||||||
printer.finish_layer(level_id);
|
printer.finish_layer(level_id);
|
||||||
|
@ -1644,7 +1641,6 @@ sla::RasterWriter & SLAPrint::init_printer()
|
||||||
sla::Raster::Resolution res;
|
sla::Raster::Resolution res;
|
||||||
sla::Raster::PixelDim pxdim;
|
sla::Raster::PixelDim pxdim;
|
||||||
std::array<bool, 2> mirror;
|
std::array<bool, 2> mirror;
|
||||||
double gamma;
|
|
||||||
|
|
||||||
double w = m_printer_config.display_width.getFloat();
|
double w = m_printer_config.display_width.getFloat();
|
||||||
double h = m_printer_config.display_height.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[X] = m_printer_config.display_mirror_x.getBool();
|
||||||
mirror[Y] = m_printer_config.display_mirror_y.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(w, h);
|
||||||
std::swap(pw, ph);
|
std::swap(pw, ph);
|
||||||
|
|
||||||
// XY flipping implicitly does an X mirror
|
|
||||||
mirror[X] = !mirror[X];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = sla::Raster::Resolution{pw, ph};
|
res = sla::Raster::Resolution{pw, ph};
|
||||||
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
||||||
|
sla::Raster::Trafo tr{orientation, mirror};
|
||||||
gamma = m_printer_config.gamma_correction.getFloat();
|
tr.gamma = m_printer_config.gamma_correction.getFloat();
|
||||||
|
|
||||||
m_printer.reset(new sla::RasterWriter(res, pxdim, mirror, gamma));
|
m_printer.reset(new sla::RasterWriter(res, pxdim, tr));
|
||||||
m_printer->set_config(m_full_print_config);
|
m_printer->set_config(m_full_print_config);
|
||||||
return *m_printer;
|
return *m_printer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -461,12 +461,11 @@ private:
|
||||||
|
|
||||||
sla::RasterWriter &init_printer();
|
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();
|
auto ro = m_printer_config.display_orientation.getInt();
|
||||||
return ro == sla::RasterWriter::roPortrait ?
|
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
|
||||||
sla::RasterWriter::roPortrait :
|
sla::Raster::roLandscape;
|
||||||
sla::RasterWriter::roLandscape;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend SLAPrintObject;
|
friend SLAPrintObject;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
add_executable(sla_print_tests sla_print_tests_main.cpp)
|
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})
|
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>
|
#include <map>
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
|
@ -8,8 +11,9 @@
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "libslic3r/SLA/SLAPad.hpp"
|
#include "libslic3r/SLA/SLAPad.hpp"
|
||||||
#include "libslic3r/SLA/SLASupportTreeBuilder.hpp"
|
#include "libslic3r/SLA/SLASupportTreeBuilder.hpp"
|
||||||
#include "libslic3r/SLA/SLASupportTreeAlgorithm.hpp"
|
#include "libslic3r/SLA/SLASupportTreeBuildsteps.hpp"
|
||||||
#include "libslic3r/SLA/SLAAutoSupports.hpp"
|
#include "libslic3r/SLA/SLAAutoSupports.hpp"
|
||||||
|
#include "libslic3r/SLA/SLARaster.hpp"
|
||||||
#include "libslic3r/MTUtils.hpp"
|
#include "libslic3r/MTUtils.hpp"
|
||||||
|
|
||||||
#include "libslic3r/SVG.hpp"
|
#include "libslic3r/SVG.hpp"
|
||||||
|
@ -86,7 +90,7 @@ void test_pad(const std::string & obj_filename,
|
||||||
|
|
||||||
ASSERT_FALSE(out.model_contours.empty());
|
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);
|
Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
|
||||||
|
|
||||||
check_validity(out.mesh);
|
check_validity(out.mesh);
|
||||||
|
@ -369,6 +373,180 @@ TEST(SLASupportGeneration, SupportsDoNotPierceModel) {
|
||||||
test_support_model_collision(fname, supportcfg);
|
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) {
|
int main(int argc, char **argv) {
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
|
|
Loading…
Add table
Reference in a new issue