IO: add STL import/export tests
Pull Request: https://projects.blender.org/blender/blender/pulls/115164
This commit is contained in:
committed by
Aras Pranckevicius
parent
02b5e27f89
commit
0d0aad6280
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
132
source/blender/io/stl/tests/stl_exporter_tests.cc
Normal file
132
source/blender/io/stl/tests/stl_exporter_tests.cc
Normal 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
|
||||
127
source/blender/io/stl/tests/stl_importer_tests.cc
Normal file
127
source/blender/io/stl/tests/stl_importer_tests.cc
Normal 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 (°_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 (°_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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user