导读:大家好,我是小郭老师,硕士毕业于中科院,专注于运用 Python 深度集成 ANSYS 进行工程数值计算与仿真自动化开发,精通基于 Python实现ANSYS Workbench众多模块的全流程脚本驱动(建模、求解、后处理),并具备将复杂仿真流程封装为高效、可复用软件工具的专业能力,今天给大家分享一些ANSYS二次开发方面的干货。
做科研写论文时,你是不是也遇到过这样的困扰:明明做了 ANSYS 模拟,却没法快速生成如下图所示的模拟瞬态数据和实验数据的对比,导致数值模型的可信度没法直观验证?亦或者,依据项目需求,一定得导出某些关键位置的瞬态响应结果?别急,今天小郭老师就带着大家用 Mechanical 的 Python 脚本开发,解决这个问题。
也诚邀关注我的视频教程《ANSYS Workbench & Mechanical企业级二次开发程序与Python应用入门进阶》,详情见下文。
ANSYS Mechanical 脚本开发是基于Python(或 IronPython)语言,通过调用Mechanical模块的 API(应用程序接口)实现有限元分析流程自动化的技术手段。
它聚焦于Mechanical模块内部的建模、网格划分、边界条件设置、求解控制及后处理等全流程的程序化控制Mechanical,可通过两种方式入门:
一是利用 Mechanical自带的 “Record Script” 功能录制操作过程,生成基础脚本后再按需修改;
二是直接调用官方API 文档中的类与方法(如Model类用于模型管理、Mesh类用于网格控制),实现更灵活的定制。
在 Mechanical 的 Python 脚本开发中,要实现模拟数据的精准提取与后续对比,关键在于结合Named Selection(命名选择) 和Result Sets(结果集)。
先跟大家好好聊聊 Named Selection。它就像是给 ANSYS 里的 “特定对象组” 贴标签,是对已选出的实体、有限元对象、组合和集 合进行的命名操作。在二次开发中,Named Selection可以用来定位目标边、面、几何体,乃至某个区域下的网格节点。
Named Selection(命名选择)主要有两种创建方式,直接法和间接法。在二次开发场景下,两者的实用性差异很大:
直接法:第一步得先选出要命名的点、线、面等对象,接着右键点击 “create named selections”,输入名称后点击 OK 完成创建。
这个方法听起来简单,但在二次开发里实现起来非常复杂,得结合ID概念和 SelectionMana ger 类,代码逻辑比较绕,但能实现很复杂的功能。
关于这部分内容,我在《ANSYS Workbench & Mechanical 二次开发与 Python 应用》第2章有做详细介绍,涵盖基本用法、示例等各种内容。
间接法:通过 Worksheet 进行选择。只要按照 Worksheet 给定的逻辑写代码,就能快速实现 Named Selection 创建,今天咱们的实战案例就用这种方法。
Result Sets(结果集)的创建方式如图所示。右键激活已经完成后处理求解的云图对象,选择Create Results at All Sets,即可生成TreeGroup对象。该对象内包含所有时步下的云图结果。
创建结果集的适用范围非常广,仅对极少数不适用,包括:
Explicit Dynamics, Response Spectrum, Random Vibration, or Topology Optimization an alyses(显示动力学、响应谱、随机振动和拓扑优化分析)
Probe Results(探针结果)
Fracture Tool(断裂工具)
Fatigue Tool(疲劳工具)
Composite Failure Tool(复合材料失效工具)
步骤 1:用 Named Selection 筛选目标检测点附近网格节点
create_ns_by_pointsdef create_ns_by_points(point_coords, ns_name, tolerance=0.05):ns_point = Model.AddNamedSelection()ns_point.Name = ns_namens_point.ScopingMethod = GeometryDefineByType.Worksheetaxes_config = [{"criterion": SelectionCriterionType.LocationX, # X坐标选择准则"action": SelectionActionType.Add # 添加操作(第一个条件)},{"criterion": SelectionCriterionType.LocationY, # Y坐标选择准则"action": SelectionActionType.Filter # 过滤操作(后续条件)},{"criterion": SelectionCriterionType.LocationZ, # Z坐标选择准则"action": SelectionActionType.Filter # 过滤操作(后续条件)}]for i, config in enumerate(axes_config):ns_point.GenerationCriteria.Add(None)criterion = ns_point.GenerationCriteria[i]criterion.EntityType = SelectionType.MeshNode # 选择网格节点criterion.Criterion = config["criterion"] # 设置选择准则(X/Y/Z坐标)criterion.Operator = SelectionOperatorType.RangeInclude # 设置操作符为范围包含criterion.Action = config["action"] # 设置动作类型(添加/过滤)coord_value = point_coords[i]lower = coord_value * (1 - tolerance)upper = coord_value * (1 + tolerance)if lower > upper:lower, upper = upper, lowerif abs(lower) < 1e-4:lower = -1e-4elif abs(upper) < 1e-4:upper = 1e-4criterion.LowerBound = Quantity('%.6f [m]' % lower) # 下界criterion.UpperBound = Quantity('%.6f [m]' % upper) # 上界ns_point.Generate()return ns_point
咱们来看看关键逻辑:
先初始化命名选择对象,设置名称和创建方式为 Worksheet;
配置坐标轴(X、Y、Z 轴)的筛选规则,比如通过位置信息筛选节点,这里用 “Add” 和 “Filter” 组合确保筛选精准;
计算每个坐标轴的边界值:根据检测点坐标和设定的公差(这里默认为0.05=5%),算出筛选范围。此外,还加了冗余判断 —— 当边界值接近 0 时(小于 1e-4),手动调整避免计算误差;
最后生成命名选择,这样目标检测点附近的网格节点就被精准 “圈” 出来了。
例如,一电子器件冷却三维模型的目标检测点筛选结果如下图所示。可以看到,在给定的监测点坐标范围内(正负5%波动),仅筛选出一个网格节点。
步骤 2:插入后处理对象 + 求解
筛选出目标节点后,下一步就是获取对应的数据。先在分析方案的 Solution 里插入后处理对象,比如温度(Temperature),然后把这个后处理对象的 Location 设置为咱们刚创建的 Named Selection(也就是目标节点组),接着运行第一次求解,让 ANSYS 计算出该节点组在最终时刻下的结果。
步骤 3:创建求解集 + 二次求解
第一次求解后,要创建该后处理对象的 “求解集”,也就是CreateResultsAtAllSets。创建完求解集后,再运行一次求解。这样子,就能获取到整个分析过程中(比如瞬态分析的每个时间步)该节点组的所有结果数据。
步骤 4:遍历求解集 + 数据写入文件
最后一步,就是把结果数据导出来,方便后续和实验数据做对比。我写的export_trasient_file函数就能实现这个功能,核心逻辑包括如下四部分内容:
1、以当前时间为后缀生成 CSV 文件,一个存所有节点的详细数据,一个存每个时间步的平均值(方便快速看整体趋势);
2、写入表头,比如 “Time (s) Node_ID X (m) Y (m) Z (m) Value”,让数据结构清晰,后续用 Excel 或 Origin 处理时直接就能用;
3、遍历每个时间步的结果集,提取时间、节点 ID、节点坐标、结果数值(比如温度值),逐行写入详细数据文件;
4、同时,计算每个时间步下所有节点结果的平均值,写入平均值文件,后续画对比图时,既可以用详细数据,也能用平均值.
函数程序如下所示:
def export_trasient_file(result_objs, root_path):timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")output_file = os.path.join(root_path, "ANSYSResult_%s.csv" % timestamp)avg_output_file = os.path.join(root_path, "avg_ANSYSResult_%s.csv" % timestamp)meshData = DataModel.MeshDataByName("Global")with open(output_file, 'wb') as csvfile, open(avg_output_file, 'wb') as avg_csvfile:writer = csv.writer(csvfile) # 详细数据写入器avg_writer = csv.writer(avg_csvfile) # 平均值数据写入器writer.writerow(["Time (s)", "Node_ID", "X (m)", "Y (m)", "Z (m)", "Value"])avg_writer.writerow(["Time (s)", "Average_Value"])total_steps = len(result_objs) # 总时间点数for i in range(total_steps):result_obj = result_objs[i] # 当前时间点的结果对象time = str(result_obj.Time).split(" ")[0]result_data = result_obj.PlotDatanode_ids = result_data["Node"]values = result_data["Values"]sum_value = 0.0 # 结果值总和for j in range(len(node_ids)):node_id = node_ids[j] # 节点IDnode_value = values[j] # 结果值node = meshData.NodeById(node_id) # 获取节点对象row_data = [time, node_id, node.X, node.Y, node.Z, node_value]writer.writerow(row_data)sum_value += node_valueif len(node_ids) > 0:avg_value = sum_value / len(node_ids)else:avg_value = 0.0avg_writer.writerow([time, avg_value])
上面讲的流程,都浓缩在下面这段代码里了,每个关键步骤我都加了注释,大家拿到手稍作修改(比如改检测点坐标、结果类型)就能用:
# 导入必要的模块import os # 提供操作系统相关功能,如文件路径操作import datetime # 提供日期和时间处理功能import csvdef create_ns_by_points(point_coords, ns_name, tolerance=0.05):"""基于点坐标创建命名选择参数:point_coords -- 点坐标列表 [X, Y, Z]ns_name -- 命名选择的名称tolerance -- 容差百分比 (默认0.05,即5%)返回:创建的命名选择对象"""# 创建新的命名选择对象ns_point = Model.AddNamedSelection()ns_point.Name = ns_name # 设置命名选择的名称ns_point.ScopingMethod = GeometryDefineByType.Worksheet # 设置作用域方法为工作表# 定义坐标轴配置列表axes_config = [{"criterion": SelectionCriterionType.LocationX, # X坐标选择准则"action": SelectionActionType.Add # 添加操作(第一个条件)},{"criterion": SelectionCriterionType.LocationY, # Y坐标选择准则"action": SelectionActionType.Filter # 过滤操作(后续条件)},{"criterion": SelectionCriterionType.LocationZ, # Z坐标选择准则"action": SelectionActionType.Filter # 过滤操作(后续条件)}]# 为每个坐标轴添加生成条件for i, config in enumerate(axes_config):# 添加新的生成条件ns_point.GenerationCriteria.Add(None)# 获取当前生成条件对象criterion = ns_point.GenerationCriteria[i]### 设置通用属性 ###criterion.EntityType = SelectionType.MeshNode # 选择网格节点criterion.Criterion = config["criterion"] # 设置选择准则(X/Y/Z坐标)criterion.Operator = SelectionOperatorType.RangeInclude # 设置操作符为范围包含criterion.Action = config["action"] # 设置动作类型(添加/过滤)### 计算并设置边界值 #### 获取当前坐标轴的值coord_value = point_coords[i]# 计算下界和上界lower = coord_value * (1 - tolerance)upper = coord_value * (1 + tolerance)# 确保上界大于下界if lower > upper:lower, upper = upper, lower# 处理接近零的边界值(避免精度问题)if abs(lower) < 1e-4:lower = -1e-4elif abs(upper) < 1e-4:upper = 1e-4# 设置边界值(带单位)criterion.LowerBound = Quantity('%.6f [m]' % lower) # 下界criterion.UpperBound = Quantity('%.6f [m]' % upper) # 上界# 生成命名选择ns_point.Generate()return ns_pointdef export_trasient_file(result_objs, root_path):"""导出瞬态结果数据到CSV文件参数:result_objs -- 结果对象列表(每个时间点一个结果对象)root_path -- 文件保存的根目录路径"""# 获取当前时间作为文件名后缀(格式:年月日_时分秒)timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")# 构建详细数据文件路径output_file = os.path.join(root_path, "ANSYSResult_%s.csv" % timestamp)# 构建平均值数据文件路径avg_output_file = os.path.join(root_path, "avg_ANSYSResult_%s.csv" % timestamp)# 获取网格数据(全局网格)meshData = DataModel.MeshDataByName("Global")# 打开两个CSV文件准备写入with open(output_file, 'wb') as csvfile, open(avg_output_file, 'wb') as avg_csvfile:# 创建CSV写入器writer = csv.writer(csvfile) # 详细数据写入器avg_writer = csv.writer(avg_csvfile) # 平均值数据写入器# 写入表头writer.writerow(["Time (s)", "Node_ID", "X (m)", "Y (m)", "Z (m)", "Value"])avg_writer.writerow(["Time (s)", "Average_Value"])# 遍历所有时间点的结果total_steps = len(result_objs) # 总时间点数for i in range(total_steps):result_obj = result_objs[i] # 当前时间点的结果对象# 提取时间值(去除单位)time = str(result_obj.Time).split(" ")[0]# 获取绘图数据result_data = result_obj.PlotData# 获取节点ID和结果值node_ids = result_data["Node"]values = result_data["Values"]# 初始化当前时间点的统计变量sum_value = 0.0 # 结果值总和# 遍历当前时间点的所有节点for j in range(len(node_ids)):node_id = node_ids[j] # 节点IDnode_value = values[j] # 结果值node = meshData.NodeById(node_id) # 获取节点对象# 准备行数据:时间, 节点ID, X坐标, Y坐标, Z坐标, 结果值row_data = [time, node_id, node.X, node.Y, node.Z, node_value]# 写入详细数据文件writer.writerow(row_data)# 累加结果值(用于计算平均值)sum_value += node_value# 计算当前时间点的平均值if len(node_ids) > 0:avg_value = sum_value / len(node_ids)else:avg_value = 0.0 # 如果没有节点,平均值设为0# 写入平均值文件:时间, 平均值avg_writer.writerow([time, avg_value])# 定义监测点坐标 [X, Y, Z]monitor_coords = [0, 1.65e-002, 5.5207e-003]# 创建监测点命名选择ns_monitor = create_ns_by_points(monitor_coords, ns_name="ns_point1")# 检查命名选择是否成功创建if ns_monitor.Location:print "定位到监测点,正在导出数据"# 获取分析解决方案对象ana lysis = DataModel.Ana lysisList[0]ana lysis_solution = ana lysis.Solution# 添加温度结果temp = a nalysis_solution.AddTemperature()# 设置温度结果的位置为监测点命名选择temp.Location = ns_monitor# 评估所有结果ana lysis_solution.EvaluateAllResults()# 获取所有时间点的结果集temp_sets = temp.CreateResultsAtAllSets()# 再次评估所有结果(确保数据最新)an alysis_solution.EvaluateAllResults()# 获取桌面路径desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")# 导出瞬态数据到CSV文件export_trasient_file(temp_sets, desktop_path)print "数据导出完毕,路径为:%s" % desktop_pathelse:print "警告:监测点命名选择创建失败"
可能有人会问,手动也能提取数据,为啥要费劲搞二次开发?
作为实战工程师,我举个实际工作中的例子:之前做过一个课题研究,需要处理瞬态温度分析结果,一共 360个时间步,每个时间步要提取10个检测点的数据,重复性工作量大,还很容易出错。
用上面的脚本,设置好坐标后点击运行,2 分钟就能出所有 CSV 文件。之后,再用 Origin 导入数据,10 分钟就能画出模拟与实验的对比曲线图,效率直接翻十几倍!
而且,这个脚本还能灵活修改。比如,把AddTemperature()改成AddEquivalentStress()就能提取应力数据,调整monitor_coords就能换检测点,甚至加个循环能同时处理多个检测点,完全适配不同的项目需求。
实际上,结合ANSYS Mechanical API和Python脚本开发,可以实现Mechanical的完全脚本化和高度可定制化,例如模板开发、自定义载荷、后处理及结果等。
看完这篇,是不是觉得 ANSYS 二次开发没那么难。如果你也在为模拟数据处理头疼,不妨试试这个脚本。强烈推荐读者朋友订阅我的《ANSYS Workbench & Mechanical企业级二次开发程序与Python应用入门进阶》。
本课程从基础脚本开发到多工况批量计算的自动化实现,手把手教你用 Python 打通 “仿真流程自动化 - 多工况批量计算” 全链路,让 “重复仿真工作自动化、复杂工况高效计算” 从想法变成日常。
我还为付费用户提供VIP群进行交流、答疑服务、持续加餐内容、提供定制化培训和咨询服务、仿真人才库高新内推就业、仿真秀还提供奖学金、学完此课程,推荐学习者报名参加工程仿真技术(CAE分析职业能力等级评价证书)。
此外,在使用过程中遇到代码报错、需求调整等问题,欢迎在评论区留言,我会一一解答。后续还会分享更多 ANSYS 二次开发的实战技巧,敬请关注。
可回放,开具发票,奖学金、直播加餐
提供vip群答疑和模型下载
《ANSYS Workbench & Mechanical企业级二次开发程序与Python应用入门进阶》
扫码查看视频教程
以下是课程大纲及主要内容:
来源:仿真秀App