#include #include #include #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(f)), std::istreambuf_iterator()); 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); }