IO: add STL import/export tests

Pull Request: https://projects.blender.org/blender/blender/pulls/115164
This commit is contained in:
Aras Pranckevicius
2023-11-20 11:19:50 +01:00
committed by Aras Pranckevicius
parent 02b5e27f89
commit 0d0aad6280
6 changed files with 302 additions and 69 deletions

View File

@@ -50,3 +50,28 @@ set(LIB
)
blender_add_lib(bf_io_stl "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/stl_exporter_tests.cc
tests/stl_importer_tests.cc
)
set(TEST_INC
${INC}
../../blenloader
../../../../tests/gtests
)
set(TEST_LIB
${LIB}
bf_blenloader_tests
bf_io_stl
)
include(GTestTesting)
blender_add_test_lib(bf_io_stl_tests "${TEST_SRC}" "${TEST_INC}" "${INC_SYS}" "${TEST_LIB}")
add_dependencies(bf_io_stl_tests bf_io_stl)
endif()

View File

@@ -30,13 +30,12 @@
namespace blender::io::stl {
void exporter_main(bContext *C, const STLExportParams &export_params)
void export_frame(Depsgraph *depsgraph,
float scene_unit_scale,
const STLExportParams &export_params)
{
std::unique_ptr<FileWriter> writer;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
/* If not exporting in batch, create single writer for all objects. */
if (!export_params.use_batch) {
writer = std::make_unique<FileWriter>(export_params.filepath, export_params.ascii_format);
@@ -77,10 +76,7 @@ void exporter_main(bContext *C, const STLExportParams &export_params)
BKE_object_get_pre_modified_mesh(obj_eval);
/* Calculate transform. */
float global_scale = export_params.global_scale;
if ((scene->unit.system != USER_UNIT_NONE) && export_params.use_scene_unit) {
global_scale *= scene->unit.scale_length;
}
float global_scale = export_params.global_scale * scene_unit_scale;
float axes_transform[3][3];
unit_m3(axes_transform);
float xform[4][4];
@@ -110,4 +106,15 @@ void exporter_main(bContext *C, const STLExportParams &export_params)
DEG_OBJECT_ITER_END;
}
void exporter_main(bContext *C, const STLExportParams &export_params)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
float scene_unit_scale = 1.0f;
if ((scene->unit.system != USER_UNIT_NONE) && export_params.use_scene_unit) {
scene_unit_scale = scene->unit.scale_length;
}
export_frame(depsgraph, scene_unit_scale, export_params);
}
} // namespace blender::io::stl

View File

@@ -10,7 +10,9 @@
namespace blender::io::stl {
/* Main export function used from within Blender. */
void exporter_main(bContext *C, const STLExportParams &export_params);
void export_frame(Depsgraph *depsgraph,
float scene_unit_scale,
const STLExportParams &export_params);
} // namespace blender::io::stl

View File

@@ -0,0 +1,132 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "tests/blendfile_loading_base_test.h"
#include "BKE_appdir.h"
#include "BKE_main.h"
#include "BLI_fileops.h"
#include "BLI_string.h"
#include "BLO_readfile.h"
#include "DEG_depsgraph.hh"
#include "IO_stl.hh"
#include "stl_export.hh"
namespace blender::io::stl {
/* Set this true to keep comparison-failing test output in temp file directory. */
constexpr bool save_failing_test_output = false;
static std::string read_temp_file_in_string(const std::string &file_path)
{
std::string res;
size_t buffer_len;
void *buffer = BLI_file_read_text_as_mem(file_path.c_str(), 0, &buffer_len);
if (buffer != nullptr) {
res.assign((const char *)buffer, buffer_len);
MEM_freeN(buffer);
}
return res;
}
class stl_export_test : public BlendfileLoadingBaseTest {
public:
bool load_file_and_depsgraph(const std::string &filepath)
{
if (!blendfile_load(filepath.c_str())) {
return false;
}
depsgraph_create(DAG_EVAL_VIEWPORT);
return true;
}
protected:
stl_export_test()
{
_params = {};
_params.forward_axis = IO_AXIS_Y;
_params.up_axis = IO_AXIS_Z;
_params.global_scale = 1.0f;
_params.apply_modifiers = true;
_params.ascii_format = true;
}
void SetUp() override
{
BlendfileLoadingBaseTest::SetUp();
BKE_tempdir_init("");
}
void TearDown() override
{
BlendfileLoadingBaseTest::TearDown();
BKE_tempdir_session_purge();
}
static std::string get_temp_filename(const std::string &filename)
{
return std::string(BKE_tempdir_base()) + SEP_STR + filename;
}
/**
* Export the given blend file with the given parameters and
* test to see if it matches a golden file (ignoring any difference in Blender version number).
* \param blendfile: input, relative to "tests" directory.
* \param golden_obj: expected output, relative to "tests" directory.
* \param params: the parameters to be used for export.
*/
void compare_to_golden(const std::string &blendfile, const std::string &golden_stl)
{
if (!load_file_and_depsgraph(blendfile)) {
return;
}
std::string out_file_path = get_temp_filename(BLI_path_basename(golden_stl.c_str()));
STRNCPY(_params.filepath, out_file_path.c_str());
std::string golden_file_path = blender::tests::flags_test_asset_dir() + SEP_STR + golden_stl;
export_frame(depsgraph, 1.0f, _params);
std::string output_str = read_temp_file_in_string(out_file_path);
std::string golden_str = read_temp_file_in_string(golden_file_path);
bool are_equal = output_str == golden_str;
if (save_failing_test_output && !are_equal) {
printf("failing test output in %s\n", out_file_path.c_str());
}
ASSERT_TRUE(are_equal);
if (!save_failing_test_output || are_equal) {
BLI_delete(out_file_path.c_str(), false, false);
}
}
STLExportParams _params;
};
TEST_F(stl_export_test, all_tris)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "all_tris.blend",
"io_tests" SEP_STR "stl" SEP_STR "all_tris.stl");
}
TEST_F(stl_export_test, all_quads)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "all_quads.blend",
"io_tests" SEP_STR "stl" SEP_STR "all_quads.stl");
}
TEST_F(stl_export_test, non_uniform_scale)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "non_uniform_scale.blend",
"io_tests" SEP_STR "stl" SEP_STR "non_uniform_scale.stl");
}
TEST_F(stl_export_test, cubes_positioned)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "cubes_positioned.blend",
"io_tests" SEP_STR "stl" SEP_STR "cubes_positioned.stl");
}
} // namespace blender::io::stl

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "tests/blendfile_loading_base_test.h"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "BLO_readfile.h"
#include "DEG_depsgraph_query.hh"
#include "stl_import.hh"
namespace blender::io::stl {
struct Expectation {
int totvert, totedge, faces_num, totloop;
float3 vert_first, vert_last;
};
class stl_importer_test : public BlendfileLoadingBaseTest {
public:
stl_importer_test()
{
params.forward_axis = IO_AXIS_NEGATIVE_Z;
params.up_axis = IO_AXIS_Y;
params.use_facet_normal = false;
params.use_scene_unit = false;
params.global_scale = 1.0f;
params.use_mesh_validate = true;
}
void import_and_check(const char *path, const Expectation &expect)
{
if (!blendfile_load("io_tests" SEP_STR "blend_geometry" SEP_STR "all_quads.blend")) {
ADD_FAILURE();
return;
}
std::string stl_path = blender::tests::flags_test_asset_dir() +
SEP_STR "io_tests" SEP_STR "stl" SEP_STR + path;
STRNCPY(params.filepath, stl_path.c_str());
importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params);
depsgraph_create(DAG_EVAL_VIEWPORT);
DEGObjectIterSettings deg_iter_settings{};
deg_iter_settings.depsgraph = depsgraph;
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | DEG_ITER_OBJECT_FLAG_VISIBLE |
DEG_ITER_OBJECT_FLAG_DUPLI;
constexpr bool print_result_scene = false;
if (print_result_scene) {
printf("Result was:\n");
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
printf(" {");
if (object->type == OB_MESH) {
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
const Span<float3> positions = mesh->vert_positions();
printf("%i, %i, %i, %i, float3(%g, %g, %g), float3(%g, %g, %g)",
mesh->totvert,
mesh->totedge,
mesh->faces_num,
mesh->totloop,
positions.first().x,
positions.first().y,
positions.first().z,
positions.last().x,
positions.last().y,
positions.last().z);
}
printf("},\n");
}
DEG_OBJECT_ITER_END;
}
size_t object_index = 0;
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
++object_index;
/* First object is from loaded scene. */
if (object_index == 1) {
continue;
}
EXPECT_V3_NEAR(object->loc, float3(0, 0, 0), 0.0001f);
EXPECT_V3_NEAR(object->rot, float3(M_PI_2, 0, 0), 0.0001f);
EXPECT_V3_NEAR(object->scale, float3(1, 1, 1), 0.0001f);
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
EXPECT_EQ(mesh->totvert, expect.totvert);
EXPECT_EQ(mesh->totedge, expect.totedge);
EXPECT_EQ(mesh->faces_num, expect.faces_num);
EXPECT_EQ(mesh->totloop, expect.totloop);
const Span<float3> positions = mesh->vert_positions();
EXPECT_V3_NEAR(positions.first(), expect.vert_first, 0.0001f);
EXPECT_V3_NEAR(positions.last(), expect.vert_last, 0.0001f);
break;
}
DEG_OBJECT_ITER_END;
}
STLImportParams params;
};
TEST_F(stl_importer_test, all_quads)
{
Expectation expect = {8, 18, 12, 36, float3(1, 1, 1), float3(1, -1, 1)};
import_and_check("all_quads.stl", expect);
}
TEST_F(stl_importer_test, cubes_positioned)
{
Expectation expect = {24, 54, 36, 108, float3(1, 1, 1), float3(5.49635f, 0.228398f, -1.11237f)};
import_and_check("cubes_positioned.stl", expect);
}
TEST_F(stl_importer_test, non_uniform_scale)
{
Expectation expect = {140, 378, 252, 756, float3(0, 0, -0.3f), float3(-0.866025f, -1.5f, 0)};
import_and_check("non_uniform_scale.stl", expect);
}
} // namespace blender::io::stl

View File

@@ -426,66 +426,6 @@ add_blender_test(
# ------------------------------------------------------------------------------
# IO TESTS
# STL Import tests
# disabled until updated & working
if(FALSE)
add_blender_test(
import_stl_cube
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/cube.stl'\)
--md5=8ceb5bb7e1cb5f4342fa1669988c66b4 --md5_method=SCENE
--write-blend=${TEST_OUT_DIR}/io_tests/import_stl_cube.blend
)
add_blender_test(
import_stl_conrod
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/conrod.stl'\)
--md5=690a4b8eb9002dcd8631c5a575ea7348 --md5_method=SCENE
--write-blend=${TEST_OUT_DIR}/io_tests/import_stl_conrod.blend
)
add_blender_test(
import_stl_knot_max_simplified
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/knot_max_simplified.stl'\)
--md5=baf82803f45a84ec4ddbad9cef57dd3e --md5_method=SCENE
--write-blend=${TEST_OUT_DIR}/io_tests/import_stl_knot_max_simplified.blend
)
endif()
# STL Export
# disabled until updated & working
if(FALSE)
add_blender_test(
export_stl_cube_all_data
${TEST_SRC_DIR}/io_tests/blend_geometry/cube_all_data.blend
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/io_tests/export_stl_cube_all_data.stl'\)
--md5_source=${TEST_OUT_DIR}/io_tests/export_stl_cube_all_data.stl
--md5=64cb97c0cabb015e1c3f76369835075a --md5_method=FILE
)
add_blender_test(
export_stl_suzanne_all_data
${TEST_SRC_DIR}/io_tests/blend_geometry/suzanne_all_data.blend
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/io_tests/export_stl_suzanne_all_data.stl'\)
--md5_source=${TEST_OUT_DIR}/io_tests/export_stl_suzanne_all_data.stl
--md5=e9b23c97c139ad64961c635105bb9192 --md5_method=FILE
)
add_blender_test(
export_stl_vertices # lame, add a better one
${TEST_SRC_DIR}/io_tests/blend_geometry/vertices.blend
--python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py --
--run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/io_tests/export_stl_vertices.stl'\)
--md5_source=${TEST_OUT_DIR}/io_tests/export_stl_vertices.stl
--md5=3fd3c877e573beeebc782532cc005820 --md5_method=FILE
)
endif()
# X3D Import
# disabled until updated & working
if(FALSE)