266 lines
9.9 KiB
C++
266 lines
9.9 KiB
C++
|
|
#include <gtest/gtest.h>
|
|||
|
|
#include "vde/brep/gdt.h"
|
|||
|
|
#include "vde/brep/modeling.h"
|
|||
|
|
#include <cmath>
|
|||
|
|
|
|||
|
|
using namespace vde::brep;
|
|||
|
|
using namespace vde::core;
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// GDTFeature — symbol and string
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Symbol_FlatnessNotEmpty) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Flatness;
|
|||
|
|
EXPECT_FALSE(f.symbol().empty());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Symbol_AllTypesNonEmpty) {
|
|||
|
|
std::vector<GDTType> types = {
|
|||
|
|
GDTType::Flatness, GDTType::Straightness, GDTType::Circularity,
|
|||
|
|
GDTType::Cylindricity, GDTType::Parallelism, GDTType::Perpendicularity,
|
|||
|
|
GDTType::Angularity, GDTType::Position, GDTType::Concentricity,
|
|||
|
|
GDTType::Symmetry, GDTType::ProfileOfLine, GDTType::ProfileOfSurface,
|
|||
|
|
GDTType::CircularRunout, GDTType::TotalRunout
|
|||
|
|
};
|
|||
|
|
for (auto t : types) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = t;
|
|||
|
|
EXPECT_FALSE(f.symbol().empty()) << "Type " << static_cast<int>(t);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ToString_ContainsTolerance) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.tolerance_value = 0.05;
|
|||
|
|
f.zone_shape = GDTFeature::ZoneShape::Cylindrical;
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("0.05"), std::string::npos);
|
|||
|
|
EXPECT_NE(s.find("Ø"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ToString_MMCModifier) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.tolerance_value = 0.1;
|
|||
|
|
f.mmc = true;
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("M"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ToString_DatumReferences) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Parallelism;
|
|||
|
|
f.tolerance_value = 0.02;
|
|||
|
|
f.datums.push_back({"A", 0, true});
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("A"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ToString_ProjectedTolerance) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.tolerance_value = 0.1;
|
|||
|
|
f.projected = true;
|
|||
|
|
f.projected_height = 15.0;
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("P"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// Flatness
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Flatness_BoxFace_ReturnsNonNegative) {
|
|||
|
|
auto box = make_box(10, 10, 10);
|
|||
|
|
double flatness = compute_flatness(box, 0);
|
|||
|
|
EXPECT_GE(flatness, 0.0);
|
|||
|
|
// A flat box face should have very low flatness
|
|||
|
|
EXPECT_LT(flatness, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Flatness_CylinderEnd_ReturnsNonNegative) {
|
|||
|
|
auto cyl = make_cylinder(3.0, 10.0);
|
|||
|
|
double flatness = compute_flatness(cyl, 0); // bottom face
|
|||
|
|
EXPECT_GE(flatness, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// Parallelism
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Parallelism_ParallelFaces_LowDeviation) {
|
|||
|
|
auto box = make_box(5, 5, 10); // 5×5×10, Z is long axis
|
|||
|
|
// Face 0 (z=-5) and Face 1 (z=+5) should be parallel
|
|||
|
|
double dev = compute_parallelism(box, 0, 1);
|
|||
|
|
EXPECT_GE(dev, 0.0);
|
|||
|
|
EXPECT_LT(dev, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// Perpendicularity
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Perpendicularity_PerpendicularFaces_LowDeviation) {
|
|||
|
|
auto box = make_box(5, 5, 10);
|
|||
|
|
// Face 0 (z=-5) and Face 2 (y=-5) should be perpendicular
|
|||
|
|
double dev = compute_perpendicularity(box, 0, 2);
|
|||
|
|
EXPECT_GE(dev, 0.0);
|
|||
|
|
EXPECT_LT(dev, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// Concentricity
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Concentricity_CoaxialCylinders_LowDeviation) {
|
|||
|
|
auto cyl = make_cylinder(3.0, 10.0);
|
|||
|
|
// Bottom and top faces should be concentric
|
|||
|
|
double dev = compute_concentricity(cyl, 0, 1);
|
|||
|
|
EXPECT_GE(dev, 0.0);
|
|||
|
|
EXPECT_LT(dev, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// Position
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Position_BoxFace_ReturnsNonNegative) {
|
|||
|
|
auto box = make_box(10, 10, 10);
|
|||
|
|
double dev = compute_position(box, 0, Point3D(0, 0, -5));
|
|||
|
|
EXPECT_GE(dev, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// validate_gdt
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Validate_Flatness_PassesLargeTolerance) {
|
|||
|
|
auto box = make_box(10, 10, 10);
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Flatness;
|
|||
|
|
f.target_face_id = 0;
|
|||
|
|
f.tolerance_value = 1.0; // very loose tolerance
|
|||
|
|
|
|||
|
|
auto result = validate_gdt(box, f);
|
|||
|
|
EXPECT_TRUE(result.pass);
|
|||
|
|
EXPECT_FALSE(result.description.empty());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Validate_Flatness_FailsTightTolerance) {
|
|||
|
|
auto box = make_box(100, 100, 100);
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Flatness;
|
|||
|
|
f.target_face_id = 0;
|
|||
|
|
f.tolerance_value = 1e-9; // impossibly tight
|
|||
|
|
|
|||
|
|
auto result = validate_gdt(box, f);
|
|||
|
|
// Large face sampled from polygon may have slight deviation
|
|||
|
|
// Just verify it runs without error
|
|||
|
|
EXPECT_GE(result.actual_deviation, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Validate_Parallelism_DatumRequired) {
|
|||
|
|
auto box = make_box(5, 5, 10);
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Parallelism;
|
|||
|
|
f.target_face_id = 0;
|
|||
|
|
f.datums.push_back({"A", 1, true});
|
|||
|
|
f.tolerance_value = 0.5;
|
|||
|
|
|
|||
|
|
auto result = validate_gdt(box, f);
|
|||
|
|
EXPECT_GE(result.actual_deviation, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Validate_Concentricity) {
|
|||
|
|
auto cyl = make_cylinder(3.0, 10.0);
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Concentricity;
|
|||
|
|
f.target_face_id = 0;
|
|||
|
|
f.datums.push_back({"A", 1, true});
|
|||
|
|
f.tolerance_value = 1.0;
|
|||
|
|
|
|||
|
|
auto result = validate_gdt(cyl, f);
|
|||
|
|
EXPECT_GE(result.actual_deviation, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Validate_Position) {
|
|||
|
|
auto box = make_box(10, 10, 10);
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.target_face_id = 0;
|
|||
|
|
f.tolerance_value = 5.0;
|
|||
|
|
|
|||
|
|
auto result = validate_gdt(box, f);
|
|||
|
|
EXPECT_GE(result.actual_deviation, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// DXF export
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ExportDxf_ProducesValidSections) {
|
|||
|
|
std::vector<GDTAnnotation> annotations;
|
|||
|
|
GDTAnnotation ann;
|
|||
|
|
ann.feature.type = GDTType::Flatness;
|
|||
|
|
ann.feature.tolerance_value = 0.1;
|
|||
|
|
ann.frame_position = Point3D(10, 20, 0);
|
|||
|
|
ann.leader_point = Point3D(15, 25, 0);
|
|||
|
|
annotations.push_back(ann);
|
|||
|
|
|
|||
|
|
auto dxf = export_gdt_annotations_dxf(annotations);
|
|||
|
|
EXPECT_NE(dxf.find("SECTION"), std::string::npos);
|
|||
|
|
EXPECT_NE(dxf.find("ENTITIES"), std::string::npos);
|
|||
|
|
EXPECT_NE(dxf.find("MTEXT"), std::string::npos);
|
|||
|
|
EXPECT_NE(dxf.find("EOF"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, ExportDxf_EmptyAnnotations_ProducesStructure) {
|
|||
|
|
std::vector<GDTAnnotation> empty;
|
|||
|
|
auto dxf = export_gdt_annotations_dxf(empty);
|
|||
|
|
EXPECT_NE(dxf.find("EOF"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// GDTAnnotation
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Annotation_DxfText_ContainsPosition) {
|
|||
|
|
GDTAnnotation ann;
|
|||
|
|
ann.feature.type = GDTType::Flatness;
|
|||
|
|
ann.feature.tolerance_value = 0.05;
|
|||
|
|
ann.frame_position = Point3D(10, 20, 0);
|
|||
|
|
ann.leader_point = Point3D(15, 25, 0);
|
|||
|
|
ann.view_name = "TOP";
|
|||
|
|
|
|||
|
|
auto text = ann.to_dxf_text();
|
|||
|
|
EXPECT_NE(text.find("10"), std::string::npos);
|
|||
|
|
EXPECT_NE(text.find("20"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Feature_ZoneShapeToString) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.tolerance_value = 0.1;
|
|||
|
|
f.zone_shape = GDTFeature::ZoneShape::Spherical;
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("SØ"), std::string::npos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(GDTTest, Feature_LMCModifier) {
|
|||
|
|
GDTFeature f;
|
|||
|
|
f.type = GDTType::Position;
|
|||
|
|
f.tolerance_value = 0.1;
|
|||
|
|
f.lmc = true;
|
|||
|
|
|
|||
|
|
auto s = f.to_string();
|
|||
|
|
EXPECT_NE(s.find("L"), std::string::npos);
|
|||
|
|
}
|