mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 15:21:09 +00:00
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes. clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer. * Remove Unused Project Includes From Files With Platform-Specific Code A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block. * Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass The platform-file pass treated pchheader.hpp as an ordinary header and emptied it, and left GUI_Preview.hpp and 14 other files relying on headers they no longer reached directly. * Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call * Include Headers That Files Reached Through Ones the Cleanup Removed * Drop Includes Duplicated by the Cleanup or by Main's Own Additions * Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
586 lines
21 KiB
C++
586 lines
21 KiB
C++
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
|
|
#include "libslic3r/Point.hpp"
|
|
#include <cstddef>
|
|
#include "libslic3r/libslic3r.h"
|
|
#include <utility>
|
|
#include "libslic3r/BoundingBox.hpp"
|
|
#include "libslic3r/Polygon.hpp"
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include "libslic3r/SLA/RasterBase.hpp"
|
|
#include <agg/agg_gamma_functions.h>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include "libslic3r/ExPolygon.hpp"
|
|
#include <catch2/catch_message.hpp>
|
|
#include <catch2/matchers/catch_matchers.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include "libslic3r/TriangleMesh.hpp"
|
|
#include <cstdlib>
|
|
#include <catch2/benchmark/catch_benchmark.hpp>
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
#include "test_utils.hpp"
|
|
|
|
#include <fstream>
|
|
|
|
#include <libslic3r/MarchingSquares.hpp>
|
|
#include <libslic3r/SLA/RasterToPolygons.hpp>
|
|
|
|
#include <libslic3r/SLA/AGGRaster.hpp>
|
|
#include <libslic3r/MTUtils.hpp>
|
|
#include <libslic3r/SVG.hpp>
|
|
#include <libslic3r/ClipperUtils.hpp>
|
|
|
|
#include <libslic3r/TriangleMeshSlicer.hpp>
|
|
#include <libslic3r/SlicesToTriangleMesh.hpp>
|
|
#include <libslic3r/StreamUtils.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
|
|
|
using namespace Slic3r;
|
|
using namespace Catch::Matchers;
|
|
|
|
// Note this tests SLA/RasterToPolygons.hpp, SLA/AGGRaster.hpp, and
|
|
// ClipperUtils.hpp at least as much as MarchingSquares.hpp.
|
|
|
|
// Get the Point corresponding to a raster column and row.
|
|
Point rstPoint(const sla::RasterGrayscaleAA& rst, const size_t c, const size_t r)
|
|
{
|
|
size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px;
|
|
auto pxd = rst.pixel_dimensions();
|
|
auto tr = rst.trafo();
|
|
coord_t width = scaled(cols * pxd.h_mm), height = scaled(rows * pxd.w_mm);
|
|
Point p = Point::new_scale(c * pxd.w_mm, r * pxd.h_mm);
|
|
// reverse the raster transformations
|
|
if (tr.mirror_y)
|
|
p.y() = height - p.y();
|
|
if (tr.mirror_x)
|
|
p.x() = width - p.x();
|
|
p.x() -= tr.center_x;
|
|
p.y() -= tr.center_y;
|
|
if (tr.flipXY)
|
|
std::swap(p.x(), p.y());
|
|
return p;
|
|
}
|
|
|
|
// Get the size of a raster pixel in coord_t.
|
|
static Point rstPixel(const sla::RasterGrayscaleAA& rst)
|
|
{
|
|
auto pxd = rst.pixel_dimensions();
|
|
return Point::new_scale(pxd.w_mm, pxd.h_mm);
|
|
}
|
|
|
|
// Get the size of a raster in coord_t.
|
|
static Point rstSize(const sla::RasterGrayscaleAA& rst)
|
|
{
|
|
auto pxd = rst.pixel_dimensions();
|
|
auto res = rst.resolution();
|
|
return Point::new_scale(pxd.w_mm * res.width_px, pxd.h_mm * res.height_px);
|
|
}
|
|
|
|
// Get the bounding box of a raster.
|
|
static BoundingBox rstBBox(const sla::RasterGrayscaleAA& rst)
|
|
{
|
|
auto center = rst.trafo().get_center();
|
|
return BoundingBox(Point(0, 0) - center, rstSize(rst) - center);
|
|
}
|
|
|
|
// Get the ExPolygons directly corresponding to a raster.
|
|
static ExPolygons rstGetPolys(sla::RasterGrayscaleAA& rst)
|
|
{
|
|
size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px;
|
|
Polygons polys;
|
|
for (auto r = 0; r < rows; r++) {
|
|
// use c0==cols as a sentinel marker for "no start column yet".
|
|
size_t c0 = cols;
|
|
for (auto c = 0; c <= cols; c++) {
|
|
if (c < cols && rst.read_pixel(c, r) > 128) {
|
|
// We have set pixels, set the c0 start column if it is not yet set.
|
|
if (c0 == cols)
|
|
c0 = c;
|
|
} else if (c0 < cols) {
|
|
// There is no pixel set, but we do have a c0 start column. Output a
|
|
// "row-rectangle" poly for this row between the start column c0 and
|
|
// the current column.
|
|
polys.push_back({rstPoint(rst, c0, r), rstPoint(rst, c0, r + 1), rstPoint(rst, c, r + 1), rstPoint(rst, c, r)});
|
|
// Make sure the poly is anti-clockwise, which it might not be
|
|
// depending on how rstPoint() reverses the raster transformations
|
|
// from (c,r) to (x,y) coordinates.
|
|
if (polys.back().is_clockwise())
|
|
polys.back().reverse();
|
|
// Clear the start column c0 for the next row-rectangle.
|
|
c0 = cols;
|
|
}
|
|
}
|
|
}
|
|
// Merge all the row-rectangle polys into contiguous raster ExPolygons.
|
|
return union_ex(polys);
|
|
}
|
|
|
|
// Get the length in mm of a "vector" Point.
|
|
static double len(const Point& v) { return unscaled(v.norm()); }
|
|
// Get the area in mm^2 of a box with corners at the origin and a Point.
|
|
static double area(const Point& v) { return unscaled(v.x()) * unscaled(v.y()); }
|
|
|
|
// Find the index of the nearest extracted ExPolygon for a reference ExPolygon.
|
|
static int find_closest_ext(const ExPolygons& exts, ExPolygon ref)
|
|
{
|
|
auto ref_center = ref.contour.bounding_box().center();
|
|
|
|
auto closest = std::min_element(exts.begin(), exts.end(), [&ref_center](auto a, auto b) {
|
|
auto a_center = a.contour.bounding_box().center();
|
|
auto b_center = b.contour.bounding_box().center();
|
|
return a_center.distance_to(ref_center) < b_center.distance_to(ref_center);
|
|
});
|
|
return std::distance(exts.begin(), closest);
|
|
}
|
|
|
|
static Slic3r::sla::RasterGrayscaleAA create_raster(const sla::Resolution& res, double disp_w = 100., double disp_h = 100.)
|
|
{
|
|
sla::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();
|
|
|
|
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<double> 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};
|
|
}
|
|
|
|
static const Vec2i32 W2x2 = {2, 2};
|
|
static const Vec2i32 W1x1 = {1, 1};
|
|
|
|
template<class Rst>
|
|
static void test_expolys(Rst&& rst, const ExPolygons& ref, Vec2i32 window, const std::string& name = "test", bool strict = true)
|
|
{
|
|
auto raster_bb = rstBBox(rst);
|
|
Point pixel_size = rstPixel(rst);
|
|
Point window_size{coord_t(pixel_size.x() * window.x()), coord_t(pixel_size.y() * window.y())};
|
|
double pixel_area = area(pixel_size);
|
|
double pixel_len = len(pixel_size);
|
|
double window_area = area(window_size);
|
|
double window_len = len(window_size);
|
|
|
|
for (const ExPolygon& expoly : ref)
|
|
rst.draw(expoly);
|
|
|
|
write_debug_stream("marchingsquares/" + name + ".png",
|
|
[&] { return rst.encode(sla::PNGRasterEncoder{}); });
|
|
|
|
const ExPolygons bmp = rstGetPolys(rst);
|
|
const ExPolygons ext = sla::raster_to_polygons(rst, window);
|
|
|
|
write_debug_svg("marchingsquares/" + name + ".svg", raster_bb, [&](SVG &svg) {
|
|
svg.draw(bmp, "green");
|
|
if (pixel_size.x() >= scale_(0.5))
|
|
svg.draw_grid(raster_bb, "grey", scale_(0.05), pixel_size.x());
|
|
if (window_size.x() >= scale_(1.0))
|
|
svg.draw_grid(raster_bb, "grey", scale_(0.10), window_size.x());
|
|
svg.draw_outline(ref, "red", "red", scale_(0.3));
|
|
svg.draw_outline(ext, "blue", "blue");
|
|
});
|
|
|
|
// Note all these areas are unscaled back to mm^2.
|
|
double raster_area = unscaled(unscaled(area(bmp)));
|
|
double reference_area = unscaled(unscaled(area(ref)));
|
|
double extracted_area = unscaled(unscaled(area(ext)));
|
|
|
|
// Note that errors accumulate with each step going from the reference
|
|
// polys to the extracted polys. The rendering of the reference polys to
|
|
// the raster does introduce pixelization errors too. This checks for
|
|
// acceptable errors going from reference to raster, and raster to
|
|
// reference.
|
|
for (size_t i = 0; i < ref.size(); ++i) {
|
|
if (ref[i].contour.size() < 20)
|
|
UNSCOPED_INFO("reference ref[" << i << "]: " << ref[i]);
|
|
}
|
|
CHECK_THAT(raster_area, WithinRel(reference_area, pixel_len * 0.05) || WithinAbs(reference_area, pixel_area));
|
|
for (size_t i = 0; i < ext.size(); ++i) {
|
|
if (ext[i].contour.size() < 20)
|
|
UNSCOPED_INFO("extracted ext[" << i << "]: " << ext[i]);
|
|
}
|
|
CHECK_THAT(extracted_area, WithinRel(raster_area, 0.05) || WithinAbs(raster_area, window_area));
|
|
for (auto i = 0; i < ext.size(); ++i) {
|
|
CHECK(ext[i].contour.is_counter_clockwise());
|
|
for (auto& h : ext[i].holes)
|
|
CHECK(h.is_clockwise());
|
|
}
|
|
|
|
BoundingBox ref_bb;
|
|
for (auto& expoly : ref)
|
|
ref_bb.merge(expoly.contour.bounding_box());
|
|
BoundingBox ext_bb;
|
|
for (auto& expoly : ext)
|
|
ext_bb.merge(expoly.contour.bounding_box());
|
|
CHECK(len(ext_bb.center() - ref_bb.center()) < pixel_len);
|
|
|
|
// In ambigous cases (when polygons just touch) there are multiple equally
|
|
// valid interpretations of the raster into polygons. Although
|
|
// MarchingSquares currently systematically selects the solution that
|
|
// breaks them into separate polygons, that might not always be true. Also,
|
|
// SLA/RasterToPolygons.hpp, and in particular union_ex() from
|
|
// ClipperUtils.hpp that it uses, can and does sometimes merge them back
|
|
// together. This means we cannot reliably make assertions about the
|
|
// extracted number of polygons and their shapes in these cases. So we skip
|
|
// the individual polygon checks for strict=false.
|
|
if (strict) {
|
|
CHECK(ext.size() == ref.size());
|
|
for (auto i = 0; i < ext.size(); ++i) {
|
|
auto j = find_closest_ext(ref, ext[i]);
|
|
INFO("Comparing ext[" << i << "] against closest ref[" << j << "]");
|
|
CHECK(ext[i].holes.size() == ref[j].holes.size());
|
|
double ext_i_area = unscaled(unscaled(ext[i].area()));
|
|
double ref_j_area = unscaled(unscaled(ref[j].area()));
|
|
CHECK_THAT(ext_i_area, WithinRel(ref_j_area, pixel_len * 0.05) || WithinAbs(ref_j_area, window_area));
|
|
auto ext_i_bb = ext[i].contour.bounding_box();
|
|
auto ref_j_bb = ref[j].contour.bounding_box();
|
|
CHECK(len(ext_i_bb.center() - ref_j_bb.center()) < pixel_len);
|
|
}
|
|
}
|
|
}
|
|
|
|
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]")
|
|
{
|
|
using namespace marchsq;
|
|
Coord crd{0, 0};
|
|
|
|
__impl::step(crd, __impl::Dir::left);
|
|
CHECK(crd == Coord(0, -1));
|
|
__impl::step(crd, __impl::Dir::down);
|
|
CHECK(crd == Coord(1, -1));
|
|
__impl::step(crd, __impl::Dir::right);
|
|
CHECK(crd == Coord(1, 0));
|
|
__impl::step(crd, __impl::Dir::up);
|
|
CHECK(crd == Coord(0, 0));
|
|
__impl::step(crd, __impl::Dir::left, 7);
|
|
CHECK(crd == Coord(0, -7));
|
|
__impl::step(crd, __impl::Dir::down, 7);
|
|
CHECK(crd == Coord(7, -7));
|
|
__impl::step(crd, __impl::Dir::right, 7);
|
|
CHECK(crd == Coord(7, 0));
|
|
__impl::step(crd, __impl::Dir::up, 7);
|
|
CHECK(crd == Coord(0, 0));
|
|
__impl::step(crd, __impl::Dir::left, -3);
|
|
CHECK(crd == Coord(0, 3));
|
|
__impl::step(crd, __impl::Dir::down, -3);
|
|
CHECK(crd == Coord(-3, 3));
|
|
__impl::step(crd, __impl::Dir::right, -3);
|
|
CHECK(crd == Coord(-3, 0));
|
|
__impl::step(crd, __impl::Dir::up, -3);
|
|
CHECK(crd == Coord(0, 0));
|
|
}
|
|
|
|
TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]")
|
|
{
|
|
auto rst = create_raster({4, 4}, 4., 4.);
|
|
|
|
ExPolygon rect = square(4);
|
|
|
|
SECTION("Full accuracy") { test_expolys(rst, {rect}, W1x1, "fully_covered_full_acc"); }
|
|
|
|
SECTION("Half accuracy") { test_expolys(rst, {rect}, W2x2, "fully_covered_half_acc"); }
|
|
}
|
|
|
|
TEST_CASE("4x4 raster with one ring", "[MarchingSquares]")
|
|
{
|
|
sla::PixelDim pixdim{1, 1};
|
|
|
|
// We need one additional row and column to detect edges
|
|
sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
|
|
|
|
ExPolygons one = {{{1, 1}, {3, 1}, {3, 3}, {2, 3}, {2, 2}, {1, 2}}};
|
|
for (ExPolygon& p : one)
|
|
p.scale(scaled(1.0));
|
|
test_expolys(rst, one, W1x1, "one_4x4");
|
|
}
|
|
|
|
TEST_CASE("10x10 raster with two rings", "[MarchingSquares]")
|
|
{
|
|
sla::PixelDim pixdim{1, 1};
|
|
|
|
// We need one additional row and column to detect edges
|
|
sla::RasterGrayscaleAA rst{{10, 10}, pixdim, {}, agg::gamma_threshold(.5)};
|
|
|
|
SECTION("Ambiguous case with 'bd' square")
|
|
{
|
|
ExPolygons ac = {{{1, 1}, {3, 1}, {3, 2}, {2, 2}, {2, 3}, {1, 3}}, {{4, 4}, {2, 4}, {2, 3}, {3, 3}, {3, 2}, {4, 2}}};
|
|
for (ExPolygon& p : ac)
|
|
p.scale(scaled(2.0));
|
|
test_expolys(rst, ac, W1x1, "bd_10x10", false);
|
|
}
|
|
|
|
SECTION("Ambiguous case with 'ac' square")
|
|
{
|
|
ExPolygons bd = {{{1, 4}, {1, 2}, {2, 2}, {2, 3}, {3, 3}, {3, 4}}, {{4, 1}, {4, 3}, {3, 3}, {3, 2}, {2, 2}, {2, 1}}};
|
|
for (ExPolygon& p : bd)
|
|
p.scale(scaled(2.0));
|
|
test_expolys(rst, bd, W1x1, "ac_10x10", false);
|
|
}
|
|
}
|
|
|
|
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, W1x1, "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, W2x2, "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, W1x1, "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, W2x2, "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, W1x1, "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, W2x2, "square_with_hole_portrait_1x1_mm_px_half");
|
|
}
|
|
|
|
SECTION("Proportional raster, 2x2 mm pixel size, full accuracy")
|
|
{
|
|
test_expolys(create_raster({50, 50}, 100., 100.), inp, W1x1, "square_with_hole_proportional_2x2_mm_px_full");
|
|
}
|
|
|
|
SECTION("Proportional raster, 2x2 mm pixel size, half accuracy")
|
|
{
|
|
test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_half");
|
|
}
|
|
|
|
SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy")
|
|
{
|
|
test_expolys(create_raster({200, 200}, 100., 100.), inp, W1x1, "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({200, 200}, 100., 100.), inp, W2x2, "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({1000, 1000}), circle_with_hole(25.), W1x1, "circle_with_hole");
|
|
}
|
|
|
|
static void recreate_object_from_rasters(const std::string& objname, float lh)
|
|
{
|
|
TriangleMesh mesh = load_model(objname);
|
|
|
|
auto bb = mesh.bounding_box();
|
|
Vec3f tr = -bb.center().cast<float>();
|
|
mesh.translate(tr.x(), tr.y(), tr.z());
|
|
bb = mesh.bounding_box();
|
|
|
|
std::vector<ExPolygons> layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh));
|
|
|
|
sla::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);
|
|
}
|
|
|
|
write_debug_stream("marchingsquares/" + objname + std::to_string(cntr) + ".png",
|
|
[&] { return rst.encode(sla::PNGRasterEncoder{}); });
|
|
|
|
ExPolygons layer_ = sla::raster_to_polygons(rst);
|
|
// float delta = scaled(std::min(rst.pixel_dimensions().h_mm,
|
|
// rst.pixel_dimensions().w_mm)) / 2;
|
|
|
|
// layer_ = expolygons_simplify(layer_, delta);
|
|
|
|
write_debug_svg("marchingsquares/" + objname + std::to_string(cntr) + ".svg", rstBBox(rst),
|
|
[&](SVG &svg) {
|
|
svg.draw(layer_);
|
|
svg.draw(layer, "green");
|
|
});
|
|
|
|
double layera = 0., layera_ = 0.;
|
|
for (auto& p : layer)
|
|
layera += p.area();
|
|
for (auto& p : layer_)
|
|
layera_ += p.area();
|
|
++cntr;
|
|
|
|
double diff = std::abs(layera_ - layera);
|
|
REQUIRE((diff <= 0.1 * layera || diff < scaled<double>(1.) * scaled<double>(1.)));
|
|
|
|
layer = std::move(layer_);
|
|
}
|
|
|
|
indexed_triangle_set out = slices_to_mesh(layers, bb.min.z(), double(lh), double(lh));
|
|
|
|
write_debug_obj("marchingsquares/out_from_rasters.obj", out);
|
|
}
|
|
|
|
TEST_CASE("Recreate object from rasters", "[SL1Import]") { recreate_object_from_rasters("frog_legs.obj", 0.05f); }
|
|
|
|
namespace marchsq {
|
|
|
|
static constexpr float layerf = 0.20; // layer height in mm (used for z values).
|
|
static constexpr float gsizef = 100.0; // grid size in mm (box volume side length).
|
|
static constexpr float wsizef = 0.50; // grid window size in mm (roughly line segment length).
|
|
static constexpr float psizef = 0.01; // raster pixel size in mm (roughly point accuracy).
|
|
static constexpr float isoval = 0.0; // iso value threshold to use.
|
|
static const long wsize = std::round(wsizef / psizef);
|
|
|
|
static float period = 10.0; // gyroid "wavelength" in mm (2x line spacing).
|
|
static float freq = 2 * PI / period; // gyroid frequency in waves per mm.
|
|
|
|
void set_period(float len = 10.0)
|
|
{
|
|
period = len;
|
|
freq = 2 * PI / period;
|
|
}
|
|
|
|
static size_t layer_n;
|
|
static size_t ring_n;
|
|
static size_t point_n;
|
|
static size_t get_n;
|
|
|
|
void reset_stats()
|
|
{
|
|
layer_n = 0;
|
|
ring_n = 0;
|
|
point_n = 0;
|
|
get_n = 0;
|
|
}
|
|
|
|
using Rings = std::vector<Ring>;
|
|
|
|
template<> struct _RasterTraits<size_t>
|
|
{
|
|
// using Rst = Slic3r::sla::RasterGrayscaleAA;
|
|
// The type of pixel cell in the raster
|
|
using ValueType = float;
|
|
|
|
// Value at a given position
|
|
static float get(const size_t& layer, size_t row, size_t col)
|
|
{
|
|
get_n++;
|
|
const float x = col * psizef * freq;
|
|
const float y = row * psizef * freq;
|
|
const float z = layer * psizef * freq;
|
|
|
|
return sinf(x) * cosf(y) + sinf(y) * cosf(z) + sinf(z) * cosf(x);
|
|
}
|
|
|
|
// Number of rows and cols of the raster
|
|
static size_t rows(const size_t& layer) { return std::round(gsizef / psizef); }
|
|
static size_t cols(const size_t& layer) { return std::round(gsizef / psizef); }
|
|
};
|
|
|
|
Rings get_gyroids(size_t l)
|
|
{
|
|
size_t layer = l;
|
|
Rings rings = execute(layer, isoval, {wsize, wsize});
|
|
layer_n++;
|
|
ring_n += rings.size();
|
|
for (auto r : rings)
|
|
point_n += r.size();
|
|
return rings;
|
|
}
|
|
|
|
}; // namespace marchsq
|
|
|
|
void benchmark_gyroid(float period)
|
|
{
|
|
marchsq::reset_stats();
|
|
marchsq::set_period(period);
|
|
INFO("grid size: " << marchsq::gsizef << "mm\nlayer height: " << marchsq::layerf << "mm\n");
|
|
INFO("window size: " << marchsq::wsizef << "mm\npoint size: " << marchsq::psizef << "mm\n");
|
|
INFO("gyroid period: " << marchsq::period << "mm\n");
|
|
BENCHMARK("indexed", i) { return marchsq::get_gyroids(i); };
|
|
INFO("output avg rings/layer: " << float(marchsq::ring_n) / float(marchsq::layer_n) << "\n");
|
|
INFO("output avg points/layer: " << float(marchsq::point_n) / float(marchsq::layer_n) << "\n");
|
|
INFO("output avg gets/layer: " << float(marchsq::get_n) / float(marchsq::layer_n) << "\n");
|
|
|
|
REQUIRE(marchsq::layer_n > 0);
|
|
}
|
|
|
|
TEST_CASE("Benchmark gyroid cube period 10.0mm", "[MarchingSquares]") { benchmark_gyroid(10.0); }
|
|
|
|
TEST_CASE("Benchmark gyroid cube period 5.0mm", "[MarchingSquares]") { benchmark_gyroid(5.0); }
|