mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-09 14:55:08 -06:00
Fixed conflicts after merge with master
This commit is contained in:
commit
e5c45405d4
80 changed files with 4288 additions and 2581 deletions
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libslic3r/ModelArrange.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -167,8 +168,7 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
|
|||
object->add_volume(std::move(t));
|
||||
object->add_instance();
|
||||
}
|
||||
model.arrange_objects(PrintConfig::min_object_distance(&config));
|
||||
model.center_instances_around_point(Slic3r::Vec2d(100, 100));
|
||||
arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))});
|
||||
for (ModelObject *mo : model.objects) {
|
||||
mo->ensure_on_bed();
|
||||
print.auto_assign_extruders(mo);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
@ -41,8 +42,7 @@ SCENARIO("Model construction", "[Model]") {
|
|||
}
|
||||
}
|
||||
model_object->add_instance();
|
||||
model.arrange_objects(PrintConfig::min_object_distance(&config));
|
||||
model.center_instances_around_point(Slic3r::Vec2d(100, 100));
|
||||
arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))});
|
||||
model_object->ensure_on_bed();
|
||||
print.auto_assign_extruders(model_object);
|
||||
THEN("Print works?") {
|
||||
|
|
|
@ -472,32 +472,30 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
|
|||
namespace {
|
||||
using namespace libnest2d;
|
||||
|
||||
template<long long SCALE = 1, class Bin>
|
||||
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
|
||||
std::string loc = "out";
|
||||
template<long long SCALE = 1, class It>
|
||||
void exportSVG(const char *loc, It from, It to) {
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
static const char* svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)raw";
|
||||
|
||||
int i = idx;
|
||||
auto r = result;
|
||||
// for(auto r : result) {
|
||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
||||
std::fstream out(loc, std::fstream::out);
|
||||
if(out.is_open()) {
|
||||
out << svg_header;
|
||||
Item rbin( RectangleItem(bin.width(), bin.height()) );
|
||||
for(unsigned j = 0; j < rbin.vertexCount(); j++) {
|
||||
auto v = rbin.vertex(j);
|
||||
setY(v, -getY(v)/SCALE + 500 );
|
||||
setX(v, getX(v)/SCALE);
|
||||
rbin.setVertex(j, v);
|
||||
}
|
||||
out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
|
||||
for(Item& sh : r) {
|
||||
Item tsh(sh.transformedShape());
|
||||
// Item rbin( RectangleItem(bin.width(), bin.height()) );
|
||||
// for(unsigned j = 0; j < rbin.vertexCount(); j++) {
|
||||
// auto v = rbin.vertex(j);
|
||||
// setY(v, -getY(v)/SCALE + 500 );
|
||||
// setX(v, getX(v)/SCALE);
|
||||
// rbin.setVertex(j, v);
|
||||
// }
|
||||
// out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
|
||||
for(auto it = from; it != to; ++it) {
|
||||
const Item &itm = *it;
|
||||
Item tsh(itm.transformedShape());
|
||||
for(unsigned j = 0; j < tsh.vertexCount(); j++) {
|
||||
auto v = tsh.vertex(j);
|
||||
setY(v, -getY(v)/SCALE + 500);
|
||||
|
@ -509,10 +507,16 @@ void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin
|
|||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
|
||||
// i++;
|
||||
// }
|
||||
}
|
||||
|
||||
template<long long SCALE = 1>
|
||||
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, int idx = 0) {
|
||||
exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(),
|
||||
result.begin(), result.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BottomLeftStressTest", "[Geometry]") {
|
||||
|
@ -541,7 +545,7 @@ TEST_CASE("BottomLeftStressTest", "[Geometry]") {
|
|||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
||||
if(!valid) {
|
||||
std::cout << "error index: " << i << std::endl;
|
||||
exportSVG(result, bin, i);
|
||||
exportSVG<SCALE>(result, i);
|
||||
}
|
||||
REQUIRE(valid);
|
||||
} else {
|
||||
|
@ -894,7 +898,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
|
|||
|
||||
int TEST_CASEcase = 0;
|
||||
|
||||
auto& exportfun = exportSVG<SCALE, Box>;
|
||||
auto& exportfun = exportSVG<SCALE>;
|
||||
|
||||
auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
|
||||
TEST_CASEcase++;
|
||||
|
@ -941,7 +945,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
|
|||
std::ref(stationary), std::ref(tmp), std::ref(infp)
|
||||
};
|
||||
|
||||
exportfun(inp, bin, TEST_CASEcase*i++);
|
||||
exportfun(inp, TEST_CASEcase*i++);
|
||||
}
|
||||
|
||||
REQUIRE(touching);
|
||||
|
@ -1096,3 +1100,91 @@ TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") {
|
|||
REQUIRE(succ);
|
||||
}
|
||||
}
|
||||
|
||||
template<class It> MultiPolygon merged_pile(It from, It to, int bin_id)
|
||||
{
|
||||
MultiPolygon pile;
|
||||
pile.reserve(size_t(to - from));
|
||||
|
||||
for (auto it = from; it != to; ++it) {
|
||||
if (it->binId() == bin_id) pile.emplace_back(it->transformedShape());
|
||||
}
|
||||
|
||||
return nfp::merge(pile);
|
||||
}
|
||||
|
||||
TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]")
|
||||
{
|
||||
static const constexpr ClipperLib::cInt W = 10000000;
|
||||
|
||||
// Get the input items and define the bin.
|
||||
std::vector<RectangleItem> input(9, {W, W});
|
||||
|
||||
auto bin = Box::infinite();
|
||||
|
||||
NfpPlacer::Config pconfig;
|
||||
|
||||
pconfig.object_function = [](const Item &item) -> double {
|
||||
return pl::magnsq<PointImpl, double>(item.boundingBox().center());
|
||||
};
|
||||
|
||||
size_t bins = nest(input, bin, 0, NestConfig{pconfig});
|
||||
|
||||
REQUIRE(bins == 1);
|
||||
|
||||
// Gather the items into piles of arranged polygons...
|
||||
MultiPolygon pile;
|
||||
pile.reserve(input.size());
|
||||
|
||||
for (auto &itm : input) {
|
||||
REQUIRE(itm.binId() == 0);
|
||||
pile.emplace_back(itm.transformedShape());
|
||||
}
|
||||
|
||||
MultiPolygon m = merged_pile(input.begin(), input.end(), 0);
|
||||
|
||||
REQUIRE(m.size() == 1);
|
||||
|
||||
REQUIRE(sl::area(m) == Approx(9. * W * W));
|
||||
}
|
||||
|
||||
TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]")
|
||||
{
|
||||
static const constexpr ClipperLib::cInt W = 10000000;
|
||||
static const constexpr size_t N = 100;
|
||||
|
||||
// Get the input items and define the bin.
|
||||
std::vector<RectangleItem> input(N, {W, W});
|
||||
|
||||
auto bin = Box::infinite();
|
||||
|
||||
NfpPlacer::Config pconfig;
|
||||
pconfig.rotations = {0.};
|
||||
Box pile_box;
|
||||
pconfig.before_packing =
|
||||
[&pile_box](const MultiPolygon &pile,
|
||||
const _ItemGroup<PolygonImpl> &/*packed_items*/,
|
||||
const _ItemGroup<PolygonImpl> &/*remaining_items*/) {
|
||||
pile_box = sl::boundingBox(pile);
|
||||
};
|
||||
|
||||
pconfig.object_function = [&pile_box](const Item &item) -> double {
|
||||
Box b = sl::boundingBox(item.boundingBox(), pile_box);
|
||||
double area = b.area<double>() / (W * W);
|
||||
return -area;
|
||||
};
|
||||
|
||||
size_t bins = nest(input, bin, 0, NestConfig{pconfig});
|
||||
|
||||
// To debug:
|
||||
exportSVG<1000000>("out", input.begin(), input.end());
|
||||
|
||||
REQUIRE(bins == 1);
|
||||
|
||||
MultiPolygon pile = merged_pile(input.begin(), input.end(), 0);
|
||||
Box bb = sl::boundingBox(pile);
|
||||
|
||||
// Here the result shall be a stairway of boxes
|
||||
REQUIRE(pile.size() == N);
|
||||
REQUIRE(bb.area() == N * N * W * W);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
|
|||
test_stl.cpp
|
||||
test_meshsimplify.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
test_timeutils.cpp
|
||||
)
|
||||
|
||||
|
|
371
tests/libslic3r/test_marchingsquares.cpp
Normal file
371
tests/libslic3r/test_marchingsquares.cpp
Normal file
|
@ -0,0 +1,371 @@
|
|||
#define NOMINMAX
|
||||
|
||||
#include <catch2/catch.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/TriangulateWall.hpp>
|
||||
#include <libslic3r/Tesselate.hpp>
|
||||
#include <libslic3r/SlicesToTriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
static double area(const sla::RasterBase::PixelDim &pxd)
|
||||
{
|
||||
return pxd.w_mm * pxd.h_mm;
|
||||
}
|
||||
|
||||
static Slic3r::sla::RasterGrayscaleAA create_raster(
|
||||
const sla::RasterBase::Resolution &res,
|
||||
double disp_w = 100.,
|
||||
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();
|
||||
|
||||
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 Vec2i W4x4 = {4, 4};
|
||||
static const Vec2i W2x2 = {2, 2};
|
||||
|
||||
template<class Rst>
|
||||
static void test_expolys(Rst && rst,
|
||||
const ExPolygons & ref,
|
||||
Vec2i window,
|
||||
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, window);
|
||||
|
||||
SVG svg(name + ".svg");
|
||||
svg.draw(extracted);
|
||||
svg.draw(ref, "green");
|
||||
svg.Close();
|
||||
|
||||
double max_rel_err = 0.1;
|
||||
sla::RasterBase::PixelDim pxd = rst.pixel_dimensions();
|
||||
double max_abs_err = area(pxd) * scaled(1.) * scaled(1.);
|
||||
|
||||
BoundingBox ref_bb;
|
||||
for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box());
|
||||
|
||||
double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2));
|
||||
max_displacement *= scaled<double>(1.) * scaled(1.);
|
||||
|
||||
REQUIRE(extracted.size() == ref.size());
|
||||
for (size_t i = 0; i < ref.size(); ++i) {
|
||||
REQUIRE(extracted[i].contour.is_counter_clockwise());
|
||||
REQUIRE(extracted[i].holes.size() == ref[i].holes.size());
|
||||
|
||||
for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise());
|
||||
|
||||
double refa = ref[i].area();
|
||||
double abs_err = std::abs(extracted[i].area() - refa);
|
||||
double rel_err = abs_err / refa;
|
||||
|
||||
REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err));
|
||||
|
||||
BoundingBox bb;
|
||||
for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box());
|
||||
|
||||
Point d = bb.center() - ref_bb.center();
|
||||
REQUIRE(double(d.transpose() * d) <= max_displacement);
|
||||
}
|
||||
}
|
||||
|
||||
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("Fully covered raster should result in a rectangle", "[MarchingSquares]") {
|
||||
auto rst = create_raster({4, 4}, 4., 4.);
|
||||
|
||||
ExPolygon rect = square(4);
|
||||
|
||||
SECTION("Full accuracy") {
|
||||
test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc");
|
||||
}
|
||||
|
||||
SECTION("Half accuracy") {
|
||||
test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
|
||||
|
||||
sla::RasterBase::PixelDim pixdim{1, 1};
|
||||
|
||||
// We need one additional row and column to detect edges
|
||||
sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
|
||||
|
||||
// Draw a triangle from individual pixels
|
||||
rst.draw(square(1., {0500000, 0500000}));
|
||||
rst.draw(square(1., {1500000, 0500000}));
|
||||
rst.draw(square(1., {2500000, 0500000}));
|
||||
|
||||
rst.draw(square(1., {1500000, 1500000}));
|
||||
rst.draw(square(1., {2500000, 1500000}));
|
||||
|
||||
rst.draw(square(1., {2500000, 2500000}));
|
||||
|
||||
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, W2x2, "square_with_hole_proportional_1x1_mm_px_full");
|
||||
}
|
||||
|
||||
SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") {
|
||||
test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half");
|
||||
}
|
||||
|
||||
SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") {
|
||||
test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full");
|
||||
}
|
||||
|
||||
SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") {
|
||||
test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half");
|
||||
}
|
||||
|
||||
SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") {
|
||||
test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full");
|
||||
}
|
||||
|
||||
SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") {
|
||||
test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half");
|
||||
}
|
||||
|
||||
SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") {
|
||||
test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full");
|
||||
}
|
||||
|
||||
SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") {
|
||||
test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half");
|
||||
}
|
||||
|
||||
SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") {
|
||||
test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full");
|
||||
}
|
||||
|
||||
SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") {
|
||||
test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole");
|
||||
}
|
||||
|
||||
static void recreate_object_from_rasters(const std::string &objname, float lh) {
|
||||
TriangleMesh mesh = load_model(objname);
|
||||
|
||||
auto bb = mesh.bounding_box();
|
||||
Vec3f tr = -bb.center().cast<float>();
|
||||
mesh.translate(tr.x(), tr.y(), tr.z());
|
||||
bb = mesh.bounding_box();
|
||||
|
||||
std::vector<ExPolygons> 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)) / 2;
|
||||
|
||||
// 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<double>(1.) * scaled<double>(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("frog_legs.obj", 0.05f);
|
||||
}
|
|
@ -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]")
|
||||
|
|
|
@ -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<double>(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.;
|
||||
|
|
|
@ -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 <class A, int N> 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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue