第四章:ZPL流程控制——让脚本学会“思考”

流程控制,说白了就是让脚本能自己做决定。

没有流程控制的脚本,就像一条笔直的单行道——从头走到尾,不会拐弯,也不会回头。但实际做光学设计时,哪有这么简单?你想想看,一个镜片参数超差了,你得停下来判断;一组数据没收敛,你得循环迭代。这些场景,都离不开流程控制。

4.1 IF-ELSE条件判断:让脚本学会“看情况”

IF-ELSE是ZPL里最基础的条件判断。我个人习惯把它叫做“岔路口”——满足条件走一条路,不满足就走另一条。

语法其实很简单:

IF (条件)
    执行语句1
ELSE
    执行语句2
ENDIF

注意这里的条件要加括号,这是ZPL的硬性要求。我刚开始写的时候老忘,结果脚本跑起来直接报错。

举个实际例子。判断一个镜片的曲率半径是否合理:

curv = RADI(1)
IF (curv > 50)
    PRINT "曲率半径过大,建议优化"
ELSE
    PRINT "曲率半径在合理范围内"
ENDIF

嗯,这里要注意:ZPL里没有“else if”这种写法。如果你需要多分支判断,得用嵌套IF。我曾经在项目里写过四层嵌套,调试的时候差点把自己绕晕。后来我学乖了——超过两层嵌套,就考虑用GOTO或者重新设计逻辑。

避坑指南:

我曾经犯过一个低级错误——在IF条件里用了“=”而不是“==”。ZPL里“=”是赋值,“==”才是判断相等。这个坑我踩了不止一次,现在每次写条件判断都会下意识检查一下。

4.2 FOR循环:重复劳动交给脚本

做光学设计,经常要批量处理数据。比如遍历所有镜面、扫描整个视场。这时候FOR循环就派上用场了。

基本语法:

FOR i, 1, 10, 1
    执行语句
NEXT

参数说明:i是循环变量,从1开始,到10结束,步长为1。步长可以改成2、0.5,甚至负数——不过负步长我很少用,容易搞混。

来个实战例子。批量打印每个镜面的曲率半径:

n = NSUR()
FOR i, 1, n, 1
    curv = RADI(i)
    PRINT "面", i, "的曲率半径:", curv
NEXT

你看,几行代码就把所有镜面的数据抓出来了。要是手动操作,一个面一个面地查,得花多少时间?

我的小技巧:

FOR循环里尽量别做太复杂的计算。我遇到过有人把整个优化循环塞进FOR里,结果脚本跑了半小时还没结束。FOR循环适合做“轻量级”的重复工作,重活还是交给Zemax的优化器吧。

4.3 WHILE循环:不知道循环多少次?用它

WHILE循环和FOR循环的区别在哪?

FOR循环适合“我知道要循环多少次”的场景。WHILE循环适合“我不知道要循环多少次,但我知道什么时候该停”的场景。

语法:

WHILE (条件)
    执行语句
END

举个例子。不断调整镜片位置,直到像差小于某个阈值:

aberr = 100
WHILE (aberr > 0.01)
    THICK 3, 0.5
    UPDATE
    aberr = OPEV(1, 0, 0, 0, 0, 0, 0)
END

这里有个致命问题——如果永远达不到条件,就成了死循环。我刚开始用WHILE时,有一次忘了在循环里更新条件,结果脚本卡死了,Zemax直接无响应。从那以后,我每次写WHILE都会加一个“最大循环次数”的保护:

count = 0
WHILE (aberr > 0.01 & count < 1000)
    ...
    count = count + 1
END

重要提醒:

WHILE循环一定要确保条件最终会变成假。否则你的Zemax会“假死”,只能强制关闭。我建议养成习惯——每次写WHILE都加一个计数器保护。

4.4 BREAK与CONTINUE:循环里的“急刹车”和“跳过去”

这两个关键字,说白了就是控制循环流程的“开关”。

BREAK:直接跳出整个循环。比如你正在遍历镜面,突然发现某个镜面有问题,想立刻停止:

FOR i, 1, n, 1
    curv = RADI(i)
    IF (curv < 0)
        PRINT "发现负曲率,停止检查"
        BREAK
    ENDIF
NEXT

CONTINUE:跳过当前这次循环,继续下一次。比如你只想处理非球面:

FOR i, 1, n, 1
    IF (ASPH(i) == 0)
        CONTINUE
    ENDIF
    PRINT "面", i, "是非球面"
NEXT

我个人更喜欢用CONTINUE来“过滤”数据,这样代码看起来更干净。不过要注意——CONTINUE在嵌套循环里只作用于最内层的循环。

4.5 GOTO语句:最后的“杀手锏”

GOTO,争议很大。很多编程教材说“不要用GOTO”,但我觉得——工具没有好坏,关键看你怎么用。

语法:

GOTO 标签名
...
标签名: 执行语句

我什么时候会用GOTO?

  • 多层嵌套循环里想直接跳出——用BREAK只能跳一层,GOTO可以一步到位
  • 错误处理——发现异常直接跳到脚本末尾的清理代码
  • 实现“重试”逻辑——比如优化失败后跳回重新设置参数

举个例子:

FOR i, 1, 10, 1
    FOR j, 1, 10, 1
        IF (条件)
            PRINT "找到目标,退出"
            GOTO END_LOOP
        ENDIF
    NEXT
NEXT
END_LOOP: PRINT "循环结束"

我的建议:

GOTO可以用,但别滥用。我给自己定了个规矩——一个脚本里GOTO不超过3个。超过3个,说明代码逻辑有问题,需要重构。记住,GOTO是“杀手锏”,不是“日常工具”。

4.6 综合实战:一个自动优化脚本

最后,我把今天讲的内容串起来,写一个完整的脚本。这个脚本的功能是:自动调整镜片厚度,直到像差满足要求,最多尝试100次。

n = NSUR()
count = 0
aberr = 1.0

WHILE (aberr > 0.01 & count < 100)
    count = count + 1
    
    FOR i, 2, n-1, 1
        thick = THIC(i)
        IF (thick < 1.0)
            CONTINUE  ' 跳过太薄的镜片
        ENDIF
        THICK i, thick * 0.95
    NEXT
    
    UPDATE
    aberr = OPEV(1, 0, 0, 0, 0, 0, 0)
    
    IF (count > 90)
        PRINT "接近最大尝试次数,当前像差:", aberr
    ENDIF
END

IF (aberr <= 0.01)
    PRINT "优化成功!"
ELSE
    PRINT "优化失败,请检查初始结构"
    GOTO CLEANUP
ENDIF

CLEANUP: PRINT "脚本执行完毕"

这个脚本里,WHILE控制主循环,FOR遍历镜面,IF-ELSE做判断,CONTINUE跳过特殊情况,GOTO做最后的错误处理。你看,所有流程控制都用上了,而且逻辑清晰。

好了,流程控制这部分就讲到这里。下一章我们聊聊ZPL的字符串操作——别小看它,处理文件路径、生成报告都离不开字符串。到时候见。