lib3mf 集成系列(一):编译与示例测试

背景

学习flutter有半个月了, 看Flutter实战看得看不下去了, 打算写点东西练练手.
又不想写基础的UI界面, 所以想试试Flutter FFI, 以及和安卓原生的交互, 再牵涉到一些OpenGL的渲染.
于是就有了此文.
本文是系列文章的一部分, 主要记录如何编译lib3mf, 以及基础用法.
为保证环境一致, 编译时基于9ada20e4287fec118a29d5643f5714ea68302e21 (master分支当时的最新提交)

3mf文件介绍

3MF(3D Manufacturing Format)是一种专为3D打印设计的开放式、基于XML的文档格式,旨在解决STL等传统格式信息缺失的问题。它能在一个压缩文件中完整描述模型几何形状、颜色、材质、支撑和打印参数,2025年已成为国际标准(ISO/IEC 25422:2025)。

lib3mf介绍

lib3mf 是一个开源的 C++ 库,用于读写 3MF 文件。它提供了 3MF 文件的完整实现,包括 3MF 核心规范以及所有相关的扩展。

准备工作下载源码
1
2
git clone https://github.com/3MFConsortium/lib3mf.git
git submodule update
编译
1
2
3
4
mkdir build
cd build
cmake ..
make

成功编译后, 生成的文件位于build目录下, 包含lib3mf.2.dylib等依赖库.

编译SDK/EXample/CPP

lib3mf 的 C++ 示例程序涵盖了从基础几何体生成到高级扩展(如点阵、切片、加密)的应用场景。

直接编译会提示缺少头文件和库文件, 需要修复CMakeLists.txt文件.

修复CMakeLists.txt内路径错误

主要是路径错误, 直接编译报找不到lib3mf_implicit.hpp和lib3mf库找不到问题, 需要手动修复include_directorieslink_directories等路径.
后面我编译的是CPP目录下的Example, 所以路径修改如下:

1
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../Bindings/Cpp)

1
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../Autogenerated/Bindings/Cpp)

1
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../Bin)

1
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../build)

1
add_custom_command(TARGET ${MyTarget} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../../Bin/lib3mf.${LSUFFIX}" $<TARGET_FILE_DIR:${MyTarget}>/lib3mf${LSUFFIXOUT})

1
add_custom_command(TARGET ${MyTarget} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../../../build/lib3mf.${LSUFFIX}" $<TARGET_FILE_DIR:${MyTarget}>/lib3mf${LSUFFIXOUT})

再次编译

再次make得到可执行文件

1
2
3
4
5
6
❯ ls                 
CMakeCache.txt Example_Components Example_SecureCube GenerateVS2017.bat lib3mf.2.dylib
CMakeFiles Example_Converter Example_Slice GenerateVS2019.bat
CMakeLists.txt Example_Cube Example_TextureCube Makefile
Example_BeamLattice Example_ExtractInfo GenerateMake.sh Source
Example_ColorCube Example_FillMeshWithGyroid GenerateVS2015.bat cmake_install.cmake

运行Example_ColorCube得到了一个colorcube.3mf, 原来这是生成3mf文件的例子
使用Bambu Studio打开后预览模型
Color Cube Preview
发现不是立方体, 但是不知道为啥例子叫做Cube, 而且没有颜色.
为了验证颜色, 使用MeshLab打开正确识别到了颜色
Color Cube Preview

lib3mf 示例程序 (CPP) 运行报告

前面步骤编译出了所有的Example可执行文件, 索性在这里记录下所有的Example的功能, 以及在不同查看器中的表现.

1. 基础模型与转换
示例名称 功能描述 兼容性与备注 预览图
Cube 普通长方体模型 最基础的几何体生成示例。
ColorCube 彩色长方体 演示如何为模型添加颜色属性。
Components 对象复用特性 生成多个不同位置的长方体,支持导出 STL/3MF。
Converter 格式转换 实现 3MF ↔ STL 的双向转换。 -
ExtractInfo 信息提取 从 3MF 文件中读取并打印模型元数据。 -

2. 复杂结构与兼容性分析

针对 3MF 高级扩展(Lattice)的专项测试:

示例名称 核心原理 查看器表现 (Bambu Studio/ MeshLab / 3mfViewer) 预览图
BeamLattice 基于“梁”单元(Beam Lattice)的框架。 Bambu Studio打不开;MeshLab仅显轮廓;3mfViewer支持良好。
FillMeshGyroid 数学公式生成的 Gyroid 陀螺面点阵。 Bambu Studio/3mfViewer均无法打开。 -

3MF 并非单一标准,它由 Core Spec 和多个 Extensions 组成, 不同切片软件可能会按需实现Extensions。


3. 功能扩展示例
  • Slice (内置切片)
    • 功能: 生成包含内置切片信息的 3MF 文件。
    • 表现: Bambu Studio 忽略了此信息;但在 3mf Viewer 中,右侧会出现专用的 Slice 滑动条供逐层观察。
  • SecureCube (安全扩展)
    • 功能: 演示 3MF 的安全内容扩展(如加密、数字签名)。
    • 状态: 暂未深入研究。
为 Flutter FFI 做准备

在进入下一篇之前,我们需要确认编译出的动态库是否符合 FFI 调用要求。

检查导出符号

使用 nm -gU lib3mf.2.dylib 检查。lib3mf 提供了专门的 C-Wrapper,这对于 Flutter(仅能直接调用 C 风格接口)至关重要。

1
2
3
4
5
6
7
8
❯ nm -gU lib3mf.2.dylib 
00000000001abe30 T _lib3mf_accessright_getconsumer
00000000001ac8ec T _lib3mf_accessright_getdigestmethod
00000000001ac54c T _lib3mf_accessright_getmgfalgorithm
00000000001ac1b4 T _lib3mf_accessright_getwrappingalgorithm
00000000001e09fc T _lib3mf_acquire
000000000016c3fc T _lib3mf_attachment_getpath
...

Handle 机制

lib3mf 使用句柄(Handle)管理对象,这意味着在 Flutter 侧我们需要维护一套指针映射,防止内存泄漏。

总结

本文完成了 lib3mf 的环境搭建与功能验证。虽然部分高级扩展在商用切片软件中兼容性不佳,但 lib3mf 强大的 C-API 为我们在 Flutter 中操作 3D 模型提供了坚实基础。

Flutter 嵌入安卓原生 View,以及与原生交互

在跨端开发里,有些场景是 Flutter 处理起来比较麻烦或者利用原生组件实现更高效。

这时候就得祭出 PlatformViewMethodChannel。不仅把一个 Android 原生 TextView 塞进了 Flutter 布局,还能实现Flutter和原生Android View的双向交互。

为了直观,我打算基于Flutter默认的计数器模板演示, 界面布局和悬浮按钮还是 Flutter 的,但中间显示的那个数字,换成安卓原生的TextView

阅读更多
球面几何计算:基于经纬度的距离(Great-Circle)推导

球面几何计算:基于经纬度的距离(Great-Circle)推导

背景

最近在做一个基于当前 GPS 坐标,通过输入目标经纬度来计算相对位置与距离的硬件装置。

在查阅相关算法时,我发现网上的推导过程大多较为碎片化或直接给出结论。为了彻底理解其底层的数学逻辑,我决定从零开始重新推导一遍,并将其系统地记录下来。

本文将完整呈现这一推导过程:从建立三维空间直角坐标系开始,利用向量点积导出球面余弦定律(Law of Cosines)。此外,我们还会深入探讨该公式在短距离场景下产生“精度丢失”的物理本质,以及如何利用半角恒等式引入 Haversine 公式 来修正误差。最后,我将分享如何通过数学化简,精简公式降低运算量,从而实现更高的运算效能。

阅读更多

网站性能优化实战:PageSpeed Insights 从 47 分到 97 分的优化历程

This article is also available in the following language: English.
前言

最近为了优化站点体验, 我决定借助PageSpeed Insights的分析,专项优化站点性能表现.
本文起初基于Cactus主题进行优化, 从47分提高到了94分, 在撰写期间, 又将主题切换到了Icarus, 结果之前的部分优化失效.
不得不重新优化, 但是按照同样思路, 甚至把PageSpeed Insights的分数刷新到了97分.

阅读更多
Android 进阶:如何在安卓中实现像 LVGL 一样的"实体"Border?

Android 进阶:如何在安卓中实现像 LVGL 一样的"实体"Border?

做嵌入式的时候用过 LVGL,它的样式系统给我留下了深刻印象。LVGL 的 Border 是”实体”的——会占据布局空间,把内容往里挤。

安卓这边就没这么痛快了。ShapeDrawableMaterialCardView 的边框更像是”装饰品”,想让边框、圆角、内边距各自独立可控?原生组件做起来挺别扭。

所以我干脆手搓了一个 BorderFrameLayout,把 LVGL 那套逻辑搬过来。
BorderFrameLayout层级示意图
图中不同的颜色代表真实占用的空间。
绿色代表Border, 红色代表内部Padding, 蓝色代表Child可使用空间.

阅读更多
屏幕不够,算法来凑(一):Ditherpunk 抖动算法原理与 JS 实时演示

屏幕不够,算法来凑(一):Ditherpunk 抖动算法原理与 JS 实时演示

背景

在嵌入式开发领域,我们经常会遇到色彩位数极低的显示设备:

  • 经典的 SSD1306 (0.96寸 OLED),仅支持黑白两色。
  • 电子墨水屏 (E-Ink),通常只有黑白,且刷新率极低。

如果直接将 24 位真彩图片进行量化处理,其结果往往如同烧焦的木炭,细节丢失殆尽。但若引入 抖动算法(Dithering),这些 1-Bit 屏幕便能模拟出细腻的灰度感。

此前我曾尝试用 Rust 实现过一个版本,但作为博客演示,使用 JavaScript 与 Canvas 在浏览器中直接进行仿真最为直观。本文将介绍几种主流抖动算法的原理及其 JS 实现。


阅读更多

使用Bindgen为ELK生成Rust绑定

介绍

bindgen 是一个能自动为 C(或 C++)库生成 Rust 绑定的辅助库和命令行工具。

elk 是一个迷你的JS引擎.
能够实现类似于这样的效果

main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
##include <stdio.h>
##include "elk.h"

// C function that adds two numbers. Will be called from JS
jsval_t sum(struct js *js, jsval_t *args, int nargs) {
if (nargs != 2) return js_err(js, "2 args expected");
double a = js_getnum(args[0]); // Fetch 1st arg
double b = js_getnum(args[1]); // Fetch 2nd arg
return js_mknum(a + b);
}

int main(void) {
char mem[200];
struct js *js = js_create(mem, sizeof(mem)); // Create JS instance
js_set(js, js_glob(js), "sum", js_mkfun(sum))); // Import sum()
jsval_t result = js_eval(js, "sum(3, 4);", ~0); // Call sum
printf("result: %s\n", js_str(js, result)); // result: 7
return 0;
}

如果这个执行内容来自于服务器下发,那就可以很方便地动态下发程序然后执行特定的任务.

阅读更多