diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 0f9df1e41..751df6fbf 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -42,6 +42,7 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/Utils.hpp" #include "PrusaSlicer.hpp" @@ -422,7 +423,8 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - + SL1Archive sla_archive(sla_print.printer_config()); + sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -462,7 +464,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_raster(outfile_final); + sla_archive.export_print(outfile_final, sla_print); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2dc18728c..eb0f87e50 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -77,6 +77,8 @@ add_library(libslic3r STATIC Format/PRUS.hpp Format/STL.cpp Format/STL.hpp + Format/SL1.hpp + Format/SL1.cpp GCode/Analyzer.cpp GCode/Analyzer.hpp GCode/ThumbnailData.cpp @@ -162,6 +164,8 @@ add_library(libslic3r STATIC SLAPrint.hpp Slicing.cpp Slicing.hpp + SlicesToTriangleMesh.hpp + SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp SupportMaterial.cpp @@ -177,6 +181,8 @@ add_library(libslic3r STATIC Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp + TriangulateWall.hpp + TriangulateWall.cpp utils.cpp Utils.hpp Time.cpp @@ -191,6 +197,7 @@ add_library(libslic3r STATIC SimplifyMesh.hpp SimplifyMeshImpl.hpp SimplifyMesh.cpp + MarchingSquares.hpp ${OpenVDBUtils_SOURCES} SLA/Common.hpp SLA/Common.cpp @@ -208,10 +215,11 @@ add_library(libslic3r STATIC SLA/Rotfinder.cpp SLA/BoostAdapter.hpp SLA/SpatIndex.hpp - SLA/Raster.hpp - SLA/Raster.cpp - SLA/RasterWriter.hpp - SLA/RasterWriter.cpp + SLA/RasterBase.hpp + SLA/RasterBase.cpp + SLA/AGGRaster.hpp + SLA/RasterToPolygons.hpp + SLA/RasterToPolygons.cpp SLA/ConcaveHull.hpp SLA/ConcaveHull.cpp SLA/Hollowing.hpp diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp new file mode 100644 index 000000000..ba5e89330 --- /dev/null +++ b/src/libslic3r/Format/SL1.cpp @@ -0,0 +1,171 @@ +#include "SL1.hpp" +#include "GCode/ThumbnailData.hpp" +#include "libslic3r/Time.hpp" + +#include +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +using ConfMap = std::map; + +namespace { + +std::string to_ini(const ConfMap &m) +{ + std::string ret; + for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; + + return ret; +} + +std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) +{ + std::string ret; + + if (cfg.has(key)) { + auto opt = cfg.option(key); + if (opt) ret = opt->serialize(); + } + + return ret; +} + +void fill_iniconf(ConfMap &m, const SLAPrint &print) +{ + auto &cfg = print.full_print_config(); + m["layerHeight"] = get_cfg_value(cfg, "layer_height"); + m["expTime"] = get_cfg_value(cfg, "exposure_time"); + m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); + m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); + m["printerModel"] = get_cfg_value(cfg, "printer_model"); + m["printerVariant"] = get_cfg_value(cfg, "printer_variant"); + m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); + m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); + m["fileCreationTimestamp"] = Utils::utc_timestamp(); + m["prusaSlicerVersion"] = SLIC3R_BUILD_ID; + + SLAPrintStatistics stats = print.print_statistics(); + // Set statistics values to the printer + + double used_material = (stats.objects_used_material + + stats.support_used_material) / 1000; + + int num_fade = print.default_object_config().faded_layers.getInt(); + num_fade = num_fade >= 0 ? num_fade : 0; + + m["usedMaterial"] = std::to_string(used_material); + m["numFade"] = std::to_string(num_fade); + m["numSlow"] = std::to_string(stats.slow_layers_count); + m["numFast"] = std::to_string(stats.fast_layers_count); + m["printTime"] = std::to_string(stats.estimated_print_time); + + m["action"] = "print"; +} + +void fill_slicerconf(ConfMap &m, const SLAPrint &print) +{ + using namespace std::literals::string_view_literals; + + // Sorted list of config keys, which shall not be stored into the ini. + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; + + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; + + auto &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) + m[key] = cfg.opt_serialize(key); + +} + +} // namespace + +uqptr SL1Archive::create_raster() const +{ + sla::RasterBase::Resolution res; + sla::RasterBase::PixelDim pxdim; + std::array mirror; + + double w = m_cfg.display_width.getFloat(); + double h = m_cfg.display_height.getFloat(); + auto pw = size_t(m_cfg.display_pixels_x.getInt()); + auto ph = size_t(m_cfg.display_pixels_y.getInt()); + + mirror[X] = m_cfg.display_mirror_x.getBool(); + mirror[Y] = m_cfg.display_mirror_y.getBool(); + + auto ro = m_cfg.display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + std::swap(pw, ph); + } + + res = sla::RasterBase::Resolution{pw, ph}; + pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + sla::RasterBase::Trafo tr{orientation, mirror}; + + double gamma = m_cfg.gamma_correction.getFloat(); + + return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); +} + +sla::EncodedRaster SL1Archive::encode_raster(const sla::RasterBase &rst) const +{ + return rst.encode(sla::PNGRasterEncoder()); +} + +void SL1Archive::export_print(Zipper& zipper, + const SLAPrint &print, + const std::string &prjname) +{ + std::string project = + prjname.empty() ? + boost::filesystem::path(zipper.get_filename()).stem().string() : + prjname; + + ConfMap iniconf, slicerconf; + fill_iniconf(iniconf, print); + + iniconf["jobDir"] = project; + + fill_slicerconf(slicerconf, print); + + try { + zipper.add_entry("config.ini"); + zipper << to_ini(iniconf); + zipper.add_entry("prusaslicer.ini"); + zipper << to_ini(slicerconf); + + size_t i = 0; + for (const sla::EncodedRaster &rst : m_layers) { + + std::string imgname = project + string_printf("%.5d", i++) + "." + + rst.extension(); + + zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); + } + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp new file mode 100644 index 000000000..1b9e95392 --- /dev/null +++ b/src/libslic3r/Format/SL1.hpp @@ -0,0 +1,44 @@ +#ifndef ARCHIVETRAITS_HPP +#define ARCHIVETRAITS_HPP + +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +class SL1Archive: public SLAPrinter { + SLAPrinterConfig m_cfg; + +protected: + uqptr create_raster() const override; + sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override; + +public: + + SL1Archive() = default; + explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} + explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} + + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); + void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, print, projectname); + } + + void apply(const SLAPrinterConfig &cfg) override + { + auto diff = m_cfg.diff(cfg); + if (!diff.empty()) { + m_cfg.apply_only(cfg, diff); + m_layers = {}; + } + } +}; + + +} // namespace Slic3r::sla + +#endif // ARCHIVETRAITS_HPP diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 170a6599c..294f9ae6f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -126,10 +126,10 @@ inline IntegerOnly> reserve_vector(I capacity) } /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template +template> inline std::vector linspace_vector(const ArithmeticOnly &start, const T &stop, - const IntegerOnly &n) + const I &n) { std::vector vals(n, T()); diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp new file mode 100644 index 000000000..a5aeb0953 --- /dev/null +++ b/src/libslic3r/MarchingSquares.hpp @@ -0,0 +1,432 @@ +#ifndef MARCHINGSQUARES_HPP +#define MARCHINGSQUARES_HPP + +#include +#include +#include +#include +#include + +namespace marchsq { + +// Marks a square in the grid +struct Coord { + size_t 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) {} + + 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; } + Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; } +}; + +// Closed ring of cell coordinates +using Ring = std::vector; + +// Specialize this struct to register a raster type for the Marching squares alg +template struct _RasterTraits { + + // The type of pixel cell in the raster + using ValueType = typename T::ValueType; + + // Value at a given position + static ValueType get(const T &raster, size_t row, size_t col); + + // Number of rows and cols of the raster + static size_t rows(const T &raster); + static size_t cols(const T &raster); +}; + +// Specialize this to use parellel loops within the algorithm +template struct _Loop { + template static void for_each(It from, It to, Fn &&fn) + { + for (auto it = from; it < to; ++it) fn(*it, size_t(it - from)); + } +}; + +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); +} + +template size_t cols(const T &raster) +{ + return RasterTraits::cols(raster); +} + +template +void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn) +{ + _Loop::for_each(from, to, fn); +} + +// Type of squares (tiles) depending on which vertices are inside an ROI +// The vertices would be marked a, b, c, d in counter clockwise order from the +// bottom left vertex of a square. +// d --- c +// | | +// | | +// a --- b +enum class SquareTag : uint8_t { +// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full +}; + +template constexpr std::underlying_type_t _t(E e) noexcept +{ + return static_cast>(e); +} + +enum class Dir: uint8_t { left, down, right, up, none}; + +static const constexpr Dir NEXT_CCW[] = { + /* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go) + /* 01 */ Dir::left, // SquareTag::a + /* 02 */ Dir::down, // SquareTag::b + /* 03 */ Dir::left, // SquareTag::ab + /* 04 */ Dir::right, // SquareTag::c + /* 05 */ Dir::none, // SquareTag::ac (ambiguous case) + /* 06 */ Dir::down, // SquareTag::bc + /* 07 */ Dir::left, // SquareTag::abc + /* 08 */ Dir::up, // SquareTag::d + /* 09 */ Dir::up, // SquareTag::ad + /* 10 */ Dir::none, // SquareTag::bd (ambiguous case) + /* 11 */ Dir::up, // SquareTag::abd + /* 12 */ Dir::right, // SquareTag::cd + /* 13 */ Dir::right, // SquareTag::acd + /* 14 */ Dir::down, // SquareTag::bcd + /* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go) +}; + +static const constexpr uint8_t PREV_CCW[] = { + /* 00 */ 1 << _t(Dir::none), + /* 01 */ 1 << _t(Dir::up), + /* 02 */ 1 << _t(Dir::left), + /* 03 */ 1 << _t(Dir::left), + /* 04 */ 1 << _t(Dir::down), + /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down), + /* 06 */ 1 << _t(Dir::down), + /* 07 */ 1 << _t(Dir::down), + /* 08 */ 1 << _t(Dir::right), + /* 09 */ 1 << _t(Dir::up), + /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right), + /* 11 */ 1 << _t(Dir::left), + /* 12 */ 1 << _t(Dir::right), + /* 13 */ 1 << _t(Dir::up), + /* 14 */ 1 << _t(Dir::right), + /* 15 */ 1 << _t(Dir::none) +}; + +const constexpr uint8_t DIRMASKS[] = { + /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00 +}; + +inline Coord step(const Coord &crd, Dir d) +{ + uint8_t dd = DIRMASKS[uint8_t(d)]; + return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)}; +} + +template class Grid { + const Rst * m_rst = nullptr; + Coord m_cellsize, m_res_1, m_window, m_gridsize; + 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}; + } + + 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 + { + 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)); + } + + // 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); + + 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}; } + + size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); } + + bool is_visited(size_t idx, Dir d = Dir::none) const + { + SquareTag t = get_tag(idx); + uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d)); + return t == SquareTag::full || t == SquareTag::none || + ((m_tags[idx] & 0xf0) >> 4) == ref; + } + + void set_visited(size_t idx, Dir d = Dir::none) + { + m_tags[idx] |= (1 << (_t(d)) << 4); + } + + bool is_ambiguous(size_t idx) const + { + SquareTag t = get_tag(idx); + return t == SquareTag::ac || t == SquareTag::bd; + } + + // Search for a new starting square + size_t search_start_cell(size_t i = 0) const + { + // Skip ambiguous tags as starting tags due to unknown previous + // direction. + while ((i < m_tags.size() && is_visited(i)) || is_ambiguous(i)) ++i; + + return i; + } + + SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); } + + Dir next_dir(Dir prev, SquareTag tag) const + { + // Treat ambiguous cases as two separate regions in one square. + switch (tag) { + case SquareTag::ac: + switch (prev) { + case Dir::down: return Dir::right; + case Dir::up: return Dir::left; + default: assert(false); return Dir::none; + } + case SquareTag::bd: + switch (prev) { + case Dir::right: return Dir::up; + case Dir::left: return Dir::down; + default: assert(false); return Dir::none; + } + default: + return NEXT_CCW[uint8_t(tag)]; + } + + return Dir::none; + } + + struct CellIt { + Coord crd; Dir dir= Dir::none; const Rst *rst = nullptr; + TRasterValue operator*() const { return isoval(*rst, 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; } + + using value_type = TRasterValue; + using pointer = TRasterValue *; + using reference = TRasterValue &; + using difference_type = long; + using iterator_category = std::forward_iterator_tag; + }; + + // Two cell iterators representing an edge of a square. This is then + // used for binary search for the first active pixel on the edge. + struct Edge { CellIt from, to; }; + + Edge edge(const Coord &ringvertex) + { + size_t idx = ringvertex.r; + Coord cell = coord(idx); + uint8_t tg = m_tags[ringvertex.r]; + SquareTag t = SquareTag(tg & 0x0f); + + switch (t) { + case SquareTag::a: + case SquareTag::ab: + case SquareTag::abc: + return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case SquareTag::b: + case SquareTag::bc: + case SquareTag::bcd: + return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case SquareTag::c: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::ac: + switch (Dir(ringvertex.c)) { + case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + default: assert(false); + } + case SquareTag::d: + case SquareTag::ad: + case SquareTag::abd: + return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + case SquareTag::bd: + switch (Dir(ringvertex.c)) { + case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + default: assert(false); + } + case SquareTag::cd: + case SquareTag::acd: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::full: + case SquareTag::none: { + Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}}; + return {{crd, Dir::none, m_rst}, crd}; + } + } + + return {}; + } + +public: + explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap) + : m_rst{&rst} + , m_cellsize{cellsz} + , 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_tags(m_gridsize.r * m_gridsize.c, 0) + {} + + // Go through the cells and mark them with the appropriate tag. + template + void tag_grid(ExecutionPolicy &&policy, TRasterValue isoval) + { + // parallel for r + for_each (std::forward(policy), + m_tags.begin(), m_tags.end(), + [this, isoval](uint8_t& tag, size_t idx) { + tag = get_tag_for_cell(coord(idx), isoval); + }); + } + + // Scan for the rings on the tagged grid. Each ring vertex stores the + // sequential index of the cell and the next direction (Dir). + // This info can be used later to calculate the exact raster coordinate. + std::vector scan_rings() + { + std::vector rings; + size_t startidx = 0; + while ((startidx = search_start_cell(startidx)) < m_tags.size()) { + Ring ring; + + size_t idx = startidx; + 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)}; + ring.emplace_back(ringvertex); + set_visited(idx, prev); + + idx = seq(step(coord(idx), next)); + prev = next; + next = next_dir(next, get_tag(idx)); + } + + // To prevent infinite loops in case of degenerate input + if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none); + + if (ring.size() > 1) { + ring.pop_back(); + rings.emplace_back(ring); + } + } + + return rings; + } + + // Calculate the exact raster position from the cells which store the + // sequantial index of the square and the next direction + template + void interpolate_rings(ExecutionPolicy && policy, + std::vector &rings, + TRasterValue isov) + { + for_each(std::forward(policy), + 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; + } + }); + } +}; + +template +std::vector execute_with_policy(ExecutionPolicy && policy, + const Raster & raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + if (!rows(raster) || !cols(raster)) return {}; + + size_t ratio = cols(raster) / rows(raster); + + if (!windowsize.r) windowsize.r = 2; + if (!windowsize.c) + windowsize.c = std::max(size_t(2), windowsize.r * ratio); + + Coord overlap{1}; + + Grid grid{raster, windowsize, overlap}; + + grid.tag_grid(std::forward(policy), isoval); + std::vector rings = grid.scan_rings(); + grid.interpolate_rings(std::forward(policy), rings, isoval); + + return rings; +} + +template +std::vector execute(const Raster &raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + return execute_with_policy(nullptr, raster, isoval, windowsize); +} + +} // namespace __impl + +using __impl::execute_with_policy; +using __impl::execute; + +} // namespace marchsq + +#endif // MARCHINGSQUARES_HPP diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp new file mode 100644 index 000000000..37baed9e8 --- /dev/null +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -0,0 +1,222 @@ +#ifndef AGGRASTER_HPP +#define AGGRASTER_HPP + +#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/MTUtils.hpp" +#include + +// For rasterizing +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Slic3r { + +inline const Polygon& contour(const ExPolygon& p) { return p.contour; } +inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } + +inline const Polygons& holes(const ExPolygon& p) { return p.holes; } +inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } + +namespace sla { + +template struct Colors { + static const Color White; + static const Color Black; +}; + +template const Color Colors::White = Color{255}; +template const Color Colors::Black = Color{0}; + +template*/> class Renderer, + class Rasterizer = agg::rasterizer_scanline_aa<>, + class Scanline = agg::scanline_p8> +class AGGRaster: public RasterBase { +public: + using TColor = typename PixelRenderer::color_type; + using TValue = typename TColor::value_type; + using TPixel = typename PixelRenderer::pixel_type; + using TRawBuffer = agg::rendering_buffer; + +protected: + + Resolution m_resolution; + PixelDim m_pxdim_scaled; // used for scaled coordinate polygons + + std::vector m_buf; + agg::rendering_buffer m_rbuf; + + PixelRenderer m_pixrenderer; + + agg::renderer_base m_raw_renderer; + Renderer> m_renderer; + + Trafo m_trafo; + Scanline m_scanlines; + Rasterizer m_rasterizer; + + void flipy(agg::path_storage &path) const + { + path.flip_y(0, double(m_resolution.height_px)); + } + + void flipx(agg::path_storage &path) const + { + path.flip_x(0, double(m_resolution.width_px)); + } + + double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } + double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } + agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } + double getPx(const ClipperLib::IntPoint &p) { return p.X * m_pxdim_scaled.w_mm; } + double getPy(const ClipperLib::IntPoint& p) { return p.Y * m_pxdim_scaled.h_mm; } + + template agg::path_storage _to_path(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPx(*it), getPy(*it)); + while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); + path.line_to(getPx(v.front()), getPy(v.front())); + + return path; + } + + template 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 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.center_x * m_pxdim_scaled.w_mm, + m_trafo.center_y * m_pxdim_scaled.h_mm); + + if(m_trafo.mirror_x) flipx(path); + if(m_trafo.mirror_y) flipy(path); + + return path; + } + + template void _draw(const P &poly) + { + m_rasterizer.reset(); + + m_rasterizer.add_path(to_path(contour(poly))); + for(auto& h : holes(poly)) m_rasterizer.add_path(to_path(h)); + + agg::render_scanlines(m_rasterizer, m_scanlines, m_renderer); + } + +public: + template AGGRaster(const Resolution &res, + const PixelDim & pd, + const Trafo & trafo, + const TColor & foreground, + const TColor & background, + GammaFn && gammafn) + : m_resolution(res) + , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) + , m_buf(res.pixels()) + , m_rbuf(reinterpret_cast(m_buf.data()), + unsigned(res.width_px), + unsigned(res.height_px), + int(res.width_px *PixelRenderer::num_components)) + , m_pixrenderer(m_rbuf) + , m_raw_renderer(m_pixrenderer) + , m_renderer(m_raw_renderer) + , m_trafo(trafo) + { + m_renderer.color(foreground); + clear(background); + + m_rasterizer.gamma(gammafn); + } + + Trafo trafo() const override { return m_trafo; } + Resolution resolution() const override { return m_resolution; } + PixelDim pixel_dimensions() const override + { + return {SCALING_FACTOR / m_pxdim_scaled.w_mm, + SCALING_FACTOR / m_pxdim_scaled.h_mm}; + } + + void draw(const ExPolygon &poly) override { _draw(poly); } + void draw(const ClipperLib::Polygon &poly) override { _draw(poly); } + + EncodedRaster encode(RasterEncoder encoder) const override + { + return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1); + } + + void clear(const TColor color) { m_raw_renderer.clear(color); } +}; + +/* + * Captures an anti-aliased monochrome canvas where vectorial + * polygons can be rasterized. Fill color is always white and the background is + * black. Contours are anti-aliased. + * + * A gamma function can be specified at compile time to make it more flexible. + */ +using _RasterGrayscaleAA = + AGGRaster; + +class RasterGrayscaleAA : public _RasterGrayscaleAA { + using Base = _RasterGrayscaleAA; + using typename Base::TColor; + using typename Base::TValue; +public: + template + RasterGrayscaleAA(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + GammaFn && fn) + : Base(res, pd, trafo, Colors::White, Colors::Black, + std::forward(fn)) + {} + + uint8_t read_pixel(size_t col, size_t row) const + { + static_assert(std::is_same::value, "Not grayscale pix"); + + uint8_t px; + Base::m_buf[row * Base::resolution().width_px + col].get(px); + return px; + } + + void clear() { Base::clear(Colors::Black); } +}; + +class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { +public: + RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + double gamma = 1.) + : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) + {} +}; + +}} // namespace Slic3r::sla + +#endif // AGGRASTER_HPP diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index cf1786758..d933ef5ed 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -11,6 +11,8 @@ #include "Tesselate.hpp" #include "MTUtils.hpp" +#include "TriangulateWall.hpp" + // For debugging: // #include // #include @@ -27,186 +29,27 @@ namespace Slic3r { namespace sla { namespace { -/// This function will return a triangulation of a sheet connecting an upper -/// and a lower plate given as input polygons. It will not triangulate the -/// plates themselves only the sheet. The caller has to specify the lower and -/// upper z levels in world coordinates as well as the offset difference -/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the -/// offset difference is negative, the resulting triangle orientation will be -/// reversed. -/// -/// IMPORTANT: This is not a universal triangulation algorithm. It assumes -/// that the lower and upper polygons are offsetted versions of the same -/// original polygon. In general, it assumes that one of the polygons is -/// completely inside the other. The offset difference is the reference -/// distance from the inner polygon's perimeter to the outer polygon's -/// perimeter. The real distance will be variable as the clipper offset has -/// different strategies (rounding, etc...). This algorithm should have -/// O(2n + 3m) complexity where n is the number of upper vertices and m is the -/// number of lower vertices. Contour3D walls( const Polygon &lower, const Polygon &upper, double lower_z_mm, - double upper_z_mm, - double offset_difference_mm, - ThrowOnCancel thr = [] {}) + double upper_z_mm) { + Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm); + Contour3D ret; - - if(upper.points.size() < 3 || lower.size() < 3) return ret; - - // The concept of the algorithm is relatively simple. It will try to find - // the closest vertices from the upper and the lower polygon and use those - // as starting points. Then it will create the triangles sequentially using - // an edge from the upper polygon and a vertex from the lower or vice versa, - // depending on the resulting triangle's quality. - // The quality is measured by a scalar value. So far it looks like it is - // enough to derive it from the slope of the triangle's two edges connecting - // the upper and the lower part. A reference slope is calculated from the - // height and the offset difference. - - // Offset in the index array for the ceiling - const auto offs = upper.points.size(); - - // Shorthand for the vertex arrays - auto& upts = upper.points, &lpts = lower.points; - auto& rpts = ret.points; auto& ind = ret.faces3; - - // If the Z levels are flipped, or the offset difference is negative, we - // will interpret that as the triangles normals should be inverted. - bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; - - // Copy the points into the mesh, convert them from 2D to 3D - rpts.reserve(upts.size() + lpts.size()); - ind.reserve(2 * upts.size() + 2 * lpts.size()); - for (auto &p : upts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); - for (auto &p : lpts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); - - // Create pointing indices into vertex arrays. u-upper, l-lower - size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; - - // Simple squared distance calculation. - auto distfn = [](const Vec3d& p1, const Vec3d& p2) { - auto p = p1 - p2; return p.transpose() * p; - }; - - // We need to find the closest point on lower polygon to the first point on - // the upper polygon. These will be our starting points. - double distmin = std::numeric_limits::max(); - for(size_t l = lidx; l < rpts.size(); ++l) { - thr(); - double d = distfn(rpts[l], rpts[uidx]); - if(d < distmin) { lidx = l; distmin = d; } - } - - // Set up lnextidx to be ahead of lidx in cyclic mode - lnextidx = lidx + 1; - if(lnextidx == rpts.size()) lnextidx = offs; - - // This will be the flip switch to toggle between upper and lower triangle - // creation mode - enum class Proceed { - UPPER, // A segment from the upper polygon and one vertex from the lower - LOWER // A segment from the lower polygon and one vertex from the upper - } proceed = Proceed::UPPER; - - // Flags to help evaluating loop termination. - bool ustarted = false, lstarted = false; - - // The variables for the fitness values, one for the actual and one for the - // previous. - double current_fit = 0, prev_fit = 0; - - // Every triangle of the wall has two edges connecting the upper plate with - // the lower plate. From the length of these two edges and the zdiff we - // can calculate the momentary squared offset distance at a particular - // position on the wall. The average of the differences from the reference - // (squared) offset distance will give us the driving fitness value. - const double offsdiff2 = std::pow(offset_difference_mm, 2); - const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); - - // Mark the current vertex iterator positions. If the iterators return to - // the same position, the loop can be terminated. - size_t uendidx = uidx, lendidx = lidx; - - do { thr(); // check throw if canceled - - prev_fit = current_fit; - - switch(proceed) { // proceed depending on the current state - case Proceed::UPPER: - if(!ustarted || uidx != uendidx) { // there are vertices remaining - // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[uidx]; - const Vec3d& p_low = rpts[lidx]; - const Vec3d& p_up2 = rpts[unextidx]; - - // Calculate fitness: the average of the two connecting edges - double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); - double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { // fit is worse than previously - proceed = Proceed::LOWER; - } else { // good to go, create the triangle - inverted - ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) - : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); - - // Increment the iterators, rotate if necessary - ++uidx; ++unextidx; - if(unextidx == offs) unextidx = 0; - if(uidx == offs) uidx = 0; - - ustarted = true; // mark the movement of the iterators - // so that the comparison to uendidx can be made correctly - } - } else proceed = Proceed::LOWER; - - break; - case Proceed::LOWER: - // Mode with lower segment, upper vertex. Same structure: - if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[lidx]; - const Vec3d& p_low2 = rpts[lnextidx]; - const Vec3d& p_up = rpts[uidx]; - - double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); - double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { - proceed = Proceed::UPPER; - } else { - inverted - ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) - : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); - - ++lidx; ++lnextidx; - if(lnextidx == rpts.size()) lnextidx = offs; - if(lidx == rpts.size()) lidx = offs; - - lstarted = true; - } - } else proceed = Proceed::UPPER; - - break; - } // end of switch - } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); - + ret.points = std::move(w.first); + ret.faces3 = std::move(w.second); + return ret; } // Same as walls() but with identical higher and lower polygons. Contour3D inline straight_walls(const Polygon &plate, double lo_z, - double hi_z, - ThrowOnCancel thr) + double hi_z) { - return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); + return walls(plate, plate, lo_z, hi_z); } // Function to cut tiny connector cavities for a given polygon. The input poly @@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, top_poly = pdiff.front(); double z_min = -cfg.wing_height, z_max = 0; - double offset_difference = -wing_distance; - pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, - offset_difference, thr)); - + pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max)); + thr(); pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); return true; @@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); if (bottom_poly.empty()) continue; - + thr(); + double z_min = -cfg.height, z_max = 0; - ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, - cfg.bottom_offset(), thr)); + ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min)); if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) z_max = -cfg.wing_height; for (auto &h : bottom_poly.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); } @@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, double z_max = 0., z_min = -cfg.height; for (const ExPolygon &pad_part : skeleton) { - ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); + thr(); + ret.merge(straight_walls(pad_part.contour, z_max, z_min)); for (auto &h : pad_part.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); } diff --git a/src/libslic3r/SLA/Raster.cpp b/src/libslic3r/SLA/Raster.cpp deleted file mode 100644 index 9b7f1db7d..000000000 --- a/src/libslic3r/SLA/Raster.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#ifndef SLARASTER_CPP -#define SLARASTER_CPP - -#include - -#include -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" -#include - -// For rasterizing -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -// Experimental minz image write: -#include - -namespace Slic3r { - -inline const Polygon& contour(const ExPolygon& p) { return p.contour; } -inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } - -inline const Polygons& holes(const ExPolygon& p) { return p.holes; } -inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } - -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; -using TPixel = TPixelRenderer::color_type; -using TRawBuffer = agg::rendering_buffer; -using TBuffer = std::vector; - -using TRendererAA = agg::renderer_scanline_aa_solid; - -class Raster::Impl { -public: - - static const TPixel ColorWhite; - static const TPixel ColorBlack; - - using Format = Raster::RawData; - -private: - Raster::Resolution m_resolution; - Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons - TBuffer m_buf; - TRawBuffer m_rbuf; - TPixelRenderer m_pixfmt; - TRawRenderer m_raw_renderer; - TRendererAA m_renderer; - - std::function m_gammafn; - Trafo m_trafo; - - inline void flipy(agg::path_storage& path) const { - path.flip_y(0, double(m_resolution.height_px)); - } - - inline void flipx(agg::path_storage& path) const { - path.flip_x(0, double(m_resolution.width_px)); - } - -public: - inline Impl(const Raster::Resolution & res, - const Raster::PixelDim & pd, - const Trafo &trafo) - : m_resolution(res) - , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) - , m_buf(res.pixels()) - , m_rbuf(reinterpret_cast(m_buf.data()), - unsigned(res.width_px), - unsigned(res.height_px), - int(res.width_px * TPixelRenderer::num_components)) - , m_pixfmt(m_rbuf) - , m_raw_renderer(m_pixfmt) - , m_renderer(m_raw_renderer) - , m_trafo(trafo) - { - m_renderer.color(ColorWhite); - - if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma); - else m_gammafn = agg::gamma_threshold(0.5); - - clear(); - } - - template void draw(const P &poly) { - agg::rasterizer_scanline_aa<> ras; - agg::scanline_p8 scanlines; - - ras.gamma(m_gammafn); - - ras.add_path(to_path(contour(poly))); - for(auto& h : holes(poly)) ras.add_path(to_path(h)); - - agg::render_scanlines(ras, scanlines, m_renderer); - } - - inline void clear() { - m_raw_renderer.clear(ColorBlack); - } - - inline TBuffer& buffer() { return m_buf; } - inline const TBuffer& buffer() const { return m_buf; } - - - inline const Raster::Resolution resolution() { return m_resolution; } - inline const Raster::PixelDim pixdim() - { - return {SCALING_FACTOR / m_pxdim_scaled.w_mm, - SCALING_FACTOR / m_pxdim_scaled.h_mm}; - } - -private: - inline double getPx(const Point& p) { - return p(0) * m_pxdim_scaled.w_mm; - } - - inline double getPy(const Point& p) { - return p(1) * m_pxdim_scaled.h_mm; - } - - inline agg::path_storage to_path(const Polygon& poly) - { - return to_path(poly.points); - } - - inline double getPx(const ClipperLib::IntPoint& p) { - return p.X * m_pxdim_scaled.w_mm; - } - - inline double getPy(const ClipperLib::IntPoint& p) { - return p.Y * m_pxdim_scaled.h_mm; - } - - template agg::path_storage _to_path(const PointVec& v) - { - agg::path_storage path; - - auto it = v.begin(); - path.move_to(getPx(*it), getPy(*it)); - while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); - path.line_to(getPx(v.front()), getPy(v.front())); - - return path; - } - - template 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 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); - - return path; - } - -}; - -const TPixel Raster::Impl::ColorWhite = TPixel(255); -const TPixel Raster::Impl::ColorBlack = TPixel(0); - -Raster::Raster() { reset(); } - -Raster::Raster(const Raster::Resolution &r, - const Raster::PixelDim & pd, - const Raster::Trafo & tr) -{ - reset(r, pd, tr); -} - -Raster::~Raster() = default; - -Raster::Raster(Raster &&m) = default; -Raster &Raster::operator=(Raster &&) = default; - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - const Trafo &trafo) -{ - m_impl.reset(); - m_impl.reset(new Impl(r, pd, trafo)); -} - -void Raster::reset() -{ - m_impl.reset(); -} - -Raster::Resolution Raster::resolution() const -{ - if (m_impl) return m_impl->resolution(); - - return Resolution{0, 0}; -} - -Raster::PixelDim Raster::pixel_dimensions() const -{ - if (m_impl) return m_impl->pixdim(); - - return PixelDim{0., 0.}; -} - -void Raster::clear() -{ - assert(m_impl); - m_impl->clear(); -} - -void Raster::draw(const ExPolygon &expoly) -{ - assert(m_impl); - m_impl->draw(expoly); -} - -void Raster::draw(const ClipperLib::Polygon &poly) -{ - assert(m_impl); - m_impl->draw(poly); -} - -uint8_t Raster::read_pixel(size_t x, size_t y) const -{ - assert (m_impl); - TPixel::value_type px; - m_impl->buffer()[y * resolution().width_px + x].get(px); - return px; -} - -PNGImage & PNGImage::serialize(const Raster &raster) -{ - 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(rawdata); - - m_buffer.reserve(s); - std::copy(ptr, ptr + s, std::back_inserter(m_buffer)); - - MZ_FREE(rawdata); - return *this; -} - -std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes) -{ - stream.write(reinterpret_cast(bytes.data()), - std::streamsize(bytes.size())); - - return stream; -} - -Raster::RawData::~RawData() = default; - -PPMImage & PPMImage::serialize(const Raster &raster) -{ - 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(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 diff --git a/src/libslic3r/SLA/Raster.hpp b/src/libslic3r/SLA/Raster.hpp deleted file mode 100644 index 4d76b3290..000000000 --- a/src/libslic3r/SLA/Raster.hpp +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef SLA_RASTER_HPP -#define SLA_RASTER_HPP - -#include -#include -#include -#include -#include -#include - -#include - -namespace ClipperLib { struct Polygon; } - -namespace Slic3r { -namespace sla { - -/** - * @brief Raster captures an anti-aliased monochrome canvas where vectorial - * polygons can be rasterized. Fill color is always white and the background is - * black. Contours are anti-aliased. - * - * It also supports saving the raster data into a standard output stream in raw - * or PNG format. - */ -class Raster { - class Impl; - std::unique_ptr m_impl; -public: - - // Raw byte buffer paired with its size. Suitable for compressed image data. - class RawData - { - protected: - std::vector m_buffer; - const Impl& get_internals(const Raster& raster); - public: - RawData() = default; - RawData(std::vector&& 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. - struct Resolution { - size_t width_px; - size_t height_px; - - inline Resolution(size_t w = 0, size_t h = 0) - : width_px(w), height_px(h) - {} - - inline size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm; - double h_mm; - inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): - w_mm(px_width_mm), h_mm(px_height_mm) {} - }; - - enum Orientation { roLandscape, roPortrait }; - - using TMirroring = std::array; - static const TMirroring NoMirror; - static const TMirroring MirrorX; - static const TMirroring MirrorY; - static const TMirroring MirrorXY; - - struct Trafo { - bool mirror_x = false, mirror_y = false, flipXY = false; - coord_t origin_x = 0, origin_y = 0; - - // If gamma is zero, thresholding will be performed which disables AA. - double gamma = 1.; - - // Portrait orientation will make sure the drawed polygons are rotated - // by 90 degrees. - Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) - // XY flipping implicitly does an X mirror - : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) - , mirror_y(!mirror[1]) // Makes raster origin to be top left corner - , flipXY(o == roPortrait) - {} - }; - - Raster(); - Raster(const Resolution &r, - const PixelDim & pd, - const Trafo & tr = {}); - - Raster(const Raster& cpy) = delete; - Raster& operator=(const Raster& cpy) = delete; - Raster(Raster&& m); - Raster& operator=(Raster&&); - ~Raster(); - - /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution& r, - const PixelDim& pd, - const Trafo &tr = {}); - - /** - * Release the allocated resources. Drawing in this state ends in - * unspecified behavior. - */ - void reset(); - - /// Get the resolution of the raster. - Resolution resolution() const; - PixelDim pixel_dimensions() const; - - /// Clear the raster with black color. - void clear(); - - /// Draw a polygon with holes. - void draw(const ExPolygon& poly); - void draw(const ClipperLib::Polygon& poly); - - uint8_t read_pixel(size_t w, size_t h) const; - - inline bool empty() const { return ! bool(m_impl); } - -}; - -class PNGImage: public Raster::RawData { -public: - PNGImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "png"; } -}; - -class PPMImage: public Raster::RawData { -public: - PPMImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "ppm"; } -}; - -std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes); - -} // sla -} // Slic3r - - -#endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp new file mode 100644 index 000000000..581e84880 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -0,0 +1,89 @@ +#ifndef SLARASTER_CPP +#define SLARASTER_CPP + +#include + +#include +#include + +// minz image write: +#include + +namespace Slic3r { namespace sla { + +const RasterBase::TMirroring RasterBase::NoMirror = {false, false}; +const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; +const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; +const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; + +EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + size_t s = 0; + + void *rawdata = tdefl_write_image_to_png_file_in_memory( + ptr, int(w), int(h), int(num_components), &s); + + // On error, data() will return an empty vector. No other info can be + // retrieved from miniz anyway... + if (rawdata == nullptr) return EncodedRaster({}, "png"); + + auto pptr = static_cast(rawdata); + + buf.reserve(s); + std::copy(pptr, pptr + s, std::back_inserter(buf)); + + MZ_FREE(rawdata); + return EncodedRaster(std::move(buf), "png"); +} + +std::ostream &operator<<(std::ostream &stream, const EncodedRaster &bytes) +{ + stream.write(reinterpret_cast(bytes.data()), + std::streamsize(bytes.size())); + + return stream; +} + +EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + + auto header = std::string("P5 ") + + std::to_string(w) + " " + + std::to_string(h) + " " + "255 "; + + auto sz = w * h * num_components; + size_t s = sz + header.size(); + + buf.reserve(s); + + auto buff = reinterpret_cast(ptr); + std::copy(header.begin(), header.end(), std::back_inserter(buf)); + std::copy(buff, buff+sz, std::back_inserter(buf)); + + return EncodedRaster(std::move(buf), "ppm"); +} + +std::unique_ptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma, + const RasterBase::Trafo & tr) +{ + std::unique_ptr rst; + + if (gamma > 0) + rst = std::make_unique(res, pxdim, tr, gamma); + else + rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); + + return rst; +} + +} // namespace sla +} // namespace Slic3r + +#endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp new file mode 100644 index 000000000..431c731a6 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -0,0 +1,124 @@ +#ifndef SLA_RASTERBASE_HPP +#define SLA_RASTERBASE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ClipperLib { struct Polygon; } + +namespace Slic3r { + +template using uqptr = std::unique_ptr; +template using shptr = std::shared_ptr; +template using wkptr = std::weak_ptr; + +namespace sla { + +// Raw byte buffer paired with its size. Suitable for compressed image data. +class EncodedRaster { +protected: + std::vector m_buffer; + std::string m_ext; +public: + EncodedRaster() = default; + explicit EncodedRaster(std::vector &&buf, std::string ext) + : m_buffer(std::move(buf)), m_ext(std::move(ext)) + {} + + size_t size() const { return m_buffer.size(); } + const void * data() const { return m_buffer.data(); } + const char * extension() const { return m_ext.c_str(); } +}; + +using RasterEncoder = + std::function; + +class RasterBase { +public: + + enum Orientation { roLandscape, roPortrait }; + + using TMirroring = std::array; + 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 center_x = 0, center_y = 0; + + // 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) + {} + + TMirroring get_mirror() const { return { (roPortrait ? !mirror_x : mirror_x), mirror_y}; } + Orientation get_orientation() const { return flipXY ? roPortrait : roLandscape; } + Point get_center() const { return {center_x, center_y}; } + }; + + /// Type that represents a resolution in pixels. + struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } + }; + + /// Types that represents the dimension of a pixel in millimeters. + struct PixelDim { + double w_mm = 0.; + double h_mm = 0.; + + PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} + }; + + virtual ~RasterBase() = default; + + /// Draw a polygon with holes. + virtual void draw(const ExPolygon& poly) = 0; + virtual void draw(const ClipperLib::Polygon& poly) = 0; + + /// Get the resolution of the raster. + virtual Resolution resolution() const = 0; + virtual PixelDim pixel_dimensions() const = 0; + virtual Trafo trafo() const = 0; + + virtual EncodedRaster encode(RasterEncoder encoder) const = 0; +}; + +struct PNGRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +struct PPMRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); + +// If gamma is zero, thresholding will be performed which disables AA. +uqptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma = 1.0, + const RasterBase::Trafo & tr = {}); + +}} // namespace Slic3r::sla + +#endif // SLARASTERBASE_HPP diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp new file mode 100644 index 000000000..6b1d8992c --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -0,0 +1,88 @@ +#include "RasterToPolygons.hpp" + +#include "AGGRaster.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "MTUtils.hpp" +#include "ClipperUtils.hpp" + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = Slic3r::sla::RasterGrayscaleAA; + + // 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.read_pixel(col, row); } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.resolution().height_px; } + static size_t cols(const Rst &rst) { return rst.resolution().width_px; } +}; + +} // namespace Slic3r::marchsq + +namespace Slic3r { namespace sla { + +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); +} + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy) +{ + 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); + + std::vector rings = + marchsq::execute(rst, 128, {w_rows, w_cols}); + + polys.reserve(rings.size()); + + auto pxd = rst.pixel_dimensions(); + 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 * pxd.w_mm), scaled(crd.r * pxd.h_mm)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + ExPolygons unioned = union_ex(polys); + coord_t width = scaled(cols * pxd.h_mm), height = scaled(rows * pxd.w_mm); + + auto tr = rst.trafo(); + for (ExPolygon &expoly : unioned) { + if (tr.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (tr.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-tr.center_x, -tr.center_y); + + if (tr.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((tr.mirror_x + tr.mirror_y + tr.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } + + return unioned; +} + +}} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp new file mode 100644 index 000000000..131fe518e --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -0,0 +1,15 @@ +#ifndef RASTERTOPOLYGONS_HPP +#define RASTERTOPOLYGONS_HPP + +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { +namespace sla { + +class RasterGrayscaleAA; + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy = 1.f); + +}} + +#endif // RASTERTOPOLYGONS_HPP diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp deleted file mode 100644 index 13aef7d8a..000000000 --- a/src/libslic3r/SLA/RasterWriter.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include - -#include - -#include "libslic3r/PrintConfig.hpp" -#include -#include - -#include "ExPolygon.hpp" -#include - -#include -#include - -namespace Slic3r { namespace sla { - -void RasterWriter::write_ini(const std::map &m, std::string &ini) -{ - for (auto ¶m : m) ini += param.first + " = " + param.second + "\n"; -} - -std::string RasterWriter::create_ini_content(const std::string& projectname) const -{ - std::string out("action = print\njobDir = "); - out += projectname + "\n"; - write_ini(m_config, out); - return out; -} - -RasterWriter::RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma) - : m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma) -{} - -void RasterWriter::save(const std::string &fpath, const std::string &prjname) -{ - try { - Zipper zipper(fpath); // zipper with no compression - save(zipper, prjname); - zipper.finalize(); - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -void RasterWriter::save(Zipper &zipper, const std::string &prjname) -{ - try { - std::string project = - prjname.empty() ? - boost::filesystem::path(zipper.get_filename()).stem().string() : - prjname; - - zipper.add_entry("config.ini"); - - zipper << create_ini_content(project); - - zipper.add_entry("prusaslicer.ini"); - std::string prusaslicer_ini; - write_ini(m_slicer_config, prusaslicer_ini); - zipper << prusaslicer_ini; - - for(unsigned i = 0; i < m_layers_rst.size(); i++) - { - if(m_layers_rst[i].rawbytes.size() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - - // Add binary entry to the zipper - zipper.add_entry(zfilename, - m_layers_rst[i].rawbytes.data(), - m_layers_rst[i].rawbytes.size()); - } - } - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -namespace { - -std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) -{ - std::string ret; - - if (cfg.has(key)) { - auto opt = cfg.option(key); - if (opt) ret = opt->serialize(); - } - - return ret; -} - -void append_full_config(const DynamicPrintConfig &cfg, std::map &keys) -{ - using namespace std::literals::string_view_literals; - - // Sorted list of config keys, which shall not be stored into the ini. - static constexpr auto banned_keys = { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "printhost_apikey"sv, - "printhost_cafile"sv - }; - - assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return std::binary_search(banned_keys.begin(), banned_keys.end(), key); - }; - - for (const std::string &key : cfg.keys()) - if (! is_banned(key) && ! cfg.option(key)->is_nil()) - keys[key] = cfg.opt_serialize(key); -} - -} // namespace - -void RasterWriter::set_config(const DynamicPrintConfig &cfg) -{ - m_config["layerHeight"] = get_cfg_value(cfg, "layer_height"); - m_config["expTime"] = get_cfg_value(cfg, "exposure_time"); - m_config["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); - m_config["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); - m_config["printerModel"] = get_cfg_value(cfg, "printer_model"); - m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant"); - m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); - m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); - m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); - m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; - append_full_config(cfg, m_slicer_config); -} - -void RasterWriter::set_statistics(const PrintStatistics &stats) -{ - m_config["usedMaterial"] = std::to_string(stats.used_material); - m_config["numFade"] = std::to_string(stats.num_fade); - m_config["numSlow"] = std::to_string(stats.num_slow); - m_config["numFast"] = std::to_string(stats.num_fast); - m_config["printTime"] = std::to_string(stats.estimated_print_time_s); -} - -} // namespace sla -} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp deleted file mode 100644 index 75162893d..000000000 --- a/src/libslic3r/SLA/RasterWriter.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef SLA_RASTERWRITER_HPP -#define SLA_RASTERWRITER_HPP - -// For png export of the sliced model -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Slic3r { - -class DynamicPrintConfig; - -namespace sla { - -// API to write the zipped sla output layers and metadata. -// Implementation uses PNG raster output. -// Be aware that if a large number of layers are allocated, it can very well -// exhaust the available memory especially on 32 bit platform. -// This class is designed to be used in parallel mode. Layers have an ID and -// each layer can be written and compressed independently (in parallel). -// At the end when all layers where written, the save method can be used to -// write out the result into a zipped archive. -class RasterWriter -{ -public: - - // Used for addressing parameters of set_statistics() - struct PrintStatistics - { - double used_material = 0.; - double estimated_print_time_s = 0.; - size_t num_fade = 0; - size_t num_slow = 0; - size_t num_fast = 0; - }; - -private: - - // A struct to bind the raster image data and its compressed bytes together. - struct Layer { - Raster raster; - PNGImage rawbytes; - - Layer() = default; - - // The image is big, do not copy by accident - Layer(const Layer&) = delete; - Layer& operator=(const Layer&) = delete; - - Layer(Layer &&m) = default; - Layer &operator=(Layer &&) = default; - }; - - // We will save the compressed PNG data into RawBytes type buffers in - // parallel. Later we can write every layer to the disk sequentially. - std::vector m_layers_rst; - Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - Raster::Trafo m_trafo; - double m_gamma; - - std::map m_config; - std::map m_slicer_config; - - static void write_ini(const std::map &m, std::string &ini); - std::string create_ini_content(const std::string& projectname) const; - -public: - - // SLARasterWriter is using Raster in custom mirroring mode - RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma = 1.); - - RasterWriter(const RasterWriter& ) = delete; - RasterWriter& operator=(const RasterWriter&) = delete; - RasterWriter(RasterWriter&& m) = default; - RasterWriter& operator=(RasterWriter&&) = default; - - inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } - inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - - template void draw_polygon(const Poly& p, unsigned lyr) - { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void begin_layer(unsigned lyr) { - if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void begin_layer() { - m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void finish_layer(unsigned lyr_id) { - assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster); - m_layers_rst[lyr_id].raster.reset(); - } - - inline void finish_layer() { - if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster); - m_layers_rst.back().raster.reset(); - } - } - - void save(const std::string &fpath, const std::string &prjname = ""); - void save(Zipper &zipper, const std::string &prjname = ""); - - void set_statistics(const PrintStatistics &statistics); - - void set_config(const DynamicPrintConfig &cfg); -}; - -} // namespace sla -} // namespace Slic3r - -#endif // SLARASTERWRITER_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4ec5aae29..2402207a8 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -227,6 +227,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); + + if (m_printer) m_printer->apply(m_printer_config); struct ModelObjectStatus { enum Status { @@ -482,7 +484,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con } if(m_objects.empty()) { - m_printer.reset(); m_printer_input = {}; m_print_statistics = {}; } @@ -657,6 +658,12 @@ std::string SLAPrint::validate() const return ""; } +void SLAPrint::set_printer(SLAPrinter *arch) +{ + invalidate_step(slapsRasterize); + m_printer = arch; +} + bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -676,7 +683,7 @@ void SLAPrint::process() // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered - Steps printsteps{this}; + Steps printsteps(this); // We want to first process all objects... std::vector level1_obj_steps = { @@ -729,7 +736,7 @@ void SLAPrint::process() throw_if_canceled(); po->set_done(step); } - + incr = printsteps.progressrange(step); } } @@ -754,7 +761,7 @@ void SLAPrint::process() throw_if_canceled(); set_done(currentstep); } - + st += printsteps.progressrange(currentstep); } @@ -855,36 +862,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector mirror; - - double w = m_printer_config.display_width.getFloat(); - double h = m_printer_config.display_height.getFloat(); - auto pw = size_t(m_printer_config.display_pixels_x.getInt()); - auto ph = size_t(m_printer_config.display_pixels_y.getInt()); - - mirror[X] = m_printer_config.display_mirror_x.getBool(); - mirror[Y] = m_printer_config.display_mirror_y.getBool(); - - auto orientation = get_printer_orientation(); - if (orientation == sla::Raster::roPortrait) { - std::swap(w, h); - std::swap(pw, ph); - } - - res = sla::Raster::Resolution{pw, ph}; - pxdim = sla::Raster::PixelDim{w / pw, h / ph}; - sla::Raster::Trafo tr{orientation, mirror}; - tr.gamma = m_printer_config.gamma_correction.getFloat(); - - m_printer.reset(new sla::RasterWriter(res, pxdim, tr)); - m_printer->set_config(m_full_print_config); - return *m_printer; -} - // Returns true if an object step is done on all objects and there's at least one object. bool SLAPrint::is_step_done(SLAPrintObjectStep step) const { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 70f773f6b..a27207565 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,7 +3,7 @@ #include #include "PrintBase.hpp" -#include "SLA/RasterWriter.hpp" +#include "SLA/RasterBase.hpp" #include "SLA/SupportTree.hpp" #include "Point.hpp" #include "MTUtils.hpp" @@ -369,6 +369,31 @@ struct SLAPrintStatistics } }; +class SLAPrinter { +protected: + std::vector m_layers; + + virtual uqptr create_raster() const = 0; + virtual sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const = 0; + +public: + virtual ~SLAPrinter() = default; + + virtual void apply(const SLAPrinterConfig &cfg) = 0; + + // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); + template void draw_layers(size_t layer_num, Fn &&drawfn) + { + m_layers.resize(layer_num); + sla::ccr::enumerate(m_layers.begin(), m_layers.end(), + [this, &drawfn](sla::EncodedRaster& enc, size_t idx) { + auto rst = create_raster(); + drawfn(*rst, idx); + enc = encode_raster(*rst); + }); + } +}; + /** * @brief This class is the high level FSM for the SLA printing process. * @@ -403,18 +428,6 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - inline void export_raster(const std::string& fpath, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(fpath, projectname); - } - - inline void export_raster(Zipper &zipper, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(zipper, projectname); - } - const PrintObjects& objects() const { return m_objects; } const SLAPrintConfig& print_config() const { return m_print_config; } @@ -445,14 +458,15 @@ public: std::vector m_transformed_slices; - template void transformed_slices(Container&& c) { + template void transformed_slices(Container&& c) + { m_transformed_slices = std::forward(c); } friend class SLAPrint::Steps; public: - + explicit PrintLayer(coord_t lvl) : m_level(lvl) {} // for being sorted in their container (see m_printer_input) @@ -474,8 +488,11 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - + + void set_printer(SLAPrinter *archiver); + private: + // Implement same logic as in SLAPrintObject bool invalidate_step(SLAPrintStep st); @@ -491,13 +508,13 @@ private: std::vector m_stepmask; // Ready-made data for rasterization. - std::vector m_printer_input; - - // The printer itself - std::unique_ptr m_printer; - + std::vector m_printer_input; + + // The archive object which collects the raster images after slicing + SLAPrinter *m_printer = nullptr; + // Estimated print time, material consumed. - SLAPrintStatistics m_print_statistics; + SLAPrintStatistics m_print_statistics; class StatusReporter { @@ -512,15 +529,6 @@ private: double status() const { return m_st; } } m_report_status; - - sla::RasterWriter &init_printer(); - - inline sla::Raster::Orientation get_printer_orientation() const - { - auto ro = m_printer_config.display_orientation.getInt(); - return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait : - sla::Raster::roLandscape; - } friend SLAPrintObject; }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 01220a633..e421e9c1d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -816,16 +816,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled()) return; - - auto &print_statistics = m_print->m_print_statistics; - auto &printer_input = m_print->m_printer_input; - - // Set up the printer, allocate space for all the layers - sla::RasterWriter &printer = m_print->init_printer(); - - auto lvlcnt = unsigned(printer_input.size()); - printer.layers(lvlcnt); + if(canceled() || !m_print->m_printer) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -837,7 +828,7 @@ void SLAPrint::Steps::rasterize() // pst: previous state double pst = current_status(); - double increment = (slot * sd) / printer_input.size(); + double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); sla::ccr::SpinningMutex slck; @@ -845,20 +836,14 @@ void SLAPrint::Steps::rasterize() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, increment, &dstatus, &pst] - (PrintLayer& printlayer, size_t idx) + [this, &slck, increment, &dstatus, &pst] + (sla::RasterBase& raster, size_t idx) { + PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - auto level_id = unsigned(idx); - // Switch to the appropriate layer in the printer - printer.begin_layer(level_id); - - for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) - printer.draw_polygon(poly, level_id); - - // Finish the layer for later saving it. - printer.finish_layer(level_id); + for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) + raster.draw(poly); // Status indication guarded with the spinlock { @@ -875,24 +860,8 @@ void SLAPrint::Steps::rasterize() // last minute escape if(canceled()) return; - // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); - // Print all the layers in parallel - sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn); - - // Set statistics values to the printer - sla::RasterWriter::PrintStatistics stats; - stats.used_material = (print_statistics.objects_used_material + - print_statistics.support_used_material) / 1000; - - int num_fade = m_print->m_default_object_config.faded_layers.getInt(); - stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0); - stats.num_fast = print_statistics.fast_layers_count; - stats.num_slow = print_statistics.slow_layers_count; - stats.estimated_print_time_s = print_statistics.estimated_print_time; - - printer.set_statistics(stats); + m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index d3341bc14..19b64d4a9 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -46,7 +46,7 @@ private: void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); public: - Steps(SLAPrint *print); + explicit Steps(SLAPrint *print); void hollow_model(SLAPrintObject &po); void drill_holes (SLAPrintObject &po); diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp new file mode 100644 index 000000000..bd0961d04 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -0,0 +1,83 @@ + +#include "SlicesToTriangleMesh.hpp" + +#include "libslic3r/TriangulateWall.hpp" +#include "libslic3r/SLA/Contour3D.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Tesselate.hpp" + +namespace Slic3r { + +inline sla::Contour3D walls(const Polygon &lower, + const Polygon &upper, + 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); + + return ret; +} + +// Same as walls() but with identical higher and lower polygons. +sla::Contour3D inline straight_walls(const Polygon &plate, + double lo_z, + double hi_z) +{ + return walls(plate, plate, lo_z, hi_z); +} + +sla::Contour3D inline straight_walls(const ExPolygon &plate, + double lo_z, + double hi_z) +{ + sla::Contour3D ret; + ret.merge(straight_walls(plate.contour, lo_z, hi_z)); + for (auto &h : plate.holes) ret.merge(straight_walls(h, lo_z, hi_z)); + return ret; +} + +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)); + + 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; + + 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; + } + + cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_UP)); + + mesh.merge(sla::to_triangle_mesh(cntr3d)); + mesh.require_shared_vertices(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/SlicesToTriangleMesh.hpp b/src/libslic3r/SlicesToTriangleMesh.hpp new file mode 100644 index 000000000..133312d56 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.hpp @@ -0,0 +1,24 @@ +#ifndef SLICESTOTRIANGLEMESH_HPP +#define SLICESTOTRIANGLEMESH_HPP + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { + +void slices_to_triangle_mesh(TriangleMesh & mesh, + const std::vector &slices, + double zmin, + double lh, + double ilh); + +inline TriangleMesh slices_to_triangle_mesh( + const std::vector &slices, double zmin, double lh, double ilh) +{ + TriangleMesh out; slices_to_triangle_mesh(out, slices, zmin, lh, ilh); + return out; +} + +} // namespace Slic3r + +#endif // SLICESTOTRIANGLEMESH_HPP diff --git a/src/libslic3r/TriangulateWall.cpp b/src/libslic3r/TriangulateWall.cpp new file mode 100644 index 000000000..ec2945b10 --- /dev/null +++ b/src/libslic3r/TriangulateWall.cpp @@ -0,0 +1,133 @@ +#include "TriangulateWall.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +class Ring { + size_t idx = 0, nextidx = 1, startidx = 0, begin = 0, end = 0; + +public: + explicit Ring(size_t from, size_t to) : begin(from), end(to) { init(begin); } + + size_t size() const { return end - begin; } + std::pair pos() const { return {idx, nextidx}; } + bool is_lower() const { return idx < size(); } + + void inc() + { + if (nextidx != startidx) nextidx++; + if (nextidx == end) nextidx = begin; + idx ++; + if (idx == end) idx = begin; + } + + void init(size_t pos) + { + startidx = begin + (pos - begin) % size(); + idx = startidx; + nextidx = begin + (idx + 1 - begin) % size(); + } + + bool is_finished() const { return nextidx == idx; } +}; + +static double sq_dst(const Vec3d &v1, const Vec3d& v2) +{ + Vec3d v = v1 - v2; + return v.x() * v.x() + v.y() * v.y() /*+ v.z() * v.z()*/; +} + +static double score(const Ring& onring, const Ring &offring, + const std::vector &pts) +{ + double a = sq_dst(pts[onring.pos().first], pts[offring.pos().first]); + double b = sq_dst(pts[onring.pos().second], pts[offring.pos().first]); + return (std::abs(a) + std::abs(b)) / 2.; +} + +class Triangulator { + const std::vector *pts; + Ring *onring, *offring; + + double calc_score() const + { + return Slic3r::score(*onring, *offring, *pts); + } + + void synchronize_rings() + { + Ring lring = *offring; + auto minsc = Slic3r::score(*onring, lring, *pts); + size_t imin = lring.pos().first; + + lring.inc(); + + while(!lring.is_finished()) { + double score = Slic3r::score(*onring, lring, *pts); + if (score < minsc) { minsc = score; imin = lring.pos().first; } + lring.inc(); + } + + offring->init(imin); + } + + void emplace_indices(std::vector &indices) + { + Vec3i tr{int(onring->pos().first), int(onring->pos().second), + int(offring->pos().first)}; + if (onring->is_lower()) std::swap(tr(0), tr(1)); + indices.emplace_back(tr); + } + +public: + void run(std::vector &indices) + { + synchronize_rings(); + + double score = 0, prev_score = 0; + while (!onring->is_finished() || !offring->is_finished()) { + prev_score = score; + if (onring->is_finished() || (score = calc_score()) > prev_score) { + std::swap(onring, offring); + } else { + emplace_indices(indices); + onring->inc(); + } + } + } + + explicit Triangulator(const std::vector *points, + Ring & lower, + Ring & upper) + : pts{points}, onring{&upper}, offring{&lower} + {} +}; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm) +{ + if (upper.points.size() < 3 || lower.points.size() < 3) return {}; + + Wall wall; + auto &pts = wall.first; + auto &ind = wall.second; + + pts.reserve(lower.points.size() + upper.points.size()); + for (auto &p : lower.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); + for (auto &p : upper.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + + ind.reserve(2 * (lower.size() + upper.size())); + + Ring lring{0, lower.points.size()}, uring{lower.points.size(), pts.size()}; + Triangulator t{&pts, lring, uring}; + t.run(ind); + + return wall; +} + +} // namespace Slic3r diff --git a/src/libslic3r/TriangulateWall.hpp b/src/libslic3r/TriangulateWall.hpp new file mode 100644 index 000000000..68bf4b0ac --- /dev/null +++ b/src/libslic3r/TriangulateWall.hpp @@ -0,0 +1,17 @@ +#ifndef TRIANGULATEWALL_HPP +#define TRIANGULATEWALL_HPP + +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { + +using Wall = std::pair, std::vector>; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm); +} + +#endif // TRIANGULATEWALL_HPP diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index a5b53584d..ec9d3aa16 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -167,7 +167,7 @@ void Zipper::add_entry(const std::string &name) m_entry = name; } -void Zipper::add_entry(const std::string &name, const uint8_t *data, size_t l) +void Zipper::add_entry(const std::string &name, const void *data, size_t l) { if(!m_impl->is_alive()) return; diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index be1e69b5c..d203ea7b2 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -49,7 +49,7 @@ public: /// Add a new binary file entry with an instantly given byte buffer. /// This method throws exactly like finish_entry() does. - void add_entry(const std::string& name, const std::uint8_t* data, size_t l); + void add_entry(const std::string& name, const void* data, size_t bytes); // Writing data to the archive works like with standard streams. The target // within the zip file is the entry created with the add_entry method. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1e9f3b862..c0853fb65 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -182,6 +182,8 @@ set(SLIC3R_GUI_SOURCES Utils/HexFile.cpp Utils/HexFile.hpp Utils/Thread.hpp + Utils/SLAZipFileImport.hpp + Utils/SLAZipFileImport.cpp ) if (APPLE) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 7ce942855..cfd38354b 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -19,6 +19,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/libslic3r.h" #include @@ -149,7 +150,7 @@ void BackgroundSlicingProcess::process_sla() const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); Zipper zipper(export_path); - m_sla_print->export_raster(zipper); + m_sla_archive.export_print(zipper, *m_sla_print); if (m_thumbnail_cb != nullptr) { @@ -473,9 +474,9 @@ void BackgroundSlicingProcess::prepare_upload() m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - + Zipper zipper{source_path.string()}; - m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string()); + m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); if (m_thumbnail_cb != nullptr) { ThumbnailsList thumbnails; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index b868a233f..efaea1d11 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -10,6 +10,7 @@ #include #include "libslic3r/Print.hpp" +#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -19,6 +20,7 @@ class DynamicPrintConfig; class GCodePreviewData; class Model; class SLAPrint; +class SL1Archive; class SlicingStatusEvent : public wxEvent { @@ -47,7 +49,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; } + void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } @@ -155,6 +157,7 @@ private: GCodePreviewData *m_gcode_preview_data = nullptr; // Callback function, used to write thumbnails into gcode. ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + SL1Archive m_sla_archive; // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6a1964506..f7d7a6cac 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -589,6 +589,11 @@ void MainFrame::init_menubar() append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); + + append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")), + [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, + [this](){return m_plater != nullptr; }, this); + import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7f323c554..75ef32762 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" @@ -72,6 +73,7 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/SLAZipFileImport.hpp" #include "RemovableDriveManager.hpp" #if ENABLE_NON_STATIC_CANVAS_MANAGER #ifdef __APPLE__ @@ -4251,6 +4253,27 @@ void Plater::add_model() load_files(paths, true, false); } +void Plater::import_sl1_archive() +{ + wxFileDialog dlg(this, _(L("Choose SL1 archive:")), + from_u8(wxGetApp().app_config->get_last_dir()), "", + "SL1 archive files (*.sl1)|*.sl1;*.SL1;*.zip;*.ZIP", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + 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(); + } + } catch (std::exception &ex) { + show_error(this, ex.what()); + } + } +} + void Plater::extract_config_from_project() { wxString input_file; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e71eb3db4..2ac4f23c1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -159,6 +159,7 @@ public: void load_project(); void load_project(const wxString& filename); void add_model(); + void import_sl1_archive(); void extract_config_from_project(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); diff --git a/src/slic3r/Utils/SLAZipFileImport.cpp b/src/slic3r/Utils/SLAZipFileImport.cpp new file mode 100644 index 000000000..6543e8674 --- /dev/null +++ b/src/slic3r/Utils/SLAZipFileImport.cpp @@ -0,0 +1,144 @@ +#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 new file mode 100644 index 000000000..4e36f86f4 --- /dev/null +++ b/src/slic3r/Utils/SLAZipFileImport.hpp @@ -0,0 +1,14 @@ +#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/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 2353414f9..b41dbf8ba 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshsimplify.cpp test_meshboolean.cpp + test_marchingsquares.cpp test_timeutils.cpp ) diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp new file mode 100644 index 000000000..ce1c0e3ff --- /dev/null +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -0,0 +1,342 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Slic3r; + +static Slic3r::sla::RasterGrayscaleAA create_raster( + const sla::RasterBase::Resolution &res, + double disp_w = 100., + double disp_h = 100.) +{ + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + + 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); + + return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; +} + +static ExPolygon square(double a, Point center = {0, 0}) +{ + ExPolygon poly; + coord_t V = scaled(a / 2.); + + poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygon square_with_hole(double a, Point center = {0, 0}) +{ + ExPolygon poly = square(a); + + poly.holes.emplace_back(); + coord_t V = scaled(a / 4.); + poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; + + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygons circle_with_hole(double r, Point center = {0, 0}) { + + ExPolygon poly; + + std::vector pis = linspace_vector(0., 2 * PI, 100); + + coord_t rs = scaled(r); + for (double phi : pis) { + poly.contour.points.emplace_back(rs * std::cos(phi), rs * std::sin(phi)); + } + + poly.holes.emplace_back(poly.contour); + poly.holes.front().reverse(); + for (auto &p : poly.holes.front().points) p /= 2; + + poly.translate(center.x(), center.y()); + + return {poly}; +} + +template +static void test_expolys(Rst && rst, + const ExPolygons & ref, + float accuracy, + const std::string &name = "test") +{ + for (const ExPolygon &expoly : ref) rst.draw(expoly); + + std::fstream out(name + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst, accuracy); + + SVG svg(name + ".svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == ref.size()); + for (size_t i = 0; i < ref.size(); ++i) { + REQUIRE(extracted[i].contour.is_counter_clockwise()); + REQUIRE(extracted[i].holes.size() == ref[i].holes.size()); + + 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); + } +} + +TEST_CASE("Empty raster should result in empty polygons", "[MarchingSquares]") { + sla::RasterGrayscaleAAGammaPower rst{{}, {}, {}}; + ExPolygons extracted = sla::raster_to_polygons(rst); + REQUIRE(extracted.size() == 0); +} + +TEST_CASE("Marching squares directions", "[MarchingSquares]") { + marchsq::Coord crd{1, 1}; + + REQUIRE(step(crd, marchsq::__impl::Dir::left).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::left).c == 0); + + REQUIRE(step(crd, marchsq::__impl::Dir::down).r == 2); + REQUIRE(step(crd, marchsq::__impl::Dir::down).c == 1); + + REQUIRE(step(crd, marchsq::__impl::Dir::right).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::right).c == 2); + + REQUIRE(step(crd, marchsq::__impl::Dir::up).r == 0); + REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1); +} + +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)}; + + // Draw a triangle from individual pixels + 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{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 1); +} + +TEST_CASE("4x4 raster with two rings", "[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)}; + + SECTION("Ambiguous case with 'ac' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {3500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + rst.draw(square(1., {2500000, 1500000})); + rst.draw(square(1., {1500000, 1500000})); + rst.draw(square(1., {1500000, 2500000})); + + std::fstream out("4x4_ac.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_ac.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } + + SECTION("Ambiguous case with 'bd' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 1500000})); + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {2500000, 1500000})); + + rst.draw(square(1., {1500000, 2500000})); + rst.draw(square(1., {1500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + std::fstream out("4x4_bd.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_bd.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } +} + +TEST_CASE("Square with hole in the middle", "[MarchingSquares]") { + using namespace Slic3r; + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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_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"); +} + +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(); + + std::vector layers; + slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); + + sla::RasterBase::Resolution res{2560, 1440}; + double disp_w = 120.96; + double disp_h = 68.04; + + size_t cntr = 0; + for (ExPolygons &layer : layers) { + auto rst = create_raster(res, disp_w, disp_h); + + for (ExPolygon &island : layer) { + rst.draw(island); + } + + std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons layer_ = sla::raster_to_polygons(rst); +// float delta = scaled(std::min(rst.pixel_dimensions().h_mm, +// rst.pixel_dimensions().w_mm)); + +// layer_ = expolygons_simplify(layer_, delta); + + SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)})); + svg.draw(layer_); + svg.draw(layer, "green"); + svg.Close(); + + double layera = 0., layera_ = 0.; + for (auto &p : layer) layera += p.area(); + for (auto &p : layer_) layera_ += p.area(); + + std::cout << cntr++ << std::endl; + double diff = std::abs(layera_ - layera); + REQUIRE((diff <= 0.1 * layera || diff < scaled(1.) * scaled(1.))); + + layer = std::move(layer_); + } + + TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh)); + + out.require_shared_vertices(); + out.WriteOBJFile("out_from_rasters.obj"); +} + +TEST_CASE("Recreate object from rasters", "[SL1Import]") { + recreate_object_from_rasters("triang.obj", 0.05f); +} diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 10f5742d3..82df2c1a6 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -154,19 +154,12 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { test_support_model_collision(fname, supportcfg); } -TEST_CASE("DefaultRasterShouldBeEmpty", "[SLARasterOutput]") { - sla::Raster raster; - REQUIRE(raster.empty()); -} - TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; - sla::Raster raster; - raster.reset(res, pixdim); - REQUIRE_FALSE(raster.empty()); + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); REQUIRE(raster.resolution().height_px == res.height_px); REQUIRE(raster.pixel_dimensions().w_mm == Approx(pixdim.w_mm)); @@ -174,13 +167,14 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { } TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { - sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, - sla::Raster::MirrorX, - sla::Raster::MirrorY, - sla::Raster::MirrorXY}; + sla::RasterBase::TMirroring mirrorings[] = {sla::RasterBase::NoMirror, + sla::RasterBase::MirrorX, + sla::RasterBase::MirrorY, + sla::RasterBase::MirrorXY}; + + sla::RasterBase::Orientation orientations[] = + {sla::RasterBase::roLandscape, sla::RasterBase::roPortrait}; - sla::Raster::Orientation orientations[] = {sla::Raster::roLandscape, - sla::Raster::roPortrait}; for (auto orientation : orientations) for (auto &mirror : mirrorings) check_raster_transformations(orientation, mirror); @@ -189,10 +183,11 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { 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::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; - sla::Raster raster{res, pixdim}; + double gamma = 1.; + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); ExPolygon poly = square_with_hole(10.); @@ -215,6 +210,13 @@ TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { diff = std::abs(a - ra); REQUIRE(diff <= predict_error(poly, pixdim)); + + sla::RasterGrayscaleAA raster0(res, pixdim, {}, [](double) { return 0.; }); + REQUIRE(raster_pxsum(raster0) == 0); + + raster0.draw(poly); + ra = raster_white_area(raster); + REQUIRE(raster_pxsum(raster0) == 0); } TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index a844b2eae..883e4268a 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -1,4 +1,5 @@ #include "sla_test_utils.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, const sla::SupportConfig &input_supportcfg, @@ -293,18 +294,19 @@ void check_validity(const TriangleMesh &input_mesh, int flags) } } -void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring) +void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::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}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::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::RasterBase::Trafo trafo{o, mirroring}; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + double gamma = 1.; - sla::Raster raster{res, pixdim, trafo}; + sla::RasterGrayscaleAAGammaPower raster{res, pixdim, trafo, gamma}; // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors) coord_t pw = 32 * coord_t(std::ceil(scaled(pixdim.w_mm))); @@ -319,7 +321,7 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr // Now calculate the position of the translated box according to output // trafo. - if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); + if (o == sla::RasterBase::Orientation::roPortrait) expected_box.rotate(PI / 2.); if (mirroring[X]) for (auto &p : expected_box.contour.points) p.x() = -p.x(); @@ -340,10 +342,9 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr 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); + outf << raster.encode(sla::PNGRasterEncoder()); } REQUIRE(px == FullWhite); @@ -361,9 +362,21 @@ ExPolygon square_with_hole(double v) return poly; } -double raster_white_area(const sla::Raster &raster) +long raster_pxsum(const sla::RasterGrayscaleAA &raster) { - if (raster.empty()) return std::nan(""); + auto res = raster.resolution(); + long a = 0; + + for (size_t x = 0; x < res.width_px; ++x) + for (size_t y = 0; y < res.height_px; ++y) + a += raster.read_pixel(x, y); + + return a; +} + +double raster_white_area(const sla::RasterGrayscaleAA &raster) +{ + if (raster.resolution().pixels() == 0) return std::nan(""); auto res = raster.resolution(); double a = 0; @@ -377,7 +390,7 @@ double raster_white_area(const sla::Raster &raster) return a; } -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index f3727bd39..3652b1f81 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -16,7 +16,7 @@ #include "libslic3r/SLA/SupportTreeBuilder.hpp" #include "libslic3r/SLA/SupportTreeBuildsteps.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp" -#include "libslic3r/SLA/Raster.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/MTUtils.hpp" @@ -170,18 +170,19 @@ static constexpr const TPixel FullBlack = 0; template constexpr int arraysize(const A (&)[N]) { return N; } -void check_raster_transformations(sla::Raster::Orientation o, - sla::Raster::TMirroring mirroring); +void check_raster_transformations(sla::RasterBase::Orientation o, + sla::RasterBase::TMirroring mirroring); ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } -double raster_white_area(const sla::Raster &raster); +double raster_white_area(const sla::RasterGrayscaleAA &raster); +long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); #endif // SLA_TEST_UTILS_HPP