mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-25 18:00:57 +00:00
Test that a baked texture still slices
The bake is covered in the libslic3r suite, but nothing carried its result through the slicer - where a mesh it cannot use shows up as a crash or as a print with no toolpaths, which is what was reported on macOS and Linux. Paints a cube, bakes it as the gizmo commits it (mesh replaced, hull recomputed, paint cleared), slices it and checks the G-code has toolpaths and layers. Once under its budget and once capped, since a capped bake also goes through the simplification and the repair.
This commit is contained in:
@@ -20,6 +20,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_skirt_brim.cpp
|
||||
test_slicing_pipeline_hook.cpp
|
||||
test_support_material.cpp
|
||||
test_texture_displacement.cpp
|
||||
test_tree_support.cpp
|
||||
test_trianglemesh.cpp
|
||||
test_wipe.cpp
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
#include <boost/filesystem.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"
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user