Air 系列应用交流/AirCad(from FreeCad)|AirCad · 鸿蒙 PC 适配全记录

AirCad(from FreeCad)|AirCad · 鸿蒙 PC 适配全记录

【Air 系列 · 鸿蒙 PC 适配全记录】07 / 56

■ 应用:AirCad(from FreeCad)(AirCad)
■ 定位:FreeCAD 移植,参数化 3D CAD 建模
■ 上游:FreeCAD 26.3.0-dev | 路线:Qt6 6.11.1+gl4es | 版本:1.3.0
■ 开源: https://gitcode.com/Andy88/Harmony-freecad
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917612153129759654
■ 官网(免费下载): https://www.shunidc.cn/

————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/

PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://gitcode.com/Andy88/Harmony-freecad

环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

▍一、为什么要适配 AirCad

参数化三维建模是工程软件皇冠上的明珠:它要求精确的几何内核、可脚本化的自动化接口、以及一个能承载菜单、工具栏、模型树与 3D 视口的完整桌面框架。FreeCAD 是这一领域最重要的开源实现,其技术栈横跨 C++ 几何内核 OpenCASCADE、Python 运行时、Qt 界面框架与 Coin3D 三维渲染。把 FreeCAD 完整带到鸿蒙设备上,检验的是鸿蒙平台对大型桌面工程软件的承载极限,其结论对整个 CAD/CAE 类目的移植都有参考价值。

工程代号 AirCad,上游为 FreeCAD 26.3.0-dev(main 快照),目标平台覆盖平板、2in1 与手机(deviceTypes: tablet/2in1/phone,单 entry 三端合一),包名 com.aircad.app,版本 1.2.0,目标 ABI arm64-v8a。README 记载当前状态为 M8+ 真机可用:MainWindow、菜单、工具栏、Combo View 全量上屏,3D 视口真实渲染,中文 UI,系统选择器保存导出(STEP/IGES/STL/OBJ/BREP),IME 软键盘、鼠标光标与截图链路可用。部署规模也体现了"完整移植"的分量:163 个 .so 约 672MB,外加 47MB 的 FreeCAD 数据文件随 HAP 携带、首启解压到沙箱。

▍二、先划清边界:不把编译通过当成适配完成

FreeCAD 面向的是 x86 桌面 Linux/Windows/macOS:Qt 走桌面 QPA、OpenGL 直连驱动、Python 解释器来自系统、文件对话框是原生 QFileDialog。把这些整体搬到 musl libc 与自有窗口体系的鸿蒙上,编译器只解决最小的一环。工程划定边界的方式是逐层声明替代方案,下表为对照。

层次 原项目实现(桌面 FreeCAD) 鸿蒙侧当前方案
应用入口 main + QApplication ArkTS Stage EntryAbility + XComponent(TEXTURE)
界面框架 Qt 桌面 QPA(xcb/cocoa/windows) 自研 ohos QPA 插件(QT_QPA_PLATFORM=ohos,Qt 6.11.1)
3D 渲染 Coin3D 直连桌面 OpenGL Coin3D→gl4es 1.1.7(桌面 GL 转 GLES2,部署为 libGL.so)→ Maleoon GPU
呈现合成 桃面合成器直接上屏 QOpenGLWidget FBO 子纹理合成进 raster backingstore,单一 eglSwapBuffers
Python 环境 系统 Python + pip Python 3.11.9 交叉编译,56 个扩展模块经 bundle libs 通道部署
文件对话框 原生 QFileDialog 系统 Picker:保存/另存为/导出全走带 UI 系统选择器
窗口装饰 Qt 自绘或系统标题栏 系统标题栏隐藏,文档切换走 ArkTS 顶部标签菜单栏

上游 26.3.0-dev 的 OHOS 适配以 PATCH-xx(AirCad) 与 __OHOS__ 标记就地分布在 15 个以上文件(Application.cpp、MainWindow.cpp、FileDialog.cpp、View3DInventor.cpp 等),发布树整树公开于 gitcode 的 Harmony-freecad 仓库。边界同样包括尚未完成的缺口:PySide6 未移植、ASSEMBLY 模块因 OndselSolver 子模块缺源关闭——这些在 README 与 AGENTS.md 中都有明示。

▍三、鸿蒙版的整体架构

架构遵循 Air 系成熟模式:薄 NAPI 模块加重型核心运行时 dlopen。libcadbridge.so 只链系统库,负责 napi 接口与 TSFN 回调;libaircad_core.so 在运行时拉起 FreeCAD App/Gui、Python、OpenCASCADE、Coin3D 与 Qt 全栈。渲染上的关键决策是把 Qt raster UI 与 QOpenGLWidget 3D 视口合成到同一个共享 EGLSurface:Coin 的桌面 GL 调用经 gl4es 翻译为 GLES2,QPA 层把 QOpenGLWidget 的 FBO 作为子纹理与 raster 帧合成后统一上屏。

text
ArkTS 壳(entry/src/main/ets,Index.ets 约 2100 行)
│ 顶部 32vp 标签菜单栏(8 菜单 142 项)+ XComponent(TEXTURE) + 系统 picker/IME/启动遮罩
│ NAPI:libcadbridge.so(薄壳,只链系统库)
▼ 运行时 dlopen
libaircad_core.so(重型核心)
├── FreeCAD App + Gui(libFreeCADGui.so 43MB + 19 个 Mod 模块)
├── Python 3.11.9(libpython3.11.so + 56 个扩展模块)
├── OpenCASCADE 7.8.1(51 个 libTK*.so)+ Coin3D
├── Qt 6.11.1(13 模块)+ libqohos.so(自研 ohos QPA)
└── libGL.so = gl4es(GOT/PLT 改写接管 Coin 的 gl* 符号)


主要目录如下:

text
AirCad/
├── AGENTS.md # 权威状态与操作基线(里程碑战报/构建链路/禁止事项)
├── README.md / ROADMAP.md # 项目定位、运行时架构、P1-P5 路线
├── ai_log/ # 过程记录与真机证据
├── entry/src/main/
│ ├── ets/ # ArkTS 壳(Index/pages/managers/utils)
│ └── cpp/
│ ├── freecad_bridge.cpp / aircad_core.cpp / napi_init.cpp
│ ├── aircad_got_rewrite.h # GOT/PLT 运行时改写
│ ├── qt_platform/ohos_qt6/ # Qt6 QPA 插件(qohosintegration 等)
│ ├── libs/ # 163 个 .so 部署清单
│ └── patch_libs.py / fix_needed_paths.py / fix_elf_align.py
└── native/
├── scripts/ # env.sh → build_base_deps → build_3d_stack →
│ # build_freecad(_gui) → build_gl4es → deploy
├── cross/ # CMake 工具链、两套 preset、C++20 兼容 shim
├── sources/ # FreeCAD-main、Python、Boost、occt、coin、gl4es 等
├── install/ # 交叉编译产物前缀
└── glu_stub/ # GLU 桩库(真实实现走 gl4es)


▍四、核心适配过程

### 1. Qt6 OHOS QPA 插件:桌面 GUI 框架的第 25 号平台

FreeCAD 的全部界面都建立在 QWidget/Qt6 之上,重写 UI 不可行,工程选择把 Qt 当作可携带的 GUI 内核:自研 libqohos.so 平台插件,源起 AirKrita 的 Qt5 版 QPA 并移植到 Qt6。插件承担窗口创建、事件分发(createUnixEventDispatcher 泵队列)、字体(自包含 freetype,HarmonyOS Sans CJK 221 族)、光标与剪贴板。M4 装配阶段 Gui::Application::runApplication 在 Qt 主线程跑通,libFreeCADGui.so 被 dlopen 后原生 MainWindow 直接上屏,8 个顶级菜单与 12 个工具栏 68 个动作全部来自上游原生 UI,而非 ArkTS 重绘。

### 2. 三维渲染链:Coin 桌面 GL 到 Maleoon GLES2 的翻译

Coin3D 只有桌面 GL 立即模式渲染路径,而移动 GPU 暴露的是 GLES。工程的解法不是移植 Coin,而是引入 gl4es 1.1.7 翻译层部署为 libGL.so,并在加载后做 GOT/PLT 运行时改写,让 Coin 的 gl* 符号在 musl 平坦符号域下不会被 ArkUI 预载的驱动库劫走。QPA 侧再以 PATCH-27 把 QOpenGLWidget 的 FBO 作为子纹理与 raster 帧合成。这条 Coin→gl4es→GPU→FBO 子纹理→单一 eglSwapBuffers 链路经真机实证:创建长方体、Fit All、拖拽旋转、恢复对话框交互全部可用。

### 3. 文件链路与命令总线的系统化

M8 把保存/另存为/全部保存/导出全部改走带 UI 的系统选择器:native 侧新增 saveDocument、exportFile(按 Python 扩展名分发 Import.export/Mesh.export)与 registerSaveRequestCallback(TSFN),freecad_gui_run_command 在命令入口拦截 Std_Save/SaveAs/Export/SaveAll,不再放行进 QFileDialog,僵尸白框来源被根除;导出前经 ArkUI ActionSheet 选择 STEP/IGES/STL/OBJ/BREP 格式。单文档保存状态升级为按文档绑定的 docFileMap,多文档串档隐患随之修复。中文化由 LANGUAGE=zh_CN 与预置 .qm 资源完成,文档快照经 2 秒轮询驱动 ArkTS 标签栏。

### 4. 交叉编译流水线与 ELF 后处理

native/scripts/ 提供六步构建:env.sh 定位 OHOS NDK(clang aarch64-unknown-linux-ohos),build_base_deps.sh 顺序产出 zlib/Boost/Python/ICU/XercesC/Eigen 等 9 项,build_3d_stack.sh 编 OCCT 与 Coin3D,build_freecad.sh(headless)与 build_freecad_gui.sh 用两套 CMake preset 编 FreeCAD 本体,build_gl4es.sh 出 libGL.so,最后 deploy.sh 执行部署清单与 ELF 后处理。后处理三件套是 musl dlopen 存活的关键:fix_needed_paths.py 把绝对路径依赖 basename 化、patch_libs.py 给 Python 扩展补 --add-needed libpython3.11.so、fix_elf_align.py 修复 64K 段对齐。FreeCAD 数据文件(Mod/Ext/stdlib/share 材料库)走 rawfile 首启异步解压到沙箱。

### 5. 三端合一与启动体验

M10 移除 JIT 权限链后,应用收敛为单 entry 三端:运行时差异全部走 deviceInfo 分支(手机 uiScale 2.0、其余 1.5,经 QPA 导出的 QOhos_SetUiScale 注入),手机长菜单由 phoneMenuOverlay 自定义可滚动面板承接,锁横屏保证 CAD 视口方向。M11 补上 Air 系同款启动遮罩(白底标题 + LoadingProgress + 阶段文字,guiShown=1 后 600ms 撤层保首帧),遮住了 672MB 核心库加载的空窗期。

▍五、适配中遇到的几个难点

### 难点1:同进程两个 GL 世界互打状态缓存

Qt raster 合成直连驱动 GLES,Coin 经 gl4es 走桌面 GL 翻译,两者共享默认 VAO 与属性状态。真机现象是拖拽旋转后画面定格发黑:gl4es 的属性 enable/disable 缓存记住了"已启用",而 Qt 直连驱动的路径改写了真实 GL 状态。修复是 gl4es 的 PATCH-AIRCAD-1/2——属性状态不信缓存、无条件重发,另加 external_default_fbo 记录恢复(PATCH-AIRCAD-3)防止 Coin 的离屏渲染目标劫持 Qt 的 FBO。这一组补丁定义了混合 GL 栈的共存规则:任何一侧都不允许假设自己是状态唯一持有者。

### 难点2:Python 扩展模块的部署通道

Python 的 56 个 .cpython-311.so 扩展既不能进 rawfile 也不能进 files 目录——沙箱禁止对 files 路径做可执行映射,且交叉编译的扩展默认不链 libpython,dlopen 时报 PyModuleDef_Init: symbol not found。工程走 bundle libs 通道部署并对每个扩展执行 --add-needed libpython3.11.so,符号经 RTLD_GLOBAL 全局域补齐 _ctypes 对 libffi 的依赖。这条链路后来在 M9 又经历一次考验:llvm-objcopy --add-section 会重排 p_offset 破坏 64K 对齐同余,导致 _ctypes 加载即 SIGSEGV,对策是 objcopy 后必须重跑 fix_elf_align,并写进工程禁止事项。

### 难点3:EGLSurface 的生命周期线程纪律

窗口 resize 卡死与全屏切换后画面永久停摆是两个同族问题:ArkUI 线程销毁 EGLSurface 时,该 surface 可能仍是 Qt 主线程的 current;全屏切换后新建的 surface 毫秒级即死(EGL_BAD_ALLOC 风暴每秒约百次)。PATCH-73 把销毁与窗口调和全部下沉到 Qt 主线程执行(qohosRunOnQtThread),PATCH-74 给全部 7 处 BAD_SURFACE 站点武装重试链,并以 eglMakeCurrent 实测判定 surface 活性,僵尸不重试、活体才唤醒呈现。真机回归 0.7 秒间隔快速切换五轮全部存活。

### 难点4:透明浮动面板吞掉视口事件

手机端出现过单指旋转视口无效而渲染正常的现象,事件探针显示触摸被 QSint::ActionLabel/TaskView 接收。活体几何探针定案真凶:FreeCAD 26.3 的 OverlayManager 把 Tasks 面板放进右下锚定的透明浮动面板,屏幕上不可见但 widget 几何活着;桌面版靠 QCursor::pos() 实现透明区鼠标穿透,而本平台的 QPlatformCursor::pos() 回退值把 DIP 坐标误当 native 再除缩放系数,穿透判定永不命中。PATCH-82 在 initialize 时补发 handleScreenGeometryChange 修正 QScreen 陈旧几何,PATCH-83 让 QOhosCursor::pos() 返回最近注入的触摸/鼠标点,把触摸设备的 QCursor 语义与桌面对齐,overlay 穿透链随之复活,Pura X 生产包终验通过。

### 难点5:构建树丢失暴露的 preset 隐性依赖

native/build 构建树被磁盘清理连带删除后,全新全量编译暴露出 M11 增量构建一直没踩到的雷:QtCore.h 与 config.h 两个生成头丢失;全新 configure 还必须预置 GLESv2/EGL 的 find 变量——headless preset 此前靠旧构建树缓存侥幸通过。修复把这组变量(OHOS_SYSROOT_GL_DIR 加 GLESv2/EGL 五变量)固化进 freecad_ohos_preset.cmake,纯 preset 全新 configure 验收通过。这个案例的教训是:依赖缓存的构建脚本等于没有构建脚本,清理一次反而完成了最严格的一次验收。

▍六、编译、安装与启动

完整构建分 native 与 ArkTS 两段。native 侧在 native/scripts/ 下按顺序执行:env.sh 初始化 OHOS NDK 环境,build_base_deps.sh 编基础依赖,build_3d_stack.sh 编 OCCT 与 Coin3D,build_freecad.sh 与 build_freecad_gui.sh 分别产出 headless 与 GUI 形态,build_gl4es.sh 出 gl4es,deploy.sh 完成部署清单拷贝与 ELF 三件套后处理(patch_libs、fix_needed_paths、fix_elf_align)。ArkTS 侧使用标准 hvigor 流程:

bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk

hvigorw --mode module -p module=entry assembleHap --no-daemon


签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。安装启动:

bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.aircad.app


AGENTS.md 记载了完整的真机验证口径:hap 约 928MB(163 .so + rawfile 数据),M11.5 轮在 MatePad 11.5 上装机启动、pid 存活零 faultlog、guiShown=1 三秒/六秒双确认、主窗口菜单/工具栏/模型树/视口渐变/NaviCube 中文面标签视觉验证健康。首次启动会先解压 47MB FreeCAD 数据到沙箱,启动遮罩覆盖该过程。

▍七、当前功能边界

当前版本已覆盖的能力:

- FreeCAD 原生 GUI 全量上屏:MainWindow、8 个顶级菜单、工具栏、Combo View、Report View、多文档标签(ArkTS 顶栏切换);
- 3D 视口真实渲染:Part 基础体创建、拖拽旋转缩放、NaviCube、明暗着色、单指/双指手势(双指 PAN 合成中键拖拽);
- 19 个上游 Mod 模块(Part/Sketcher/Mesh/TechDraw/Spreadsheet/Measure 等,REQUIRES_MODS 依赖环同开);
- 文件链路:打开/保存/另存为/全部保存/导出全走系统选择器,导出支持 STEP/IGES/STL/OBJ/BREP,按文档绑定保存路径;
- Python 3.11.9 运行时与 56 个扩展模块可用,ctypes 闭包经 libffi 回退路径全通(JIT 权限链已验证无依赖并整体移除);
- 中文 UI、IME 软键盘、鼠标光标样式、平板沉浸式、截图保存(Std_ViewScreenShot 全链可用);
- 三端形态:平板/2in1/手机单 hap 通装,手机 uiScale 2.0 与横屏锁定,启动遮罩。

尚未覆盖或已登记的缺口(出处为 README 状态行与 AGENTS.md 里程碑记录):PySide6 未移植,依赖它的 Part py 命令集不可用;ASSEMBLY 模块因 OndselSolver 子模块缺源而关闭;DRAFT 工作台因运行期依赖 pivy 未移植而暂不激活;Qt 内部快捷键(如 Ctrl+S)可直达 QAction 旁路命令总线,个别路径仍可能唤起原生 QFileDialog;Tasks overlay 在手机端的 AutoHide/TaskShow 参数化仍在待办。M9 时代记录的第二文档 NaviCube 纯黑缺口在 M11.5 的视觉验证中未复现。

因此,当前版本更准确的定位是"FreeCAD 鸿蒙真机可用版":参数化建模的主工作流——建模、视口导航、保存导出、脚本运行时——已在平板与 2in1 上闭环,剩余项集中在 Python GUI 绑定与个别上游模块的依赖补齐。

▍八、总结

AirCad 的价值在于验证了一条完整的重工程软件移植路径:Qt6 以自研 QPA 的方式获得鸿蒙窗口与输入,Coin3D 以 gl4es 翻译层绕开 GLES 缺口,Python 与 OpenCASCADE 以交叉编译加 ELF 后处理的方式在 musl 沙箱内存活,文件与命令链路则桥接到系统 Picker 与 ArkTS 总线。163 个 .so 的部署规模之下,真正起支撑作用的是那套可重复的六步流水线与 ELF 后处理规则——它们让 672MB 的核心栈从源码到 HAP 的每一步都可复现。

后续工作的重心在 AGENTS.md 的遗留清单里:PySide6 与 pivy 的移植决定 DRAFT 等工作台能否激活,QAction 层的命令拦截是文件链路的最后一处直通口,手机端 Tasks overlay 参数化与上游 OverlayManager 行为的适配仍在演进。每一步推进依然以"可构建的 HAP 加真机全链验证"为验收口径,这与工程从 M1 到 M11 一路走来的方式一致。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。