跳至内容
返回

lib3mf 集成系列(三):在 Flutter 中解析 3MF 文件信息

发布于:  at  09:22 上午

前言

lib3mf 集成系列(二):Flutter FFI 插件创建与 Android 端集成中,我们已经成功将 lib3mf 预编译库集成到 Flutter 插件中,并通过版本查询函数验证了库的可用性。现在,我们将基于 lib3mf 官方的 ExtractInfo 示例,实现从 3MF 文件中读取模型元数据的功能。

lib3mf 解析流程概述

lib3mf 的 API 采用面向对象设计,核心对象包括:

解析一个 3MF 文件的基本步骤如下:

  1. 加载库(CWrapper::loadLibrary())。
  2. 创建模型对象(wrapper->CreateModel())。
  3. 创建读取器并设置选项(如关闭严格模式)。
  4. 调用 ReadFromFile 读取文件。
  5. 遍历模型中的元数据、对象、构建项等,输出所需信息。

下面我们将逐步实现这一过程。

核心代码实现

1. 平台差异处理

由于我们的插件需要同时支持 Android 和 macOS,而 Android 上 std::cout 不会输出到 logcat,因此需要使用条件编译选择合适的输出方式。这里定义两个宏 LOGILOGE,分别用于普通信息和错误信息:

#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


在以下平台分享此文章: