Files
OrcaSlicer/tests/fff_print/test_texture_displacement.cpp
T
HanifKoh 4895bc03b4 Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced (#16099)
* 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
2026-10-05 16:47:17 +08:00

163 lines
6.3 KiB
C++

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <catch2/catch_all.hpp>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <ios>
#include <iterator>
#include <boost/filesystem.hpp>
#include <memory>
#include <vector>
#include <utility>
#include <string>
#include "libslic3r/PrintConfig.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include "libslic3r/Model.hpp"
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/TextureDisplacement.hpp"
#include "libslic3r/TriangleSelector.hpp"
#include "test_helpers.hpp"
#include "libslic3r/TriangleMesh.hpp"
using namespace Slic3r;
using namespace Slic3r::Test;
// What a bake leaves behind has to survive the slicer. The bake itself is covered in the libslic3r
// suite; these tests carry its result through Print::process and the G-code, which is where a mesh
// the slicer cannot use shows up - as a crash, or as a print with nothing in it.
namespace {
// A striped height map, so the relief has real steps in it rather than a flat offset.
std::shared_ptr<std::vector<unsigned char>> stripes_png(size_t w = 32, size_t h = 32)
{
std::vector<uint8_t> pixels(w * h);
for (size_t y = 0; y < h; ++y)
for (size_t x = 0; x < w; ++x)
pixels[y * w + x] = ((x / 4) % 2 == 0) ? 20 : 235;
const boost::filesystem::path tmp_path = boost::filesystem::temp_directory_path() /
boost::filesystem::unique_path("texdisp_slice_%%%%%%%%.png");
REQUIRE(Slic3r::png::write_gray_to_file(tmp_path.string(), w, h, pixels));
std::vector<unsigned char> bytes;
{
std::ifstream ifs(tmp_path.string(), std::ios::binary);
bytes.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
}
boost::system::error_code ec;
boost::filesystem::remove(tmp_path, ec);
REQUIRE_FALSE(bytes.empty());
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
}
// Paints every facet of `volume` with the texture layer in slot 0, as painting over the whole model
// does, and gives the layer a relief to bake.
void paint_whole_volume(ModelVolume &volume, float depth_mm, float tile_mm)
{
TextureDisplacementLayer layer;
layer.slot = 0;
layer.image_data = stripes_png();
layer.depth_mm = depth_mm;
layer.tiling_scale = tile_mm;
volume.texture_displacement_layers = { layer };
TriangleSelector selector(volume.mesh());
for (int i = 0; i < int(volume.mesh().its.indices.size()); ++i)
selector.set_facet(i, EnforcerBlockerType::ENFORCER);
volume.texture_displacement_facet(0).set(selector);
}
// The bake as the gizmo's job commits it: the mesh is replaced, the hull recomputed and the paint of
// the baked layer cleared. `budget_k` and `resolution_mm` stand in for the two panel controls.
size_t bake_into_volume(ModelVolume &volume, int budget_k, float resolution_mm)
{
TextureDisplacementOptions options;
options.pipeline_v2 = true;
options.v2_refine_mm = resolution_mm;
options.v2_max_triangles_k = budget_k;
TextureDisplacementFacetsData facets;
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
facets[size_t(i)] = volume.texture_displacement_facet(i).get_data();
TriangleMesh baked(build_texture_displacement(volume.mesh().its, volume.texture_displacement_layers, facets, options));
REQUIRE_FALSE(baked.empty());
volume.set_mesh(std::move(baked));
volume.set_new_unique_id();
volume.calculate_convex_hull();
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
volume.texture_displacement_facet(i).reset();
return volume.mesh().its.indices.size();
}
// Extruding moves in the G-code: what tells a print with toolpaths from one without.
size_t count_extrusions(const std::string &gcode_text)
{
size_t count = 0;
for (size_t pos = 0; (pos = gcode_text.find("\nG1 ", pos)) != std::string::npos; ++pos) {
const size_t eol = gcode_text.find('\n', pos + 1);
if (gcode_text.find(" E", pos) < eol)
++count;
}
return count;
}
} // namespace
TEST_CASE("A baked texture slices into a print with toolpaths", "[TextureDisplacement]")
{
Model model = Slic3r::Test::model("cube", Test::cube(20.));
ModelVolume &volume = *model.objects.front()->volumes.front();
paint_whole_volume(volume, /* depth */ 0.4f, /* tile */ 8.f);
// Coarse on purpose: this test is about the result reaching the slicer, not about how fine it is.
const size_t baked_triangles = bake_into_volume(volume, /* budget */ 60, /* resolution */ 0.8f);
CHECK(baked_triangles > Test::cube(20.).its.indices.size()); // the relief added geometry
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "layer_height", "0.2" }, { "initial_layer_print_height", "0.2" } });
Print print;
Model sliced_model;
init_print({ volume.mesh() }, print, sliced_model, config);
REQUIRE(print.objects().size() == 1);
print.process();
const std::string gcode_text = Test::gcode(print);
REQUIRE_FALSE(gcode_text.empty());
CHECK(count_extrusions(gcode_text) > 1000);
CHECK(print.objects().front()->layer_count() > 10);
}
TEST_CASE("A baked texture slices the same whether or not the budget capped it", "[TextureDisplacement]")
{
// The budget decides how much of the relief survives, and a capped bake goes through the
// simplification and the repair that follow it. Both have to leave a mesh the slicer can print.
const int budget_k = GENERATE(10, 400);
Model model = Slic3r::Test::model("cube", Test::cube(20.));
ModelVolume &volume = *model.objects.front()->volumes.front();
paint_whole_volume(volume, /* depth */ 0.4f, /* tile */ 8.f);
bake_into_volume(volume, budget_k, /* resolution */ 0.5f);
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "layer_height", "0.2" }, { "initial_layer_print_height", "0.2" } });
Print print;
Model sliced_model;
init_print({ volume.mesh() }, print, sliced_model, config);
print.process();
const std::string gcode_text = Test::gcode(print);
REQUIRE_FALSE(gcode_text.empty());
CHECK(count_extrusions(gcode_text) > 1000);
}