2021年5月9日 星期日

如何將2 Ports元件(R, L, C)設為Edge Ports

滑鼠框選範圍,範圍當中的2 port元件會被disable並設為Edge Ports

(程式碼仍有瑕疵,僅供參考)

# ----------------------------------------------
# Script Recorded by ANSYS Electronics Desktop Version 2021.1.0
# 8:17:33 May 09, 2021
# ----------------------------------------------
from math import atan, degrees
import ScriptEnv

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oDesktop.ClearMessages("", "", 2)

oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oEditor = oDesign.GetActiveEditor()
oModule = oDesign.GetModule('Excitations')


def getPadInfo():
oDefinitionManager = oProject.GetDefinitionManager()
oPadstackManager = oDefinitionManager.GetManager("Padstack")
scale = 0.0000254
x = oPadstackManager.GetNames()
result = {}
for i in x:
try:
info = oPadstackManager.GetData(i)
if info[9][9][1][6][1] == 'Rct':
x, y = info[9][9][1][6][3]
result[i] = (float(x[:-3]) / 2 * scale, float(y[:-3]) / 2 * scale)
if info[9][9][1][6][1] == 'Sq':
x = info[9][9][1][6][3][0]
result[i] = (float(x[:-3]) / 2 * scale, float(x[:-3]) / 2 * scale)
except:
pass
return result


padinfo = getPadInfo()

def getLayerID():
result = {}
for i in oEditor.GetStackupLayerNames():
x = oEditor.GetLayerInfo(i)
result[i] = int(x[10].split(':')[1])
return result


ID = getLayerID()


def disableModel(cmp_name):
oEditor.ChangeProperty(
[
"NAME:AllTabs",
[
"NAME:BaseElementTab",
[
"NAME:PropServers",
cmp_name
],
[
"NAME:ChangedProps",
[
"NAME:Model Info",
[
"NAME:Model",
"RLCProp:=",
["CompPropEnabled:=", False, "Pid:=", -1, "Pmo:=", "0", "CompPropType:=", 0, "PinPairRLC:=",
["RLCModelType:=", 0, "ppr:=", ["p1:=", "1", "p2:=", "2", "rlc:=",
["r:=", "240ohm", "re:=", True, "l:=", "0", "le:=", False,
"c:=", "0", "ce:=", False, "p:=", False, "lyr:=", 1]]]],
"CompType:=", 1
]
]
]
]
])


def createPort(sx0, sy0, ex0, ey0, sx1, sy1, ex1, ey1):
ports_old = oModule.GetAllPortsList()
oEditor.CreateEdgePort(
[
"NAME:Contents",
"edge:=",
["et:=", "pse", "sel:=", "{}-2".format(i), "layer:=", layerid, "sx:=", sx0, "sy:=", sy0, "ex:=", ex0,
"ey:=", ey0, "h:=", 0, "rad:=", 0],
"external:=", True,
"btype:=", 0
])
ports_new = oModule.GetAllPortsList()

portname = list(set(ports_new) - set(ports_old))
oEditor.AddRefPort(portname,
[
"NAME:Contents",
"edge:=",
["et:=", "pse", "sel:=", "{}-1".format(i), "layer:=", layerid, "sx:=", sx1, "sy:=", sy1,
"ex:=", ex1, "ey:=", ey1, "h:=", 0, "rad:=", 0]
])
renamePort(portname[0], 'port_{}'.format(i))


def renamePort(old, new):
oDesign.ChangeProperty(
[
"NAME:AllTabs",
[
"NAME:EM Design",
[
"NAME:PropServers",
"Excitations:{}".format(old)
],
[
"NAME:ChangedProps",
[
"NAME:Port",
"Value:=" , new
]
]
]
])

def getAngle(x, y):
x = 1e-12 if x == 0 else x
z = round(degrees(atan(y/x)))
if x < 0:
return (z-90)%360-180
else:
return (z-90)%360

allcmps = oEditor.FindObjects('type', 'component')
for i in oEditor.GetSelections():

if i not in allcmps:
continue
if len(oEditor.GetComponentPins(i)) != 2:
continue

try:
pds = oEditor.GetPropertyValue('BaseElementTab', i +'-1', 'Padstack Definition')
W, H = padinfo[pds]
except:
AddErrorMessage('{} does not belong to "square" or "rectangle"!'.format(i))
continue

layername = oEditor.GetPropertyValue('BaseElementTab', i, 'PlacementLayer')
layerid = ID[layername]

try:
disableModel(i)
except:
pass

angle = oEditor.GetPropertyValue('BaseElementTab', i + '-1', 'Angle')
angle = int(float(angle[:-3]))
loc1 = oEditor.GetPropertyValue('BaseElementTab', i + '-1', 'Location')
loc2 = oEditor.GetPropertyValue('BaseElementTab', i + '-2', 'Location')
x1, y1 = [float(j) for j in loc1.split(',')]
x2, y2 = [float(j) for j in loc2.split(',')]
dx, dy = x1 - x2, y1 - y2
orien = getAngle(dx, dy)

theta = int(angle - orien)%360
if theta == 0:
createPort(W, H, -W, H, W, -H, -W, -H)
elif theta == 90:
createPort(W, -H, W, H, -W, -H, -W, H)
elif theta == 180:
createPort(W, -H, -W, -H, W, H, -W, H)
elif theta == 270:
createPort(-W, -H, -W, H, W, -H, W, H)
else:
pass

AddWarningMessage(str(theta))
AddWarningMessage('port_{}'.format(i))

(圖一)框選的2 port元件設為Edge Ports


2021年5月7日 星期五

如何移除3D Layout錄製腳本當中函數參數之間多餘空白

 3D Layout錄製的腳本函數的參數中間有多個空白導致閱讀困難,先Ctrl+C選擇腳本所有程式碼,在AEDT底下執行下面腳本removeDummySpaces.py,在Ctrl+V貼回程式碼即可。

removeDummySpaces.py

import re
import clr

clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import Clipboard

x = Clipboard.GetText()

result = ''
for i in x.splitlines():
i = i.replace('\t', ' ')
i = re.sub(',\s+', ', ', i)
i = re.sub('\s+,', ',', i)
i = re.sub('\[\s+', '[', i)
result += (i + '\n')

Clipboard.SetText(result)

(圖一) 參數之間多餘空白被移除


2021年5月5日 星期三

逐個激發port並記錄每個面的近場到單一pickle檔案

# coding=UTF-8
import pickle
import ScriptEnv
import time

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oDesktop.ClearMessages("", "", 2)
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oEditor = oDesign.SetActiveEditor("3D Modeler")


class PortIterator():
def __init__(self):
oModule = oDesign.GetModule("BoundarySetup")
self.ports = [i.replace(':1', '') for i in oModule.GetExcitations()[::2]]
self.max_num = len(self.ports)
self.index = -1

def __iter__(self):
return self

def next(self):
oModule = oDesign.GetModule("Solutions")
self.index += 1
y = []
if self.index == self.max_num:
raise StopIteration

for i in range(self.max_num):
if i == self.index:
y.append(["Name:=", self.ports[i], "Magnitude:=", "1W", "Phase:=", "0deg"])
else:
y.append(["Name:=", self.ports[i], "Magnitude:=", "0W", "Phase:=", "0deg"])

oModule.EditSources([["IncludePortPostProcessing:=", False, "SpecifySystemPower:=", False], ] + y)
return self.ports[self.index]


def getSheets():
if oDesktop.GetVersion() == '2020.1.0':
seg_string = '{}:CreatePolyline:2:Segment{}'
else:
seg_string = '{}:CreatePolyline:1:Segment{}'

result = {}
for obj in oEditor.GetObjectsInGroup('Sheets'):
try:
num = oEditor.GetPropertyValue('Geometry3DCmdTab', '{}:CreatePolyline:1'.format(obj), 'Number of curves')
result[obj] = []
for i in range(int(num)):
cs = oEditor.GetPropertyValue('Geometry3DPolylineTab', seg_string.format(obj, i), 'Point1')
result[obj].append(map(float, cs.split(','))[0:2])
result[obj].append(result[obj][0])
except:
pass

for i in result:
result[i] = zip(*result[i])

return result


def getNFRectangle():
result = []
radiation = oDesign.GetChildObject('Radiation')
for i in radiation.GetChildNames():
if oDesign.GetPropertyValue('RadFieldSetupTab', 'RadField:{}'.format(i), 'Type') == 'Rectangle':
result.append(i)
return result


def getNearEH(solution, freq, nf):
oModule = oDesign.GetModule("ReportSetup")
EH = ["NearEX", "NearEY", "NearEZ", "NearHX", "NearHY", "NearHZ"]
try:
arr = oModule.GetSolutionDataPerVariation("Near Fields", solution, ["Context:=", nf], ['Freq:=', [freq]], EH)
except:
AddErrorMessage("無模擬資料可輸出!")
result = {}
result["v"] = list(arr[0].GetSweepValues("_v"))
x = list(arr[0].GetSweepValues("_u"))
result["u"] = x[0:int(len(x) / len(result["v"]))]

for i in EH:
result[i] = [complex(x, y) for x, y in zip(arr[0].GetRealDataValues(i), arr[0].GetImagDataValues(i))]
return result


def outputPickle(solution, freq, pickle_path):
result = {'sheet': getSheets()}

for portname in PortIterator():
result[portname] = {}
AddWarningMessage("激發端口: {}".format(portname))
for nf in getNFRectangle():
result[portname][nf] = getNearEH(solution, freq, nf)

with open(pickle_path, 'wb') as f:
pickle.dump(result, f)
AddInfoMessage("檔案輸出: {}".format(pickle_path))


outputPickle("Setup1 : LastAdaptive", "28GHz", 'd:/demo/data.pickle')

2021年5月4日 星期二

如何計算多個beam所合成的EIRP輻射場型

腳本讀取每個beam並記錄其對應的rETotal。完成特定角度最大值運算之後會輸出.tab檔。在HFSS當中匯入.tab檔即可畫出其3D幅射場型及2D contour。

# coding=UTF-8
# User Input--------------------------------------------------------
solution = "Setup1 : LastAdaptive"
freq = "28e9"
code_dir = "D:/demo/code"
output_path = 'd:/demo/EIRP.tab'

# Don't Revise Code Below-------------------------------------------
from math import log10
import os
import time
import json
import itertools
import ScriptEnv

t0 = time.time()

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oDesktop.ClearMessages("", "", 2)
oModule = oDesign.GetModule("ReportSetup")
try:
oModule.DeleteAllReports()
except:
pass


def getrE():
arr = oModule.GetSolutionDataPerVariation(
"Far Fields",
solution,
[
"Context:=" , "3D"
],
['Freq:=', [freq]],
["rETotal"])

rETotal = [x for x in arr[0].GetRealDataValues("rETotal")]
if len(rETotal) != 65341:
raise Exception("Theta, Phi範圍錯誤!")
return rETotal

def setExcitation(csv_path):
oModule = oDesign.GetModule("BoundarySetup")
ports = [i.replace(':1', '') for i in oModule.GetExcitations()[::2]]
x = {name: ("0W", "0deg") for name in ports}

try:
with open(csv_path) as f:
text = f.readlines()

for i in text[1:]:
try:
source, magnitude, phase = i.split(',')
x[source.replace(':1', '')] = (magnitude, phase)
except:
pass

oModule = oDesign.GetModule("Solutions")
y = []
for name in x:
magnitude, phase = x[name]
y.append([
"Name:=" , name,
"Magnitude:=" , magnitude,
"Phase:=" , phase
])

oModule.EditSources(
[
[
"IncludePortPostProcessing:=", False,
"SpecifySystemPower:=" , False
],
] + y)

for name in x:
magnitude, phase = x[name]
# AddInfoMessage("{}: {}, {}".format(name, magnitude, phase))
AddWarningMessage('Load "{}" successfully!'.format(csv_path))

except:
AddErrorMessage('Load "{}" failed!'.format(csv_path))

data = []
for i in os.listdir(code_dir):
csv_path = os.path.join(code_dir, i)
setExcitation(csv_path)
data.append(getrE())

max_table = list(map(max, zip(*data)))

with open(output_path, 'w') as f:
f.writelines('Phi[deg],Theta[deg],EIRP[dbm/sr]\n')

for Er, (phi, theta) in zip(max_table, itertools.product(range(-180 ,181), range(0 ,181) ,)):
maxU = 10*log10(Er**2/377/2)+30
f.writelines('{}\t{}\t{}\n'.format(phi, theta, maxU))

AddWarningMessage(str(time.time( )-t0))

(圖一) 3D角度設定範圍及解析度


(圖二) EIRP



2021年4月29日 星期四

如何輸出不同視角的圖片(HFSS, Q3D, Maxwell. Icepak)

import ScriptEnv

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oEditor = oDesign.SetActiveEditor("3D Modeler")
oEditor.FitAll()
for i in ['Top', 'Bottom', 'Right', 'Left', 'Front', 'Back', 'Trimetric', 'Dimetric', 'Isometric']:
oEditor.ExportModelImageToFile("d:/demo/{}.png".format(i), 1027, 768,
["NAME:SaveImageParams",
"ShowAxis:=" , "False",
"ShowGrid:=" , "False",
"ShowRuler:=" , "False",
"ShowRegion:=" , "Default",
"Selections:=" , "",
"Orientation:=" , i
])

(圖一) 不同視角的圖片


Excitation如何載入只有部分Ports的CSV檔

在5G設計當中,使用者必須設定所有的port才能載入到HFSS當中觀察遠場,就算是沒用到的port也必須手動加入port名稱並設定0W, 0deg。本腳本可以讓使用者載入只有記錄部分ports的csv檔,沒有設定到的ports會自動設置為0W, 0deg。

import sys
import clr
from System.Windows.Forms import DialogResult, OpenFileDialog
clr.AddReference("System.Windows.Forms")
oDesktop.ClearMessages("", "", 2)

def setExcitation(csv_path):
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()

oModule = oDesign.GetModule("BoundarySetup")
ports = [i.replace(':1', '') for i in oModule.GetExcitations()[::2]]
x = {name: ("0W", "0deg") for name in ports}

try:
with open(csv_path) as f:
text = f.readlines()

for i in text[1:]:
try:
source, magnitude, phase = i.split(',')
x[source.replace(':1', '')] = (magnitude, phase)
except:
pass

oModule = oDesign.GetModule("Solutions")
y = []
for name in x:
magnitude, phase = x[name]
y.append([
"Name:=" , name,
"Magnitude:=" , magnitude,
"Phase:=" , phase
])

oModule.EditSources(
[
[
"IncludePortPostProcessing:=", False,
"SpecifySystemPower:=" , False
],
] + y)

for name in x:
magnitude, phase = x[name]
AddInfoMessage("{}: {}, {}".format(name, magnitude, phase))
AddWarningMessage('Load "{}" successfully!'.format(csv_path))

except:
AddErrorMessage('Load "{}" failed!'.format(csv_path))

dialog = OpenFileDialog()
dialog.Title = "Load Excitation"
dialog.Filter = "csv files (*.csv)|*.csv"

if dialog.ShowDialog() == DialogResult.OK:
csv_path = dialog.FileName
setExcitation(csv_path)
else:
pass

2021年4月28日 星期三

如何在AEDT底下生成一個包含下拉選單和按鈕的簡單視窗

用下面兩個檔案就可以在AEDT底下生成一個包含combo box的簡單視窗.

template.xaml

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Application" Height="120" Width="240" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<ComboBox x:Name="item_cb" VerticalAlignment="Top" Margin="10,10,10,0" SelectedIndex="0">
<ComboBoxItem Content="item1"/>
</ComboBox>
<Button x:Name="Run_bt" Content="Run" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="75" Margin="0,40,10,10" Height="30" Click="Run_bt_Click"/>

</Grid>
</Window>


template.py

import os, sys, re, clr
import math, cmath
import collections

win64_dir = oDesktop.GetExeDir()
dll_dir = os.path.join(win64_dir, 'common/IronPython/DLLs')
sys.path.append(dll_dir)
clr.AddReference('IronPython.Wpf')
import copy
import wpf
from System.Windows import Window, MessageBox
from System.Windows.Controls import ListBoxItem
from System.Windows.Forms import OpenFileDialog, SaveFileDialog, DialogResult, FolderBrowserDialog

os.chdir(os.path.dirname(__file__))


# Functions---------------------------------------------------------------------|


# GUI---------------------------------------------------------------------------|
class MyWindow(Window):
def __init__(self):
wpf.LoadComponent(self, 'template.xaml')
obj = self.item_cb.Items[0]
self.item_cb.Items.Clear()

for i in ['A1', 'A2', 'A3']:
x = copy.deepcopy(obj)
x.Content = i
self.item_cb.Items.Add(x)

self.item_cb.SelectedIndex = 0

def Run_bt_Click(self, sender, e):
AddWarningMessage(self.item_cb.SelectedItem.Content)


# Code End----------------------------------------------------------------------|
MyWindow().ShowDialog()

(圖一) AEDT的簡單combo box