diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp index a5aeb09537..d5f07fbde6 100644 --- a/src/libslic3r/MarchingSquares.hpp +++ b/src/libslic3r/MarchingSquares.hpp @@ -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 using RasterTraits = _RasterTraits>; template using TRasterValue = typename RasterTraits::ValueType; -template TRasterValue isoval(const T &raster, const Coord &crd) -{ - return RasterTraits::get(raster, crd.r, crd.c); -} - template size_t rows(const T &raster) { return RasterTraits::rows(raster); @@ -67,6 +62,11 @@ template size_t cols(const T &raster) return RasterTraits::cols(raster); } +template TRasterValue isoval(const T &rst, const Coord &crd) +{ + return RasterTraits::get(rst, crd.r, crd.c); +} + template 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 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 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 bottomleft(const Coord &cell) const - { - return isoval(*m_rst, bl(cell)); - } - TRasterValue bottomright(const Coord &cell) const + bool is_within(const Coord &crd) { - return isoval(*m_rst, br(cell)); - } - - TRasterValue topright(const Coord &cell) const - { - return isoval(*m_rst, tr(cell)); - } - - TRasterValue 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 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 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 Grid { } struct CellIt { - Coord crd; Dir dir= Dir::none; const Rst *rst = nullptr; - TRasterValue operator*() const { return isoval(*rst, crd); } + Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr; + + TRasterValue 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 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 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 isov) { for_each(std::forward(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 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}; diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp index 6b1d8992c1..cd84a3cb4a 100644 --- a/src/libslic3r/SLA/RasterToPolygons.cpp +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -33,15 +33,15 @@ template 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 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()); diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp index 131fe518ed..c0e1f41145 100644 --- a/src/libslic3r/SLA/RasterToPolygons.hpp +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -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 diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index bd0961d04a..d6a5469619 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -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 +#include + 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 &slices, + double zmin, + const std::vector & grid) +{ + assert(slices.size() == grid.size()); + + using Layers = std::vector; + std::vector 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& 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 &slices, double zmin, double lh, double ilh) { - sla::Contour3D cntr3d; - double h = zmin; + std::vector wall_meshes(slices.size()); + std::vector 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(); } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index ec9d3aa16d..02f022083b 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -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 diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index d203ea7b24..bbaf2f05e7 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -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... diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp index 17cc136fc4..76b4cb4e55 100644 --- a/src/libslic3r/miniz_extension.cpp +++ b/src/libslic3r/miniz_extension.cpp @@ -1,9 +1,17 @@ +#include + #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 diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp index 8d0967cbcc..006226bf24 100644 --- a/src/libslic3r/miniz_extension.hpp +++ b/src/libslic3r/miniz_extension.hpp @@ -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 diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c0853fb656..1f1284a9ed 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6297cace3c..1f2ce0221d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -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 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(), -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 diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 609411cd59..72e130737c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -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> &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); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 75ef327626..f9fe06a857 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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()); } diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp new file mode 100644 index 0000000000..442025a77d --- /dev/null +++ b/src/slic3r/Utils/SLAImport.cpp @@ -0,0 +1,314 @@ +#include "SLAImport.hpp" + +#include + +#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 +#include +#include + +#include +#include + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + 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 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 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()(r1.extension(), r2.extension()); + }); + + arch.images.insert(it, read_png(entry, zip, name)); + } + } + } + + return arch; +} + +ExPolygons rings_to_expolygons(const std::vector &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 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("display_pixels_x"); + auto *opt_disp_rows = cfg.option("display_pixels_y"); + auto *opt_disp_w = cfg.option("display_width"); + auto *opt_disp_h = cfg.option("display_height"); + auto *opt_mirror_x = cfg.option("display_mirror_x"); + auto *opt_mirror_y = cfg.option("display_mirror_y"); + auto *opt_orient = cfg.option>("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("layer_height"); + auto *opt_init_layerh = cfg.option("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 extract_slices_from_sla_archive( + ArchiveData & arch, + const RasterParams & rstp, + std::function progr) +{ + auto jobdir = arch.config.get("jobDir"); + for (auto &c : jobdir) c = std::tolower(c); + + std::vector 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 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 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 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 diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp new file mode 100644 index 0000000000..a819bd7e76 --- /dev/null +++ b/src/slic3r/Utils/SLAImport.hpp @@ -0,0 +1,36 @@ +#ifndef SLAIMPORT_HPP +#define SLAIMPORT_HPP + +#include + +#include +#include +#include + +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 progr = [](int) { return true; }); + +inline void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + std::function progr = [](int) { return true; }) +{ + DynamicPrintConfig profile; + import_sla_archive(zipfname, windowsize, out, profile, progr); +} + +} + +#endif // SLAIMPORT_HPP diff --git a/src/slic3r/Utils/SLAZipFileImport.cpp b/src/slic3r/Utils/SLAZipFileImport.cpp deleted file mode 100644 index 6543e86748..0000000000 --- a/src/slic3r/Utils/SLAZipFileImport.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include - -#include - -#include - -namespace marchsq { - -// Specialize this struct to register a raster type for the Marching squares alg -template<> struct _RasterTraits { - 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 &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 files; - - while (auto entry = std::unique_ptr(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("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 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 diff --git a/src/slic3r/Utils/SLAZipFileImport.hpp b/src/slic3r/Utils/SLAZipFileImport.hpp deleted file mode 100644 index 4e36f86f49..0000000000 --- a/src/slic3r/Utils/SLAZipFileImport.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SLAZIPFILEIMPORT_HPP -#define SLAZIPFILEIMPORT_HPP - -#include "libslic3r/TriangleMesh.hpp" - -#include - -namespace Slic3r { - -TriangleMesh import_model_from_sla_zip(const wxString &zipfname); - -} - -#endif // SLAZIPFILEIMPORT_HPP diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index ce1c0e3ff6..9912ff2ca7 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include @@ -5,6 +7,7 @@ #include #include + #include #include #include @@ -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 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(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 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(); -// mesh.translate(tr.x(), tr.y(), tr.z()); -// bb = mesh.bounding_box(); + Vec3f tr = -bb.center().cast(); + mesh.translate(tr.x(), tr.y(), tr.z()); + bb = mesh.bounding_box(); std::vector 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); }