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


如何計算波形曲線通過特定電壓的時間點

在AEDT報告當中,如果要顯示波形通過特定電壓的所有時間點可以採用Y marker。但Y marker無法匯出時間點數值到CSV當中。一個解決方法是先按右鍵輸出.csv,再執行下面的script將所有時間點數值輸出到crossTime.csv。

# User Input
threshold = 0.5
wavform_path = 'D:/demo/Transient Voltage Plot 1.csv'

# Don't Change Code Below
import os
os.chdir(os.path.dirname(__file__))

with open(wavform_path) as f:
data = f.readlines()

time, voltage = data[0].split(',')

waveform = []
for i in data[1:]:
try:
t, v = i.strip().split(',')
waveform.append((float(t), float(v)))
except:
pass

crossT = []
for n, (t, v) in enumerate(waveform[:-1]):
if v == threshold:
crossT.append(t)
else:
t_1, v_1 = waveform[n+1]
if v < threshold < v_1 or v > threshold > v_1:
x = t + ((t_1-t)*abs((v-threshold)/(v_1-v)))
crossT.append(x)

with open('crossTime.csv', 'w') as f:
f.writelines(time + '\n')
f.writelines('\n'.join(map(str, crossT)))
(圖一)計算通過特定電壓的時間

2021年4月23日 星期五

在Q3D當中如何輸出self RLCG值

 # User Input

matrix_name = "Original"
frequency = 1000000000

# Don't Revise Code Below!-----------------------------------------------------
import os

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

import ScriptEnv

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oDesktop.ClearMessages("", "", 2)
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oDesign.ExportMatrixData("matrix.csv", "C, DC RL, AC RL", "", "Setup1:LastAdaptive", matrix_name, "ohm", "nH", "pF",
"mSie", frequency, "Maxwell,Spice,Couple", 0, True, 15, 20, 1)
Q3D_name = oDesign.GetName()

alltype = ['DCPlusAC Resistance Matrix',
'DCPlusAC Resistance Matrix Coupling Coefficient',
'Capacitance Matrix',
'Capacitance Matrix Coupling Coefficient',
'Spice Capacitance Matrix',
'Conductance Matrix',
'Conductance Matrix Coupling Coefficient',
'Spice Conductance Matrix',
'DC Inductance Matrix',
'DC Inductance Matrix Coupling Coefficient',
'DC Resistance Matrix',
'DC Resistance Matrix Coupling Coefficient',
'AC Inductance Matrix',
'AC Inductance Matrix Coupling Coefficient',
'AC Resistance Matrix',
'AC Resistance Matrix Coupling Coefficient', ]

group = {}
with open("matrix.csv") as f:
for i in f:
if len(i.strip()) == 0:
continue

if i.strip() in alltype:
mtype = i.strip()
group[mtype] = []
continue

try:
data = i.strip().split(',')
if data:
group[mtype].append(data)
except:
pass

result = {}
for mtype in group:
result[mtype] = []
for n, data in enumerate(group[mtype][1:]):
name = data[0]
values = data[1:]
result[mtype].append((name, values[n]))

with open('self.csv', 'w') as f:
f.writelines('Q3D Design: {}\n'.format(Q3D_name))
for mtype in result:
f.writelines('\n[{}]\n'.format(mtype))
for item, value in result[mtype]:
f.writelines('{:20} {:20}\n'.format(item, value))

AddWarningMessage('"self.csv" is saved!')

(圖一) 輸出self.csv的內容


2021年4月21日 星期三

如何將大文字檔(~GB)分割成多個小檔案

有時候模擬出來的資料檔過大,文字編輯器打不開或開啟過於緩慢,則可以將大檔案分割成小檔案以利操作。

# -*- coding: utf-8 -*-
"""
Created on Tue Apr 20 14:41:43 2021

@author: mlin
"""

big_file_path = 'd:/demo/0000_CPA_Sim_1.sp'
filesize_MB = 100

# Don't Change Code Below-------------------------------------
import os

N = filesize_MB * (2 ** 20)
n = 0
num = 1
data = []
output_dir = os.path.dirname(big_file_path)

with open(big_file_path) as fin:
name = '{}/part0.txt'.format(output_dir)
fout = open(name, 'w')
for i in fin:
if n / N > 1:
print(fout)
fout.close()

name = '{}/part{}.txt'.format(output_dir, num)
fout = open(name, 'w')

num += 1
n = 0

fout.writelines(i)
n += len(i)
(圖一) 分割檔案


2021年4月18日 星期日

如何在HFSS當中將物件以netname命名

從3D Layout匯出HFSS,HFSS物件的名稱為層數及編號。用net命名更加清楚,底下兩組腳本可以用來完成重新命名的工作。

在3D Layout執行,從3D Layout輸出座標訊息到rename.json檔案當中。 

Rename_Net_1.py

import ScriptEnv
import json, os
from collections import OrderedDict

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oDesktop.ClearMessages("", "", 2)
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oEditor = oDesign.SetActiveEditor("Layout")
os.chdir(os.path.dirname(__file__))
db = {}
layerinfo = OrderedDict()
lower_elevation = 0
for layer in oEditor.GetStackupLayerNames()[::-1]:
info = oEditor.GetLayerInfo(layer)
layer_thickness = float(info[12].split(':')[1])

layerinfo[layer] = lower_elevation + layer_thickness / 2
lower_elevation += layer_thickness
db[layer] = []

for i in oEditor.FindObjects('Layer', layer):
otype = oEditor.GetPropertyValue('BaseElementTab', i, 'Type')
if otype in ['Pin', 'Via']:
location = oEditor.GetPropertyValue('BaseElementTab', i, 'Location')
elif otype in ['line', 'poly']:
location = oEditor.GetPropertyValue('BaseElementTab', i, 'Pt1')
else:
continue

x, y = location.split(',')
net = oEditor.GetPropertyValue('BaseElementTab', i, 'Net')
db[layer].append((x.strip(), y.strip(), net, otype))

with open('rename.json', 'w') as f:
json.dump((layerinfo, db), f, indent=4)
AddWarningMessage('Completed!')

在HFSS執行,讀取rename.json完成重新命名工作
Rename_Net_2.py
# ----------------------------------------------
# Script Recorded by ANSYS Electronics Desktop Version 2021.1.0
# 14:33:37 Apr 16, 2021
# ----------------------------------------------
import json, os
import ScriptEnv

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oDesktop.ClearMessages("", "", 2)
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()
oEditor = oDesign.SetActiveEditor("3D Modeler")
os.chdir(os.path.dirname(__file__))
unit = oEditor.GetModelUnits()
with open('rename.json') as f:
layer_info, db = json.load(f)

result = {}
dielectric = []
for layer in db:
z = layer_info[layer]
for obj in db[layer]:
x, y, net, otype = obj
if net not in result:
result[net] = []

names = oEditor.GetBodyNamesByPosition(["NAME:Parameters",
"XPosition:=", "{}{}".format(x, unit),
"YPosition:=", "{}{}".format(y, unit),
"ZPosition:=", "{}{}".format(z * 1000, 'mm')
])

for name in names:
if name in dielectric:
continue
else:
dk = oEditor.GetPropertyValue('Geometry3DAttributeTab', name, 'Solve Inside')
if str(dk) == 'true':
dielectric.append(name)
elif name not in result[net]:
result[net].append(name)
else:
pass

# AddWarningMessage(str(result))

total = len(result)
n = 0
for net in result:
try:
n += 1
oDesktop.AddMessage(oProject.GetName(), oDesign.GetName(), 0, '{}/{}'.format(n, total))

oEditor.ChangeProperty(
[
"NAME:AllTabs",
[
"NAME:Geometry3DAttributeTab",
[
"NAME:PropServers"
] + result[net],
[
"NAME:ChangedProps",
[
"NAME:Name",
"Value:=" , ''.join(x if x.isalnum() else '_' for x in net)
]
]
]
])
except:
AddErrorMessage(str(result[name]))
oDesktop.AddMessage(oProject.GetName(), oDesign.GetName() ,0 ,'Completed!')

(圖一)以netname命名物件


2021年4月15日 星期四

如何插入編號遞增的HFSS設計並返回設計名稱

在project插入新的HFSS design需要設定design name,如果design name存在的話,程式則會中斷。以下函式呼叫可以遞增design編號的方式插入新的HFSS design。

import re
import ScriptEnv

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


def insertHFSS(custom_name=None):
existing_designs = [i.GetName() for i in oProject.GetDesigns()]

if custom_name:
design_name = custom_name
else:
try:
oDesign = oProject.GetActiveDesign()
design_name = oDesign.GetName()
except:
design_name = 'HFSSDesign0'

m = re.search('(\d+)$', design_name)
if m:
N = int(m.group(1))
else:
design_name = design_name + str(0)
N = 0

while True:
design_name = re.sub('(\d+)$', str(N + 1), design_name)
if design_name not in existing_designs:
break
else:
N += 1

oProject.InsertDesign("HFSS", design_name, "DrivenTerminal", "")

return design_name


insertHFSS()

(圖一) 編號遞增插入設計


2021年4月12日 星期一

如何快速讀取兩個Q3D Matrix CSV並計算及輸出偏差值圖及CSV表

 

"""
Created on Thu Apr 8 11:16:35 2021
Output deviation of 2 Q3D matrix
@author: mlin
"""
# -------------------------------User Input-------------------------------------
path = 'd:/demo'
f_pair = [('case1.csv', 'case2.csv'), ]

# ---------------------------Don't Revise Code Below----------------------------
import os, math
import matplotlib.pyplot as plt

matrix_type = ['Capacitance Matrix',
'Conductance Matrix',
'DC Inductance Matrix',
'DC Resistance Matrix',
'AC Inductance Matrix',
'AC Resistance Matrix', ]


def getDiagonal(x):
N = int(math.sqrt(len(x)))
result = [x[i * N + i] for i in range(N)]
return result


def readQ3Dcsv(csv_path):
data = {i: [] for i in matrix_type}
name = {i: [] for i in matrix_type}

with open(csv_path) as f:
for line in f:
if line.strip() in matrix_type:
mtype = line.strip()
continue
else:
for i in line.strip().split(','):
try:
data[mtype].append(float(i))
continue
except:
...

try:
if i.strip() != '':
name[mtype].append(i)
except:
...

result = {i: getDiagonal(data[i]) for i in data}
return result, name


for f1, f2 in f_pair:
label = '{} - {}'.format(f1.split('.')[0], f2.split('.')[0])

f1 = os.path.join(path, f1)
f2 = os.path.join(path, f2)
data1, name1 = readQ3Dcsv(f1)
data2, name2 = readQ3Dcsv(f2)

with open(os.path.join(path, label + '.csv'), 'w') as f:
for mtype in data1:
result = []
f.writelines(f'\n[{mtype}]\n')
png_name = f'{label} [{mtype}]'
title = f'{label}\n[{mtype}]'
try:
for name, v1, v2 in zip(name1[mtype], data1[mtype], data2[mtype]):
deviation = (v2 - v1) * 100 / v1
result.append(deviation)
f.writelines(f'{name:36}{v2:12}{v1:12}{deviation:12.3f}%\n')
plt.plot(result, linewidth=1, color='b')
plt.title(title)
plt.ylim(-2, 2)
plt.grid()
plt.ylabel('deviation(%)')
plt.savefig(os.path.join(path, png_name + '.png'))
plt.show()
except:
print(f'Failed: {label}, {mtype}')

(圖一)差異圖

(圖二)差異表



2021年4月7日 星期三

將剪貼簿的內容輸出到netlist視窗當中

因為AEDT網表視窗沒有匯入及編輯功能,很難開發電路自動化工具。一個方法是將修改的網表填入Clipboard.SetText()當中,文字檔會送到剪貼簿保存起來。接著用CtrlV()觸發Ctrl+V按鍵事件將剪貼簿內容填入網表視窗。接著便可以執行電路模擬。

要模擬不同按鈕事件,按鈕編號可再以下連結查詢:

https://docs.microsoft.com/zh-tw/windows/win32/inputdev/virtual-key-codes?redirectedfrom=MSDN\

# ----------------------------------------------

# Script Recorded by ANSYS Electronics Desktop Version 2021.1.0
# 15:00:13 Apr 07, 2021
# ----------------------------------------------
import ScriptEnv
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import Clipboard

ScriptEnv.Initialize("Ansoft.ElectronicsDesktop")
oDesktop.RestoreWindow()
oProject = oDesktop.GetActiveProject()
oDesign = oProject.GetActiveDesign()

import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
KEYEVENTF_SCANCODE = 0x0008

MAPVK_VK_TO_VSC = 0

wintypes.ULONG_PTR = wintypes.WPARAM

class MOUSEINPUT(ctypes.Structure):
_fields_ = (("dx", wintypes.LONG),
("dy", wintypes.LONG),
("mouseData", wintypes.DWORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))

class KEYBDINPUT(ctypes.Structure):
_fields_ = (("wVk", wintypes.WORD),
("wScan", wintypes.WORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", wintypes.ULONG_PTR))

def __init__(self, *args, **kwds):
super(KEYBDINPUT, self).__init__(*args, **kwds)
# some programs use the scan code even if KEYEVENTF_SCANCODE
# isn't set in dwFflags, so attempt to map the correct code.
if not self.dwFlags & KEYEVENTF_UNICODE:
self.wScan = user32.MapVirtualKeyExW(self.wVk,
MAPVK_VK_TO_VSC, 0)

class HARDWAREINPUT(ctypes.Structure):
_fields_ = (("uMsg", wintypes.DWORD),
("wParamL", wintypes.WORD),
("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
class _INPUT(ctypes.Union):
_fields_ = (("ki", KEYBDINPUT),
("mi", MOUSEINPUT),
("hi", HARDWAREINPUT))
_anonymous_ = ("_input",)
_fields_ = (("type", wintypes.DWORD),
("_input", _INPUT))

LPINPUT = ctypes.POINTER(INPUT)

def _check_count(result, func, args):
if result == 0:
raise ctypes.WinError(ctypes.get_last_error())
return args

user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
LPINPUT, # pInputs
ctypes.c_int) # cbSize

# Functions

def PressKey(hexKeyCode):
x = INPUT(type=INPUT_KEYBOARD,
ki=KEYBDINPUT(wVk=hexKeyCode))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):
x = INPUT(type=INPUT_KEYBOARD,
ki=KEYBDINPUT(wVk=hexKeyCode,
dwFlags=KEYEVENTF_KEYUP))
user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def CtrlV():
PressKey(0x11)
PressKey(0x56)
ReleaseKey(0x11)
ReleaseKey(0x56)
if __name__ =="__main__":
Clipboard.SetText('HelloWorld')
CtrlV()

(圖一) 執行腳本可輸出文字到netlist視窗當中


2021年4月5日 星期一

將HFSS個別輸出的遠場圖更名為port的名稱

import os

path = 'd:/demo_FFD'

with open(os.path.join(path, 'exportelement.txt')) as f:
text = f.readlines()

for line in text[1:]:
try:
port_name, file_name = line.strip().split()
port_name = port_name.split(':')[0]
os.rename('{}/{}.ffd'.format(path, file_name),
'{}/{}.ffd'.format(path, port_name))
except:
pass


2021年4月4日 星期日

一次畫出IBIS當中所有[Model]的[Rising Waveform]跟[Falling Waveform]

 指定IBIS檔案路徑,執行以下腳本即可。

# -*- coding: utf-8 -*-
"""
Created on Sun Apr 4 09:23:17 2021

@author: mlin
"""
import matplotlib.pyplot as plt


def plotWaveform(model_name, waveform):
plt.figure(figsize=(7, 4))
plt.clf()
header = []
_time_, _typ_, _min_, _max_ = [], [], [], []
for i in waveform:
if '=' in i:
header.append(i.strip())
continue
if '|' in i:
continue
try:
_time, _typ, _min, _max = i.strip().split()
_time_.append(float(_time.lower().replace('ps', 'e-12').replace('ns', 'e-9').replace('us', 'e-6')))
_typ_.append(
float(_typ.lower().replace('nv', 'e-9').replace('uv', 'e-6').replace('mv', 'e-3').replace('v', '')))
_min_.append(
float(_min.lower().replace('nv', 'e-9').replace('uv', 'e-6').replace('mv', 'e-3').replace('v', '')))
_max_.append(
float(_max.lower().replace('nv', 'e-9').replace('uv', 'e-6').replace('mv', 'e-3').replace('v', '')))
except:
pass
plt.plot(_time_, _typ_, 'g')
plt.plot(_time_, _min_, 'b')
plt.plot(_time_, _max_, 'r')
plt.title('{}\n{}\n{}\n{}'.format(model_name, ';'.join(header[0:3]), ';'.join(header[3:6]), ';'.join(header[6:9])))
plt.xlabel('Time(sec)')
plt.ylabel('Waveform(v)')
plt.grid()
plt.show()


data = {}
container = None
with open('D:\OneDrive - ANSYS, Inc/Models/IBIS/tpz015lg.ibs') as f:
for i in f:
if i.startswith('[Model]'):
_, model_name = i.strip().split()
data[model_name] = {}
continue

if i.startswith('['):
container = None

if '[rising waveform]' in i.lower():
try:
data[model_name]['rising_waveform'].append([])
except:
data[model_name]['rising_waveform'] = [[]]
finally:
container = data[model_name]['rising_waveform'][-1]
continue

if '[falling waveform]' in i.lower():
try:
data[model_name]['falling_waveform'].append([])
except:
data[model_name]['falling_waveform'] = [[]]
finally:
container = data[model_name]['falling_waveform'][-1]
continue

try:
container.append(i)
except:
pass

for model in data:
try:
for i in data[model]['rising_waveform']:
plotWaveform(model, i)
except:
pass
for model in data:
try:
for i in data[model]['falling_waveform']:
plotWaveform(model, i)
except:
pass
[圖一]畫出所有[Model]的[Rising Waveform]跟[Falling Waveform]