本文摘要(由AI生成):
函数是打包好的一系列程序语句的集 合,可以降低二次开发的难度。好的函数应该尽可能短,并且能够实现代码重用。函数可以接受数组名和字典名作为参数,这种用法对于参数较多的情况特别方便。此外,函数也可以接受数组名和字典名作为参数,
什么是函数?
二次开发有意思的主要的地方就是可以编写自定义函数,而调用别人已经编写调试好的程序包可以大大降低二次开发的难度。
简单地说函数就是打包好了的作为一个整体执行的一系列程序语句的集 合,比方说下面的语句是用来删除模型中所有可见的几何体:
只需要改成如下形式就变成了一个函数:
有实际使用价值的函数未必要很长,而且实际上是恰恰相反,好的函数应该尽可能地短。在 Utility 界面下,opti tab 页的 Del All Opt Entities 功能是被用户使用的频率最高的二次开发功能之一。但是它的代码大概也就10条命令,思路和上面写的几乎一模一样:
在本文的后半部分我们会介绍如何安装 tcllib 这个包,里面会有很多源代码。你会发现很多函数都只有短短几行,函数之间互相调用。函数可以把复杂的程序按照逻辑关系分解为不同的步骤,让程序容易编写也容易理解。
上面的例子没有参数,也有很多函数是带参数的。例如下面这个例子是为了在脚本运行的时候暂时关闭图像界面交互,也是被人使用频率最高的功能之一。该函数通过临时关闭不必要的图形交互功能显著加速脚本的执行速度。该脚本只带一个参数,on 或者 off。
代码重用
有了函数后每次遇到同样的问题时就可以直接调用已经调试好的函数而不必每次都重复实现,不仅降低了编程的难度,也显著减少了出错的可能性。这也是函数存在最主要的价值——代码重用。我们总是尽可能避免在不同的地方重复实现相同的功能,比如接下来介绍的 getdist 函数以及 角度/弧度转换函数,虽然只有一句也非常值得写成一个函数。
好的函数定义应当是功能单一,逻辑结构清晰,执行结果不依赖外部条件。比如在很多程序里都会存在求两个点之间的距离的需求,我们可以写成一个函数反复调用:
这里的函数参数列表为6个坐标分量,如果输入是两个节点 id 呢?我们先使用节点 id 号获取6个坐标,然后还是调用上面定义好的函数来实现其余功能而不是重新实现一遍 getdist 已经实现的功能。
函数接受列表作为参数
函数也接受列表作为参数,比如下面这个例子中把一系列的单元 id 号进行分解,把相连的单元作为一组输出。请注意这里的输出 output_list 也是一个列表类型。通常如果需要返回多个值,建议将输出作为一个列表返回。
运行程序前的模型如下:
运行函数后再输入:
*createmark elems 1 "all"
set e_list [hm_getmark elems 1]
set sep_ids [seperate_elems $e_list]
得到输出:
{301 302 303 304 305 306 307 308 309 310 311 312}
{101 102 103 104 105 106 107 108 109 110 111 112}
{401 402 403 404 405 406 407 408 409 410 411 412}
{201 202 203 204 205 206 207 208 209 210 211 212}
这样就实现了把复杂问题分而治之的目的。接下来对每个孔分别进行处理就容易多了,比如我们来创建一些 RBE2,这里需要先从单元 id 号找到对应的节点 id 号,然后才能创建。
函数接受数组名和字典名作为参数
此外,函数也可以接受数组名和字典名作为参数,这种用法对于参数较多的情况特别方便,因为只需要一个数组或者字典的名称就可以引用所有数组或字典里的键了。下面这个例子里通过传入一个字典名来保存函数中创建的所有内容。
通常情况函数都会使用值传递,因为使用值传递时 tcl 解释器总是先把参数值在另外的内存中备份一份供函数内部使用而不是直接操纵参数的原始值,所以,参数的原始值不会有被修改的危险(因此,函数无论是第几次调用结果都不会有差别)。而 upvar 表示这里使用的是引用传递而不是值传递,函数会直接指向原始的字典内存,如果还有别的函数也同时使用该字典,那么程序的运行结果会变得无法预测。
程序输出如下:
=> force_mag:123.500
=> theta_lst:45 90 135 180 225 270 315 360
=> spc_id:4
=> sys_id:1
=> base_x:1000
=> base_y:0
=> base_z:0
这里把与用户交互的部分放在了一个函数里面,后续的脚本只需要使用 dict get 命名就可以很方便地获取字典中的相应内容。也就是说数据的输入和数据的处理实现了完全解耦,处理数据输入的函数只需要了解要收集哪些数据以及存放在字典的什么键下而不需要关心这些数据的用途;而使用这些数据的函数也只需要知道各个值保存在字典的哪个键下而不必再费心去关心数据获取的过程。这就是函数化编程的思路,也就是让程序的各个模块尽可能地解耦。
参数不确定如何定义函数
很多情况下我们在定义函数的时候无法确定参数的个数,或者只能确定一部分参数,剩余部分只有在调用函数的时候才能确定,这时我们可以按照下面的格式定义函数的参数列表:
接下来我们可以使用任意个参数调用 max
max 1 2
max 3 4 5
max 4 5 7 9
或者还可以使用如下方式来定义 max 函数:
这时候函数体里面就免去了 lindex 进行输出,同样也很优雅。
函数参数还可以有默认值,请参考下例(这个例子也是很常用的):
程序说明:
最后的两行注释是测试语句,在这种简单的函数定义的时候我们可以直接把测试语句也用注释的方式写到里面去,如果函数众多或者很复杂,可以使用 tcltest 模块进行单元测试。
应用实例:求解器工况创建
最后我们来讲一个关于求解器工况创建的函数作为例子。先给出脚本:
这里的难点是 command.tcl 文件记录了太多的命令,很难从里面挑出需要的命令,大家可以按照以下方法进行甄别。
①、 打开一个已经施加好 spc 和 force 两个 load collector 的模型(OptiStruct 模板)
②、 选择 edit/command file 打开命令记录窗口,并清空 command.tcl 文件后保存。
③、 在底部命令行执行 hm_writeviewcommand 0 以免记录一些无用的视图操作命令
④、 在 model browser 手工操作一次,命令记录有83条之多(你的可能稍有不同),而且这些命令是没有文档可以查询的。
⑤、 把记录的命令复 制到文本文件 ls1.txt 中保存。
⑥、 重复1-5,但是这次要选择不同的 spc 和载荷 load collector
⑦、 把记录的命令复 制到文本文件 ls2.txt 中保存。
⑧、 在 notepad++ 打开两个文件,把 ls2.txt 文件中的所有 id=5(你的可能是其它数字)改成 ls1.txt 中的值。
⑨、 用开源的文本比较软件 winmerge 对两个文本文件进行比较。忽略 *endnotehistorystate 之类的历史记录命令,你很容易就能得到真正有用的那几句命令了(我们针对默认 loadstep 设置只更改了分析类型、SPC、Load 三项,命令记录对应 ID 号为 4709、4145、4147)。分析类型对应的 id 号因为两个文件中是一样的,你需要改成不同的分析类型才能进行有效区分。
为确保万无一失,把这些命令复 制到命令窗口运行一下(因为 id 号每次会变化,可以按照上图脚本中的方法临时获取)。这里再次建议大家亲自动手上机实践一下,光看文字可能不会有很深的体会,只有碰几次壁才能真正懂得这里面的精髓。
如何使用函数库?
一个人的知识、能力和时间都是有限的,在二次开发的过程中实际上大部分功能都是通过调用别人已经写好的函数或者你自己之前写好的函数实现的。如果只是调用一些简单的函数我们可以直接使用 souce 命令。souce 命令的效果和把被 source 的文件内容直接复 制到相应位置的效果是完全一致的。对于复杂的包,通常会以 package 的形式存在,而且会自带名字空间。需要使用 package require packagename 的方式来调用包。接下来我们以tcl中非常常用的 tcllib 包的安装使用为例告诉大家如何下载/安装/使用外部包。
1、下载安装 tcllib 到本地目录
2、按照 readme.txt 里面的步骤进行安装
例如我的电脑上安装在 C:\Tcl\lib
安装完成后的目录名称为 tcllib1.18
3、使用时需要在调用 tcllib 的脚本中加上
set auto_path [linsert $auto_path 0 {C:\Tcl\lib}]
或者
lappend auto_path {C:\Tcl\lib}
注意:路径永远只使用绝对路径!!!
然后就可以使用 tcllib 了,例如:
package require math::linearalgebra
如果没有出错信息,那么恭喜你 tcllib 已经安装成功啦~
也可以直接在HyperMesh的命令行简单测试一下:
目前 tcllib 包里面包含了400多个函数,包括线性代数、微积分、csv、网络工具、集 合数据类型等等。相信能为你的编程提供很大的帮助,而且这些都是带着 tcl 源代码和帮助信息来的(在 activetcl 的帮助中查找),对你学习 tcl 是宝贵的资料。唯一一点不好的就是这个包不是 tcl 默认安装的,如果你的程序使用了 tcllib,你的脚本拷贝到另外一台电脑后可能会无法执行。
在二次开发部分的最后一段,给大家推荐一下 tcl 单元测试的包 tcltest,因为很多人认识 tcl 语言的原因并不是 HyperMesh 的关系,而是作为一个自动化测试语言。