193 lines
5.3 KiB
C++
193 lines
5.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include "vde/foundation/io_3mf.h"
|
|
|
|
using namespace vde::foundation;
|
|
using namespace vde::mesh;
|
|
|
|
// Helper: build a tetrahedron
|
|
static ThreeMFMesh make_tetrahedron(const std::string& name = "") {
|
|
ThreeMFMesh m;
|
|
m.name = name;
|
|
m.mesh.build_from_triangles(
|
|
{
|
|
Point3D(0, 0, 0), Point3D(1, 0, 0),
|
|
Point3D(0.5, 0.866, 0), Point3D(0.5, 0.289, 0.816)
|
|
},
|
|
{
|
|
{0, 1, 2}, {0, 3, 1}, {1, 3, 2}, {0, 2, 3}
|
|
}
|
|
);
|
|
return m;
|
|
}
|
|
|
|
// Helper: build a cube (8 vertices, 12 triangles)
|
|
static ThreeMFMesh make_cube(const std::string& name = "") {
|
|
ThreeMFMesh m;
|
|
m.name = name;
|
|
m.mesh.build_from_triangles(
|
|
{
|
|
Point3D(0,0,0), Point3D(1,0,0), Point3D(1,1,0), Point3D(0,1,0),
|
|
Point3D(0,0,1), Point3D(1,0,1), Point3D(1,1,1), Point3D(0,1,1)
|
|
},
|
|
{
|
|
{0,1,2}, {0,2,3}, // front
|
|
{4,5,6}, {4,6,7}, // back
|
|
{0,4,5}, {0,5,1}, // bottom
|
|
{2,1,5}, {2,5,6}, // right
|
|
{3,2,6}, {3,6,7}, // top
|
|
{0,3,7}, {0,7,4} // left
|
|
}
|
|
);
|
|
return m;
|
|
}
|
|
|
|
// ── Export / round-trip ─────────────────────────────────────
|
|
|
|
TEST(Io3mfTest, ExportThenReadTetrahedron) {
|
|
auto orig = make_tetrahedron("Tet");
|
|
const char* path = "/tmp/test_3mf_tet.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {orig}, "VDE-Test"));
|
|
|
|
auto loaded = read_3mf(path);
|
|
ASSERT_EQ(loaded.size(), 1u);
|
|
EXPECT_EQ(loaded[0].mesh.num_vertices(), 4u);
|
|
EXPECT_EQ(loaded[0].mesh.num_faces(), 4u);
|
|
EXPECT_EQ(loaded[0].name, "Tet");
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, ExportThenReadCube) {
|
|
auto orig = make_cube("MyBox");
|
|
const char* path = "/tmp/test_3mf_cube.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {orig}));
|
|
|
|
auto loaded = read_3mf(path);
|
|
ASSERT_EQ(loaded.size(), 1u);
|
|
EXPECT_EQ(loaded[0].mesh.num_vertices(), 8u);
|
|
EXPECT_EQ(loaded[0].mesh.num_faces(), 12u);
|
|
EXPECT_EQ(loaded[0].name, "MyBox");
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, NamePreserved) {
|
|
auto orig = make_tetrahedron("DesignerPart001");
|
|
const char* path = "/tmp/test_3mf_name.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {orig}));
|
|
auto loaded = read_3mf(path);
|
|
ASSERT_EQ(loaded.size(), 1u);
|
|
EXPECT_EQ(loaded[0].name, "DesignerPart001");
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, NameEmptyPreserved) {
|
|
auto orig = make_tetrahedron(); // no name
|
|
const char* path = "/tmp/test_3mf_noname.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {orig}));
|
|
auto loaded = read_3mf(path);
|
|
ASSERT_EQ(loaded.size(), 1u);
|
|
EXPECT_TRUE(loaded[0].name.empty());
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, ReadNonExistentFile) {
|
|
auto result = read_3mf("/tmp/nonexistent_xyz_3mf.3mf");
|
|
EXPECT_TRUE(result.empty());
|
|
}
|
|
|
|
TEST(Io3mfTest, EmptyMeshesReturnsFalse) {
|
|
const char* path = "/tmp/test_3mf_empty.3mf";
|
|
std::remove(path);
|
|
EXPECT_FALSE(write_3mf(path, {}));
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, MultipleMeshes) {
|
|
auto t1 = make_tetrahedron("PartA");
|
|
auto t2 = make_cube("PartB");
|
|
const char* path = "/tmp/test_3mf_multi.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {t1, t2}));
|
|
|
|
auto loaded = read_3mf(path);
|
|
EXPECT_EQ(loaded.size(), 2u);
|
|
EXPECT_EQ(loaded[0].name, "PartA");
|
|
EXPECT_EQ(loaded[1].name, "PartB");
|
|
EXPECT_EQ(loaded[0].mesh.num_vertices(), 4u);
|
|
EXPECT_EQ(loaded[1].mesh.num_vertices(), 8u);
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, CreatorStoredInMetadata) {
|
|
auto orig = make_tetrahedron();
|
|
const char* path = "/tmp/test_3mf_creator.3mf";
|
|
std::remove(path);
|
|
|
|
ASSERT_TRUE(write_3mf(path, {orig}, "SuperCAD-2025"));
|
|
|
|
// Read raw XML and verify metadata tag
|
|
auto zip_content = read_3mf(path);
|
|
ASSERT_FALSE(zip_content.empty());
|
|
|
|
// Re-read the raw file to check metadata
|
|
std::ifstream f(path, std::ios::binary);
|
|
ASSERT_TRUE(f.good());
|
|
std::string raw((std::istreambuf_iterator<char>(f)),
|
|
std::istreambuf_iterator<char>());
|
|
f.close();
|
|
|
|
// Should contain the creator in metadata
|
|
EXPECT_NE(raw.find("SuperCAD-2025"), std::string::npos);
|
|
|
|
std::remove(path);
|
|
}
|
|
|
|
TEST(Io3mfTest, VertexPositionsRoundTrip) {
|
|
// Build a mesh with specific coordinates
|
|
ThreeMFMesh orig;
|
|
orig.name = "PrecisionTest";
|
|
orig.mesh.build_from_triangles(
|
|
{
|
|
Point3D(1.23456789, 9.87654321, -5.5),
|
|
Point3D(0.0, 0.0, 0.0),
|
|
Point3D(100.0, -50.0, 25.0)
|
|
},
|
|
{{0, 1, 2}}
|
|
);
|
|
|
|
const char* path = "/tmp/test_3mf_precision.3mf";
|
|
std::remove(path);
|
|
ASSERT_TRUE(write_3mf(path, {orig}));
|
|
|
|
auto loaded = read_3mf(path);
|
|
ASSERT_EQ(loaded.size(), 1u);
|
|
ASSERT_EQ(loaded[0].mesh.num_vertices(), 3u);
|
|
|
|
// Check coordinates are close (XML text round-trip precision)
|
|
auto& v0 = loaded[0].mesh.vertex(0);
|
|
auto& v1 = loaded[0].mesh.vertex(1);
|
|
auto& v2 = loaded[0].mesh.vertex(2);
|
|
EXPECT_NEAR(v0.x(), 1.23456789, 1e-5);
|
|
EXPECT_NEAR(v0.y(), 9.87654321, 1e-5);
|
|
EXPECT_NEAR(v0.z(), -5.5, 1e-5);
|
|
EXPECT_NEAR(v1.x(), 0.0, 1e-12);
|
|
EXPECT_NEAR(v2.x(), 100.0, 1e-12);
|
|
|
|
std::remove(path);
|
|
}
|