- lib3mf integration series (1): compilation and sample testing
- lib3mf integration series (2): Flutter FFI plug-in creation and Android integration
- lib3mf integration series (3): Parsing 3MF file information in Flutter
- lib3mf integration series (4): Flutter Texture and C++ OpenGL cross-terminal rendering architecture
- lib3mf integrated series (5): C++ extraction of 3MF data and OpenGL rendering practice
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:
- Load library (
CWrapper::loadLibrary()). - Create a model object (
wrapper->CreateModel()). - Create a reader and set options (such as turning off strict mode).
- Call
ReadFromFileto read the file. - 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