导读:大家好,我是小郭老师,硕士毕业于中科院,专注于运用 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_points
def create_ns_by_points(point_coords, ns_name, tolerance=0.05):
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-4
elif abs(upper) < 1e-4:
upper = 1e-4
criterion.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.PlotData
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] # 节点ID
node_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_value
if len(node_ids) > 0:
avg_value = sum_value / len(node_ids)
else:
avg_value = 0.0
avg_writer.writerow([time, avg_value])
上面讲的流程,都浓缩在下面这段代码里了,每个关键步骤我都加了注释,大家拿到手稍作修改(比如改检测点坐标、结果类型)就能用:
# 导入必要的模块
import os # 提供操作系统相关功能,如文件路径操作
import datetime # 提供日期和时间处理功能
import csv
def 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-4
elif abs(upper) < 1e-4:
upper = 1e-4
# 设置边界值(带单位)
criterion.LowerBound = Quantity('%.6f [m]' % lower) # 下界
criterion.UpperBound = Quantity('%.6f [m]' % upper) # 上界
# 生成命名选择
ns_point.Generate()
return ns_point
def 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] # 节点ID
node_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_path
else:
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