SLA archive import with miniz, marching square bugfixes

Fix compilation on Windows


Fix array subscript out of range error in MarchingSquares


Fix normals of mesh constructed from slices


Improve performance of mesh construction from slices
This commit is contained in:
tamasmeszaros 2020-04-23 19:12:07 +02:00
parent 247fca6d55
commit 217477a9ff
17 changed files with 690 additions and 373 deletions

View file

@ -11,11 +11,11 @@ namespace marchsq {
// Marks a square in the grid
struct Coord {
size_t r = 0, c = 0;
long r = 0, c = 0;
Coord() = default;
explicit Coord(size_t s) : r(s), c(s) {}
Coord(size_t _r, size_t _c): r(_r), c(_c) {}
explicit Coord(long s) : r(s), c(s) {}
Coord(long _r, long _c): r(_r), c(_c) {}
size_t seq(const Coord &res) const { return r * res.c + c; }
Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; }
@ -52,11 +52,6 @@ namespace __impl {
template<class T> using RasterTraits = _RasterTraits<std::decay_t<T>>;
template<class T> using TRasterValue = typename RasterTraits<T>::ValueType;
template<class T> TRasterValue<T> isoval(const T &raster, const Coord &crd)
{
return RasterTraits<T>::get(raster, crd.r, crd.c);
}
template<class T> size_t rows(const T &raster)
{
return RasterTraits<T>::rows(raster);
@ -67,6 +62,11 @@ template<class T> size_t cols(const T &raster)
return RasterTraits<T>::cols(raster);
}
template<class T> TRasterValue<T> isoval(const T &rst, const Coord &crd)
{
return RasterTraits<T>::get(rst, crd.r, crd.c);
}
template<class ExecutionPolicy, class It, class Fn>
void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn)
{
@ -142,55 +142,46 @@ inline Coord step(const Coord &crd, Dir d)
template<class Rst> class Grid {
const Rst * m_rst = nullptr;
Coord m_cellsize, m_res_1, m_window, m_gridsize;
Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1;
std::vector<uint8_t> m_tags; // Assign tags to each square
Coord rastercoord(const Coord &crd) const
{
return {crd.r * m_window.r, crd.c * m_window.c};
return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c};
}
Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; }
Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; }
Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; }
Coord tl(const Coord &crd) const { return rastercoord(crd); }
TRasterValue<Rst> bottomleft(const Coord &cell) const
{
return isoval(*m_rst, bl(cell));
}
TRasterValue<Rst> bottomright(const Coord &cell) const
bool is_within(const Coord &crd)
{
return isoval(*m_rst, br(cell));
}
TRasterValue<Rst> topright(const Coord &cell) const
{
return isoval(*m_rst, tr(cell));
}
TRasterValue<Rst> topleft(const Coord &cell) const
{
return isoval(*m_rst, tl(cell));
}
long R = rows(*m_rst), C = cols(*m_rst);
return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C;
};
// Calculate the tag for a cell (or square). The cell coordinates mark the
// top left vertex of a square in the raster. v is the isovalue
uint8_t get_tag_for_cell(const Coord &cell, TRasterValue<Rst> v)
{
uint8_t t = (bottomleft(cell) >= v) +
((bottomright(cell) >= v) << 1) +
((topright(cell) >= v) << 2) +
((topleft(cell) >= v) << 3);
{
Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)};
uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) +
((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) +
((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) +
((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3);
assert(t < 16);
return t;
}
// Get a cell coordinate from a sequential index
Coord coord(size_t i) const { return {i / m_gridsize.c, i % m_gridsize.c}; }
Coord coord(size_t i) const
{
return {long(i) / m_gridsize.c, long(i) % m_gridsize.c};
}
size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); }
bool is_visited(size_t idx, Dir d = Dir::none) const
@ -217,7 +208,7 @@ template<class Rst> class Grid {
{
// Skip ambiguous tags as starting tags due to unknown previous
// direction.
while ((i < m_tags.size() && is_visited(i)) || is_ambiguous(i)) ++i;
while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i;
return i;
}
@ -248,8 +239,9 @@ template<class Rst> class Grid {
}
struct CellIt {
Coord crd; Dir dir= Dir::none; const Rst *rst = nullptr;
TRasterValue<Rst> operator*() const { return isoval(*rst, crd); }
Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr;
TRasterValue<Rst> operator*() const { return isoval(*grid, crd); }
CellIt& operator++() { crd = step(crd, dir); return *this; }
CellIt operator++(int) { CellIt it = *this; ++(*this); return it; }
bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; }
@ -265,7 +257,7 @@ template<class Rst> class Grid {
// used for binary search for the first active pixel on the edge.
struct Edge { CellIt from, to; };
Edge edge(const Coord &ringvertex)
Edge _edge(const Coord &ringvertex) const
{
size_t idx = ringvertex.r;
Coord cell = coord(idx);
@ -312,6 +304,28 @@ template<class Rst> class Grid {
return {};
}
Edge edge(const Coord &ringvertex) const
{
const long R = rows(*m_rst), C = cols(*m_rst);
const long R_1 = R - 1, C_1 = C - 1;
Edge e = _edge(ringvertex);
e.to.dir = e.from.dir;
++e.to;
e.from.crd.r = std::min(e.from.crd.r, R_1);
e.from.crd.r = std::max(e.from.crd.r, 0l);
e.from.crd.c = std::min(e.from.crd.c, C_1);
e.from.crd.c = std::max(e.from.crd.c, 0l);
e.to.crd.r = std::min(e.to.crd.r, R);
e.to.crd.r = std::max(e.to.crd.r, 0l);
e.to.crd.c = std::min(e.to.crd.c, C);
e.to.crd.c = std::max(e.to.crd.c, 0l);
return e;
}
public:
explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap)
: m_rst{&rst}
@ -319,8 +333,8 @@ public:
, m_res_1{m_cellsize.r - 1, m_cellsize.c - 1}
, m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r,
overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c}
, m_gridsize{(rows(rst) - overlap.r) / m_window.r,
(cols(rst) - overlap.c) / m_window.c}
, m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r,
2 + (long(cols(rst)) - overlap.c) / m_window.c}
, m_tags(m_gridsize.r * m_gridsize.c, 0)
{}
@ -350,7 +364,7 @@ public:
Dir prev = Dir::none, next = next_dir(prev, get_tag(idx));
while (next != Dir::none && !is_visited(idx, prev)) {
Coord ringvertex{idx, size_t(next)};
Coord ringvertex{long(idx), long(next)};
ring.emplace_back(ringvertex);
set_visited(idx, prev);
@ -379,9 +393,11 @@ public:
TRasterValue<Rst> isov)
{
for_each(std::forward<ExecutionPolicy>(policy),
rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) {
rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t)
{
for (Coord &ringvertex : ring) {
Edge e = edge(ringvertex);
CellIt found = std::lower_bound(e.from, e.to, isov);
ringvertex = found.crd;
}
@ -401,7 +417,7 @@ std::vector<marchsq::Ring> execute_with_policy(ExecutionPolicy && policy,
if (!windowsize.r) windowsize.r = 2;
if (!windowsize.c)
windowsize.c = std::max(size_t(2), windowsize.r * ratio);
windowsize.c = std::max(2l, long(windowsize.r * ratio));
Coord overlap{1};

View file

@ -33,15 +33,15 @@ template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
for (auto &p : h.points) fn(p);
}
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy)
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize)
{
size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px;
if (rows < 2 || cols < 2) return {};
Polygons polys;
size_t w_rows = (2 + rows / 8) - size_t(accuracy * rows / 8);
size_t w_cols = std::max(size_t(2), w_rows * cols / rows);
long w_rows = std::max(2l, long(windowsize.y()));
long w_cols = std::max(2l, long(windowsize.x()));
std::vector<marchsq::Ring> rings =
marchsq::execute(rst, 128, {w_rows, w_cols});
@ -49,6 +49,9 @@ ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy)
polys.reserve(rings.size());
auto pxd = rst.pixel_dimensions();
pxd.w_mm = (rst.resolution().width_px * pxd.w_mm) / (rst.resolution().width_px - 1);
pxd.h_mm = (rst.resolution().height_px * pxd.h_mm) / (rst.resolution().height_px - 1);
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
pts.reserve(ring.size());

View file

@ -8,8 +8,8 @@ namespace sla {
class RasterGrayscaleAA;
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy = 1.f);
ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize = {2, 2});
}}
}} // namespace Slic3r::sla
#endif // RASTERTOPOLYGONS_HPP

View file

@ -1,23 +1,40 @@
#include "SlicesToTriangleMesh.hpp"
#include "libslic3r/TriangulateWall.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/SLA/Contour3D.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Tesselate.hpp"
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
namespace Slic3r {
inline sla::Contour3D walls(const Polygon &lower,
const Polygon &upper,
double lower_z_mm,
double upper_z_mm)
inline sla::Contour3D wall_strip(const Polygon &poly,
double lower_z_mm,
double upper_z_mm)
{
Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm);
sla::Contour3D ret;
ret.points = std::move(w.first);
ret.faces3 = std::move(w.second);
size_t startidx = ret.points.size();
size_t offs = poly.points.size();
ret.points.reserve(ret.points.size() + 2 *offs);
for (const Point &p : poly.points)
ret.points.emplace_back(to_3d(unscaled(p), lower_z_mm));
for (const Point &p : poly.points)
ret.points.emplace_back(to_3d(unscaled(p), upper_z_mm));
for (size_t i = startidx + 1; i < startidx + offs; ++i) {
ret.faces3.emplace_back(i - 1, i, i + offs - 1);
ret.faces3.emplace_back(i, i + offs, i + offs - 1);
}
ret.faces3.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1);
ret.faces3.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1);
return ret;
}
@ -27,7 +44,7 @@ sla::Contour3D inline straight_walls(const Polygon &plate,
double lo_z,
double hi_z)
{
return walls(plate, plate, lo_z, hi_z);
return wall_strip(plate, lo_z, hi_z);
}
sla::Contour3D inline straight_walls(const ExPolygon &plate,
@ -43,7 +60,7 @@ sla::Contour3D inline straight_walls(const ExPolygon &plate,
sla::Contour3D inline straight_walls(const ExPolygons &slice,
double lo_z,
double hi_z)
{
{
sla::Contour3D ret;
for (const ExPolygon &poly : slice)
ret.merge(straight_walls(poly, lo_z, hi_z));
@ -51,32 +68,60 @@ sla::Contour3D inline straight_walls(const ExPolygons &slice,
return ret;
}
sla::Contour3D slices_to_triangle_mesh(const std::vector<ExPolygons> &slices,
double zmin,
const std::vector<float> & grid)
{
assert(slices.size() == grid.size());
using Layers = std::vector<sla::Contour3D>;
std::vector<sla::Contour3D> layers(slices.size());
size_t len = slices.size() - 1;
tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) {
const ExPolygons &upper = slices[i + 1];
const ExPolygons &lower = slices[i];
ExPolygons dff1 = diff_ex(lower, upper);
ExPolygons dff2 = diff_ex(upper, lower);
layers[i].merge(triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP));
layers[i].merge(triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN));
layers[i].merge(straight_walls(upper, grid[i], grid[i + 1]));
});
sla::Contour3D ret = tbb::parallel_reduce(
tbb::blocked_range(layers.begin(), layers.end()),
sla::Contour3D{},
[](const tbb::blocked_range<Layers::iterator>& r, sla::Contour3D init) {
for(auto it = r.begin(); it != r.end(); ++it ) init.merge(*it);
return init;
},
[]( const sla::Contour3D &a, const sla::Contour3D &b ) {
sla::Contour3D res{a}; res.merge(b); return res;
});
ret.merge(triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN));
ret.merge(straight_walls(slices.front(), zmin, grid.front()));
ret.merge(triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP));
return ret;
}
void slices_to_triangle_mesh(TriangleMesh & mesh,
const std::vector<ExPolygons> &slices,
double zmin,
double lh,
double ilh)
{
sla::Contour3D cntr3d;
double h = zmin;
std::vector<sla::Contour3D> wall_meshes(slices.size());
std::vector<float> grid(slices.size(), zmin + ilh);
auto it = slices.begin(), xt = std::next(it);
cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_DOWN));
cntr3d.merge(straight_walls(*it, h, h + ilh));
h += ilh;
while (xt != slices.end()) {
ExPolygons dff1 = diff_ex(*it, *xt);
ExPolygons dff2 = diff_ex(*xt, *it);
cntr3d.merge(triangulate_expolygons_3d(dff1, h, NORMALS_UP));
cntr3d.merge(triangulate_expolygons_3d(dff2, h, NORMALS_UP));
cntr3d.merge(straight_walls(*xt, h, h + lh));
h += lh;
++it; ++xt;
}
for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh;
cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_UP));
mesh.merge(sla::to_triangle_mesh(cntr3d));
sla::Contour3D cntr = slices_to_triangle_mesh(slices, zmin, grid);
mesh.merge(sla::to_triangle_mesh(cntr));
mesh.repaired = true;
mesh.require_shared_vertices();
}

View file

@ -17,90 +17,14 @@
namespace Slic3r {
class Zipper::Impl {
class Zipper::Impl: public MZ_Archive {
public:
mz_zip_archive arch;
std::string m_zipname;
static std::string get_errorstr(mz_zip_error mz_err)
{
switch (mz_err)
{
case MZ_ZIP_NO_ERROR:
return "no error";
case MZ_ZIP_UNDEFINED_ERROR:
return L("undefined error");
case MZ_ZIP_TOO_MANY_FILES:
return L("too many files");
case MZ_ZIP_FILE_TOO_LARGE:
return L("file too large");
case MZ_ZIP_UNSUPPORTED_METHOD:
return L("unsupported method");
case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
return L("unsupported encryption");
case MZ_ZIP_UNSUPPORTED_FEATURE:
return L("unsupported feature");
case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
return L("failed finding central directory");
case MZ_ZIP_NOT_AN_ARCHIVE:
return L("not a ZIP archive");
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
return L("invalid header or archive is corrupted");
case MZ_ZIP_UNSUPPORTED_MULTIDISK:
return L("unsupported multidisk archive");
case MZ_ZIP_DECOMPRESSION_FAILED:
return L("decompression failed or archive is corrupted");
case MZ_ZIP_COMPRESSION_FAILED:
return L("compression failed");
case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
return L("unexpected decompressed size");
case MZ_ZIP_CRC_CHECK_FAILED:
return L("CRC-32 check failed");
case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
return L("unsupported central directory size");
case MZ_ZIP_ALLOC_FAILED:
return L("allocation failed");
case MZ_ZIP_FILE_OPEN_FAILED:
return L("file open failed");
case MZ_ZIP_FILE_CREATE_FAILED:
return L("file create failed");
case MZ_ZIP_FILE_WRITE_FAILED:
return L("file write failed");
case MZ_ZIP_FILE_READ_FAILED:
return L("file read failed");
case MZ_ZIP_FILE_CLOSE_FAILED:
return L("file close failed");
case MZ_ZIP_FILE_SEEK_FAILED:
return L("file seek failed");
case MZ_ZIP_FILE_STAT_FAILED:
return L("file stat failed");
case MZ_ZIP_INVALID_PARAMETER:
return L("invalid parameter");
case MZ_ZIP_INVALID_FILENAME:
return L("invalid filename");
case MZ_ZIP_BUF_TOO_SMALL:
return L("buffer too small");
case MZ_ZIP_INTERNAL_ERROR:
return L("internal error");
case MZ_ZIP_FILE_NOT_FOUND:
return L("file not found");
case MZ_ZIP_ARCHIVE_TOO_LARGE:
return L("archive is too large");
case MZ_ZIP_VALIDATION_FAILED:
return L("validation failed");
case MZ_ZIP_WRITE_CALLBACK_FAILED:
return L("write calledback failed");
default:
break;
}
return "unknown error";
}
std::string formatted_errorstr() const
{
return L("Error with zip archive") + " " + m_zipname + ": " +
get_errorstr(arch.m_last_error) + "!";
get_errorstr() + "!";
}
SLIC3R_NORETURN void blow_up() const

View file

@ -28,7 +28,7 @@ public:
// Will blow up in a runtime exception if the file cannot be created.
explicit Zipper(const std::string& zipfname,
e_compression level = NO_COMPRESSION);
e_compression level = FAST_COMPRESSION);
~Zipper();
// No copies allwed, this is a file resource...

View file

@ -1,9 +1,17 @@
#include <exception>
#include "miniz_extension.hpp"
#if defined(_MSC_VER) || defined(__MINGW64__)
#include "boost/nowide/cstdio.hpp"
#endif
#include "I18N.hpp"
//! macro used to mark string used at localization,
//! return same string
#define L(s) Slic3r::I18N::translate(s)
namespace Slic3r {
namespace {
@ -68,4 +76,84 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname)
bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); }
bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); }
MZ_Archive::MZ_Archive()
{
mz_zip_zero_struct(&arch);
}
std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
{
switch (mz_err)
{
case MZ_ZIP_NO_ERROR:
return "no error";
case MZ_ZIP_UNDEFINED_ERROR:
return L("undefined error");
case MZ_ZIP_TOO_MANY_FILES:
return L("too many files");
case MZ_ZIP_FILE_TOO_LARGE:
return L("file too large");
case MZ_ZIP_UNSUPPORTED_METHOD:
return L("unsupported method");
case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
return L("unsupported encryption");
case MZ_ZIP_UNSUPPORTED_FEATURE:
return L("unsupported feature");
case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
return L("failed finding central directory");
case MZ_ZIP_NOT_AN_ARCHIVE:
return L("not a ZIP archive");
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
return L("invalid header or archive is corrupted");
case MZ_ZIP_UNSUPPORTED_MULTIDISK:
return L("unsupported multidisk archive");
case MZ_ZIP_DECOMPRESSION_FAILED:
return L("decompression failed or archive is corrupted");
case MZ_ZIP_COMPRESSION_FAILED:
return L("compression failed");
case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
return L("unexpected decompressed size");
case MZ_ZIP_CRC_CHECK_FAILED:
return L("CRC-32 check failed");
case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
return L("unsupported central directory size");
case MZ_ZIP_ALLOC_FAILED:
return L("allocation failed");
case MZ_ZIP_FILE_OPEN_FAILED:
return L("file open failed");
case MZ_ZIP_FILE_CREATE_FAILED:
return L("file create failed");
case MZ_ZIP_FILE_WRITE_FAILED:
return L("file write failed");
case MZ_ZIP_FILE_READ_FAILED:
return L("file read failed");
case MZ_ZIP_FILE_CLOSE_FAILED:
return L("file close failed");
case MZ_ZIP_FILE_SEEK_FAILED:
return L("file seek failed");
case MZ_ZIP_FILE_STAT_FAILED:
return L("file stat failed");
case MZ_ZIP_INVALID_PARAMETER:
return L("invalid parameter");
case MZ_ZIP_INVALID_FILENAME:
return L("invalid filename");
case MZ_ZIP_BUF_TOO_SMALL:
return L("buffer too small");
case MZ_ZIP_INTERNAL_ERROR:
return L("internal error");
case MZ_ZIP_FILE_NOT_FOUND:
return L("file not found");
case MZ_ZIP_ARCHIVE_TOO_LARGE:
return L("archive is too large");
case MZ_ZIP_VALIDATION_FAILED:
return L("validation failed");
case MZ_ZIP_WRITE_CALLBACK_FAILED:
return L("write calledback failed");
default:
break;
}
return "unknown error";
}
} // namespace Slic3r

View file

@ -11,6 +11,25 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip);
}
class MZ_Archive {
public:
mz_zip_archive arch;
MZ_Archive();
static std::string get_errorstr(mz_zip_error mz_err);
std::string get_errorstr() const
{
return get_errorstr(arch.m_last_error) + "!";
}
bool is_alive() const
{
return arch.m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
}
};
} // namespace Slic3r
#endif // MINIZ_EXTENSION_HPP

View file

@ -182,8 +182,8 @@ set(SLIC3R_GUI_SOURCES
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/Thread.hpp
Utils/SLAZipFileImport.hpp
Utils/SLAZipFileImport.cpp
Utils/SLAImport.hpp
Utils/SLAImport.cpp
)
if (APPLE)

View file

@ -2071,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name)
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name)
{
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
const wxString name = _(L("Shape")) + "-" + _(type_name);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->add_instance(); // each object should have at list one instance
ModelVolume* new_volume = new_object->add_volume(mesh);
new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
new_object->center_around_origin();
new_object->ensure_on_bed();
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
paste_objects_into_list(object_idxs);
#ifdef _DEBUG

View file

@ -24,6 +24,7 @@ class ConfigOptionsGroup;
class DynamicPrintConfig;
class ModelObject;
class ModelVolume;
class TriangleMesh;
enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
@ -265,6 +266,7 @@ public:
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);

View file

@ -73,7 +73,7 @@
#include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp"
#include "../Utils/SLAZipFileImport.hpp"
#include "../Utils/SLAImport.hpp"
#include "RemovableDriveManager.hpp"
#if ENABLE_NON_STATIC_CANVAS_MANAGER
#ifdef __APPLE__
@ -4263,11 +4263,7 @@ void Plater::import_sl1_archive()
if (dlg.ShowModal() == wxID_OK) {
try {
TriangleMesh mesh = import_model_from_sla_zip(dlg.GetPath());
ModelObject * obj = p->model.add_object(wxFileName(dlg.GetPath()).GetName(), "", mesh);
if (obj) {
obj->add_instance();
update();
}
p->sidebar->obj_list()->load_mesh_object(mesh, wxFileName(dlg.GetPath()).GetName());
} catch (std::exception &ex) {
show_error(this, ex.what());
}

View file

@ -0,0 +1,314 @@
#include "SLAImport.hpp"
#include <sstream>
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/miniz_extension.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include <wx/image.h>
#include <wx/mstream.h>
namespace marchsq {
// Specialize this struct to register a raster type for the Marching squares alg
template<> struct _RasterTraits<wxImage> {
using Rst = wxImage;
// The type of pixel cell in the raster
using ValueType = uint8_t;
// Value at a given position
static uint8_t get(const Rst &rst, size_t row, size_t col)
{
return rst.GetRed(col, row);
}
// Number of rows and cols of the raster
static size_t rows(const Rst &rst) { return rst.GetHeight(); }
static size_t cols(const Rst &rst) { return rst.GetWidth(); }
};
} // namespace marchsq
namespace Slic3r {
namespace {
struct ArchiveData {
boost::property_tree::ptree profile, config;
std::vector<sla::EncodedRaster> images;
};
static const constexpr char *CONFIG_FNAME = "config.ini";
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip)
{
std::string buf(size_t(entry.m_uncomp_size), '\0');
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
boost::property_tree::ptree tree;
std::stringstream ss(buf);
boost::property_tree::read_ini(ss, tree);
return tree;
}
sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip,
const std::string & name)
{
std::vector<uint8_t> buf(entry.m_uncomp_size);
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
return sla::EncodedRaster(std::move(buf),
name.empty() ? entry.m_filename : name);
}
ArchiveData extract_sla_archive(const std::string &zipfname,
const std::string &exclude)
{
ArchiveData arch;
// Little RAII
struct Arch: public MZ_Archive {
Arch(const std::string &fname) {
if (!open_zip_reader(&arch, fname))
throw std::runtime_error(get_errorstr());
}
~Arch() { close_zip_reader(&arch); }
} zip (zipfname);
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
for (mz_uint i = 0; i < num_entries; ++i)
{
mz_zip_archive_file_stat entry;
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
{
std::string name = entry.m_filename;
boost::algorithm::to_lower(name);
if (boost::algorithm::contains(name, exclude)) continue;
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
if (boost::filesystem::path(name).extension().string() == ".png") {
auto it = std::lower_bound(
arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name),
[](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) {
return std::less<std::string>()(r1.extension(), r2.extension());
});
arch.images.insert(it, read_png(entry, zip, name));
}
}
}
return arch;
}
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h)
{
ExPolygons polys; polys.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
pts.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
polys.emplace_back(poly);
}
// reverse the raster transformations
return union_ex(polys);
}
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
{
for (auto &p : poly.contour.points) fn(p);
for (auto &h : poly.holes)
for (auto &p : h.points) fn(p);
}
void invert_raster_trafo(ExPolygons & expolys,
const sla::RasterBase::Trafo &trafo,
coord_t width,
coord_t height)
{
for (auto &expoly : expolys) {
if (trafo.mirror_y)
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
if (trafo.mirror_x)
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
expoly.translate(-trafo.center_x, -trafo.center_y);
if (trafo.flipXY)
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
expoly.contour.reverse();
for (auto &h : expoly.holes) h.reverse();
}
}
}
struct RasterParams {
sla::RasterBase::Trafo trafo; // Raster transformations
coord_t width, height; // scaled raster dimensions (not resolution)
double px_h, px_w; // pixel dimesions
marchsq::Coord win; // marching squares window size
};
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
{
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw std::runtime_error("Invalid SL1 file");
RasterParams rstp;
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ?
sla::RasterBase::roLandscape :
sla::RasterBase::roPortrait,
{opt_mirror_x->value, opt_mirror_y->value}};
rstp.height = scaled(opt_disp_h->value);
rstp.width = scaled(opt_disp_w->value);
return rstp;
}
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
{
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw std::runtime_error("Invalid SL1 file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}
std::vector<ExPolygons> extract_slices_from_sla_archive(
ArchiveData & arch,
const RasterParams & rstp,
std::function<bool(int)> progr)
{
auto jobdir = arch.config.get<std::string>("jobDir");
for (auto &c : jobdir) c = std::tolower(c);
std::vector<ExPolygons> slices(arch.images.size());
struct Status
{
double incr, val, prev;
bool stop = false;
tbb::spin_mutex mutex;
} st {100. / slices.size(), 0., 0.};
tbb::parallel_for(size_t(0), arch.images.size(),
[&arch, &slices, &st, &rstp, progr](size_t i) {
// Status indication guarded with the spinlock
{
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
if (st.stop) return;
st.val += st.incr;
double curr = std::round(st.val);
if (curr > st.prev) {
st.prev = curr;
st.stop = !progr(int(curr));
}
}
auto &buf = arch.images[i];
wxMemoryInputStream stream{buf.data(), buf.size()};
wxImage img{stream};
auto rings = marchsq::execute(img, 128, rstp.win);
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
// Invert the raster transformations indicated in
// the profile metadata
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
slices[i] = std::move(expolys);
});
if (st.stop) slices = {};
return slices;
}
} // namespace
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
{
ArchiveData arch = extract_sla_archive(zipfname, "png");
out.load(arch.profile);
}
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr)
{
// Ensure minimum window size for marching squares
windowsize.x() = std::max(2, windowsize.x());
windowsize.y() = std::max(2, windowsize.y());
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
profile.load(arch.profile);
RasterParams rstp = get_raster_params(profile);
rstp.win = {windowsize.y(), windowsize.x()};
SliceParams slicp = get_slice_params(profile);
std::vector<ExPolygons> slices =
extract_slices_from_sla_archive(arch, rstp, progr);
if (!slices.empty())
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
}
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef SLAIMPORT_HPP
#define SLAIMPORT_HPP
#include <functional>
#include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/PrintConfig.hpp>
namespace Slic3r {
class TriangleMesh;
class DynamicPrintConfig;
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
}
}
#endif // SLAIMPORT_HPP

View file

@ -1,144 +0,0 @@
#include "SLAZipFileImport.hpp"
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <wx/mstream.h>
#include <wx/sstream.h>
#include <wx/image.h>
#include <tbb/parallel_for.h>
#include <boost/property_tree/ini_parser.hpp>
namespace marchsq {
// Specialize this struct to register a raster type for the Marching squares alg
template<> struct _RasterTraits<wxImage> {
using Rst = wxImage;
// The type of pixel cell in the raster
using ValueType = uint8_t;
// Value at a given position
static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.GetRed(col, row); }
// Number of rows and cols of the raster
static size_t rows(const Rst &rst) { return rst.GetHeight(); }
static size_t cols(const Rst &rst) { return rst.GetWidth(); }
};
} // namespace marchsq
namespace Slic3r {
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h)
{
ExPolygons polys; polys.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
pts.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
polys.emplace_back(poly);
}
// reverse the raster transformations
return union_ex(polys);
}
TriangleMesh import_model_from_sla_zip(const wxString &zipfname)
{
wxFileInputStream in(zipfname);
wxZipInputStream zip(in, wxConvUTF8);
std::map<std::string, wxMemoryOutputStream> files;
while (auto entry = std::unique_ptr<wxZipEntry>(zip.GetNextEntry())) {
auto fname = wxFileName(entry->GetName());
wxString name_lo = fname.GetFullName().Lower();
if (fname.IsDir() || name_lo.Contains("thumbnail")) continue;
if (!zip.OpenEntry(*entry))
throw std::runtime_error("Cannot read archive");
wxMemoryOutputStream &stream = files[name_lo.ToStdString()];
zip.Read(stream);
std::cout << name_lo << " read bytes: " << zip.LastRead() << std::endl;
if (!zip.LastRead()) std::cout << zip.GetLastError() << std::endl;
}
using boost::property_tree::ptree;
auto load_ini = [&files](const std::string &key, ptree &tree) {
auto it = files.find(key);
if (it != files.end()) {
wxString str;
wxStringOutputStream oss{&str};
wxMemoryInputStream inp{it->second};
oss.Write(inp);
std::stringstream iss(str.ToStdString());
boost::property_tree::read_ini(iss, tree);
files.erase(it);
} else {
throw std::runtime_error(key + " is missing");
}
};
ptree profile_tree, config;
load_ini("prusaslicer.ini", profile_tree);
load_ini("config.ini", config);
DynamicPrintConfig profile;
profile.load(profile_tree);
size_t disp_cols = profile.opt_int("display_pixels_x");
size_t disp_rows = profile.opt_int("display_pixels_y");
double disp_w = profile.opt_float("display_width");
double disp_h = profile.opt_float("display_height");
double px_w = disp_w / disp_cols;
double px_h = disp_h / disp_rows;
auto jobdir = config.get<std::string>("jobDir");
for (auto &c : jobdir) c = std::tolower(c);
for (auto it = files.begin(); it != files.end();)
if (it->first.find(jobdir) == std::string::npos ||
wxFileName(it->first).GetExt().Lower() != "png")
it = files.erase(it);
else ++it;
std::vector<ExPolygons> slices(files.size());
size_t i = 0;
for (auto &item : files) {
wxMemoryOutputStream &imagedata = item.second;
wxMemoryInputStream stream{imagedata};
wxImage img{stream, "image/png"};
std::cout << img.GetWidth() << " " << img.GetHeight() << std::endl;
auto rings = marchsq::execute(img, 128);
slices[i++] = rings_to_expolygons(rings, px_w, px_h);
}
TriangleMesh out;
if (!slices.empty()) {
double lh = profile.opt_float("layer_height");
double ilh = profile.opt_float("initial_layer_height");
out = slices_to_triangle_mesh(slices, 0, lh, ilh);
}
return out;
}
} // namespace Slic3r

View file

@ -1,14 +0,0 @@
#ifndef SLAZIPFILEIMPORT_HPP
#define SLAZIPFILEIMPORT_HPP
#include "libslic3r/TriangleMesh.hpp"
#include <wx/string.h>
namespace Slic3r {
TriangleMesh import_model_from_sla_zip(const wxString &zipfname);
}
#endif // SLAZIPFILEIMPORT_HPP

View file

@ -1,3 +1,5 @@
#define NOMINMAX
#include <catch2/catch.hpp>
#include <test_utils.hpp>
@ -5,6 +7,7 @@
#include <libslic3r/MarchingSquares.hpp>
#include <libslic3r/SLA/RasterToPolygons.hpp>
#include <libslic3r/SLA/AGGRaster.hpp>
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/SVG.hpp>
@ -17,6 +20,11 @@
using namespace Slic3r;
static double area(const sla::RasterBase::PixelDim &pxd)
{
return pxd.w_mm * pxd.h_mm;
}
static Slic3r::sla::RasterGrayscaleAA create_raster(
const sla::RasterBase::Resolution &res,
double disp_w = 100.,
@ -26,10 +34,8 @@ static Slic3r::sla::RasterGrayscaleAA create_raster(
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::RasterBase::Trafo trafo;
// trafo.center_x = bb.center().x();
// trafo.center_y = bb.center().y();
// trafo.center_x = scaled(pixdim.w_mm);
// trafo.center_y = scaled(pixdim.h_mm);
trafo.center_x = bb.center().x();
trafo.center_y = bb.center().y();
return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)};
}
@ -78,10 +84,13 @@ static ExPolygons circle_with_hole(double r, Point center = {0, 0}) {
return {poly};
}
static const Vec2i W4x4 = {4, 4};
static const Vec2i W2x2 = {2, 2};
template<class Rst>
static void test_expolys(Rst && rst,
const ExPolygons & ref,
float accuracy,
Vec2i window,
const std::string &name = "test")
{
for (const ExPolygon &expoly : ref) rst.draw(expoly);
@ -90,12 +99,23 @@ static void test_expolys(Rst && rst,
out << rst.encode(sla::PNGRasterEncoder{});
out.close();
ExPolygons extracted = sla::raster_to_polygons(rst, accuracy);
ExPolygons extracted = sla::raster_to_polygons(rst, window);
SVG svg(name + ".svg");
svg.draw(extracted);
svg.draw(ref, "green");
svg.Close();
double max_rel_err = 0.1;
sla::RasterBase::PixelDim pxd = rst.pixel_dimensions();
double max_abs_err = area(pxd) * scaled(1.) * scaled(1.);
BoundingBox ref_bb;
for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box());
double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2));
max_displacement *= scaled<double>(1.) * scaled(1.);
REQUIRE(extracted.size() == ref.size());
for (size_t i = 0; i < ref.size(); ++i) {
REQUIRE(extracted[i].contour.is_counter_clockwise());
@ -104,7 +124,16 @@ static void test_expolys(Rst && rst,
for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise());
double refa = ref[i].area();
REQUIRE(std::abs(extracted[i].area() - refa) < 0.1 * refa);
double abs_err = std::abs(extracted[i].area() - refa);
double rel_err = abs_err / refa;
REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err));
BoundingBox bb;
for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box());
Point d = bb.center() - ref_bb.center();
REQUIRE(double(d.transpose() * d) <= max_displacement);
}
}
@ -130,22 +159,36 @@ TEST_CASE("Marching squares directions", "[MarchingSquares]") {
REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1);
}
TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]") {
auto rst = create_raster({4, 4}, 4., 4.);
ExPolygon rect = square(4);
SECTION("Full accuracy") {
test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc");
}
SECTION("Half accuracy") {
test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc");
}
}
TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
sla::RasterBase::PixelDim pixdim{1, 1};
// We need one additional row and column to detect edges
sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)};
sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
// Draw a triangle from individual pixels
rst.draw(square(1., {0500000, 0500000}));
rst.draw(square(1., {1500000, 0500000}));
rst.draw(square(1., {2500000, 0500000}));
rst.draw(square(1., {1500000, 1500000}));
rst.draw(square(1., {2500000, 1500000}));
rst.draw(square(1., {3500000, 1500000}));
rst.draw(square(1., {2500000, 2500000}));
rst.draw(square(1., {3500000, 2500000}));
rst.draw(square(1., {3500000, 3500000}));
std::fstream out("4x4.png", std::ios::out);
out << rst.encode(sla::PNGRasterEncoder{});
@ -222,73 +265,59 @@ TEST_CASE("Square with hole in the middle", "[MarchingSquares]") {
ExPolygons inp = {square_with_hole(50.)};
SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({100, 100}, 100., 100.), inp, 1.f, "square_with_hole_proportional_1x1_mm_px_full");
test_expolys(create_raster({100, 100}, 100., 100.), inp, W2x2, "square_with_hole_proportional_1x1_mm_px_full");
}
SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({100, 100}, 100., 100.), inp, .5f, "square_with_hole_proportional_1x1_mm_px_half");
test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half");
}
SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({150, 100}, 150., 100.), inp, 1.f, "square_with_hole_landsc_1x1_mm_px_full");
test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full");
}
SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({150, 100}, 150., 100.), inp, .5f, "square_with_hole_landsc_1x1_mm_px_half");
test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half");
}
SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") {
test_expolys(create_raster({100, 150}, 100., 150.), inp, 1.f, "square_with_hole_portrait_1x1_mm_px_full");
test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full");
}
SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") {
test_expolys(create_raster({100, 150}, 100., 150.), inp, .5f, "square_with_hole_portrait_1x1_mm_px_half");
test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half");
}
SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") {
test_expolys(create_raster({200, 200}, 100., 100.), inp, 1.f, "square_with_hole_proportional_2x2_mm_px_full");
test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full");
}
SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") {
test_expolys(create_raster({200, 200}, 100., 100.), inp, .5f, "square_with_hole_proportional_2x2_mm_px_half");
test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half");
}
SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") {
test_expolys(create_raster({50, 50}, 100., 100.), inp, 1.f, "square_with_hole_proportional_0.5x0.5_mm_px_full");
test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full");
}
SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") {
test_expolys(create_raster({50, 50}, 100., 100.), inp, .5f, "square_with_hole_proportional_0.5x0.5_mm_px_half");
test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half");
}
}
TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") {
using namespace Slic3r;
test_expolys(create_raster({100, 100}), circle_with_hole(25.), 1.f, "circle_with_hole");
}
static void recreate_object_from_slices(const std::string &objname, float lh) {
TriangleMesh mesh = load_model(objname);
mesh.require_shared_vertices();
auto bb = mesh.bounding_box();
std::vector<ExPolygons> layers;
slice_mesh(mesh, grid(float(bb.min.z()), float(bb.max.z()), lh), layers, 0.f, []{});
TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh));
out.require_shared_vertices();
out.WriteOBJFile("out_from_slices.obj");
test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole");
}
static void recreate_object_from_rasters(const std::string &objname, float lh) {
TriangleMesh mesh = load_model(objname);
auto bb = mesh.bounding_box();
// Vec3f tr = -bb.center().cast<float>();
// mesh.translate(tr.x(), tr.y(), tr.z());
// bb = mesh.bounding_box();
Vec3f tr = -bb.center().cast<float>();
mesh.translate(tr.x(), tr.y(), tr.z());
bb = mesh.bounding_box();
std::vector<ExPolygons> layers;
slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{});
@ -311,7 +340,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) {
ExPolygons layer_ = sla::raster_to_polygons(rst);
// float delta = scaled(std::min(rst.pixel_dimensions().h_mm,
// rst.pixel_dimensions().w_mm));
// rst.pixel_dimensions().w_mm)) / 2;
// layer_ = expolygons_simplify(layer_, delta);
@ -338,5 +367,5 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) {
}
TEST_CASE("Recreate object from rasters", "[SL1Import]") {
recreate_object_from_rasters("triang.obj", 0.05f);
recreate_object_from_rasters("frog_legs.obj", 0.05f);
}