Files
ViewDesignEngine/tests/brep/test_iges_import.cpp
T
茂之钳 ebc47a25c9
CI / Build & Test (push) Failing after 41s
CI / Release Build (push) Failing after 32s
fix: remove stray namespace close in test_iges_import
2026-07-24 08:16:51 +00:00

548 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <gtest/gtest.h>
#include "vde/brep/iges_import.h"
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include <cmath>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>
using namespace vde::brep;
using namespace vde::core;
// ─────────────────────────────────────────────────────────────
// Helper: Build IGES 80-character fixed-width records
// ─────────────────────────────────────────────────────────────
namespace {
/// Pad a string to exactly `width` characters (right-justified for integers, left for strings)
std::string pad_right(const std::string& s, int width) {
std::string r = s;
if (static_cast<int>(r.size()) > width) r = r.substr(0, width);
else r.append(width - r.size(), ' ');
return r;
}
std::string pad_left(int val, int width) {
std::string s = std::to_string(val);
if (static_cast<int>(s.size()) > width) s = s.substr(0, width);
else s.insert(0, width - s.size(), ' ');
return s;
}
/// Build one IGES Directory Entry record (72 chars of fields + section char + 7-char seq)
std::string de_line(const std::vector<int>& fields, char section, int seq) {
// fields: 9 values, each right-justified in 8 chars
std::string line;
for (size_t i = 0; i < fields.size() && i < 9; ++i) {
std::string val = std::to_string(fields[i]);
// Right-justify integer in 8-char field
if (val.size() < 8) val.insert(0, 8 - val.size(), ' ');
line += val;
}
// pad remaining to 72
while (line.size() < 72) line += ' ';
line = line.substr(0, 72);
line += section;
line += pad_left(seq, 7);
return line;
}
/// Build one IGES Parameter Data record
std::string pd_line(const std::string& data, int de_ptr, int seq) {
std::string line = data;
// Data occupies cols 1-64 max
while (line.size() < 65) line += ' ';
line = line.substr(0, 64);
// Pad to col 65 (char index 64)
// Col 65 is blank
line += ' ';
// DE pointer in cols 66-72 (right justified in 7 chars)
std::string deplabel = std::to_string(de_ptr);
if (deplabel.size() < 7) deplabel = std::string(7 - deplabel.size(), ' ') + deplabel;
line += deplabel;
line += 'P';
line += pad_left(seq, 7);
return line;
}
/// Build a full IGES file string
struct IgesBuilder {
std::vector<std::string> s_lines;
std::vector<std::string> g_lines;
std::vector<std::string> d_lines; // pairs: 2 per entity
std::vector<std::string> p_lines;
void add_s_line(const std::string& txt) {
std::string line = pad_right(txt, 72) + "S";
line += pad_left(static_cast<int>(s_lines.size() + 1), 7);
s_lines.push_back(line);
}
void add_g_line(const std::string& txt) {
std::string line = pad_right(txt, 72) + "G";
line += pad_left(static_cast<int>(g_lines.size() + 1), 7);
g_lines.push_back(line);
}
// Add a directory entry (two 80-char lines)
void add_de(int type_num, int pd_ptr, int struct_val, int line_font, int level,
int view, int transform, int label, int status,
int line_weight, int color, int param_count, int form_num) {
int seq = static_cast<int>(d_lines.size() + 1);
// Line 1
d_lines.push_back(de_line({type_num, pd_ptr, struct_val, line_font, level,
view, transform, label, status}, 'D', seq));
// Line 2: type, line_weight, color, param_count, form_num, 0, 0, label, subscript
d_lines.push_back(de_line({type_num, line_weight, color, param_count, form_num,
0, 0, label, 0}, 'D', seq + 1));
}
// Add parameter data
void add_pd(const std::string& data, int de_ptr) {
int seq = static_cast<int>(p_lines.size() + 1);
p_lines.push_back(pd_line(data, de_ptr, seq));
}
std::string build() {
std::string result;
for (const auto& l : s_lines) result += l + "\n";
for (const auto& l : g_lines) result += l + "\n";
for (const auto& l : d_lines) result += l + "\n";
for (const auto& l : p_lines) result += l + "\n";
// Terminate section
int s_cnt = static_cast<int>(s_lines.size());
int g_cnt = static_cast<int>(g_lines.size());
int d_cnt = static_cast<int>(d_lines.size());
int p_cnt = static_cast<int>(p_lines.size());
std::string t_line = pad_right(
pad_left(s_cnt, 8) + pad_left(g_cnt, 8) + pad_left(d_cnt, 8) + pad_left(p_cnt, 8),
72) + "T" + pad_left(1, 7);
result += t_line + "\n";
return result;
}
};
/// Standard global section for minimal IGES files
std::string standard_global() {
return "1H,,1H;,4HSLOT,1,,,13,0.016,0.1,,1.0,1.0,1.0,2,1,1,8,,1.0,1,1,0,0;";
}
} // anonymous namespace
// ─────────────────────────────────────────────────────────────
TEST(IgesImport, EmptyData) {
auto bodies = import_iges_from_string("");
EXPECT_TRUE(bodies.empty());
EXPECT_EQ(iges_last_error(), IgesError::ParseError);
}
TEST(IgesImport, TruncatedFile) {
// Missing sections — just a start section
IgesBuilder b;
b.add_s_line("Truncated test file");
auto bodies = import_iges_from_string(b.build());
EXPECT_TRUE(bodies.empty());
EXPECT_NE(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, MinimalLine) {
IgesBuilder b;
b.add_s_line("Test IGES file with a single line");
b.add_g_line(standard_global());
// Single line entity: type 110
// DE entry: PD pointer 1, param count 1
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
// Line: X1,Y1,Z1,X2,Y2,Z2 = (0,0,0) to (10,0,0)
b.add_pd("0.0,0.0,0.0,10.0,0.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
// Should produce at least one body (fallback mode with a surface)
ASSERT_FALSE(bodies.empty());
EXPECT_GT(bodies[0].num_surfaces(), 0u);
}
TEST(IgesImport, SingleArc) {
IgesBuilder b;
b.add_s_line("Test IGES file with a circular arc");
b.add_g_line(standard_global());
// Type 100: Circular Arc — 7 params: ZT, Xc, Yc, Xs, Ys, Xe, Ye
// Full circle: center (0,0), radius 5, start (5,0), end (5,0) → full circle
b.add_de(100, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,5.0,0.0,5.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
ASSERT_FALSE(bodies.empty());
}
TEST(IgesImport, SingleLine110) {
// Test line (type 110): basic endpoint geometry
IgesBuilder b;
b.add_s_line("Line test");
b.add_g_line(standard_global());
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1.0,2.0,3.0,4.0,5.0,6.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, CylinderSurface) {
IgesBuilder b;
b.add_s_line("Cylinder test");
b.add_g_line(standard_global());
// Type 192: Right Circular Cylindrical Surface
// location(0,0,0), axis(0,0,1), radius=5
b.add_de(192, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,0.0,0.0,1.0,5.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, SphereSurface) {
IgesBuilder b;
b.add_s_line("Sphere test");
b.add_g_line(standard_global());
// Type 196: Spherical Surface — center(0,0,0), radius=10
b.add_de(196, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,10.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, ConeSurface) {
IgesBuilder b;
b.add_s_line("Cone test");
b.add_g_line(standard_global());
// Type 194: Right Circular Conical Surface
// location(0,0,0), axis(0,0,1), radius=5, semi_angle=0.5 rad
b.add_de(194, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,0.0,0.0,1.0,5.0,0.5;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, TorusSurface) {
IgesBuilder b;
b.add_s_line("Torus test");
b.add_g_line(standard_global());
// Type 198: Toroidal Surface
// center(0,0,0), axis(0,0,1), R=10, r=3
b.add_de(198, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,0.0,0.0,1.0,10.0,3.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, PlaneSurface) {
IgesBuilder b;
b.add_s_line("Plane test");
b.add_g_line(standard_global());
// Type 108: Plane — equation Ax+By+Cz+D=0
// XY plane: 0x+0y+1z+0=0
b.add_de(108, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,1.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, BSplineCurve) {
IgesBuilder b;
b.add_s_line("B-Spline curve test");
b.add_g_line(standard_global());
// Type 126: Rational B-Spline Curve
// K=5, M=2 (degree 2), PROP1=0, PROP2=0 (non-rational), PROP3=0, N=3 (4 CPs)
// Knot sequence: A(0..K) = K+1=6 values: 0,0,0,0.5,1,1
// CPs: (0,0,0), (2,3,0), (5,1,0), (7,4,0)
b.add_de(126, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("5,2,0,0,0,3,0.0,0.0,0.0,0.5,1.0,1.0,0.0,0.0,0.0,2.0,3.0,0.0,5.0,1.0,0.0,7.0,4.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, BSplineSurface) {
IgesBuilder b;
b.add_s_line("B-Spline surface test");
b.add_g_line(standard_global());
// Type 128: Rational B-Spline Surface
// K1=1, K2=1, M1=1, M2=1, PROP1-4=0, PROP5=0, N1=0, N2=0 → 1×1 CP, deg 1×1
// U knots: 0.0, 1.0 (K1+1=2 values)
// V knots: 0.0, 1.0 (K2+1=2 values)
// CP: (0,0,0)
b.add_de(128, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,1,1,1,0,0,0,0,0,0,0,0.0,1.0,0.0,1.0,0.0,0.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, RuledSurface) {
IgesBuilder b;
b.add_s_line("Ruled surface test");
b.add_g_line(standard_global());
// Need two curves as DE entries first, then a ruled surface referencing them
// Line 1: (0,0,0) → (10,0,0), DE index 1
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,10.0,0.0,0.0;", 1);
// Line 2: (0,5,0) → (10,5,0), DE index 2
b.add_de(110, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,5.0,0.0,10.0,5.0,0.0;", 2);
// Ruled surface 118 referencing lines 1 and 2
b.add_de(118, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,2,0,0;", 3);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, RevolutionSurface) {
IgesBuilder b;
b.add_s_line("Revolution surface test");
b.add_g_line(standard_global());
// Profile line: (5,0,0) → (5,0,10), DE index 1
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("5.0,0.0,0.0,5.0,0.0,10.0;", 1);
// Surface of revolution 120: profile DE=1, axis origin=(0,0,0), axis dir=(0,0,1)
b.add_de(120, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,0.0,0.0,0.0,0.0,0.0,1.0,0.0,6.283185307;", 2);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, TabulatedCylinder) {
IgesBuilder b;
b.add_s_line("Tabulated cylinder test");
b.add_g_line(standard_global());
// Profile line: (5,0,0) → (5,0,0)... actually a single point doesn't work
// Let's use a simple line: (0,0,0) → (5,0,0), DE 1
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,5.0,0.0,0.0;", 1);
// Tabulated cylinder 122: curve DE=1, direction (0,0,10)
b.add_de(122, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,0.0,0.0,10.0;", 2);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, CopiousDataPolyline) {
IgesBuilder b;
b.add_s_line("Polyline test");
b.add_g_line(standard_global());
// Type 106: Copious Data, IP=1 (polyline), N=4 points
// Points: (0,0,0), (2,0,0), (2,2,0), (0,2,0)
b.add_de(106, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,4,0.0,0.0,0.0,2.0,0.0,0.0,2.0,2.0,0.0,0.0,2.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, ManifoldSolidBox) {
// Build a minimal B-Rep box via IGES topology entities
IgesBuilder b;
b.add_s_line("Manifold solid box test");
b.add_g_line(standard_global());
// Entities:
// 1: Vertex (0,0,0) type 502
// 2: Vertex (10,0,0)
// 3: Vertex (10,10,0)
// 4: Vertex (0,10,0)
// 5: Vertex (0,0,10)
// 6: Vertex (10,0,10)
// 7: Vertex (10,10,10)
// 8: Vertex (0,10,10)
// 9: Line (0,0,0)-(10,0,0) type 110 → edge uses curve
// ... and so on. Let's do a simpler subset.
// Vertex 1: (0,0,0)
b.add_de(502, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0;", 1);
// Vertex 2: (10,0,0)
b.add_de(502, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("10.0,0.0,0.0;", 2);
// Vertex 3: (10,10,0)
b.add_de(502, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("10.0,10.0,0.0;", 3);
// Vertex 4: (0,10,0)
b.add_de(502, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,10.0,0.0;", 4);
// Line for edge: (0,0,0)-(10,0,0)
b.add_de(110, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,10.0,0.0,0.0;", 5);
// Edge 1: curve type=1 (line), curve DE=5, start vx DE=1, sv_index=0, end vx DE=2, ev_index=1
b.add_de(504, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,5,1,0,2,1,0,0.0,1.0;", 6);
// Plane for bottom face: z=0 plane
b.add_de(108, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,1.0,0.0;", 7);
// Loop: N=1 edge, type=0, edge_DE=6, vx_count=2, start=1, end=2, cur_srf=0, sparams=0, eparams=0
b.add_de(508, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,0,6,2,1,2,0,0.0,1.0;", 8);
// Face: surface_DE=7, N_loops=1, loop_flag=0, loop_DE=8
b.add_de(510, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("7,1,0,8;", 9);
// Shell: N=1 face, face_DE=9
b.add_de(514, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1,9;", 10);
// Manifold Solid B-Rep 186: shell DE=10
b.add_de(186, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("10;", 11);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
ASSERT_FALSE(bodies.empty());
EXPECT_GT(bodies[0].num_faces(), 0u);
}
TEST(IgesImport, RoundtripBox) {
// Create a box via modeling, then verify IGES import infrastructure works
auto box = make_box(2.0, 1.0, 3.0);
EXPECT_GT(box.num_faces(), 0u);
// Verify we can build and parse IGES with a cylinder
IgesBuilder b;
b.add_s_line("Roundtrip test - cylinder");
b.add_g_line(standard_global());
b.add_de(192, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,0.0,0.0,1.0,5.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, InvalidSectionOrder) {
// Put sections in wrong order — only S and G with no D
IgesBuilder b;
b.add_s_line("Bad file");
b.add_g_line(standard_global());
// No D or P sections — just T
std::string result;
for (const auto& l : b.s_lines) result += l + "\n";
for (const auto& l : b.g_lines) result += l + "\n";
// Manually add T with wrong section
std::string t_line = " T 1\n";
result += t_line;
auto bodies = import_iges_from_string(result);
EXPECT_TRUE(bodies.empty());
EXPECT_NE(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, DEExponentNotation) {
// IGES uses D exponent notation (e.g., 1.0D+2 = 100)
IgesBuilder b;
b.add_s_line("D exponent notation test");
b.add_g_line(standard_global());
b.add_de(110, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("1.0D+2,2.0D+1,3.5D+0,4.0D-1,5.0D-2,6.0D+3;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, MultipleSurfaces) {
IgesBuilder b;
b.add_s_line("Multiple surface types test");
b.add_g_line(standard_global());
// Plane
b.add_de(108, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,1.0,0.0;", 1);
// Cylinder
b.add_de(192, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,0.0,0.0,1.0,5.0;", 2);
// Sphere
b.add_de(196, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("10.0,0.0,0.0,3.0;", 3);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
}
TEST(IgesImport, FileNotFound) {
auto bodies = import_iges("/nonexistent/path/file.iges");
EXPECT_TRUE(bodies.empty());
EXPECT_EQ(iges_last_error(), IgesError::FileNotFound);
}
TEST(IgesImport, VerifyArcGeometry) {
// A 90-degree arc: center(0,0), start(5,0), end(0,5)
IgesBuilder b;
b.add_s_line("Arc geometry test");
b.add_g_line(standard_global());
b.add_de(100, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,5.0,0.0,0.0,5.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
ASSERT_FALSE(bodies.empty());
}
TEST(IgesImport, FullCircleArc) {
// Full circle: center(0,0), start(10,0), end(10,0) → 360°
IgesBuilder b;
b.add_s_line("Full circle arc test");
b.add_g_line(standard_global());
b.add_de(100, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
b.add_pd("0.0,0.0,0.0,10.0,0.0,10.0,0.0;", 1);
auto bodies = import_iges_from_string(b.build());
EXPECT_EQ(iges_last_error(), IgesError::Ok);
ASSERT_FALSE(bodies.empty());
}