- lib3mf 集成系列(一):编译与示例测试
- lib3mf 集成系列(二):Flutter FFI 插件创建与 Android 端集成
- lib3mf 集成系列(三):在 Flutter 中解析 3MF 文件信息
- lib3mf 集成系列(四):Flutter Texture 与 C++ OpenGL 跨端渲染架构
- lib3mf 集成系列(五):C++ 提取 3MF 数据与 OpenGL 渲染实战
前言
在lib3mf 集成系列(二):Flutter FFI 插件创建与 Android 端集成中,我们已经成功将 lib3mf 预编译库集成到 Flutter 插件中,并通过版本查询函数验证了库的可用性。现在,我们将基于 lib3mf 官方的 ExtractInfo 示例,实现从 3MF 文件中读取模型元数据的功能。
lib3mf 解析流程概述
lib3mf 的 API 采用面向对象设计,核心对象包括:
PWrapper:库的入口,用于加载库、创建模型等。PModel:代表一个 3MF 文档,包含所有对象、资源、构建项等。PObject:模型中的对象,可以是网格对象(PMeshObject)或组件对象(PComponentsObject)。PReader:用于从文件或流中读取 3MF 内容。
解析一个 3MF 文件的基本步骤如下:
- 加载库(
CWrapper::loadLibrary())。 - 创建模型对象(
wrapper->CreateModel())。 - 创建读取器并设置选项(如关闭严格模式)。
- 调用
ReadFromFile读取文件。 - 遍历模型中的元数据、对象、构建项等,输出所需信息。
下面我们将逐步实现这一过程。
核心代码实现
1. 平台差异处理
由于我们的插件需要同时支持 Android 和 macOS,而 Android 上 std::cout 不会输出到 logcat,因此需要使用条件编译选择合适的输出方式。这里定义两个宏 LOGI 和 LOGE,分别用于普通信息和错误信息:
#ifdef __ANDROID__
#include <android/log.h>
#define LOG_TAG "NativeLib3mf"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#else
#include <cstdio>
#define LOGI(...) printf(__VA_ARGS__)
#define LOGE(...) fprintf(stderr, __VA_ARGS__)
#endif
2. 加载库与读取文件
我们直接使用 lib3mf 的 CWrapper::loadLibrary() 加载动态库,然后创建模型和读取器。关键代码段如下:
extern "C" FFI_PLUGIN_EXPORT int32_t extract_info(const char* path) {
auto wrapper = CWrapper::loadLibrary();
LOGI("----------------------------------------");
LOGI("3MF Read example");
// 输出库版本(代码略,见gist)
auto model = wrapper->CreateModel();
auto reader = model->QueryReader("3mf");
reader->SetStrictModeActive(false); // 关闭严格模式,允许非标准扩展
reader->ReadFromFile(path);
// 输出读取过程中的警告信息
for (Lib3MF_uint32 i = 0; i < reader->GetWarningCount(); i++) {
Lib3MF_uint32 code;
std::string msg = reader->GetWarning(i, code);
LOGI("Warning #%u: %s", code, msg.c_str());
}
// 后续遍历代码...
}
注意:SetStrictModeActive(false) 允许读取包含非核心扩展的 3MF 文件,例如带有梁结构或切片的文件。如果文件不符合核心规范,关闭严格模式可以避免读取失败。
3.遍历对象与输出信息
lib3mf 使用迭代器模式遍历模型中的各种元素。例如,获取所有对象的迭代器:
PObjectIterator objectIterator = model->GetObjects();
while (objectIterator->MoveNext()) {
PObject object = objectIterator->GetCurrentObject();
if (object->IsMeshObject()) {
ShowMeshObjectInformation(model->GetMeshObjectByID(object->GetResourceID()));
} else if (object->IsComponentsObject()) {
ShowComponentsObjectInformation(model->GetComponentsObjectByID(object->GetResourceID()));
} else {
LOGI("unknown object #%u", object->GetResourceID());
}
}
其中 ShowMeshObjectInformation 和 ShowComponentsObjectInformation 分别输出网格对象和组件对象的详细信息,包括顶点数、三角形数、构件列表等。这些辅助函数的实现与官方示例基本一致,仅将输出方式替换为 LOGI。完整代码见文末 gist。
4.资源文件准备和处理
为了快速验证,我们将一个测试 3MF 文件(如 cube.3mf)放入 Flutter 项目的 assets 目录,并在 pubspec.yaml 中声明:
assets:
- assets/cube.3mf
由于 FFI 函数需要文件系统的绝对路径,而 assets 中的文件无法直接访问,我们需要将其复制到应用的临时目录。使用 path_provider 获取临时目录,然后通过 rootBundle 读取 assets 文件并写入:
Future<String> getAssetPath(String assetName) async {
// 1. 获取应用私有的临时目录
final directory = await getTemporaryDirectory();
final filePath = "${directory.path}/$assetName";
final file = File(filePath);
// 2. 如果文件不存在,则从 Assets 中读取并写入
if (!await file.exists()) {
final byteData = await rootBundle.load("assets/$assetName");
await file.writeAsBytes(byteData.buffer.asUint8List());
}
return file.path;
}
然后在按钮点击时调用:
FilledButton(
onPressed: () {
getAssetPath('cube.3mf').then((path) {
debugPrint("Asset path: $path");
debugPrint(nn.extractInfo(path).toString());
}).catchError((error) {
debugPrint("Error getting asset path: $error");
});
},
child: Text('lib3mf extractInfo'),
)
运行验证
点击按钮后,日志输出如下(已过滤无关信息):
http://127.0.0.1:62556/H6lsMopbsUI=/devtools/?uri=ws://127.0.0.1:62556/H6lsMopbsUI=/ws
I/flutter (14057): Asset path: /data/user/0/com.chaosgoo.metasequoia.metasequoia/cache/cube.3mf
I/NativeLib3mf(14057): ------------------------------------------------------------------
I/NativeLib3mf(14057): 3MF Read example
I/NativeLib3mf(14057): lib3mf version = 2.4.1
I/NativeLib3mf(14057): ------------------------------------------------------------------
I/NativeLib3mf(14057): ReadFromFile:
I/NativeLib3mf(14057): /data/user/0/com.chaosgoo.metasequoia.metasequoia/cache/cube.3mf
I/NativeLib3mf(14057): mesh object #1:
I/NativeLib3mf(14057): Name: "Box"
I/NativeLib3mf(14057): PartNumber: ""
I/NativeLib3mf(14057): Object type: model
I/NativeLib3mf(14057): Vertex count: 8
I/NativeLib3mf(14057): Triangle count: 12
I/NativeLib3mf(14057): Build item (Object #1):
I/NativeLib3mf(14057): Transformation: none
I/NativeLib3mf(14057): Part number: ""
I/NativeLib3mf(14057): done
与第一篇中直接运行官方 Example_ExtractInfo 的输出完全一致,证明移植成功。
总结
本文基于第二篇的 FFI 插件,完整实现了从 Flutter 中调用 lib3mf 解析 3MF 文件的功能。我们处理了平台输出差异,移植了官方的 ExtractInfo 示例,并通过 assets 文件验证了解析结果。
下一步计划:获取模型中的顶点和三角形数据,并通过 OpenGL 在 Native 层进行渲染,最终在 Flutter 应用中显示 3D 模型。敬请期待第四篇。
附录:完整代码
本文中 extract_info.cpp 的完整实现可在以下 Gist 中查看: extract_info