第四章: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的字符串操作——别小看它,处理文件路径、生成报告都离不开字符串。到时候见。