Go back

Integrating lib3mf, Part 3: Reading 3MF File Information in Flutter

Published:  at  09:22 AM
⏱️ 1042 words • 6 min read

阅读中文版

Reading 3MF model metadata in Flutter through lib3mf and FFI, from asset file handling and cross-platform logging to iterating model objects on Android.

Preface

In lib3mf integration series (2): Flutter FFI plug-in creation and Android integration, we have successfully integrated the lib3mf precompiled library into the Flutter plug-in and verified the availability of the library through the version query function. Now, we will implement the function of reading model metadata from 3MF files based on the official ExtractInfo example of lib3mf.

Overview of lib3mf parsing process

The API of lib3mf adopts object-oriented design, and the core objects include:

  • PWrapper: The entrance to the library, used to load the library, create models, etc.
  • PModel: Represents a 3MF document, including all objects, resources, build items, etc.
  • PObject: An object in the model, which can be a mesh object (PMeshObject) or a component object (PComponentsObject).
  • PReader: Used to read 3MF content from a file or stream.

The basic steps for parsing a 3MF file are as follows:

  1. Load library (CWrapper::loadLibrary()).
  2. Create a model object (wrapper->CreateModel()).
  3. Create a reader and set options (such as turning off strict mode).
  4. Call ReadFromFile to read the file.
  5. Traverse the metadata, objects, construction items, etc. in the model and output the required information.

Below we will implement this process step by step.

Core code implementation

1. Platform difference handling

Since our plug-in needs to support both Android and macOS, and std::cout on Android will not be output to logcat, we need to use conditional compilation to select the appropriate output method. Two macros LOGI and LOGE are defined here, which are used for normal information and error information respectively:

#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. Loading libraries and reading files

We directly use lib3mf’s CWrapper::loadLibrary() to load the dynamic library, and then create the model and reader. The key code snippets are as follows:

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());
    }

    // 后续遍历代码...
}

NOTE: SetStrictModeActive(false) allows reading of 3MF files containing non-core extensions, such as files with beam structures or slices. Turning off strict mode can avoid read failures if the file does not comply with core specifications.

3. Traverse objects and output information

lib3mf uses the iterator pattern to traverse the various elements in the model. For example, get an iterator over all objects:

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());
    }
}

Among them, ShowMeshObjectInformation and ShowComponentsObjectInformation output detailed information of mesh objects and component objects respectively, including the number of vertices, number of triangles, component list, etc. The implementation of these auxiliary functions is basically the same as the official example, except that the output mode is replaced by LOGI. The complete code can be found in the gist at the end of the article.

4. Resource file preparation and processing

For quick verification, we put a test 3MF file (such as cube.3mf) into the assets directory of the Flutter project and declare in pubspec.yaml:

assets:
  - assets/cube.3mf

Since the FFI function requires an absolute path to the file system, and files in assets are not directly accessible, we need to copy them to the app’s temporary directory. Use path_provider to get the temporary directory, then read the assets file through rootBundle and write:

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;
  }

Then call it on button click:

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'),
)

Run verification

After clicking the button, the log output is as follows (irrelevant information has been filtered):

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

It is completely consistent with the output of directly running the official Example_ExtractInfo in the first article, proving that the transplant is successful.

Summary

This article is based on the FFI plug-in in the second article, which fully implements the function of calling lib3mf from Flutter to parse 3MF files. We addressed platform output differences, ported the official ExtractInfo example, and verified the parsing results via assets files.

The next step is to obtain the vertex and triangle data in the model, render it on the Native layer through OpenGL, and finally display the 3D model in the Flutter application. Stay tuned for the fourth article.

Appendix: Complete code

The complete implementation of extract_info.cpp in this article can be viewed in the following Gist: extract_info


Share this post on:

Previous Post
Integrating lib3mf, Part 4: Flutter Texture and a Native C++ OpenGL Rendering Pipeline
Next Post
Integrating lib3mf, Part 2: Creating a Flutter FFI Plugin and Adding Android Support