最近,一位同样做CAE公 众号的博主“仿真小王子”,找到了实现常驻窗口的方法,文中提了一下之前问过我怎么实现的:那既然有这样的办法,为什么之前没有直接告诉他?这个问题,让我又想起了几年前开始的那段折腾。
今天回头看,实现常驻效果,也许只需要知道一个接口。但当时摆在面前的,是一个很具体的需求,几条不知道能不能走通的路,以及一次次“好像有希望了,实际用起来又不太对”的尝试。
借着这个机会,我想把这件事从头讲一遍。目前的进展也说一下:窗口可以浮动,也可以嵌入底部、右侧。并用不依赖 BS 库,不依赖第三方库,不通过vscode远程调用的方法,就通过纯自带API实现方式。
不过,在讲新的结果之前,先说说为什么当初会如此在意“窗口能不能留在那里”。
五年前,我在还在安子的时候。当时涉及 ANSA 二次开发,其中一个需求与 Casting 抽取有关。车身上除了钣金就是塑料件了,塑料件网格抽出来是烂的算都算不了,钣金抽出来batchmesh虽然有质量不合格的,至少计算是没有问题,为了快速验证方案、所以就提出了针对塑料件开发的需求。
那时候是21版本的Casting 抽取的效果并不总是理想,就算是把最小厚度和单元尺寸都放到0.5和3,依然抽出来不行,有的特征还更烂。遇到处理得不好的地方,需要结合 ANSA 右侧 Panel 面板里的功能继续修整,比如加特征线、cut、缝合,再回到自己开发的工具里往下做。
所以,我想要的工作方式其实很朴素:通过插件来做重复的工作,需要软件原有功能时,就去点右侧的按钮;做完局部处理,再回来继续使用工具。
窗口一直在,操作可以来回切换,工作过程不用被打断。
但在当时的调用方式下,一旦转去点击右侧的功能,自己的窗口就不能按预期保持在那里。要继续用,又得重新调用。
放在HyperMesh开发场景里,这种交互体验很难接受。工具本来是为了让工作顺畅,结果每切换一次操作,都要重新找入口,变量全部清零,或者开发的时候储存一下就很,那时候还是手搓代码,写起来就很麻烦,用起来也很麻烦。
更现实的问题是,我并不想把 ANSA 已经具备的功能再开发一遍,ANSA的开发有意思的在于界面上你能用的功能他是没有释放API出来的,比如ansa的沿着edge进行split,这个界面上有的,但是是没有对应API的,这个解决起来也是兜兜转转很麻烦,这就是后话了。
当时能找到、能调用的 API,并不能完整覆盖我们需要的软件操作效果。有些地方需要自己补逻辑判断,有些要重新组织处理流程。继续把原生功能往自己的窗口里搬,就意味着大量重复工作。
我希望自定义工具负责它擅长的部分,软件原有功能也能随时参与进来。要把两边接起来,窗口常驻就成了绕不开的一步。
最开始,我们开发小队和供应商一起调研过这个问题,也咨询过代理商。当时我们在国内对接技术支持,主要还是通过代理商,那时国内还没有原厂,对方给出的建议,包括 User Script Button 和 Plugin 这样的方式。
这些方式可以解决脚本或插件的调用入口,但实际用起来,距离我想要的交互仍然有差距。调用不够顺手,也没有直接满足“工具留在旁边,同时继续用原生面板”的需求。
于是,就在国外的看看。那时看到过一家印度公司的插件,能在右侧保持显示。这个效果很关键,因为它至少说明:类似的交互确实有人实现过。
但看得到效果,不代表能找到实现路径。我们不知道它用的接口从哪里来,也没有拿到能直接复现的办法。
这条线索最后没有继续走通,问题也就暂时搁在那里,后面不了了之了。
做二次开发时,这种状态其实挺难受的:需求没有复杂到让人觉得不合理,别人也做出了相近效果,可自己就是找不到那个入口。继续查,未必有结果;放下,又总觉得应该还有一条路,后面这个项目就不了了之了,感觉始终是一个开发的问题,没解决心里痒痒的。
后来,新版本的软件发布了,习惯性的去看看帮助文档,我在帮助文档里翻到过一段与提交计算有关的长代码,复 制到ANSA里面运行了看了看,我去,居然可以常驻窗口。

沿着那个示例去看,继承某个类之后,可以继续编辑、组织控件。它给了我新的方向:也许常驻窗口的实现方式,就藏在这一类使用模式里。
于是又开始照着改,尝试把需要的控件放进去,观察窗口能不能按预期工作。
但实际测试并不顺利。刷新不正常,交互状态不对,狂点几下还会导致软件崩溃。这就是当时写的一篇文章。
继承的就是,an alysis_tools.RunSolverPlotViewer

窗口能出现,当然算是进展。可要放进日常工作里,还得能继续操作模型、调用其他功能,并且在一轮 操作之后正常回来。
“显示出来”和“能够稳定地用下去”,中间还有一段距离。
对于这个工具,我需要的是后者。所以,这一阶段虽然摸到了新的方向,仍然没有得到一个让我满意的结果,这是23年发现的解决办法了。
转眼又到了 24 年,又在继续写 ansa教程的,介绍了开发的基础知识过后,就要开始引入第三方库了,这也是能够让开发更强悍的必备技能了。

我先写的Tkinter,这个库很有意思,大部分情况下是直接运行的不了的,要去改一下init文件,写了过后感觉tkinter还是差点意思,但是想着这也是交互界面是不是能实现常驻窗口呢,试了一下,界面能够创建出来,但放进 ANSA 的运行环境以后,又遇到了主线程被占住,遂放弃。
后来又试了 wxPython,真的是机缘巧合的情况下了,当时代码忘记写了show了,但是窗口也可以正常显示出来了。这一次,事情向前走了一大步:窗口能够显示,控件也能用。在当时的尝试中,一个关键点是wx.App 是一个单例对象,通常在整个应用程序中只需要创建一次。按这种方式处理后,窗口可以保持下来,但是窗口不能关闭,窗口关闭整个软件就直接关闭了,或者只有关闭软件窗口才可以关闭。


这基本解决了我当时八成左右的需求。那时已经有了一条可以继续使用的路。
随之而来的问题,是部署。
wxPython 毕竟不是 ANSA 自带的界面库。自己电脑上装好、调通,并不代表把工具交给别人后就能直接用。还要额外安装、处理环境和依赖,使用体验也与软件原生界面有距离。
对于一个原本只想方便日常操作的工具来说,这些额外步骤并不轻。功能上的八成满足,也没有完全消除交付和使用上的麻烦。
于是,问题又往前推进了一层:能不能直接利用 ANSA 自己的能力,把这件事做成?
但说实话,这些方案我自己用得都很少——核心问题就是「不通用」。换个电脑就水土不服。折腾来折腾去,看似解决了问题,实则增加了额外的工作量,久而久之,还是会回到“User Script Buttons”的窗口。
实际上也算是一个未解决的心结问题,怎么就解决不了呢,再往后,我开始怀疑一件事:原厂会不会在以前的版本里释放过相关的接口,会不会并没有完全消失?

后面我下载了 ANSA 14.0,重新翻它的帮助文档。ANSA支持两种语言都去看了看,最后在 BS 库相关内容里,新版的 bs 帮助文档都没有了,所以在旧版的里面找到了能够直接显示窗口的 API,看了一下代码说明,和案例代码理解到了。
那套语法已经很老。按照当时的了解,BS 也已经属于官方放弃、退出主流开发路线的旧库。但对眼前这个问题来说,它提供了一个重要入口:可以直接利用 ANSA 自身的 API 达到需要的效果,不必再额外装一套 GUI 库。
绕了一圈,答案竟然藏在旧文档里。
这个发现让我印象很深。此前我们试过调用入口,研究过文档示例,也换过界面库;最后真正打开思路的,却是回头查一个更早版本里的接口,后面知道这个api过后,搜索安装目录下的文件,发现这个api,就在这里静静的躺着,也就是说,代码就在那里,只是一直没发现罢了“Input Generic Entities And Connectors”,这一切真的太有意思了。

后来,相关内容发出来,陆续有不少人来问:具体是哪个 API?到底怎么实现?我很理解大家为什么想直接知道答案。因为当初,我也是想找到那个入口的人。如果有人知道,告诉你一句话,也许就能结束一轮漫长的搜索。没有人指出方向,就只能在不同版本的帮助文档、示例代码和运行结果之间来回试。
这里面最耗时间的,未必是最后那一行代码,而是判断下一步应该往哪里找。
某个接口有没有公开?当前版本的文档是否写到了?一个例子能创建窗口,是否也能满足实际操作?看到的异常是写法问题、运行环境问题,还是这条路线本身就不合适?
在答案出现之前,这些问题往往混在一起。走了这么多弯路得到的结果,直接公布想着还是有点亏,所以是通过私信的方式,小范围传播的,这也让我认识了更多做二次开发的小伙伴了。
所以,回到开头那位博主的疑问,我更想借这篇文章,把自己经历过的过程交代清楚。今天把结果写出来,可能显得很简单。但我当时经历的是从不知道、试不通,到部分可用,再找到另一条路,伴随着调试过程中软件反复的崩溃。现在回看已经清楚的东西,在当时并没有那么明确。
这件事并没有停在旧 BS 库的办法上,毕竟能用的插件还是太少了,不好看,还是要基于guitk来实现。
目前的情况是窗口已经可以采用多种形式,已经做到以前看的国外插件的效果了:
浮动窗口:独立放置,按需要调整位置。
底部嵌入:让工具留在主界面底部。
右侧嵌入:把自定义工具组织在右侧区域。
from ansa import guitkdef main():window = guitk.BCWindowCreate("Window Init Docked Example", guitk.constants.BCOnExitDestroy)guitk.BCLabelCreate(window, "This window should start in docked position")guitk.BCTimerSingleShot(0, dockToDatabaseRight, window)guitk.BCTimerSingleShot(0, checkDocking, window)guitk.BCWindowSetSaveSettings(window, False)guitk.BCShow(window)def dockToDatabaseRight(window):guitk.BCWindowSetInitDockedPosition(window, "Database", guitk.constants.BCDockRight)return 0def checkDocking(window):if not guitk.BCWindowIsDocked(window):print("Try changing the second argument of BCWindowSetInitDockedPosition to a valid name of a docked window")return 0if __name__ == "__main__":main()

这些形式,让当年的需求有了更多实现选择。不同工具可以根据操作频率、控件数量和工作区域,选择合适的摆放方式,而不只是围绕“能不能保持显示”反复妥协。AI 还是太厉害了,不到十分钟解决了几年时间才解决的问题。
五年前,最初的愿望只是:处理 Casting 时,能随时点一下 ANSA 右侧的原生功能,再回来继续用自己的工具。
操作完,窗口还在;需要它的时候,不用重新开始。