实验三 lr分析器的设计与实现_用Python实现喇叭天线设计小工具(三)

摘要:本节主要介绍波导查值模块,以及HFSS调用模块的编写。

波导查值模块

该部分要实现的功能主要是根据输入的工作频率,自动选择合适的标准波导尺寸,免去翻资料的麻烦,实现起来逻辑很简单,也就是单纯地根据条件来查值,查值方法有两种:

  1. 根据工作频点来判断;

  2. 根据波导名直接查询;

两种方式均可用简单的函数来实现,代码如下所示(wg.py):

"""波导尺寸查询"""

# 标准波导尺寸表
wg_list = (
('BJ3', 0.32, 0.49, 584.2, 292.1),
('BJ4', 0.35, 0.53, 533.4, 266.7),
('BJ5', 0.41, 0.62, 457.2, 228.6),
('BJ6', 0.49, 0.75, 381.0, 190.5),
('BJ8', 0.64, 0.98, 292.1, 146.05),
('BJ9', 0.76, 1.15, 247.65, 123.82),
('BJ12', 0.96, 1.46, 195.58, 97.79),
('BJ14', 1.13, 1.73, 165.10, 82.55),
('BJ18', 1.45, 2.20, 129.54, 64.77),
('BJ22', 1.72, 2.61, 109.22, 54.61),
('BJ26', 2.17, 3.30, 86.36, 43.18),
('BJ32', 2.60, 3.35, 72.14, 34.04),
('BJ40', 3.22, 4.90, 58.17, 29.08),
('BJ48', 3.94, 5.99, 47.549, 22.149),
('BJ58', 4.64, 7.05, 40.386, 20.193),
('BJ70', 5.38, 8.17, 34.849, 15.799),
('BJ84', 6.57, 9.99, 28.499, 12.624),
('BJ100', 8.20, 12.5, 22.86, 10.160),
('BJ120', 9.84, 15.0, 19.050, 9.525),
('BJ140', 11.9, 18.0, 15.799, 7.899),
('BJ180', 14.5, 22.0, 12.954, 6.477),
('BJ220', 17.6, 26.7, 10.668, 4.318),
('BJ260', 21.7, 33.0, 8.636, 4.318),
('BJ320', 26.3, 40.0, 7.112, 3.556),
('BJ400', 32.9, 50.1, 5.690, 2.845),
('BJ500', 39.2, 59.6, 4.775, 2.388),
('BJ620', 49.8, 75.8, 3.759, 1.880),
)


def check_by_freq(freq):
""" 通过输入频点查找标准波导尺寸 :param freq: 频点,GHz :return: 波导的宽边a和短边b,mm """
a, b = None, None
for i in wg_list:
if i[1] <= freq <= i[2]:
a, b = i[3], i[4]
break
return a, b


def check_by_name(name):
""" 通过名称来查找标准波导尺寸 :param name: 波导名,如‘BJ100’ :return: 波导的宽边a和短边b,mm """
a, b = None, None
for i in wg_list:
if i[0] == name:
a, b = i[3], i[4]
break
return a, b


if __name__ == '__main__':
a1, b1 = check_by_freq(25)
a2, b2 = check_by_name('BJ320')
print(a1, b1)
print(a2, b2)

实际使用中第一种“自动判断”的场景要多一些,但有时候也会特别指定波导型号,故两者皆写好供调用。

HFSS调用模块

这一部分是稍微比较麻烦的,需要用到HFSS自己的“代码录制”功能和Python的win32com模块。

首先,众所周知,HFSS支持脚本控制,可将操作录制为一段脚本,便于自动化运行以及实现较为复杂的建模功能,该功能位于Tools——Record Script to File中,支持vbs和python两种格式(早期版本仅支持vbs)。

638971d74ab314b533eabdecc5a9f083.png

因此,要调用HFSS建模,自然会想到先用“代码录制”功能将建模操作录制下来,再进行简单修改,最后再想办法用python直接运行调用。

顺着这个思路,首先梳理下角锥喇叭的建模步骤,以及对应步骤抽象出来的封装函数(注:建模方法当然不止一种,以下仅为个人习惯方式):

  • 设置变量。个人习惯先把变量建好,这一步操作应抽象并封装成函数:

设置变量(set_variable)——1
  • 建喇叭天线内腔。画两个同尺寸矩形,connect而成立方体;再画两个不同尺寸矩形,connect而成锥台;两者unite而成联合体。这一步操作有:

画一个矩形(create_centered_rectangle)——2
联合两个面(connect)——3
布尔和(unite)——4
  • 建喇叭天线体并设为金属。采用同上方法画一个更大一些的联合体,然后与第一步得到的联合体相减,最后设置材料为PEC,新操作有:

复制(copy_and_paste)——5
布尔差(subtract)——6
设置材料属性(set_material)——7
  • 建空气盒子和辐射边界。操作有:

画一个区域(create_region)——8
设置辐射边界(assign_radiation_region)——9
插入方向图设置(insert_radiation_setup)——10
  • 画端口并设置解算参数。操作有:

画一个端口(assign_port)——11
设置解算参数(insert_analysis_setup)——12
  • 保存工程后仿真并生成报告。操作有:

保存(save_prj)——13
仿真(run)——14
生成仿真报告(create_reports)——15

梳理以后发现,完成一个喇叭建模,竟然大概要写15个函数。。。感觉不知比直接建模要麻烦到哪儿去了,但考虑到这些函数写好了,以后可以被大量复用,相当在于写一个小型API,故是一种先难后易的做法。

对于每一个封装函数的实现,操作步骤为先用HFSS录制脚本,在进行修改。以实现并封装set_variable这个函数为例,首先在HFSS中设一个名为w1,值为20mm的变量,把这段操作录制为py程序,打开后如下所示:

# ----------------------------------------------
# Script Recorded by ANSYS Electronics Desktop Version 2018.0.0
# 10:13:34 十一月 17, 2018
# ----------------------------------------------
import ScriptEnv
ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oProject = oDesktop.SetActiveProject("Project2")
oDesign = oProject.SetActiveDesign("HFSSDesign1")
oDesign.ChangeProperty(
[
"NAME:AllTabs",
[
"NAME:LocalVariableTab",
[
"NAME:PropServers",
"LocalVariables"
],
[
"NAME:NewProps",
[
"NAME:w1",
"PropType:=" , "VariableProp",
"UserDef:=" , True,
"Value:=" , "20mm"
]
]
]
])

这段原始代码有以下几点要注意:

一个HFSS专属坑。一定得要 先删除注释中的中文和中文符号!!!这点特别坑爹,HFSS不能正确识别任何带中文字符的路径和脚本,连它自己录制的也不行(希望HFSS下个版本能改掉这个万年bug);
引用模块调用无效。import ScriptEnv 在HFSS外部调用时没有意义,因为这不是一个通用的Python包,也就是说想在Python中写这么一段就调用HFSS是不可能的;
格式。格式有点杂乱,相当地不pythonic,这当然是不能忍的,应手动调整一下。

改写后的代码如下所示(将变量名与变量值设成了输入参数):

def set_variable(_var_name, _var_value):
_NAME = 'NAME:' + _var_name
_VALUE = str(_var_value) + 'mm'
oDesign.ChangeProperty(["NAME:AllTabs",
["NAME:LocalVariableTab",
["NAME:PropServers", "LocalVariables"],
["NAME:NewProps",
[_NAME, "PropType:=", "VariableProp", "UserDef:=", True, "Value:=", _VALUE]]]])

改写以后,是否感觉清爽了很多?采用这种思路,将15个函数统统编好,为了调用方便,再整体封装为一个对象HFSS,程序就比较像样了。

最后要强调的就是,通过查阅各种资料,我个人认为在Python中直接打开HFSS最好的方式还是要借助win32com这个模块,其介绍如下:

python模块:win32com用法详解 - 杜雪峰的个人页面 - 开源中国my.oschina.net0f0faef32f639dadafba3dcd5b7aca8d.png

综合以上,形成HFSS调用模块,其代码如下(sim.py):

from win32com import client
import os


class HFSS:
def __init__(self):
self.oAnsoftApp = client.Dispatch('AnsoftHfss.HfssScriptInterface')
self.oDesktop = self.oAnsoftApp.GetAppDesktop()
self.oProject = self.oDesktop.NewProject()
self.oProject.InsertDesign('HFSS', 'HFSSDesign1', 'DrivenModal1', '')
self.oDesign = self.oProject.SetActiveDesign("HFSSDesign1")
self.oEditor = self.oDesign.SetActiveEditor("3D Modeler")
self.oModule = self.oDesign.GetModule('BoundarySetup')
self.transparency = 0.5

def set_variable(self, _var_name, _var_value):
_NAME = 'NAME:' + _var_name
_VALUE = str(_var_value) + 'mm'
self.oDesign.ChangeProperty(["NAME:AllTabs",
["NAME:LocalVariableTab",
["NAME:PropServers", "LocalVariables"],
["NAME:NewProps",
[_NAME, "PropType:=", "VariableProp", "UserDef:=", True, "Value:=", _VALUE]]]])

def create_centered_rectangle(self, _var_x, _var_y, _var_z, _name, _dir='Z'):
self.oEditor.CreateRectangle(
[
"NAME:RectangleParameters",
"IsCovered:=" , True,
"XStart:=" , '-' + _var_x + '/2',
"YStart:=" , '-' + _var_y + '/2',
"ZStart:=" , _var_z,
"Width:=" , _var_x,
"Height:=" , _var_y,
"WhichAxis:=" , _dir
],
[
"NAME:Attributes",
"Name:=" , _name,
"Flags:=" , "",
"Color:=" , "(143 175 143)",
"Transparency:=" , 0,
"PartCoordinateSystem:=", "Global",
"UDMId:=" , "",
"MaterialValue:=" , "\"vacuum\"",
"SurfaceMaterialValue:=", "\"\"",
"SolveInside:=" , True,
"IsMaterialEditable:=" , True,
"UseMaterialAppearance:=", False
])

def connect(self, _obj1, _obj2):
self.oEditor.Connect(["NAME:Selections", "Selections:=", _obj1 + ',' + _obj2])

def unite(self, _obj1, _obj2):
self.oEditor.Unite(["NAME:Selections", "Selections:=", _obj1 + ',' + _obj2],
["NAME:UniteParameters", "KeepOriginals:=", False])

def subtract(self, _obj1, _obj2):
self.oEditor.Subtract(["NAME:Selections", "Blank Parts:=", _obj1, "Tool Parts:=", _obj2],
["NAME:SubtractParameters", "KeepOriginals:=", False])

def copy_and_paste(self, _obj):
self.oEditor.Copy(["NAME:Selections", "Selections:=", _obj])
self.oEditor.Paste()

def set_material(self, _obj, _mat='pec'):
self.oEditor.AssignMaterial(
[
"NAME:Selections",
"AllowRegionDependentPartSelectionForPMLCreation:=", True,
"AllowRegionSelectionForPMLCreation:=", True,
"Selections:=" , _obj
],
[
"NAME:Attributes",
"MaterialValue:=" , "\"" + _mat + "\"",
"SolveInside:=" , False,
"IsMaterialEditable:=" , True,
"UseMaterialAppearance:=", False
])

def assign_port(self, _obj):
self.oModule.AssignWavePort(["NAME:1", "Objects:=", [_obj],
"NumModes:=", 1, "RenormalizeAllTerminals:=", True,
"UseLineModeAlignment:=", False, "DoDeembed:=" , False,
["NAME:Modes",
["NAME:Mode1", "ModeNum:=", 1, "UseIntLine:=", False, "CharImp:=", "Zpi"]],
"ShowReporterFilter:=" , False,
"ReporterFilter:=" , [True],
"UseAnalyticAlignment:=", False])

def create_region(self, _var_ab):
self.oEditor.CreateRegion(
[
"NAME:RegionParameters",
"+XPaddingType:=" , "Absolute Offset",
"+XPadding:=" , _var_ab,
"-XPaddingType:=" , "Absolute Offset",
"-XPadding:=" , _var_ab,
"+YPaddingType:=" , "Absolute Offset",
"+YPadding:=" , _var_ab,
"-YPaddingType:=" , "Absolute Offset",
"-YPadding:=" , _var_ab,
"+ZPaddingType:=" , "Absolute Offset",
"+ZPadding:=" , _var_ab,
"-ZPaddingType:=" , "Absolute Offset",
"-ZPadding:=" , _var_ab
],
[
"NAME:Attributes",
"Name:=" , "Region",
"Flags:=" , "Wireframe#",
"Color:=" , "(143 175 143)",
"Transparency:=" , 0,
"PartCoordinateSystem:=", "Global",
"UDMId:=" , "",
"MaterialValue:=" , "\"vacuum\"",
"SurfaceMaterialValue:=", "\"\"",
"SolveInside:=" , True,
"IsMaterialEditable:=" , True,
"UseMaterialAppearance:=", False
])

def assign_radiation_region(self):
self.oModule.AssignRadiation(
[
"NAME:Rad1",
"Objects:=" , ["Region"],
"IsFssReference:=" , False,
"IsForPML:=" , False
])

def insert_radiation_setup(self):
mod = self.oDesign.GetModule('RadField')
mod.InsertFarFieldSphereSetup(
[
"NAME:Infinite Sphere1",
"UseCustomRadiationSurface:=", False,
"ThetaStart:=" , "-180deg",
"ThetaStop:=" , "180deg",
"ThetaStep:=" , "1deg",
"PhiStart:=" , "0deg",
"PhiStop:=" , "90deg",
"PhiStep:=" , "90deg",
"UseLocalCS:=" , False
])

def insert_analysis_setup(self, _freq):
mod = self.oDesign.GetModule('AnalysisSetup')
mod.InsertSetup("HfssDriven",
[
"NAME:Setup1",
"AdaptMultipleFreqs:=" , False,
"Frequency:=" , str(_freq) + 'GHz',
"MaxDeltaS:=" , 0.02,
"PortsOnly:=" , False,
"UseMatrixConv:=" , False,
"MaximumPasses:=" , 20,
"MinimumPasses:=" , 1,
"MinimumConvergedPasses:=", 1,
"PercentRefinement:=" , 30,
"IsEnabled:=" , True,
"BasisOrder:=" , 1,
"DoLambdaRefine:=" , True,
"DoMaterialLambda:=" , True,
"SetLambdaTarget:=" , False,
"Target:=" , 0.3333,
"UseMaxTetIncrease:=" , False,
"PortAccuracy:=" , 2,
"UseABCOnPort:=" , False,
"SetPortMinMaxTri:=" , False,
"UseDomains:=" , False,
"UseIterativeSolver:=" , False,
"SaveRadFieldsOnly:=" , False,
"SaveAnyFields:=" , True,
"IESolverType:=" , "Auto",
"LambdaTargetForIESolver:=", 0.15,
"UseDefaultLambdaTgtForIESolver:=", True
])

def create_reports(self):
mod = self.oDesign.GetModule('ReportSetup')
mod.CreateReport("VSWR Plot 1", "Modal Solution Data", "Rectangular Plot", "Setup1 : LastAdaptive", [],
["Freq:=", ["All"], ],
["X Component:=", "Freq",
"Y Component:=", ["VSWR(1)"]], [])

mod.CreateReport("Realized Gain Plot 1", "Far Fields", "Rectangular Plot", "Setup1 : LastAdaptive",
["Context:=", "Infinite Sphere1"],
[
"Theta:=" , ["All"],
"Phi:=" , ["All"],
"Freq:=" , ["All"],
],
[
"X Component:=" , "Theta",
"Y Component:=" , ["dB(RealizedGainTotal)"]
], [])
mod.AddTraceCharacteristics("Realized Gain Plot 1", "max", [], ["Full"])
mod.AddTraceCharacteristics("Realized Gain Plot 1", "xdb10Beamwidth", ["3"], ["Full"])

def save_prj(self):
_base_path = os.getcwd()
_prj_num = 1
while True:
_path = os.path.join(_base_path, 'Prj{}.aedt'.format(_prj_num))
if os.path.exists(_path):
_prj_num += 1
else:
break
self.oProject.SaveAs(_path, True)

def run(self):
self.oDesktop.RestoreWindow()
self.oDesign.Analyze('Setup1')

调用方法是首先实例化HFSS对象,再使用其方法,就可以顺利调用HFSS啦,例如:

if __name__ == '__main__':
h = HFSS()

# 设置变量
h.set_variable('wg_a', 22.86)
h.set_variable('wg_b', 10.16)

# 画个矩形
h.create_centered_rectangle('wg_a', 'wg_b', 0, 'wg_in')

# 保存一下
h.save_prj()

要注意的是,save_prj函数会将新建立的工程保存在与程序同样目录中,这个目录的路径照例不可以有任何中文符号,否则HFSS保存出错,至于文件名,save_prj函数中已作了简单的判重处理,不用担心覆盖,当然,有特别需求的请自行修改。

小结

本部分是相对比较麻烦的部分,相当于写了一个HFSS与Python之间的通讯接口,然而后续将会看到,有了这部分基础,主调程序才可以用相对清晰的逻辑来操作HFSS建模。

本篇介绍即到此结束,下一部分将会讲到主调用模块及GUI模块,谢谢各位观看(*^_^*)!

Peace!

转载自:知乎@况泽灵,编辑于 2018-11-22

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值