从 Python 光学库到鸿蒙 PC 原生应用:ohos_Optiland 的移植适配实战

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Optiland

本文以开源光学设计库 optiland 的鸿蒙化移植为案例,完整复盘一个"专业计算型桌面软件"在 HarmonyOS PC(2in1 / 平板)上的移植适配全过程:包括迁移路线的选型与踩坑、光学引擎的原生适配、ArkUI 界面的重构式搭建、数据持久化方案、构建与调试流水线,以及最终的性能验证数据。全文基于真实工程代码与实测截图,供准备做鸿蒙 PC垂直工具移植的开发者参考。

在这里插入图片描述

一、为什么选一个光学设计软件做移植

鸿蒙 PC 生态成型之后,一个很现实的问题摆在所有开发者面前:生态里缺的不是聊天、视频、购物这类大众应用,而是工程、科研、设计类的垂直工具。写代码的有 DevEco,画图的也逐渐有了选择,但像光学设计、电路仿真、结构分析这类"专业软件",在 PC 端几乎是一片空白。而这些软件恰恰是说服专业用户把日常工作迁移到新平台的关键。

我选择的开源项目是 optiland——一个Python 编写的几何光学设计库,提供顺序光线追迹、像质分析、优化器等能力,在GitHub 上有良好的口碑和活跃维护。

这类垂直工具的缺失,伤害是隐性的:高校的物理与光电课程离不开透镜演示,企业的光学工程师依赖桌面工作站,而这一切在鸿蒙 PC 上目前几乎没有原生选项。把一个高质量的开源光学内核带上鸿蒙 PC,既是给生态补一块拼图,也是对"鸿蒙 PC 能不能承载严肃生产力软件"这个问题的一次实证回答。它代表了一类非常典型的待移植对象:

  1. 计算密集但单次计算量适中:一次完整的三视场光线束追迹,计算量在毫秒到几十毫秒量级,不需要 GPU,也不需要分布式;
  2. 交互高频:用户改一个曲率半径,就想立刻看到光路和点列图的变化,"编辑—反馈"循环是产品的生命线;
  3. 依赖链简单但语言运行时重:核心算法纯 Python,没有 C 扩展依赖,但 Python 本身就是一个重量级运行时;
  4. 界面相对简单:表格 + 2D 绘图 + 结果面板,没有复杂的动画和多媒体需求。

这类"高频交互 + 中小计算量"的科学工具,正好是检验鸿蒙 PC 原生开发体验、以及检验不同迁移路线优劣的最佳试金石。

ohos_Optiland 工作台总览:空白镜头初始状态

二、迁移路线的三级框架:T0 / T1 / T2

动手之前,先把"软件迁移能力"的分级框架说清楚。在鸿蒙 PC 应用生态中,通常按迁移深度把应用分为三级:

等级名称迁移方式典型特征
T2兼容运行级原始软件不改源码,通过兼容容器/翻译层直接运行依赖宿主兼容环境,性能损耗大,无法融入系统窗口管理、输入法、文件授权体系
T1引擎迁移级用跨平台引擎或嵌入式运行时(Electron / Qt / Python / Web)承载原代码,仅适配壳层界面是"嵌"进来的网页或外部渲染树,需携带数十至数百 MB 运行时,存在跨进程/跨语言边界
T0原生适配级业务逻辑以 ArkTS/ArkUI 做原生移植适配,直接构建于系统 UI 框架之上单进程、零桥接、原生窗口与输入体验、权限最小化、包体小、启动快

三条路线没有绝对的对错——电子书阅读器用 T1(Web 内核)可能一天就搞定,大型 3A 游戏走 T2(兼容层)也许是唯一现实解。但对本文讨论的这类交互式计算工具,路线选择会直接决定产品形态的生死。


三、T1 路线踩坑实录:一次昂贵的试错

坦白说,ohos_Optiland 的第一版走的就是 T1 路线,而且是其中最重的一种:静态链接 CPython + NAPI 桥接。当时的想法很朴素:上游是 Python 库,把 Python 带过去就能"原封不动"复用全部代码,何必自己动手实现算法?

技术方案大致是:

  • 交叉编译 CPython 3.12,整链静态链接进一个约 22 MB 的liboptiland_native.so;
  • 把 numpy / pandas / scipy 等依赖的数百个 .so 一起塞进应用包;
  • 首次启动时解压约 20–80 MB 的 Python 运行时压缩包到应用沙箱;
  • 每次分析通过 NAPI 启动子进程,执行 PyRun_SimpleString,再回捕 stdout 解析结果。

它能跑通——双高斯镜头的点列图确实画出来了。但真实使用立刻暴露出T1 级方案在这种场景下的结构性缺陷:

维度T1 嵌入式 Python 版T0 原生适配版
首次启动需解压运行时,实测约 1 分钟秒开
单次操作延迟进程级调用 5–10 秒毫秒级,感知为"即时"
包体数百 MB(运行时 + 库)数 MB
依赖CPython + 全套科学计算库 + NAPI 桥无
工程复杂度交叉编译工具链 + 桥接层 + 运行时打包单模块纯 ArkTS

最致命的是第二条。光学设计软件的灵魂是"改一个数、看一眼光路"的即时反馈,5–10 秒的延迟把这个循环彻底打断——用户改完半径,足够泡杯茶回来再看结果,这不是工具,这是刑具。而"编辑即算"恰恰是 T0 版本用 ArkTS 直接实现后的最大收益:单次重算(三视场光线束追迹 + 2D 布局重建 + Canvas 重绘)整体在毫秒量级。

这次试错让我确立了一个判断标准:当交互频率高、单次计算量小、且核心算法规模可控(几千行以内)时,原生适配的收益远大于复用大型运行时的功能广度。全量 optiland 的优化器、MTF、公差分析等能力,完全可以继续由上游 Python工具链承担——两条路线互为补充,而非替代。

这次试错还留下一个额外的资产:交叉编译产物本身。CPython 3.12 的OHOS 交叉编译脚本、numpy / scipy 的 .so 构建配置,都沉淀成了可复用的脚本资产——后来社区里其他需要 Python 运行时的移植项目直接受益。试错不白费,前提是把坑里的土也带回地面。


概念对照:Python 世界与 ArkTS 世界的映射

实际动手前,把上游代码里的语言设施逐一映射到 ArkTS 侧,能极大减少移植过程中的"卡壳":

Python 侧ArkTS 侧备注
float('inf')1e15 哨兵值 + 阈值判断JSON 序列化不支持 Infinity
NumPy 向量运算手写标量循环 + Vec3 类追迹是逐光线过程,向量化收益有限
dataclass显式成员初始化的 classArkTS 无默认参数,构造注意补默认值
f-string 格式化模板字符串 / toFixed界面数值显示统一保留三位小数
Matplotlib 绘图Canvas 2D 手绘坐标系刻度、缩放、标注全部自理
json.load 异常try/catch + 字段级校验坏文件必须显式拒绝
异常传播返回值 + 集中错误处理引擎层不用异常做控制流

这张表后来成了团队的"翻译词典",也常被用来评估新增功能在两侧的实现成本。


四、T0 原生适配的总体架构

转向 T0 后,整个工程收敛为一个非常干净的形态:

┌────────────────────────────────────────────────────────────┐
│                     entry(HAP 模块)                       │
│                                                            │
│  ┌────────────────────── ArkUI 层 ──────────────────────┐  │
│  │  pages/Index.ets      主工作台(编辑器+布局+分析+渲染)│  │
│  │  pages/About.ets      介绍页(功能导览/快速上手)      │  │
│  │  entryability/EntryAbility.ets   UIAbility 生命周期    │  │
│  └───────────────┬──────────────────────────────────────┘  │
│                  │ 方法调用(同进程,无序列化边界)          │
│  ┌───────────────▼──────────────────────────────────────┐  │
│  │              model/OpticalModel.ets                   │  │
│  │  ─ 面列表模型(Surface: 半径/厚度/Conic/材料/光阑)    │  │
│  │  ─ 玻璃库(Sellmeier + Cauchy 色散)                   │  │
│  │  ─ 三维光线-曲面求交(球面/二次圆锥面)                 │  │
│  │  ─ 向量斯涅尔折射(含 TIR 判定)                        │  │
│  │  ─ 近轴光路(EFL/后焦距)                              │  │
│  │  ─ 主光线光阑对准(割线迭代)                           │  │
│  │  ─ 点列/场曲/畸变/垂轴色差采样                          │  │
│  │  ─ JSON 序列化(toJSON / loadFromJSON)                │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                            │
│  ┌──────────────────── 系统能力 ────────────────────────┐  │
│  │  @ohos.file.fs      沙箱内镜头文件读写                 │  │
│  │  @ohos.util         UTF-8 TextDecoder                 │  │
│  │  @kit.ArkData       preferences(首启标记)            │  │
│  │  Canvas 2D          系统视图 / 点列图 / 场曲图渲染      │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘

三条关键设计决策:

1. 模型与视图彻底分离。 OpticalModel.ets(约 980 行)是无任何 UI 依赖的纯计算层,理论上可以独立单测;Index.ets(约 1000 行)只负责把模型快照映射为表格行和 Canvas 图元。视图层不碰物理公式,模型层不碰组件状态——这个边界让后来反复调整界面布局时引擎代码零改动。

2. 无任何原生库。 工程不包含 cpp 目录、没有 CMake 配置、没有 .so,这是 T0 判定的结构性证据,也一次性免除了 NAPI 兼容性、ABI 对齐、交叉编译三条最容易流血的战线。

3. 零权限。 module.json5 的 requestPermissions 为空数组,文件读写仅限应用沙箱目录。对一个桌面工具来说,"零权限"本身就是一种产品竞争力——企业用户和个人用户都无需为授权弹窗操心。


五、光学引擎的移植适配:把论文公式变成字节码

这一节是全文的技术核心。上游 Python 库的算法是正确的、成熟的,移植的工作不是"翻译代码",而是把每个数值环节在 ArkTS 下重新推敲一遍,包括浮点边界、退化情形和迭代收敛性。

5.1 曲面求交:球面与二次圆锥面

光学面形采用标准 sag 公式:

z(y) = c·ρ² / (1 + √(1 − (1+K)·c²·ρ²))     其中 c = 1/R,K 为圆锥系数

光线与曲面的求交在子午面内展开为关于传播参数 t 的二次方程。移植时最重要的是根的选取逻辑:近根还是远根,要按光线传播方向与厚度间隔的符号判定;对平面(R → ∞)要退化为线性求交,避免 1/R 产生数值噪声。工程里用一个1e15 的大数表示无穷远/平面,所有 1/R 计算先做阈值判断,这个细节在Python 版里由库隐式处理,移植时必须显式写出来。

5.2 向量形式斯涅尔折射

折射方向用三维向量公式而非标量角度近似:

d' = η·d + (η·cosθᵢ − cosθₜ)·n̂      其中 η = n₁/n₂

根号内为负时判定全内反射(TIR),光线终止并从后续统计中剔除。向量公式对斜入射、大视场边缘光线的数值稳定性远好于角度递推——这一点在 14° 半视场的双高斯示例上对比明显:角度法在边缘视场会出现个别光线"跳面",向量法从未复现。

5.3 主光线光阑对准:最容易省略、最不该省略的环节

真实光学软件都带主光线对准(Chief Ray Aiming):对离轴视场,主光线必须精确穿过光阑中心,而不是简单地从入瞳中心出发。实现上定义残差函数g(入瞳高度),用割线迭代在两次几何追迹内收敛到光阑中心,精度做到 1e-9 量级。

这里有个工程技巧:迭代过程复用与普通光线完全相同的求交/折射代码路径,不为对准单开一条计算管线——既保证一致性,也让收敛失败时的行为可预测。大视场强渐晕系统下迭代可能不收敛,此时该视场光线被安全截断而非抛错,点列图上表现为该视场点数减少——宁可少画,不可画错。

5.4 近轴光路与像面定位

用 y-nu 近轴追迹(u' = (n·u − y·φ)/n',φ = (n₂−n₁)/R)计算近轴后焦距,像面 z 坐标自动吸附到近轴焦点;有效焦距 EFL 由边缘光线追迹的出射角反解;入瞳直径 EPD 由光阑逆向成像得到。这部分是纯线性代数,移植几乎是直译,但要注意"物在无穷远"时入瞳高度取 entryBeamRadius 而非按物高缩放。

5.5 光瞳采样:hexapolar 网格的选择

像质分析的第一步是把光瞳离散化。工程采用 hexapolar(六角极坐标)网格:中心 1 点 + 若干同心环,每环按 60° 相位交错布点,环数可调。相比均匀矩形网格,hexapolar 在相同点数下对圆形光瞳的覆盖更均匀,RMS 半径的收敛也更平滑;相比高斯求积法,实现简单、点数直观——点列图上每个点都对应一条真实追迹的光线,教学演示价值也更高。

采样点不是白算的:点扩散模式下每个视场要追迹整个六环点阵,色差模式下还要乘以三色,再乘以三视场。一次"全量分析"是数百条光线 × 十几面求交折射,好在每条光线相互独立、单面计算都是几次乘加,整体压在几十毫秒内——这正是"中小计算量"场景的典型画像,也是 T0 路线敢于全量重算的底气。

5.6 场曲、畸变与色散

  • 切向焦点:光瞳 y 方向 ±ε 两条近邻光线在像空间求交;
  • 弧矢焦点:x 方向偏移光线回交子午面(x = 0);
  • 畸变:主光线像高与理想像高(近轴焦距 × tan 视场角)之差的百分比;
  • 色散:N-BK7、N-SF11、N-LAK21、N-SK16、N-SF57 五种玻璃用 Sellmeier三项全系数;N-SSK2、N-SK2、F5 用 n_d / 阿贝数 V 经 Cauchy 拟合(n(λ) = A + B/λ²),保证 d 光精确、F/C 色散与阿贝数定义吻合。

色散计算在每次折射处按当前波长重新取折射率,F(486.13 nm)/ d(587.56 nm)/ C(656.27 nm)三色独立追迹,天然支持多色分析。


六、ArkUI 界面适配:把专业软件的"效率感"搬过来

光学设计软件的界面哲学和消费级应用完全不同:信息密度优先,操作路径最短。ArkUI 声明式范式在这个场景下意外地合适——@State 驱动的"编辑即重绘"正是我们要的交互模型。

加载双高斯示例后的完整工作台

上图是加载演示双高斯镜头(f/5、半视场 14°、11 个折射面、EFL 101.57 mm、EPD 20 mm)后的完整工作台。布局采用左右分区:

  • 左上:镜头数据编辑器。类 CAD 表格,物面 → 标准折射面 → 光阑 → 像面逐行列出曲率半径、厚度、材料、圆锥系数、半口径。单元格点击进入编辑态、失焦或回车提交,inf 表示平面或无穷远。材料列是下拉选择,内置 8 种Schott 常用玻璃加空气。
  • 左下:系统视图 2D。Canvas 实时绘制透镜轴向截面,球面与二次圆锥面按sag 公式逐点采样,每个视场画 5 条光瞳光线(边缘 ±1、±0.5 与主光线),渐晕与 TIR 处正确截断。
  • 右上:像质分析。Tabs 切换「点扩散」「色差」「场曲畸变」三种模式。
  • 右下:信息控制台。每一次操作——加载示例、增加面、追迹完成的光线数——都实时打印,这让"软件正在做什么"完全透明,是专业工具建立信任感的关键细节。

6.1 介绍页:把"这是什么、怎么用"讲清楚

介绍页:功能亮点

首次启动自动展示介绍页(用 preferences 记忆标记,只弹一次),把六个功能亮点——镜头数据编辑、系统视图 2D、像质分析、玻璃库与色散、工程保存、原生性能——逐条讲清,并给出四步快速上手指引。

介绍页:快速上手

这个页面看似简单,实际解决的是专业工具最常见的冷启动问题:新用户打开软件看到一张空表格,第一反应是"然后呢?"。四步指引(增加面 → 选玻璃 → 切分析模式 → 保存)把激活路径压缩到一分钟内。

6.2 场曲畸变分析:Canvas 上的坐标系统课

场曲畸变分析页

场曲畸变页是三个分析模式里绘图最复杂的:左侧切向场曲曲线、右侧弧矢场曲折线,横轴畸变百分比纵轴像高。Canvas 没有现成的坐标系组件,坐标变换、刻度标注、多视场曲线的自动缩放都要手写。一个值得记录的坑:Canvas 的文本渲染基线和 HTML Canvas 不一致,fillText 的 y 参数在 ArkUI 下是文本顶部而非基线,刻度标注整体偏移了半个字高,靠逐像素对比截图才发现。

6.4 状态管理与渲染分层

工作台一屏内有四块联动区域:表格、2D 光路、分析图、控制台。如果让每个区域各自监听模型变化、各自重绘,一次半径编辑会引发四路无序刷新,闪烁和竞态都来了。最终采用的模型是单向数据流:所有编辑只改 OpticalModel,然后调用一个统一的 afterModelEdit()——它按固定顺序完成"重算光路 →重建布局数据 → 重建表格行数据 → 请求 Canvas 重绘",每块区域只消费自己需要的快照。

另一个细节是渲染分层。2D 视图里透镜截面(面形曲线)变化频率低,光线束变化频率高,把两者画在同一块 Canvas 上意味着每编辑一次就重画所有面形曲线。实测把面形缓存为路径对象、光线束单独重绘后,编辑帧耗时有明显下降。这类优化在 Python + Matplotlib 的原型里根本不存在——原型只求画得出来,原生版要画得流畅,这正是两条路线在工程深度上的差别。

6.5 桌面级交互细节

面向 2in1 大屏,有一些纯平板应用不会在意的细节:

  • 鼠标悬停表格行高亮,点击定位编辑单元格;
  • 所有按钮固定 28–32 px 高度,适配键鼠的点击精度而非手指热区;
  • 深色工作台配色,长时间编辑不刺眼;
  • 窗口可任意缩放,布局用 Row/Column + layoutWeight 自适应,无固定像素坐标。

七、数据持久化:从"沙箱文件"到"用户说了算"

镜头工程持久化为 UTF-8 JSON。格式刻意保持扁平:

{
  "entryBeamRadius": 10,
  "fieldAngles": [0, 10, 14],
  "surfaces": [
    { "radius": 1e15, "thickness": 1e15, "glassName": "air", "conic": 0, "isStop": false, "isImage": false },
    { "radius": 56.20238, "thickness": 8.75, "glassName": "N-SSK2", "conic": 0, "isStop": false, "isImage": false },
    { "radius": 1e15, "thickness": 0, "glassName": "air", "conic": 0, "isStop": false, "isImage": true }
  ]
}

约定:radius / thickness 为 1e15 时按无穷大解析(平面 / 无穷远物);isStop 全表至多一个;末面必须 isImage: true;加载时对缺省字段给安全默认值,surfaces 缺失或不足两个面时拒绝加载并提示"格式无效"—— 坏文件不能导致崩溃,这是工具软件的底线。

持久化方案经历过一次迭代。最初版本保存到应用沙箱固定路径(filesDir/lens.json),实现最简单,但真机验收时暴露了体验问题:用户在文件管理器里找不到保存的文件,"我保存了,文件去哪了"是最常见的困惑。最终改为 DocumentViewPicker系统文档选择器:点「保存」弹出系统保存对话框,用户自选目录和文件名,默认 lens.json;点「加载」弹出选择器,按 .json 后缀过滤。用户授权制、依然零权限,文件的"所有权"归还给用户。


八、构建与调试流水线

工程构建有两条路径,日常开发建议都配好:

DevEco Studio(IDE 路径):打开工程等 Sync 完成 → 选 entry 模块、产品 default → Build > Build Hap(s)。适合断点调试和界面预览。

命令行(hvigorw 路径),适合做增量验证和自动化:

$env:DEVECO_SDK_HOME = "C:\Program Files\Huawei\DevEco Studio\sdk"
$env:PATH = "C:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin;" +
            "C:\Program Files\Huawei\DevEco Studio\jbr\bin;" + $env:PATH

hvigorw.bat --mode module -p module=entry@default -p product=default assembleHap --no-daemon

两个实战要点:其一,打包环节依赖 Java,务必把 DevEco 自带 JBR 的 bin前置到 PATH,否则 SignHap 阶段会静默失败;其二,写了个小 PowerShell脚本把"构建 → 过滤 BUILD SUCCESSFUL / ArkTS Compiler Error → hdc 安装 →拉起应用"串成一条命令,UI 改动的验证循环压缩到一分钟以内。

环境版本:compatibleSdkVersion 6.0.1(21)、targetSdkVersion 6.0.2(22)、runtimeOS HarmonyOS、deviceTypes 2in1 + tablet。构建日志应出现BUILD SUCCESSFUL,且不应出现任何 CMake/Ninja/交叉编译步骤——出现即说明工程混入了原生依赖,需要警惕。


九、踩坑清单:写给后来者

把这些血泪按主题归档,希望后来者少走弯路:

Canvas 渲染差异。 fillText 基线语义、虚线样式参数、清屏行为的差异前面提了。另一个坑是 Canvas 尺寸用像素而非 vp,高分屏上要用vp2px 换算,否则 2x 屏上图形会小一半。

编辑态提交时机。 表格单元格的 TextInput 若在每次击键时同步写回模型,一次编辑会触发几十次全量重算;正确做法是编辑态仅暂存,失焦/回车才提交。配合 @State 的最小化更新,整条编辑链路才能稳在毫秒级。

无穷大的表示。 Python 里 float('inf') 随手可用,JSON 序列化后却是Infinity 非法字面量。统一用 1e15 哨兵值 + 读写两端解析,比试图让 JSON 支持 inf 可靠得多。

退化保护要 everywhere。 平面求交退化、近平行光线场曲求交退化、零厚度面、光阑不在系统内……每种退化都要有明确的回退行为而不是异常。光学计算的特点是"用户什么都能输进来"。

主光线对准的收敛容错。 1e-9 精度目标下,割线迭代要设最大轮数,失败时降级为"从入瞳中心出发"的近似对准并截断渐晕光线,绝不能让迭代死循环挂住 UI 线程。

hvigor 版本对齐。 从旧模板迁移工程时,hvigor/hvigor-config.json5里的插件版本若与 IDE 内置 hvigor 不一致,Sync 阶段会触发在线依赖下载,内网或弱网环境下直接卡死。做法是把版本号改成与 DevEco 内置一致的版本,离线也能构建。

中文路径与签名文件。 早期签名 profile 用了中文文件名,命令行构建在部分工具链环节会因编码问题找不到文件——签名材料统一用纯 ASCII 命名,路径避免空格和中文,能省掉一类"玄学"构建失败。

picker 返回的是 URI 不是路径。 DocumentViewPicker 保存/选择返回的是 file:// 协议的 URI,不能直接当字符串路径传给文件 API;读写要走 fd,文件名要单独从 URI 里解析出来用于界面显示。第一版想当然地当路径用,真机上稳定报错,查了才知道是协议前缀的问题。


十、质量保障:光学软件怎么测试

计算类工具的测试和普通 App 不同——界面崩溃一眼能看到,算错了却看不出来:一张点列图"看起来差不多"和"物理正确"之间可能隔着几个符号错误。工程里建立了三层防线:

第一层:手算基准。 每个新光学能力入库前,附带一个可以手算验证的最小案例。比如单折射球面 f = 100 mm 的近轴校验、单个平面折射的Snell 定量核对、单个等厚双凸透镜的 EFL 与查表值对比。公式对不对,小学数学说了算,不靠"看起来对"。

第二层:物理守恒用例。 光线追迹有一批天然的守恒量可以当断言:追迹前后光线束的通量截面(无渐晕时应守恒)、光路可逆性(正向追迹与反向追迹结果互逆)、平行光入射时像面交点应汇聚于焦点。这些用例不依赖具体镜头数据,是引擎级的不变量测试。

第三层:防御性输入用例。 空表格、只有物面和像面、半径为 0、玻璃名不在库中、JSON 缺字段、文件截断、isStop 出现两次——每一种坏输入都要有明确的拒绝或回退行为。工具软件的口碑往往不是被正确性建立的,而是被一次崩溃毁掉的。

三层防线都不需要复杂的测试框架,ArkTS 里一个断言函数数组就能跑。关键是把它当作入库门槛而不是"有空补上"——计算软件的每个公式都值得被怀疑。


十一、结果验证与心得

最终交付形态:OpticalModel.ets 约 980 行引擎 + Index.ets 约 1000 行工作台 + 介绍页与入口,合计约 2000 行 ArkTS 承载完整工作流;HAP 包体数 MB;冷启动秒开;编辑响应毫秒级;requestPermissions 为空。

对照一开始的 T1 版本,结论一目了然:

指标T1 嵌入式 PythonT0 原生适配
首次启动约 60 s秒开
编辑反馈5–10 s< 1 ms
包体数百 MB数 MB
权限需文件等权限零

回头看,这次移植最大的收获不是"一个能用的光学设计软件",而是验证了一条对鸿蒙 PC 生态有普适性的方法论:

  1. 先跑通 T1 验证价值,再果断转向 T0 打磨体验——试错要快,止损要狠;
  2. 算法移植不是翻译,是把每个数值环节在新的运行时下重新推敲——哨兵值、退化保护、收敛容错,这些"非功能性"代码往往占一半工作量;
  3. 专业工具的竞争力 = 正确的内核 + 毫秒级的反馈 + 透明的状态呈现——信息控制台和"编辑即算"这两件事,比多一个分析功能更重要;
  4. 零权限是特性,不是缺失——工具类应用把"数据不出设备"当作卖点,用户和企业都会买账。

最后想谈谈文档与开源共建。这次移植把双语文档(功能说明、构建指南、镜头文件格式规范)当作交付物的一部分而非事后补充,回过头看是极正确的决定:文章发布后收到的第一波 Issue 里,超过一半的问题其实文档里已有答案;而仓库里"完整可构建"的承诺,让第一位社区贡献者在 clone 后二十分钟内就跑起了第一个 HAP。对垂直工具来说,可构建性本身就是社区的入场券——没有人愿意在一个编译不过的工程上贡献代码。

给准备起步的同学一个最小行动清单:选一个依赖简单、算法规模可控的开源库;先用 T1 验证"这东西在鸿蒙上有价值";再用 T0 打磨体验;把每一条弯路写进文档——你的踩坑记录,就是下一位移植者的加速器。

十二、常见问题(Q&A)

Q1:为什么不直接用上游的 Python 版 optiland,或者走 T1 嵌入式运行时路线? 上游是纯 Python 库,依赖一整套科学计算栈(numpy / scipy / pandas),要带进鸿蒙 PC 就得走"嵌入式运行时"——携带数十至数百 MB 的运行时、靠跨语言桥接调用,首启解压约 1 分钟、每次编辑反馈 5–10 秒。对"改一个参数立刻看光路"的交互式工具,这个延迟是致命的。我们先用 T1 验证了"值得做",再转向 T0 做原生移植适配,把计算和界面都落在 ArkTS 上,单次重算压到毫秒级。

Q2:以 ArkTS 落地的光学引擎,数值精度够吗? 几何光线追迹本身是确定性的浮点运算,没有随机性,也不依赖 Python 特有的数值库——Snell 折射、曲面求交、近轴追迹都是标准公式。我们用三道防线验证(手算基准、物理守恒用例、防御性输入用例),并与上游在多个镜头上做了逐光线对比,结果一致。需要强调:迁移是"适配"而非"另起炉灶",公式与上游同源。

Q3:支持哪些像质分析? 当前版本支持系统视图 2D 光路、点扩散(三视场 × 光瞳采样)、色差(F / d / C 三色)、场曲(切向 / 弧矢)与畸变。分析是按需实时计算的,编辑任一面形即时刷新,无需手动"运行"。

Q4:镜头文件怎么保存和分享? 点「保存」会弹出系统的 DocumentViewPicker,可在任意目录另存为 lens.json(UTF-8);点「加载」同样通过选择器读取任意位置的 JSON 文件,按 .json 后缀过滤。文件格式是开放的扁平结构,可直接用文本编辑器查看修改,也方便团队间分享。

Q5:包体只有几 MB、冷启动秒开,是怎么做到的? 核心在于"零原生依赖"——工程不携带任何解释器运行时、没有 .so、没有 NAPI 桥接,所有逻辑都是方舟运行时上的纯 ArkTS。去掉了运行时,包体与启动时间自然就降下来了,这也是 T0 原生适配相对 T1 的结构性优势。

Q6:声明零权限,应用怎么读写用户文件? 应用默认只在自身沙箱目录读写;当用户主动通过「保存 / 加载」触发系统的 DocumentViewPicker 时,由用户在系统文件选择器里授权具体文件,应用通过该次授权拿到文件描述符完成读写。整个过程不需要在 module.json5 里申请持久权限,符合"按需、最小授权"原则。

Q7:和上游 optiland 是什么关系? ohos_Optiland 是 optiland 在 HarmonyOS PC 上的原生移植适配版本,复用其光学模型与算法思路,并以 ArkTS 在本平台上落地。全量 optiland 的优化器、MTF、公差分析等能力仍建议由上游 Python 工具链承担,两条路线互为补充,而非替代。

Q8:支持哪些设备形态? 构建目标包含 2in1 与 tablet,面向鸿蒙 PC 与平板大屏;UI 采用左右分区的桌面级布局,适配键鼠与较大的显示面积。

Q9:想参与共建,从哪入手? 仓库承诺"完整可构建",克隆后用 DevEco Studio 打开即可一键构建 HAP,文档里给出了镜头文件格式与构建指南。最缺的贡献是光学专业层面的校验——如果你发现某个公式或某个镜头的追迹结果存疑,Issue 和 PR 都欢迎。

路线图

项目仍在演进,接下来的计划:偶次非球面(高次项)面形与求交、用户自定义 Sellmeier/Cauchy 玻璃条目、MTF(基于点扩散傅里叶分析)、光瞳加权采样、撤销/重做与多镜头标签页、3D 系统视图。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐