2020年7月28日 星期二

如何查詢license的使用狀況

當公司內部有許多人在使用ANSYS軟體時,在license不夠用時,我們會想要找出誰在使用license。下面程式碼可以查詢位於遠端的ANSYS license manager並輸出使用者相關訊息。

修改下面程式碼的變數ip為license manager安裝的電腦IP,接著在AEDT當中Run Script即可。幾秒鐘後便會跳出notepad顯示user.log,如圖一。

import os
import re

ip='locahost'

os.chdir(os.path.dirname(__file__))
os.environ['path']+=';{}'.format(oDesktop.GetExeDir())
os.environ['path']+=';{}\licensingclient\winx64'.format(oDesktop.GetExeDir())
os.system('lmutil lmstat -a -c 1055@{} > license_usage.log'.format(ip.strip()))
with open('license_usage.log') as f:
    text = f.readlines()

usage_info = []
flag = False
for i in text:
    if len(i.strip())==0 or 'floating license' in i:
        continue
    
    if i.startswith('Users'):
        flag = False
        continue
    
    m = re.search('"(\w*?)"', i)
    if m:
        feature = m.group(1)
        flag = True
        continue
    
    if flag == True:
        if i.endswith('licenses\n'):
            user, machine = i.split()[0:2]
            number = i.split()[-2]
            usage_info.append('{:36}{:12}{:>24} x{:<3}\n'.format(machine, user, feature, number))
        else:
            try:
                user, machine = i.split()[0:2]
                usage_info.append('{:36}{:12}{:>24} x{:<3}\n'.format(machine, user, feature, 1))
            except:
                pass

usage_info.sort()            

with open('user.log', 'w') as f:
    f.writelines(usage_info)
os.system('notepad user.log')

(圖一) 電腦、使用者、license及使用數量

        



2020年7月24日 星期五

如何取得anstsedt當中 -scriptargs所指定的參數值?

程式開發人員完成自動化程式之後,會將其封裝起來,讓使用者在不需要接觸到程式碼的狀況之下來使用程式,避免使用者無意間錯誤改動代碼。這可以通過兩種方式達到,一是透過GUI,二是透過命令列,或是兩種方式皆支援。像是在AEDT當中,使用者可以雙擊win64目錄底下的ansysedt.exe啟動AEDT環境,也可以透過命令列執行ansysedt.exe並使其在不開啟AEDT環境的狀況下完成建模,模擬與資料處理。

這裡介紹命令列的執行方式。舉個簡單的例子,假設開發者完成了myscript.py提供了使用者。使用者只要開啟命令列視窗並在其中輸入python myscript.py即可。如果需要傳入參數(argument),把參數依序加在myscript.py之後以空白隔開,如python myscript.py arg1, arg2, arg3。myscript.py可以透過sys.argv取得使用者輸入的參數值。以下的範例是將輸入的參數列印到畫面當中,如圖一。

myscript.py程式碼:

import sys
for i in sys.argv:
    print(i)

(圖一) 列印輸入的參數

接下來我們介紹如何在ansysedt執行.py並傳入參數。在命令列執行ansysedt的時候可以指定要執行的.py檔,比方說myscript.py。此時可以透過-scriptargs指定要輸入到.py檔的參數,指令如下,當中"1mm 2mm 3mm"便是要傳送到myscript.py的參數。

命令列視窗指令:

path=%path%;C:\Program Files\AnsysEM\AnsysEM20.2\Win64
ansysedt -scriptargs "1mm 2mm 3mm" -RunScript d:\demo\myscript.py

或是用python程式模擬命令列視窗指令:

import os
os.environ['path']+='%path%;C:\Program Files\AnsysEM\AnsysEM20.2\Win64'
os.system('ansysedt -scriptargs "1mm 2mm 3mm" -RunScript d:\demo\qqq.py')

那麼如何在myscript.py當中讀取到"1mm 2mm 3mm"這一組參數值呢?在myscript.py當中的變數ScriptArgument會接收到"1mm 2mm 3mm"字串,只要將字串分割便可以取出每一個參數值了。底下代碼取得命令列當中的參數,分割之後依序輸出到MessageWindow當中,如圖二。

myscript.py程式碼

for i in ScriptArgument.split():
    AddWarningMessage(i)

(圖二) 取得輸入參數,分割並輸出到訊息視窗


2020年7月22日 星期三

如何比較兩個目錄檔案的差異

最近AEDT 2020R2剛釋出,筆者想要找出2020R2跟2020R1相比多/少了那些.aedtz範例,.pdf檔或其他檔案。因此寫了一個小的script來輸出兩個版本檔案的差異。輸出2020R1有的檔案而2020R2沒有的,以及2020R2有的檔案而2020R1沒有的。比較完成之後輸出到compare.txt文檔並自動開啟notepad++顯示,兩者中間用分隔線隔開以資區別。程式碼如下,可以在Spyder或AEDT當中執行:

dir1 = r'C:\Program Files\AnsysEM\AnsysEM20.1'
dir2 = r'C:\Program Files\AnsysEM\AnsysEM20.2'
outputtxt = 'd:/demo/compare.txt'

import os
from filecmp import dircmp

result_right=[]
result_left=[]

def print_diff_files(dcmp):
    global result_right, result_left
    for name in dcmp.right_only:
        result_right.append('{}\\{}\n'.format(dcmp.right, name))
    for name in dcmp.left_only:
        result_left.append('{}\\{}\n'.format(dcmp.right, name))    
    for sub_dcmp in dcmp.subdirs.values():
        print_diff_files(sub_dcmp)

dcmp = dircmp(dir1, dir2)
print_diff_files(dcmp)
with open(outputtxt, 'w') as f:
    f.writelines(result_left)
    f.writelines('-'*120+'\n')
    f.writelines(result_right)

os.system('notepad++ d:/demo/compare.txt')

(圖一) 兩個目錄之間檔案差異比較


2020年7月19日 星期日

如何傳遞上層變數到編譯過的模組當中

我們會將重要的函數或類別包裝到模組當中,再將模組編譯成.dll以保護程式碼不會被別人所看到(請參考:如何加密自動化檔案)。模組當中有些操作會引用到AEDT的系統變量,像是oProject、oDesign或是oEditor等等。舉例來說,下面程式碼為my_module.py的內容,my_module.py當中定義了函數add_1mm_sphere(x, y, z) ,其輸入參數為座標點。執行函式會在該輸入座標位置產生1mm半徑的圓球,請注意程式當中用到了oEditor物件。

my_module.py程式碼

def add_1mm_sphere(x, y, z):
    oEditor.CreateSphere(
        [
            "NAME:SphereParameters",
            "XCenter:=" , "{}mm".format(x),
            "YCenter:=" , "{}mm".format(y),
            "ZCenter:=" , "{}mm".format(z),
            "Radius:=" , "1mm"
        ], 
        [
            "NAME:Attributes",
            "Name:=" , "Sphere1",
            "Flags:=" , "",
            "Color:=" , "(143 175 143)",
            "Transparency:=" , 0,
            "PartCoordinateSystem:=", "Global",
            "UDMId:=" , "",
            "MaterialValue:=" , "\"vacuum\"",
            "SurfaceMaterialValue:=", "\"\"",
            "SolveInside:=" , True,
            "ShellElement:=" , False,
            "ShellElementThickness:=", "0mm",
            "IsMaterialEditable:=" , True,
            "UseMaterialAppearance:=", False,
            "IsLightweight:=" , False
        ])

我們先將my_module.py編譯成my_module.dll檔。然後在主程式main.py加入my_module.dll參考,並呼叫add_1mm_sphere(x, y, z)函式。注意oEditor在main.py當中被宣告。

main.py程式碼

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

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

import clr
clr.AddReference('my_module.dll')
import my_module

my_module.add_1mm_sphere(1,2,3)

在HFSS執行main.py,此時出現錯誤訊息如下:

(圖一) 錯誤訊息

此錯誤訊息表示my_module當中的oEditor未被定義。雖然oEditor在main.py有被定義,但oEditor顯然無法被模組所識別。此時加入一行程式碼my_module.oEditor = oEditor即可將上層main.py的oEditor傳入到my_module當中。

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

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

import clr
clr.AddReference('my_module.dll')
import my_module

my_module.oEditor = oEditor
my_module.add_1mm_sphere(1,2,3)

再次執行程式main.py即可正確產生圓球結構(如圖二)。

(圖二) 程式正確輸出

如何在不開啟Anaconda的狀況底下啟動帶有PyQt5操作介面的CPython程式

筆者在Anaconda上開發了一支小程式calculator.py,搭配上用PyQT5框架開發的操作介面,可用來做複數運算。在Anaconda當中執行該程式,視窗如預期顯示在畫面當中,在視窗當中輸入複數計算式測試,輸出也都一切正常。(如圖一)

(圖一) PyQt5為介面的複數計算機

接下來筆者想要在不開啟Anaconda來啟動該程式,於是建立了批次檔calculator.bat,當中嘗試使用python.exe .\calculator.py命令來執行。雙擊該批次檔,然而在命令列視窗卻跳出了錯誤訊息:

Traceback (most recent call last):
  File ".\main.py", line 4, in <module>
    from PyQt5 import QtWidgets, uic
ImportError: DLL load failed: 找不到指定的模組。

錯誤訊息顯示程式因為找不到相關的DLL檔案而導致執行失敗。在網路搜尋一番,發現必須先加上call activate.bat指令,才能正確載入DLL檔。因此修改批次檔calculator.bat內容如下:

call C:\ProgramData\Anaconda3\Scripts\activate.bat
C:\ProgramData\Anaconda3\python.exe .\calculator.py

完成之後,雙擊該批次檔便可以正常開啟程式的操作介面了:

(圖二) 透過雙擊批次檔開啟視窗操作介面

接下來便想要將批次檔捷徑釘選到開始視窗,方便之後使用。首先對該批次檔建立捷徑,接著卻發現捷徑並不允許被釘選到開始視窗。所幸在Google找到一個解決方法。首先打開該捷徑的內容視窗。在目標(T)欄位最前面加上加上cmd /c指令,同時將執行(R)欄位設為最小化(如圖三)。完成之後,便可以將該捷徑釘選到開始視窗(如圖四)。在執行時也不會跳出命令列視窗了(最小化)。

(圖三) 修改捷徑屬性

(圖四) 將捷徑釘選到開始畫面

2020年7月17日 星期五

使用QT Designer建立QT視窗操作面簡介

QT是一個跨平台的視窗設計框架,可使用在CPython環境。在完成Anaconda安裝之後,可以在路徑C:\Users\用戶名\AppData\Local\Continuum\anaconda3\Library\bin找到designer.exe。雙擊designer.exe即可啟動Qt Designer設計工具(如圖一)。開發者可以用拖拉的方式完成視窗外觀設計並儲存成.ui檔。圖一的UI包含一個輸入框及一個按鈕。如果您想測試,可以將下方的MainForm.ui檔案程式碼複製到MainForm.ui檔案當中並存檔。

(圖一) QtDesigner工具

.ui檔必須搭配業務邏輯代碼.py檔來完成工作,打開Anaconda並將底下main.py的代碼貼到main.py檔,並與MainForm.ui儲存在相同目錄底下。完成之後,在Anaconda當中執行main.py。此時視窗會開啟,在輸入框輸入文字,點擊按鈕,輸入的文字便會顯示在Console當中,如圖二。這代碼演示了一個UI的基本工作模式,適合初學者作為QT視窗設計入門研究。

main.py程式碼

from PyQt5 import QtWidgets, uic
Ui_MainWindow, QtBaseClass = uic.loadUiType('MainForm.ui')

import sys

class MainUi(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.click)
        
    def click(self):
        text = self.lineEdit.text()
        print(text)

if __name__ == "__main__":
    def run_app():
        app = QtWidgets.QApplication(sys.argv)
        window = MainUi()
        window.show()
        app.exec_()
    run_app()

MainForm.ui檔案程式碼
    
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>345</width>
    <height>172</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>220</x>
      <y>80</y>
      <width>101</width>
      <height>41</height>
     </rect>
    </property>
    <property name="text">
     <string>Run</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="lineEdit">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>10</y>
      <width>311</width>
      <height>41</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <pointsize>16</pointsize>
     </font>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>345</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

(圖二) 在輸入框輸入字串,點擊按鈕將其輸出到Console當中

環境變數路徑Path與IronPython的模組路徑

一個大的程式是由許許多多的小程式及配置檔所組成。而這些小程式和配置檔一般是按照功能分門別類放在不同的目錄當中。如果要呼叫這些小程式及配置檔,必須連同完整路徑寫入程式才行,比如C:\Program Files\AnsysEM\AnsysEM20.2\Win64\ansysedt.exe。呼叫一多,冗長的路徑會使得程式顯得雜亂。況且如果小程式更改了安裝路徑,那麼又要逐行修改呼叫的路徑,修改工作不但麻煩且容易出錯。

(圖一) Windows作業系統Path環境變數

一個簡單的解決方法便是建立Path環境變數。在Windows作業系統當中,環境變數Path字串是由一連串透過分號分隔的目錄路徑所組成,如圖一。當我們在命令列視窗輸入要執行的程式名稱時,比方說ansysedt.exe,則作業系統會到Path當中所列出的目錄依序尋找其中是否有ansysedt.exe。如果找到便會啟動。如果找不到,則會出現尋找不到的訊息。在Python當中如果需要呼叫第三方程式,而該程式所處目錄路徑又不在作業系統的Path環境變數當中,可以用os.environ['path'] += '第三方程式目錄路徑'來添加新的目錄路徑到Path環境變數當中,以擴展作業系統的搜尋範圍。要注意的是,這只有在程式執行期間有效,並不會影響到作業系統的設定。

除了執行第三方程式,Python更常需要呼叫其他Python module來完成工作。想要知道Python搜尋模組的目錄路徑,可以輸入sys.path來檢視,如圖二。如果要加入新的自訂模組,而且該模組存放的目錄不在sys.path當中也跟程式不在相同目錄,那麼可以在程式當中使用sys.path.append('模組目錄路徑')來加入自定模組的目錄路徑。要留意的是作業系統的Path和sys.path是完全不同的事,千萬不要搞混了。

(圖二) AEDT IronPython Module的路徑

2020年7月16日 星期四

AEDT的視窗操作介面範例

由於AEDT支持的程式語言為IronPython,因此使用的視窗介面框架為WPF(Windows Presentation Foundation)。附帶一提,IronPython並無法支援CPython的tkinter。在AEDT啟動WPF視窗需要準備兩個檔案:.py檔和.xaml檔。下面兩個檔案template.py及template.xaml是我經常用來演示最基本的視窗範例,你可以在電腦當中準備這兩個檔案,並放置在同一目錄底下。完成之後,在AEDT執行template.py,即會跳出如圖一的視窗介面。在輸入框輸入文字之後按下Execute鍵,輸入值便會顯示在訊息視窗。這一組介面程式雖然簡單,但是相當適合作為初學者研究AEDT操作介面設計的入門範例。

(圖一) WPF視窗介面

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 wpf
from System.Windows import Window
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')
        oDesktop.ClearMessages("","",2)
        
    def Button_Click(self, sender, e):        
        AddWarningMessage(self.input_tb.Text)
        
#Code End----------------------------------------------------------------------|       
MyWindow().ShowDialog()

template.xaml 檔案代碼

<Window 
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       Title="Template Window" Height="144.165" Width="300" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Background="#FFEAEBFF">
    <Grid>
        <Button Content="Execute" HorizontalAlignment="Right" Height="36" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="93" Click="Button_Click"/>
        <TextBox x:Name="input_tb" HorizontalAlignment="Right" Height="27" Margin="0,10,10,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="214" VerticalContentAlignment="Center"/>
        <Label Content="Value:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="55"/>
    </Grid>
</Window> 

容器型別方法操作介紹

Python的容器型別如串列(list)或字串(string)都可以透過自身所提供的方法加以操作,但是不同的方法其作用對象略有差異,常會造成初學者感到困惑,以下做一個簡單的解釋。

第一種類型的方法是作用於自身,無返回值。比方說list的append()方法:

(圖一) 列表append()方法

第二種類型的方法是不作用在自身,有返回值。比方說string的upper()方法:

(圖二) 字串upper()方法

第三種類型的方法是作用在自身,也有返回值。比方說string的pop()方法:

(圖三) 字串的pop()方法

如果返回值本身也有方法可以操作,那麼我們可以將這些操作串接起來,如圖三:

(圖四) 串接操作

分析方法由內往外,簡述如下:
  • '{}+{}j'.format(1,2)返回字串'1+2j'
  • '1+2j'.upper()返回字串'1+2J'
  • complex('1+2J')返回複數1.0+2.0j
  • (1+2.0j).conjugate()返回共軛複數(1-2.0j)
  • (1-2.0j).imag返回虛部-2.0
如果方法沒有返回值,自然也就沒有辦法串接命令。

2020年7月15日 星期三

Python的惰性求值(Lazy Evaluation)特性

假設有兩組list分別儲存班上八位同學的數學分數及物理分數,每科皆為十分滿分。我們想要找出總分超過12分的同學人數,在Python當中我們可以用zip()將其分數配對成list of tuple,再用迴圈做運算,程式碼如下。輸出結果為4,有四位同學兩科分數加總大於12分。

score_math=[random.randint(0,10) for i in range(8)]
score_phy=[random.randint(0,10) for i in range(8)]
score_table=list(zip(score_math, score_phy))
num=0
for i, j in score_table:
    _sum = i + j
    if _sum > 12:
        num += 1
print(num)

(圖一) 分數資料結構

這組程式碼有一個問題,score_table儲存的資料數值與score_math及score_phy相同,只是排列的方法不同。同一份資料用了兩份記憶體容量意味著記憶體的浪費。一種解決方法是不要產生score_table。取而代之的是在迴圈階段再作取值。比方說,輪到第一個人時,程式到score_math及score_phy找出第一筆資料(3,1),做加總及判斷運算。接著迴圈輪到第二個人時,再到score_math及score_phy找出第二筆資料(9,5)做處理。依此類推直到所有學生的分數都處理完畢為止。要做到這樣的功能,只要在上面的程式碼拿掉zip前面的list()即可。此時變量z就不會在建立的時候被賦予值,而是等到迴圈輪到的時候再做取值的動作,這種延後取值的特性,也被稱之為Lazy Evaluation(惰性求值)。

底下我們用1,000,000筆學生資料來比較list(zip)與zip所使用的時間跟耗費的記憶體,圖二使用了list(zip),耗費220MB, 0.216sec完成計算,圖三使用zip,僅耗費147MB, 0.152秒完成計算,兩者計算結果相同,皆為297871,所耗費的計算資源卻大不相同。惰性求值已經被廣為採用作為加速運算及減少運算資源的方法。

(圖二) 用list(zip)

(圖三) 僅用zip

2020年7月14日 星期二

輸出目錄底下所有S參數的頻率範圍

一個使用者被老闆要求找出特定目錄底下每一個S參數的頻率範圍。該目錄當中存放超過上千個S參數檔案,port數從兩個到上百個的都有。有的是廠商提供的,有的是模擬得到的,有的則是網路分析儀量測結果。使用的單位有的是MHz, 有的是GHz,都不盡相同。如果要用文字編輯器一個一個打開來看,並手動紀錄檔名及頻率範圍,估計一整個禮拜也完成不了老闆交付的工作。於是該使用者找我詢問看有沒有辦法在AEDT當中一次讀取所有的S參數檔案並輸出頻率範圍。很遺憾的是AEDT並沒有這樣的功能。於是筆者花了點時間寫了下面的腳本來完成上述的工作。不到40行的程式碼讓原本預估要一整周的工作時間最後花費不到十分鐘便完成。該使用者的工作獲得了老闆的讚許,也讓他開始嘗試學習編寫腳本來提高工作的效率。

各位有興趣不妨試試看下面的程式碼。將下面程式碼複製到檔名為getSnpFreqRange.py的文字檔當中,修改程式當中snp_dir的目錄,在AEDT當中執行。待工作結束即可在路徑當中找到output.txt(如圖一),當中記錄了目錄底下每個S參數及對應的頻率範圍。

import os

snp_dir = 'd:/demo2'

def getSnpRange(snp_path):
    ext = snp_path.split('.')[-1]
    N=int(ext[1:-1])
    
    with open(snp_path) as f:
        text = f.readlines()
    
    unit_mapping = {'hz':1e0, 'khz':1e3, 'mhz':1e6, 'ghz':1e9}
    values = []
    for i in text:
        if i[0] == '!':
            continue
        elif i[0] =='#':
            unit = i.split()[1]
            scale = unit_mapping[unit.lower()]
        else:
            x = i.split()
            values += map(float, x)
    freq = [i*scale for i in values[::N*N*2+1]]
    freq_range = (freq[0], freq[-1])
    return freq_range

files = os.listdir(snp_dir)
failed = []
result = []
for i in files:
    try:
        frange = getSnpRange(snp_dir+'/'+i)
        result.append('{:80}:{}\n'.format(i, frange))
    except:
        failed.append(i)

with open(snp_dir + '/output.txt', 'w') as f:
    f.writelines(result)
    f.writelines('_'*120 + "\nFailed:\n{}".format('\n'.join(failed)))


(圖一) output.txt記錄S參數檔及其頻率範圍

利用匯出與匯入函式協助自動化程式開發

一個模擬需要相當多的輸入資料,像是結構、材料、邊界條件、網格設定等等。模擬當中也會產出各式的訊息,像是收斂狀況、模擬時間與耗用的記憶體等等。模擬完成之後又會有S參數、近電磁場、遠場、等效模型、頻寬資料需要判讀。當中大部分的資料都可以透過函式匯出到文字格式檔案當中。自動化程式便可以透過讀取或修改這些文字檔來達到更改設定或是結果分析的目的。初步估計HFSS 2020R2就提供了多達57種不同檔案的匯出。匯入的函式也有24種。開發者可以善用這些匯出/匯入函式來加速自動化程式的開發。

舉例來說,筆者在開發5G波束優化的演算法時,便是先透過ExportRadiationFieldsToFile()輸出毫米波天線陣列每個端口在1瓦/0度的輻射遠場。接著後處理再透過優化的方法,計算出毫米波天線陣列所能達成之最佳增益及對應到每個端口的相位。這些工作在外部處理比較容易且執行速度更是快上許多(參考下面影片)。



另外一個例子是在3D Layout的堆疊設定。一個使用者時常需要在3D Layout的堆疊視窗設定上百層的厚度、Dk、Df等參數,這個工作耗費他相當多的時間。他詢問是否能從EXCEL當中直接讀入PCB板廠所提供的資料。因此筆者利用了ExportStackup()及ImportStackup()開發了一個自動化工具(參考下面影片),大幅簡化了使用者設定PCB堆疊的時間。



以上兩個例子都是透過讀取匯出的資料檔來簡化自動化程式的開發。下面是HFSS 2020R2的匯出及匯入函式供各位參考:

HFSS 2020R2匯出函式

  1. ExportOptionsFiles
  2. ExportDataset
  3. ExportMaterial
  4. ExportComponent
  5. ExportDataset
  6. ExportFootprint
  7. ExportMaterial
  8. ExportPadstack
  9. ExportScript
  10. ExportSurfaceMaterial
  11. ExportSymbol
  12. ExportConvergence
  13. ExportDataset
  14. ExportMeshStats
  15. ExportProfile
  16. Export
  17. ExportGDSII
  18. ExportModelImageToFile
  19. ExportFullWaveSpice
  20. ExportNMFData
  21. ExportNetworkData
  22. ExportDOELocalSensitivity
  23. ExportDOELocalSensitivityCurve
  24. ExportDOEResponseCurve
  25. ExportDOEResponseCurveSlices
  26. ExportDOEResponseSurface
  27. ExportDXConfigFile
  28. ExportOptimetricsProfile
  29. ExportOptimetricsResult
  30. ExportParametricResults
  31. ExportRespSurfaceMinMaxTable
  32. ExportRespSurfacePlotToFile
  33. ExportRespSurfaceRefinePoints
  34. ExportRespSurfaceResponsePoints
  35. ExportRespSurfaceVerificationPoints
  36. ExportEigenmodes
  37. ExportForHSpice
  38. ExportForSpice
  39. ExportNMFData
  40. ExportNetworkData
  41. ExportTransientData
  42. ExportWElement
  43. ExportFieldPlot
  44. ExportMarkerTable
  45. ExportOnGrid
  46. ExportPlotImageToFile
  47. ExportToFile
  48. ExportRadiationFieldsToFile
  49. ExportRadiationParametersToFile
  50. ExportRadiationSurfaceMeshAndFields
  51. ExportEyeMaskViolation
  52. ExportImageToFile
  53. ExportReportDataToFile
  54. ExportTableToFile
  55. ExportToFile
  56. ExportUniformPointsToFile
  57. ExportOutputVariables

HFSS 2020R2匯入函式

  1. ImportDataset
  2. Import
  3. ImportDXF
  4. ImportFromClipboard
  5. ImportGDSII
  6. ImportANF
  7. ImportANFV2
  8. ImportAWRMicrowaveOffice
  9. ImportAutoCAD
  10. ImportEDB
  11. ImportExtracta
  12. ImportGDSII
  13. ImportGerber
  14. ImportIDF
  15. ImportIDFandMerge
  16. ImportIPC
  17. ImportODB
  18. ImportXFL
  19. ImportSetup
  20. ImportSolution
  21. ImportTable
  22. ImportIntoReport
  23. ImportReportDataIntoReport
  24. ImportOutputVariables

2020年7月13日 星期一

如何加密自動化檔案

當我們需要分享開發完成的自動化程式給其他人使用時,為了保護開發者的智慧財產權,我們會想要將程式加密起來。在IronPython當中,我們可以透過編譯dll的方式,將原本為文字格式的Python加密。接下來以一個簡單的範例介紹如何執行程式加密。

首先準備兩個Python檔案:main.py及mymodule.py,兩個檔案放在d:/demo目錄底下。main.py的程式碼:

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

import mymodule

x = mymodule.myadd(1,3)
AddWarningMessage(str(x))

主程式只包含簡單功能的代碼,像是切換工作路徑,匯入模組,呼叫模組的函式完成計算,輸出結果到訊息視窗等等。至於重要的資料及演算法代碼則是放在mymodule.py,本範例只有一個函式:

def myadd(x, y):
    return x+y

在AEDT執行main.py,訊息視窗會顯示答案,如圖一:

(圖一) 程式正確執行


功能測試無誤之後,接下來編寫一個批次檔compile.bat來針對mymodule.py執行dll編譯的工作:

PATH=%PATH%;C:\Program Files\AnsysEM\AnsysEM20.2\Win64\common\IronPython

ipy64 "C:/Program Files/AnsysEM/AnsysEM20.2/Win64/common/IronPython/Tools/Scripts/pyc.py" "D:/demo/mymodule.py"
pause

在windows命令列視窗執行compile.bat,如果編譯成功會出現如圖二的訊息,此時目錄下會出現mymodule.dll檔案。

(圖二) 成功編譯.dll訊息

最後在主程式碼當中加上clr.AddReferenceToFileAndPath("mymodule.dll")指令來鏈結.dll檔,程式碼如下。

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

import clr
clr.AddReferenceToFileAndPath("mymodule.dll")

import mymodule

x = mymodule.myadd(1,3)
AddWarningMessage(str(x))

從d:/demo目錄底下移除mymodule.py檔之後,執行main.py。如果可以正常執行,即代表鏈結無誤。接下來只要提供main.py以及mymodule.dll給需要的人就可以了。

2020年7月12日 星期日

利用UDP(User Defined Primitives)建立參數化螺旋電感簡介

螺旋電感在高頻晶片及模組上隨處可見,HFSS也經常用來計算螺旋電感的L值及Q值。然而電感結構五花八門,大小不一。許多設計者希望能透過程式自動生成螺旋電感模型,方便調整尺寸及圈數以快速找到所需的L/Q值。然而一般自動化程式雖然允許使用者設定參數(如圈數、線寬、間距、邊數、內徑、厚度...),如以下影片,但是生成出來的結構只能用在一次模擬。如果L/Q值不如預期,只能手動更改參數來生成結構,執行模擬。不斷的重複上述步驟,直到找到設計目標為止。雖然是省去了繪圖的時間,但是離完全自動化仍有一大段距離。


理想的流程是透過AEDT的優化引擎Optimetrics自動調整電感結構參數,檢查L/Q值直到找到符合設計目標的電感尺寸為止。要達到此目標,首先就是要讓結構可以參數化,然而這一點並不是那麼容易。AEDT的3D編輯環境可以做到一定程度的結構參數化,但僅限於簡單幾何操作,如平移、旋轉,張角等等。螺旋電感的結構過於複雜,無法用簡單的操作來達到參數化。要完成複雜結構參數化必須要借助UDP(User Defined Primitives)的技術。UDP的目的是讓使用著透過編程建立複雜3D結構。UDP的相關說明可參考HFSS Scripting Guide第三章。

(圖一)HFSS內建UDP範例

UDP基於物件的框架,開發者必須具備物件導向設計(OOP)的概念。繼承IUDPExtension類別,並必須實作類別當中的函式,使其可以相容AEDT。各位可以在HFSS當中開啟一個螺旋電感的UDP範例,如圖一。該範例的Python程式碼RectangularSpiral.py可以在C:\Program Files\AnsysEM\AnsysEM20.2\Win64\syslib\UserDefinedPrimitives\Examples\找到。要開發UDP比較簡單的做法是複製該檔案並修改當中的函式。要注意UDP當中能呼叫的函式與一般使用的Scripting函式不同。我們可以在HFSS Scripting Guide的UDPFunctionLibrary找到UDP函數的完整說明。 以下是其中幾個:

(圖二) UDP函式列表

這裡是以螺旋電感為例,但UDP使用並不侷限於螺旋電感。UDP產生的模型可以合併其他參數化模塊生成參數化的3D Component。


淺談EDB說明文件的問題

在"EDB(Electronic Database)簡介"一文,我簡單的介紹了EDB的概念。本文我則想從程式開發的角度來說明EDB的特性。下面這一段代碼可以生成一個.aedb目錄,當中包含了一個sample_cell的layout設計。layout當中則有"Top" 層,一個圓形,及一組名為"sample net"的net。匯入該.aedb之後,可看到設計(如圖一)。

import Ansys.Ansoft.Edb as edb
db = edb.Database.Create(r"d:\demo\sample_circle.aedb")
layer = edb.Cell.StackupLayer("TOP", edb.Cell.LayerType.SignalLayer, 1e-5, 0, "COPPER")
layerCollection = edb.Cell.LayerCollection()
layerCollection.AddStackupLayerAtElevation(layer)
cell = edb.Cell.Cell.Create(db, edb.Cell.CellType.CircuitCell, "sample_cell")
cell.GetLayout().SetLayerCollection(layerCollection)

net = edb.Cell.Net.Create(cell.GetLayout(), "sample_net")
polygon = edb.Cell.Primitive.Circle.Create(cell.GetLayout(), "TOP", net, 0, 0, .01)
db.Save()
db.Close()

(圖一) 匯入之.aedb設計

雖說這些設定用AEDT的3D layout所提供的函式庫也可以達成。但執行時,畫面須同步更新,當處理複雜結構時,效率不及EDB,這是EDB的最大好處。此外,由於EDB主要是RD人員在使用,因此EDB所能涵蓋的功能也比較新且廣,當中有些功能可能是3D layout函式庫所找不到的,這也是我們需要用到EDB的原因之一。

接下來從程式開發的角度來談談EDB。我認為採用EDB最大的障礙是說明文檔(如圖二)的問題。EDB說明文檔式到目前為止(2020R2),仍舊是以C#程式語言的框架來說明EDB函式的功能,而不是從Python的角度編寫。舉例來說,Python開發員對於Namespace這一詞可能會感到一頭霧水。其實Namespace是C#用來組織函式的架構,與Python的Package及Module觀念類似。由於EDB最初就是基於C#所開發出來的,所以說明文件自然是以C#框架為主。雖說直到近年EDB延伸到讓Python也可以使用,但是沒有提供Python版本的說明文件對想要利用EDB的人來說,開發難度依舊是極高。此外,範例過少也是一個問題,目前可以找到的幾則EDB設計範例放在目錄:C:\Program Files\AnsysEM\AnsysEM20.2\Win64\syslib\Toolkits\HFSS3DLayoutDesign\Reports

(圖二) EDB函式庫Help

從自動化程式開發的角度來說,比較適當的作法是以AEDT本身所提供的函式庫為主,EDB為輔。只有在AEDT函式效率不足或是不支援的狀況底下才尋求透過EDB來解決。至於EDB該怎麼使用,則可以請求原廠工程師協助。舉例來說,筆者最近開發的程式當中需取得ViaGroup的導電比例屬性,由於ViaGroup是一個較新的元件類別,因此這個屬性在2020R2仍舊無法從AEDT的GetPropertyValue()取得。筆者最終找到EDB當中的GetConductorPercentage()函數來完成工作,著實花了一番功夫。這個經驗也提供各位參考。釜底抽薪之計是原廠能提供Python版本的EDB說明文件,才能一勞永逸的解決這個問題。

2020年7月11日 星期六

自動化程式的檔案路徑規劃

電腦的檔案系統採取樹狀結構規劃,每個檔案都存在一個對應的路徑。從程式設計的角度,必須明確的指定路徑才能正確的存取檔案。AEDT預設有幾個重要的目錄用來存放資料及函式庫(如圖一),包括:
  • Project:存檔的預設目錄,可使用oDesktop.GetProjectDirectory()取得
  • Temp:模擬過程暫存的目錄,可使用oDesktop.GetTempDirectory()取得
  • SysLib:系統層級的腳本存放目錄,可使用oDesktop.GetSysLibDirectory()取得
  • UserLib:使用者層級的腳本存放目錄,可使用oDesktop.GetUserLibDirectory()取得
  • PersonalLib:個人層級的腳本存放目錄,可使用oDesktop.GetPersonalLibDirectory()取得

(圖一) AEDT環境目錄設定

除了上述目錄,程式開發者還必須熟悉下面幾種目錄:

工作目錄:預設為C:\Program Files\AnsysEM\AnsysEM20.2\Win64,當程式執行儲存檔案時,如果只有標示檔名而沒有標示目錄,則檔案會儲存於此目錄底下。查詢工作目錄可以使用下面代碼:

import os
cwd_path = os.path.abspath(os.getcwd())
AddWarningMessage(str(cwd_path))

程式本身存放的目錄:程式執行時有時需要配置檔,而配置檔往往都是跟程式擺放在同一目錄底下。如不指定目錄,Python會嘗試到工作目錄尋找配置檔,一但找不到配置檔則會引發FileNoFoundException錯誤。為了解決這個問題,建議程式開頭便將工作目錄切換與程式本身所在的目錄,參考代碼如下:

import os
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
AddWarningMessage(str(dname))

專案的目錄:當程式需要輸出與專案相關的資料,那麼最好是將資料跟專案放在同一目錄底下方便檢閱。如果目錄底下有多個專案,可以在輸出的檔名插入專案的名稱以利辨別。底下代碼可以取得執行腳本時的專案名稱及專案的儲存路徑:

oProject = oDesktop.GetActiveProject()
prj_path = oProject.GetPath()
prj_name = oProject.GetName()
AddWarningMessage(str(prj_path))
AddWarningMessage(str(prj_name))


AEDT支援命令列執行的程式簡介

早期的作業系統是透過輸入指令來進行工作,即便後來有了視窗及滑鼠的發明,指令輸入及控制台視窗(如圖一)仍舊被保留到了現在。最主要的原因是可以將多個操作指令可以寫在一個批次檔(.bat)當中,只要執行批次檔,指令便會依序執行完成工作。現今多數的程式依舊支援命令列的執行方式,這意味著我們可以透過批次檔串接不同的應用程式來達到自動化。

(圖一) Windows系統的控制台視窗

AEDT環境有以下幾支程式可以支援命令列的執行方式:
  • nexxim.exe:電路模擬程式,輸出結果儲存在.sdf當中
  • sdf2csv.exe:將.sdf格式轉換成.csv格式
  • PinToPinSetup.exe:根據設定檔產生.aedb資料庫,主要用在PCB或封裝的設定
  • ansysedt.exe:aedt主程式,搭配Python可以建立模型,執行模擬以及輸出模擬資料。其中包含5種模式:
    • BatchSave
    • BatchSolve
    • BatchExtract
    • RunScript
    • RunScriptandExit

除了在作業系統的控制窗輸入指令,或者執行批次檔來啟動應用程式,我們也可以用Python來模擬作業系統命令列的操作。用Python的好處是其提供的功能比作業系統支援的指令集更彈性,更強大,比方說是字串的操控。Python模擬作業系統的指令包括:
  • os.system()
  • subprocess.run()

有興趣的話,可以試著在AEDT的Python console利用os.system()指令啟動notepad來開啟.csv檔,如圖二。開啟結果如圖三。

(圖二) 使用os.system()開啟第三方工具

(圖三) 成功開啟.csv的notepad


2020年7月9日 星期四

使用PinToPinSetup針對PCB/封裝模擬的半自動化設定介紹

PCB的SI建模設定是相當繁複的工作,通常包含以下部分:
  1. 匯入版圖,刪除不要模擬的net。
  2. 切割GND net,保留適當的大小作為迴路。
  3. 更新堆疊及材料設定。
  4. 設定ports及元件模型(R, L, C, SPICE)等。
  5. 設定模擬條件,如頻寬,收斂條件等等。
使用者必須游移在不同的選單跟子視窗當中進行操作來完成模擬設定。針對不同訊號群(DDR,PCIE,USB3...)又有不同的設定條件。如果PCB改版,所有的設定又要重來一次,這是極沒有效率的方法。這個問題其實可以透過AEDT當中的PinToPinSetup工具來解決。PinToPinSetup整合上述的1、2、4、5項功能在一個介面當中(如圖一),且設定可以儲存成.xml檔(如圖二)。當下次PCB改版時只需要載入.xml檔即可複製上次的設定。之所以稱作半自動化是因為載入仍需要手動操作,堆疊及材料的修改也仍需要手動進行。儘管仍有不足之處,這個工具還是大幅減少了SI建模設定的工作量。

啟動PinToPinSetup有兩種模式,第一種是在AEDT環境當中手動啟動,另一種則是在Windows控制台方式以命令列的方式啟動。第二種方式也可以由Python的os.system()指令達到同樣的效果。這意味著可以透過Python執行PinToPinSetup,並進一步整合PCB堆疊設定,模擬執行及模型輸出,達到PCB匯入到報告輸出流程全面的自動化。這部分就留待下一篇文章介紹。

(圖一) PinToPinSetup工具操作介面

(圖二) 模擬設定儲存檔

2020年7月8日 星期三

如何將圖片輸出到PPT當中?

如果想用IronPython代碼將圖片加入到PowerPoint當中,可以參考以下代碼。該代碼將一張圖檔eye.png插入到test.pptx當中。注意ppt.Close()函式會關閉PPT檔,但是PowerPoint主程式還是保持開啟狀態。

如果需要輸出上百張AEDT Report圖檔到PPT且不想手動複製貼上,可以在程式當中加入迴圈使其邊輸出Report 的.png檔邊插入到PPT當中。同步刪除該Report可避免AEDT的Report數量過多導致程式變慢。

import clr
clr.AddReference("Microsoft.Office.Interop.PowerPoint")
import Microsoft.Office.Interop.PowerPoint as PowerPoint

powerpoint = PowerPoint.ApplicationClass()
ppt=powerpoint.Presentations.Add()

slideCounter = 1
slide=ppt.Slides.Add(slideCounter, PowerPoint.PpSlideLayout.ppLayoutTitleOnly)

slide.Shapes.AddPicture('d:\\demo\\eye.png', False, True, 60, 60, 650, 400)
ppt.SaveAs('d:\\demo\\test.pptx')
ppt.Close()

(圖一) 輸出之PPT檔