diff --git a/CMakeLists.txt b/CMakeLists.txt index d87b059293271588455347e9a4575a93e8fd1e0b..5034b36eb0c4c905cfe25922cc9762ab417c3604 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ set(CPACK_NSIS_DELETE_ICONS_EXTRA Delete '$DESKTOP\\\\${MYNAME}.lnk'" ) -set(SUPPORTED_MESH_EXTS .mesh .meshb .obj .step .stp .vtk .m .ply ) +set(SUPPORTED_MESH_EXTS .mesh .meshb .obj .step .stp .iges .igs .vtk .m .ply ) set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "") diff --git a/plugins/io/CMakeLists.txt b/plugins/io/CMakeLists.txt index ff3afb521fd08e57e28cd8ffbd3956669d3bcbe3..fbfb5ae9130bb669a624e02d42cdee81057529fb 100644 --- a/plugins/io/CMakeLists.txt +++ b/plugins/io/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory(MModelPlugin) add_subdirectory(StepModelPlugin) add_subdirectory(VtkLegacyModelPlugin) add_subdirectory(MeshMeditModelPlugin) -add_subdirectory(PlyModelPlugin) \ No newline at end of file +add_subdirectory(PlyModelPlugin) +add_subdirectory(IgesModelPlugin) diff --git a/plugins/io/IgesModelPlugin/CMakeLists.txt b/plugins/io/IgesModelPlugin/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9a34f8b7ecbc8e2543377d8384c6c14d6d43fa2c --- /dev/null +++ b/plugins/io/IgesModelPlugin/CMakeLists.txt @@ -0,0 +1,7 @@ +precess_add_io_plugin(IgesModelPlugin + SOURCES "IgesModelHandler.cpp" + PLUGIN_H "IgesModelPlugin.h" +) +precess_plugin_link_libraries(IgesModelPlugin TKDEIGES) + +add_subdirectory(test) diff --git a/plugins/io/IgesModelPlugin/IgesModelHandler.cpp b/plugins/io/IgesModelPlugin/IgesModelHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dac9ac6d118b25da087964dad16221beb97696a8 --- /dev/null +++ b/plugins/io/IgesModelPlugin/IgesModelHandler.cpp @@ -0,0 +1,96 @@ +/** + * @file IgesModelHandler.cpp + * @brief IGES 模型文件处理器实现 + * @author 范成通 + */ +#include "IgesModelHandler.h" +#include "ArgType.h" +#include "ModelData.h" +#include "SplineData.h" + +#include +#include +#include + +namespace systems::io { +using core::ArgType; + +/** + * @brief 读取 IGES 文件 + */ +std::unique_ptr IgesModelHandler::read_model(const fs::path& path, + const std::vector& args) +{ + IGESControl_Reader reader; + IFSelect_ReturnStatus stat; + // 使用UTF-8编码字符串路径,配合C++17 std::filesystem处理中文路径 + stat = reader.ReadFile(path.u8string().c_str()); + if (stat != IFSelect_RetDone) { + spdlog::error("Failed to read IGES file: {}", path.string()); + return nullptr; + } + reader.TransferRoots(); + + // SplineData - 和 STEP 一样保存为 BRep 边界表示 + auto spline_data = std::make_unique(); + spline_data->rootShape = std::make_unique(reader.OneShape()); + + // ModelData + auto model_data = std::make_unique(std::move(spline_data)); + model_data->model_name_ = path.filename().string(); + + return model_data; +} + +/** + * @brief 写入 IGES 文件 + */ +void IgesModelHandler::write_model(const ModelData& data, const fs::path& path, + const std::vector& args) +{ + auto spline_data = data.asSplineData(); + if (!spline_data) { + spdlog::error("IgesModelHandler only supports writing SplineData."); + return; + } + + IGESControl_Writer writer; + + // 将形状添加到写入器 + Standard_Boolean transferStatus = writer.AddShape(*spline_data->rootShape); + + if (!transferStatus) { + spdlog::error("Failed when transferring shape to IGES writer."); + return; + } + + writer.ComputeModel(); + + // 写入文件,使用UTF-8编码字符串路径 + Standard_Boolean writeStatus = writer.Write(path.u8string().c_str()); + + if (!writeStatus) { + spdlog::error("Failed to write IGES file: {}", path.string()); + return; + } + + spdlog::info("Successfully wrote IGES file: {}", path.string()); +} + +/** + * @brief 读取参数 - IGES 读取不需要额外参数,返回空列表 + */ +std::vector IgesModelHandler::read_args_type() const +{ + return { }; +} + +/** + * @brief 写入参数 - IGES 写入不需要额外参数,返回空列表 + */ +std::vector IgesModelHandler::write_args_type() const +{ + return { }; +} + +} // namespace systems::io \ No newline at end of file diff --git a/plugins/io/IgesModelPlugin/IgesModelHandler.h b/plugins/io/IgesModelPlugin/IgesModelHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..974c0837c1eb6ac2e163cef8f9fd6ae783659922 --- /dev/null +++ b/plugins/io/IgesModelPlugin/IgesModelHandler.h @@ -0,0 +1,30 @@ +/** + * @file IgesModelHandler.h + * @brief IGES 模型文件处理器 + * @author 范成通 + */ +#ifndef IGES_MODEL_HANDLER_H +#define IGES_MODEL_HANDLER_H +#include "ModelIOHandler.h" + +class ModelData; + +namespace systems::io { +/** + * @brief IGES 文件格式处理器,读取和写入 IGES 文件 + */ +class IgesModelHandler : public ModelIOHandler { +public: + IgesModelHandler() = default; + ~IgesModelHandler() override = default; + + std::unique_ptr read_model(const fs::path& path, + const std::vector& args) override; + void write_model(const ModelData& data, const fs::path& path, + const std::vector& args) override; + std::vector read_args_type() const override; + std::vector write_args_type() const override; +}; + +} +#endif // !IGES_MODEL_HANDLER_H \ No newline at end of file diff --git a/plugins/io/IgesModelPlugin/IgesModelPlugin.h b/plugins/io/IgesModelPlugin/IgesModelPlugin.h new file mode 100644 index 0000000000000000000000000000000000000000..e67fb81095e61c5328d960b8f540c45fddfcc94c --- /dev/null +++ b/plugins/io/IgesModelPlugin/IgesModelPlugin.h @@ -0,0 +1,25 @@ +/** + * @file IgesModelPlugin.h + * @brief IGES 文件格式读写插件 + * @author 范成通 + */ +#ifndef IGES_MODEL_PLUGIN_H +#define IGES_MODEL_PLUGIN_H +#include "HandlerCreatorDestroyerFactory.h" +#include "IgesModelHandler.h" +#include "PluginBase.h" +#include + +namespace systems::io { +class IgesModelPlugin : public QObject, public PluginBase { + Q_OBJECT + Q_INTERFACES(systems::PluginBase) + Q_PLUGIN_METADATA(IID "com.PreCess.systems.io.IgesModelPlugin/1.0" FILE "IgesModelPlugin.json") +private: + const HandlerCreatorDestroyer& getHandlerCreatorDestroyer() noexcept override final + { + return HandlerCreatorDestroyerFactory::get(); + } +}; +} +#endif // !IGES_MODEL_PLUGIN_H \ No newline at end of file diff --git a/plugins/io/IgesModelPlugin/IgesModelPlugin.json b/plugins/io/IgesModelPlugin/IgesModelPlugin.json new file mode 100644 index 0000000000000000000000000000000000000000..f4eae0d34991a74b4a83e97dee69e0e166bbe5d4 --- /dev/null +++ b/plugins/io/IgesModelPlugin/IgesModelPlugin.json @@ -0,0 +1,10 @@ +{ + "system": "ModelIOSystem", + "handler": { + "file_type": "IGES", + "extensions": [ + "igs", + "iges" + ] + } +} \ No newline at end of file diff --git a/plugins/io/IgesModelPlugin/test/CMakeLists.txt b/plugins/io/IgesModelPlugin/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..753e08ada951d1880612a81a529a6364959f9935 --- /dev/null +++ b/plugins/io/IgesModelPlugin/test/CMakeLists.txt @@ -0,0 +1,2 @@ +precess_add_test(TestIgesModelIO TestIgesModelHandler.cpp) +precess_test_link_libraries(TestIgesModelIO IgesModelPluginlib DataTest) \ No newline at end of file diff --git a/plugins/io/IgesModelPlugin/test/TestIgesModelHandler.cpp b/plugins/io/IgesModelPlugin/test/TestIgesModelHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5ca5d03c21f3f93ddc5fd8f31e9baa4341d25d3 --- /dev/null +++ b/plugins/io/IgesModelPlugin/test/TestIgesModelHandler.cpp @@ -0,0 +1,199 @@ +/** + * @file TestIgesModelHandler.cpp + * @brief IGES 模型文件处理器单元测试 + * @author PreCess Team + */ +#include "IgesModelHandler.h" +#include "MeshData.h" +#include "ModelData.h" +#include "SplineData.h" +#include "TempFile.h" +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +/** + * @brief 比较两个 TopoDS_Shape 的基本属性 + */ +static bool shapes_similar(const TopoDS_Shape& a, const TopoDS_Shape& b) +{ + // 比较类型 + if (a.ShapeType() != b.ShapeType()) { + return false; + } + + // 比较空状态 + if (a.IsNull() != b.IsNull()) { + return false; + } + + // 比较形状数量特性 + if (a.NbChildren() != b.NbChildren()) { + return false; + } + + return true; +} + +/** + * @brief 创建测试:英文路径下立方体读写回环测试 + */ +TEST_CASE("IgesModelHandler::write_model()/read_model() - 英文路径") +{ + systems::io::IgesModelHandler io; + fs::path out = core::TempFile::instance().path().string() + ".igs"; + + // 步骤1: 使用 OpenCASCADE 创建立方体 + TopoDS_Shape box = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0); + REQUIRE(!box.IsNull()); + + // 步骤2: 包装为 SplineData + auto spline_data = std::make_unique(); + spline_data->rootShape = std::make_unique(box); + + // 步骤3: 写入 IGES 文件 + std::unique_ptr model_write = std::make_unique(std::move(spline_data)); + REQUIRE_NOTHROW(io.write_model(*model_write, out, { })); + REQUIRE(fs::exists(out)); + + // 步骤4: 读取 IGES 文件 + std::unique_ptr model_read; + REQUIRE_NOTHROW(model_read = io.read_model(out, { })); + REQUIRE(model_read != nullptr); + + // 步骤5: 验证读取的数据类型 + const SplineData* read_spline = model_read->asSplineData(); + REQUIRE(read_spline != nullptr); + REQUIRE(read_spline->rootShape != nullptr); + REQUIRE(!read_spline->rootShape->IsNull()); + + // 步骤6: IGES 读写后拓扑结构会发生变化,只需验证成功读取到有效形状即可 + // IGES 转换后实体数量:立方体约49个,球体约14个 +} + +/** + * @brief 创建测试:中文文件名(仅文件名含中文) + */ +TEST_CASE("IgesModelHandler::write_model()/read_model() - 中文文件名") +{ + systems::io::IgesModelHandler io; + + // 创建中文文件名 + fs::path out = core::TempFile::instance().path(); + out.replace_filename("测试_立方体_" + out.stem().string() + ".igs"); + + // 步骤1: 使用 OpenCASCADE 创建立方体 + TopoDS_Shape box = BRepPrimAPI_MakeBox(15.0, 25.0, 35.0); + REQUIRE(!box.IsNull()); + + // 步骤2: 包装为 SplineData + auto spline_data = std::make_unique(); + spline_data->rootShape = std::make_unique(box); + + // 步骤3: 写入 IGES 文件 + std::unique_ptr model_write = std::make_unique(std::move(spline_data)); + REQUIRE_NOTHROW(io.write_model(*model_write, out, { })); + REQUIRE(fs::exists(out)); + + // 步骤4: 读取 IGES 文件 + std::unique_ptr model_read; + REQUIRE_NOTHROW(model_read = io.read_model(out, { })); + REQUIRE(model_read != nullptr); + + // 步骤5: 验证读取的数据类型 + const SplineData* read_spline = model_read->asSplineData(); + REQUIRE(read_spline != nullptr); + REQUIRE(read_spline->rootShape != nullptr); + REQUIRE(!read_spline->rootShape->IsNull()); +} + +/** + * @brief 创建测试:中文完整路径(目录+文件名均含中文) + */ +TEST_CASE("IgesModelHandler::write_model()/read_model() - 中文完整路径") +{ + systems::io::IgesModelHandler io; + + // 创建中文目录 + 中文文件名的完整路径 + fs::path out = core::TempFile::instance().path(); + out = out.parent_path() / "测试数据目录" / "子目录_模型" / ("测试_立方体_" + out.stem().string() + ".igs"); + std::filesystem::create_directories(out.parent_path()); + + // 步骤1: 使用 OpenCASCADE 创建立方体 + TopoDS_Shape box = BRepPrimAPI_MakeBox(12.0, 22.0, 32.0); + REQUIRE(!box.IsNull()); + + // 步骤2: 包装为 SplineData + auto spline_data = std::make_unique(); + spline_data->rootShape = std::make_unique(box); + + // 步骤3: 写入 IGES 文件 + std::unique_ptr model_write = std::make_unique(std::move(spline_data)); + REQUIRE_NOTHROW(io.write_model(*model_write, out, { })); + REQUIRE(fs::exists(out)); + + // 步骤4: 读取 IGES 文件 + std::unique_ptr model_read; + REQUIRE_NOTHROW(model_read = io.read_model(out, { })); + REQUIRE(model_read != nullptr); + + // 步骤5: 验证读取的数据类型 + const SplineData* read_spline = model_read->asSplineData(); + REQUIRE(read_spline != nullptr); + REQUIRE(read_spline->rootShape != nullptr); + REQUIRE(!read_spline->rootShape->IsNull()); +} + +/** + * @brief 创建测试:球体读写回环测试 + */ +TEST_CASE("IgesModelHandler::write_model()/read_model() - 球体测试") +{ + systems::io::IgesModelHandler io; + fs::path out = core::TempFile::instance().path().string() + "_sphere.igs"; + + // 步骤1: 使用 OpenCASCADE 创建球体 + TopoDS_Shape sphere = BRepPrimAPI_MakeSphere(50.0); + REQUIRE(!sphere.IsNull()); + + // 步骤2: 包装为 SplineData + auto spline_data = std::make_unique(); + spline_data->rootShape = std::make_unique(sphere); + + // 步骤3: 写入 IGES 文件 + std::unique_ptr model_write = std::make_unique(std::move(spline_data)); + REQUIRE_NOTHROW(io.write_model(*model_write, out, { })); + REQUIRE(fs::exists(out)); + + // 步骤4: 读取 IGES 文件 + std::unique_ptr model_read; + REQUIRE_NOTHROW(model_read = io.read_model(out, { })); + REQUIRE(model_read != nullptr); + + // 步骤5: 验证读取的数据类型 + const SplineData* read_spline = model_read->asSplineData(); + REQUIRE(read_spline != nullptr); + REQUIRE(read_spline->rootShape != nullptr); + REQUIRE(!read_spline->rootShape->IsNull()); +} + +/** + * @brief 测试:空模型写入(错误处理测试 + */ +TEST_CASE("IgesModelHandler::write_model() - 空模型处理") +{ + systems::io::IgesModelHandler io; + fs::path out = core::TempFile::instance().path().string() + "_null.igs"; + + // 创建一个非 SplineData 的模型(比如 MeshData) + auto mesh_data = std::make_unique(); + std::unique_ptr model = std::make_unique(std::move(mesh_data)); + + // 应该不会抛出异常,但会记录错误日志,不写入文件 + REQUIRE_NOTHROW(io.write_model(*model, out, { })); + // IGES 处理器不支持 MeshData,应该不会成功写出 +} \ No newline at end of file diff --git a/plugins/io/StepModelPlugin/StepModelHandler.cpp b/plugins/io/StepModelPlugin/StepModelHandler.cpp index d4d15d273025822dfd729a915caef19eca4973fa..437a2e64dce133360a5c1f9921c74901e7626609 100644 --- a/plugins/io/StepModelPlugin/StepModelHandler.cpp +++ b/plugins/io/StepModelPlugin/StepModelHandler.cpp @@ -15,7 +15,9 @@ namespace systems::io { std::unique_ptr StepModelHandler::read_model(const fs::path& path, const std::vector& args) { STEPControl_Reader reader; - IFSelect_ReturnStatus stat = reader.ReadFile(path.string().c_str()); + IFSelect_ReturnStatus stat; + // 使用UTF-8编码字符串路径,配合C++17 std::filesystem处理中文路径 + stat = reader.ReadFile(path.u8string().c_str()); if (stat != IFSelect_RetDone) { spdlog::error("Failed to read STEP file: {}", path.string()); return nullptr;