PyLAFではコンポーネント単位でプログラムを作成していきます。 どのようにコンポーネントを作成し、それぞれを連携させてゆけばよいのか、 振幅変調の学習プログラムを作成しながら説明してゆきます。このチュートリアルは、Pythonのプログラミングについてある程度の経験があるものと仮定して説明してゆきます。 Pythonの簡単な使用方法については〜〜にまとめたので参考にしてください。

ブロックダイアグラム

PyLAFでは任意のコンポーネントを作成・連携して所望の処理を実現してゆきます。コンポーネント同士の関連を明確にするため、チュートリアルで作成するプログラムのブロックダイアグラムを示しておきます。正弦波発生器(FGSINコンポーネント)2台と乗算器(MIXERコンポーネント)から構成されます。fga:FGSINはローカル信号を、fgb:FGSINは変調信号をそれぞれ生成します。MIXERは2つの入力端子sig_a、sig_bの入力信号を乗算したsig_a * sig_bを出力します。FGSINとMIXERとには波形表示器(Oscilloscopeコンポーネント)が内蔵されており、それぞれの出力波形を確認できます。スペクトル表示器(Spectrumコンポーネント)は、信号のフーリエ変換結果を示します。 BlockDiagram.png

コンポーネントを作成する

まずは正弦波を発生させるファンクションジェネレータのコンポーネントを作成してみましょう。MODAM1.pyにソースコードを示します。MODAM1.pyを実行すると図の様なウィンドウが表示されます。立ち上げた直後には波形は表示されていませんので、'Trig'ボタンを押すと波形が表示されます。各々のパラメータはEnterキーで確定されます。確定されないうちにマウスを欄外に移動すると値がリセットされます。様々なパラメータで正弦波を表示させてみてください。 MODAM1.png

  1. from PyLAF import *
  2. class FGSIN(EasyComponent):
  3. PLOTTER = Oscilloscope
  4. def __init__(self,master=None):
  5. EasyComponent.__init__(self,master)
  6. self.mag = EntryPort(1.,'Magnitude [mV]')
  7. self.freq = EntryPort(0.01,'Frequency [MHz]')
  8. self.phase = EntryPort(0.,'Phase Offset [deg]')
  9. self.bias = EntryPort(0.,'Bias Voltage [mV]')
  10. self.nums = EntryPort(2000,'Number of Samples')
  11. self.trig = TrigPort(False,'Trig').bind(self.trigger)
  12. def trigger(self):
  13. t = arange(self.nums.get())/self.plotter.samplerate.get()
  14. w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
  15. self.sig_out.set(self.mag.get() * w + self.bias.get())
  16. def test(master=None):
  17. o = App(master,FGSIN); o.pack()
  18. if __name__ == '__main__':
  19. test(Tkinter.Tk())
  20. Tkinter.mainloop()


また、波形表示部分を右クリックするとオプションメニューが表示されます。オプションメニューではグラフの保存、軸範囲指定、装飾などができます。保存の際に指定するファイル名の拡張子によって種々のフォーマットでグラフを保存できます。consoleでは軸範囲指定など、decolationでは軸タイトル、ポイント記号などを指定できます。パラメータを変化させてみて描画結果がどのように変わるか試してみてください。

MODAM1_menu.png console.png decolation.png

ソースコードの解説

最初に、PyLAFパッケージをインポートします。

from PyLAF import *
PyLAFコンポーネントはPyLAF.Componentクラスを継承して作成します。ファンクションジェネレータFGSINコンポーネントが継承しているEasyComponent(Component)はGUIコードを簡単に作成するためのサポートクラスです。1つのグラフとパラメータエントリーパネルを自動的に生成します。クラス変数のPLOTTERはEasyComponentのグラフのクラスを指定する変数です。PyLAF.plottersモジュールで定義されるBasePlotのサブクラスを指定できます。
class FGSIN(EasyComponent):
    PLOTTER = Oscilloscope
    def __init__(self,master=None):
        EasyComponent.__init__(self,master)


ポート(Port)はパラメータを他のコンポーネントやGUIと共有するためのクラスです。GUIの要素や他のコンポーネントの保持するポートと内容が同期しています。 PortクラスのサブクラスであるEntryPort(value,label)やTrigPort(value,label)はGUIを自動的に作成するサポートクラスです。実行画面でわかるように、記述した順番にlabel付きのEntryウィジェットが自動的に生成されます。valueは初期値、labelはEntryウィジェットに付随するラベルです。 各々のポートには、更新されたときに起動するメソッドをバインドできます。例題では、self.trigのTrigPortにself.triggerメソッドがバインドされています。トリガボタンを押したときに波形が更新されますので試してみてください。

        self.mag   = EntryPort(1.,'Magnitude [mV]')
        self.freq  = EntryPort(0.01,'Frequency [MHz]')
        self.phase = EntryPort(0.,'Phase Offset [deg]')
        self.bias  = EntryPort(0.,'Bias Voltage [mV]')
        self.nums  = EntryPort(2000,'Number of Samples')
        self.trig  = TrigPort(False,'Trig').bind(self.trigger)


例題では、self.trigger()メソッドはロジックの実装部分です。GUIで設定されたパラメータへはPortインスタンスを介してアクセスします。アクセスにはget()、set()メソッドを使用します。 例えば、周波数を取り出したい場合にはself.freq.get()、設定したい場合はself.freq.set(value)です。 Portインスタンスの内容とGUIの内容は同期しており、Portインスタンスをset(value)すればGUIのエントリも直ちに更新されます。 また簡単のため、MODAM1.pyではPLOTTERコンポーネントに内蔵されているサンプルレートを使用することにします。self.plotter.samplerate.get()で取得できます。

    def trigger(self):
        t = arange(self.nums.get())/self.plotter.samplerate.get()
        w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
        self.sig_out.set(self.mag.get() * w + self.bias.get())


PythonではMatlabと同様の高速な科学計算ライブラリscipyが利用できます。 PyLAFではをロードするとscipyも一緒にロードされます。 C言語などでは、配列の演算をforループなどを利用して実施しますが、Pythonでは配列に対する四則演算は配列の各要素に各々適用されますので次のような表記をすると高速に演算できます。

次のコードはサンプリングレートがplotter.samplerate MS/s、サンプリング点数がnums個の時間配列を用意するコードです。 arange(value)は0からvalueまで1づつ増加するvalue点数の配列を作成するコマンドです。

        t = arange(self.nums.get())/self.plotter.samplerate.get()
次のコードは正弦波形を生成するコードです。
        w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
次のコードは振幅をmag倍してバイアス電圧biasを加算し、sig_outにセットするコードです。
        self.sig_out.set(self.mag.get() * w + self.bias.get())


EasyComponentでは暗黙のうちにsig_outポートが定義されています。sig_outはPLOTTERのsig_inにリンクされていて、sig_outにarrayクラスのデータをセットすると内蔵のPLOTTERへデータを描画します。参考にEasyComponentのソースの一部を次に示します。

class EasyComponent(Component):
    PLOTTER = None
    def __init__(self,master):
        Component.__init__(self,master)
        self.colcount = 0
        self._menu    = None
        self.sig_out  = Port(array([]))
        self.plotter  = Component(self)
        dummy = self.PLOTTER()
        for key,info in getmembers(dummy,lambda x:iscontain(x,Port)):
            setattr(self.plotter,key,Port(info.get()))
        dummy.destroy()
    def gui(self,master,pltcnf={},cnf={},**kw):
        frm = Frame(master)
        if not self.PLOTTER == None:
            v = self.__class__.PLOTTER(frm,pltcnf); v.plot(); v.show(); v.pack()
            setattr(frm,'plotter',v)
            for key,info in getmembers(v,lambda x:iscontain(x,Port)):
                getattr(self.plotter,key).insertlink(0,info)
            self.sig_out.insertlink(0,v.sig_in);
        o = EasyGrid(frm,self); o.pack()
        setattr(frm,'console',o)
        return frm
次の部分でsig_outとPLOTTERのsig_inとをリンクしています。
            self.sig_out.insertlink(0,v.sig_in);


GUIを備えたコンポーネントをイクイップメントと呼びます。 イクイップメント(PyLAF.Equipment)には、トップレベルウィンドウアプリとして利用するためのApp、GUI内に埋め込んで利用するためのEmbedといったサブクラスがあり、利用シーンによって1つのコンポーネントを様々な形態に変化させることができます。 次のコードは、トップレベルウィンドウアプリとしてFGSINのイクイップメントを作成するコードです。 書式はApp(toplevel_widget,component_class)です。 toplevel_widgetにはTkinter.Tk()またはTkinter.Toplevel()ウィジェット、またはそのサブクラスが指定できます。 pack()を実行することを忘れるとウィンドウが表示されませんので注意してください。

def test(master=None):
    o = App(master,FGSIN); o.pack()

次のコードは、ルートウィジェットを引数としてtest関数を呼び出しアプリケーションウィンドウを作成した後、イベントループを起動するコードです。 大抵はこのまま変更する必要はありません。

if __name__ == '__main__':
    test(Tkinter.Tk())
    Tkinter.mainloop()

演習

以下のようにソースコードを変更したときにプログラムの動作がどのように変化するか確認してください。


グラフ表示がスペクトルアナライザ表示となります。

    PLOTTER = Spectrum
def test(master=None):
    o = App(master,FGSIN); o.pack()
    o.gui.plotter.xmin.set(0.)
    o.gui.plotter.xmax.set(0.1)
MODAM1a.png


トリガーボタンを押しても波形が更新されなくなり、一方で周波数エントリー欄を更新すると波形が更新されるようになります。

        self.freq  = EntryPort(0.01,'Frequency [MHz]').bind(self.trigger)
        self.trig  = TrigPort(False,'Trig')

ミキサコンポーネントと連携させる

次に、複数のコンポーネントを連携させる方法を学びます。 MODAM2.pyを実行すると図のようなウィンドウが表示されます。 周波数が0.001、バイアスが1mVの正弦波源が変調信号、周波数が0.1の正弦波源が搬送波、グラフだけのウィンドウがミキサと混合結果を示しています。 二つの正弦波源で信号を発生させるとミキサのウィンドウに振幅変調された搬送波が表示されるのがわかります。

MODAM2.png

  1. from PyLAF import *
  2. class FGSIN(EasyComponent):
  3. PLOTTER = Oscilloscope
  4. def __init__(self,master=None):
  5. EasyComponent.__init__(self,master)
  6. self.mag = EntryPort(1.,'Magnitude [mV]')
  7. self.freq = EntryPort(0.001,'Frequency [MHz]')
  8. self.phase = EntryPort(0.,'Phase Offset [deg]')
  9. self.bias = EntryPort(0.,'Bias Voltage [mV]')
  10. self.nums = EntryPort(2000,'Number of Samples')
  11. self.trig = TrigPort(False,'Trig').bind(self.trigger)
  12. def trigger(self):
  13. t = arange(self.nums.get())/self.plotter.samplerate.get()
  14. w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
  15. self.sig_out.set(self.mag.get() * w + self.bias.get())
  16. class MIXER(EasyComponent):
  17. PLOTTER = Oscilloscope
  18. def __init__(self,master=None):
  19. EasyComponent.__init__(self,master)
  20. self.sig_a = Port(array([])).bind(self.trigger)
  21. self.sig_b = Port(array([])).bind(self.trigger)
  22. def trigger(self):
  23. a, b = self.sig_a.get(), self.sig_b.get()
  24. if a.shape == b.shape:
  25. self.sig_out.set(a * b)
  26. def test(master=None):
  27. mix = App(master,MIXER); mix.pack()
  28. fga = App(Tkinter.Toplevel(master),FGSIN); fga.pack()
  29. fgb = App(Tkinter.Toplevel(master),FGSIN); fgb.pack()
  30. fga.component.sig_out.link(mix.component.sig_a)
  31. fgb.component.sig_out.link(mix.component.sig_b)
  32. fga.component.nums.link(fgb.component.nums)
  33. fga.component.freq.set(0.1)
  34. fgb.component.bias.set(1.)
  35. if __name__ == '__main__':
  36. test(Tkinter.Tk())
  37. Tkinter.mainloop()

ソースコードの解説

FGSIN部分は変更ありません。

クラスMIXERはFGSINと同様オシロスコープを備えたEasyComponentです。

class MIXER(EasyComponent):
    PLOTTER = Oscilloscope
    def __init__(self,master=None):
        EasyComponent.__init__(self,master)
MIXERはsig_aとsig_bの二つの配列型の入力ポートを指定するのは次のコードです。 パラメータとして編集しませんので、GUIを伴わないPortインスタンスを使います。 sig_aとsig_bいずれかが更新されるとself.triggerを呼び出します。
        self.sig_a   = Port(array([])).bind(self.trigger)
        self.sig_b   = Port(array([])).bind(self.trigger)
trigger()メソッドが呼び出されると、sig_aとsig_bの配列サイズを調べ、同じサイズであればsig_outにsig_a * sig_bを出力します。
    def trigger(self):
        a, b = self.sig_a.get(), self.sig_b.get()
        if a.shape == b.shape:
            self.sig_out.set(a * b)


test()関数でmix,fga,fgbの3つのイクイップメントを作成します。 fgaは変調信号、fgbは搬送波を作成するFGSINイクイップメントです。 mixはミキサーのMIXERイクイップメントです。 複数のトップレベルイクイップメントを作成するので、ルートウィジェットTkinter.Tk()だけでなく、トップレベルウィジェットTkinter.Toplevel(master)も作成しています。 トップレベルウィジェットをクローズしてもプログラムは終了しませんが、ルートウィジェットをクローズするとプログラムは終了します。

def test(master=None):
    mix = App(master,MIXER); mix.pack()
    fga = App(Tkinter.Toplevel(master),FGSIN); fga.pack()
    fgb = App(Tkinter.Toplevel(master),FGSIN); fgb.pack()

Port.link()メソッドでポートを同期することができます。 いちどリンクすれば、プログラムを終了するか、どちらかを破棄するかまで、値が直ちに同期されます。 イクイップメントからポートへアクセスするには、equipment_name.component.port_nameのようにアクセスします。

    fga.component.sig_out.link(mix.component.sig_a)
    fgb.component.sig_out.link(mix.component.sig_b)

パラメータも同様にしてリンクできます。 fgaとfgbの配列サイズを同じにしたいので、fga.component.numsとfgb.component.numsを同期しておきます。

    fga.component.nums.link(fgb.component.nums)

fgaは搬送波なので周波数を0.1MHzに、fgbは変調波なのでバイアス電圧を1.0Vにセットしておきます。

    fga.component.freq.set(0.1)
    fgb.component.bias.set(1.)

コンポーネントをひとつのウィンドウにまとめる

たくさんのコンポーネントを組み合わせて使うようになると、複数のイクイップメントをひとつのウィンドウにまとめたくなります。 このようなときにはEmbedクラスを使います。 MODAM3.pyを実行すると、図のような画面になります。 枠付きフレームで囲まれたコンポーネントがひとつのウィンドウにまとめられているのがわかります。 ラベルの部分を右クリック(MacではCtrl+クリック)するとコンポーネントメニューがポップアップします。 MODAM3.png

  1. from PyLAF import *
  2. class FGSIN(EasyComponent):
  3. PLOTTER = Oscilloscope
  4. def __init__(self,master=None):
  5. EasyComponent.__init__(self,master)
  6. self.mag = EntryPort(1.,'Magnitude [mV]')
  7. self.freq = EntryPort(0.001,'Frequency [MHz]')
  8. self.phase = EntryPort(0.,'Phase Offset [deg]')
  9. self.bias = EntryPort(0.,'Bias Voltage [mV]')
  10. self.nums = EntryPort(2000,'Number of Samples')
  11. self.trig = TrigPort(False,'Trig').bind(self.trigger)
  12. def trigger(self):
  13. t = arange(self.nums.get())/self.plotter.samplerate.get()
  14. w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
  15. self.sig_out.set(self.mag.get() * w + self.bias.get())
  16. class MIXER(EasyComponent):
  17. PLOTTER = Oscilloscope
  18. def __init__(self,master=None):
  19. EasyComponent.__init__(self,master)
  20. self.sig_a = Port(array([])).bind(self.trigger)
  21. self.sig_b = Port(array([])).bind(self.trigger)
  22. def trigger(self):
  23. a, b = self.sig_a.get(), self.sig_b.get()
  24. if a.shape == b.shape:
  25. self.sig_out.set(self.sig_a.get() * self.sig_b.get())
  26. def test(master=None):
  27. mix = Embed(master,MIXER,text='RF'); mix.pack(side=Tkinter.RIGHT)
  28. fga = Embed(master,FGSIN,text='LO'); fga.pack(side=Tkinter.RIGHT)
  29. fgb = Embed(master,FGSIN,text='IF'); fgb.pack(side=Tkinter.RIGHT)
  30. fga.component.sig_out.link(mix.component.sig_a)
  31. fgb.component.sig_out.link(mix.component.sig_b)
  32. fga.component.nums.link(fgb.component.nums)
  33. fga.component.freq.set(0.1)
  34. fgb.component.bias.set(1.)
  35. if __name__ == '__main__':
  36. test(Tkinter.Tk())
  37. Tkinter.mainloop()

ソースコードの解説

変更部分は次のコードだけです。 イクイップメントクラスとしてAppの代わりにEmbedを使います。Embedのコンフィグオプションでtext='label'は、枠付きフレームに表示するラベルです。 また、ルートウィジェットにすべてのエンベッド型イクイップメントを配置します。 pack(side=Tkinter.RIGHT)は順番に右詰めでパックしていく命令です。

def test(master=None):
    mix = Embed(master,MIXER,text='RF'); mix.pack(side=Tkinter.RIGHT)
    fga = Embed(master,FGSIN,text='LO'); fga.pack(side=Tkinter.RIGHT)
    fgb = Embed(master,FGSIN,text='IF'); fgb.pack(side=Tkinter.RIGHT)

複合コンポーネントを作る

ウィンドウをひとつにまとめるだけでなく、他のコンポーネントと同じように取り扱えるよう、複合コンポーネントにまとめてみましょう。 実行画面はMODAM3.pyとまったく同じです。 MODAM3.png

  1. from PyLAF import *
  2. class FGSIN(EasyComponent):
  3. PLOTTER = Oscilloscope
  4. def __init__(self,master=None):
  5. EasyComponent.__init__(self,master)
  6. self.mag = EntryPort(1.,'Magnitude [mV]')
  7. self.freq = EntryPort(0.001,'Frequency [MHz]')
  8. self.phase = EntryPort(0.,'Phase Offset [deg]')
  9. self.bias = EntryPort(0.,'Bias Voltage [mV]')
  10. self.nums = EntryPort(2000,'Number of Samples')
  11. self.trig = TrigPort(False,'Trig').bind(self.trigger)
  12. def trigger(self):
  13. t = arange(self.nums.get())/self.plotter.samplerate.get()
  14. w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
  15. self.sig_out.set(self.mag.get() * w + self.bias.get())
  16. class MIXER(EasyComponent):
  17. PLOTTER = Oscilloscope
  18. def __init__(self,master=None):
  19. EasyComponent.__init__(self,master)
  20. self.sig_a = Port(array([])).bind(self.trigger)
  21. self.sig_b = Port(array([])).bind(self.trigger)
  22. def trigger(self):
  23. a, b = self.sig_a.get(), self.sig_b.get()
  24. if a.shape == b.shape:
  25. self.sig_out.set(self.sig_a.get() * self.sig_b.get())
  26. class MODAM(Component):
  27. def __init__(self,master=None):
  28. Component.__init__(self,master)
  29. self.fga = fga = FGSIN(self)
  30. self.fgb = fgb = FGSIN(self)
  31. self.mix = mix = MIXER(self)
  32. fga.nums.link(fgb.nums)
  33. fga.sig_out.link(mix.sig_a)
  34. fgb.sig_out.link(mix.sig_b)
  35. fga.freq.set(0.1)
  36. fgb.bias.set(1.0)
  37. def gui(self,master,cnf={},**kw):
  38. frm = Frame(master)
  39. mix = Embed(frm,self.mix,text='RF'); mix.pack(side=Tkinter.RIGHT)
  40. fga = Embed(frm,self.fga,text='LO'); fga.pack(side=Tkinter.RIGHT)
  41. fgb = Embed(frm,self.fgb,text='IF'); fgb.pack(side=Tkinter.RIGHT)
  42. return frm
  43. def test(master=None):
  44. o = App(master,MODAM); o.pack()
  45. if __name__ == '__main__':
  46. test(Tkinter.Tk())
  47. Tkinter.mainloop()

ソースコードの解説

MODAM4.pyでは複合コンポーネントを作成するのにComponentクラスを継承しました。EasyComponentよりも詳細に振る舞いを設定できます。

class MODAM(Component):
    def __init__(self,master=None):
        Component.__init__(self,master)

複合コンポーネントでは、要素コンポーネントをメンバとして保持します。要素コンポーネントのmasterはselfとします。

        self.fga = fga = FGSIN(self)
        self.fgb = fgb = FGSIN(self)
        self.mix = mix = MIXER(self)

要素コンポーネントの配線と、パラメータ初期設定をします。MODAM3.pyと同様です。

        fga.sig_out.link(mix.sig_a)
        fgb.sig_out.link(mix.sig_b)
        fga.nums.link(fgb.nums)
        fga.freq.set(0.1)
        fgb.bias.set(1.0)

ComponentのGUIはself.gui()メソッドに定義します。 gui()はウィジェットを返さなければなりません。 典型的な複合コンポーネントでは要素コンポーネントのGUIを配置したPyLAF.Frameウィジェットを返します。 イクイップメントクラスの第2引数にインスタンスを与えると、与えたインスタンスとリンクしたイクイップメント型のGUIが生成されます。 複合コンポーネントのGUI構築に便利です。 MODAM4.pyでは右詰めで各要素のEmbed型イクイップメントをpackしています。

    def gui(self,master,cnf={},**kw):
        frm = Frame(master)
        mix = Embed(frm,self.mix,text='RF'); mix.pack(side=Tkinter.RIGHT)
        fga = Embed(frm,self.fga,text='LO'); fga.pack(side=Tkinter.RIGHT)
        fgb = Embed(frm,self.fgb,text='IF'); fgb.pack(side=Tkinter.RIGHT)
        return frm

以上でMODAMをコンポーネントと同様に扱えます。 複合コンポーネントMODAMにまとめたので、test()関数の内容がめでたくMODAM1.pyと同様の形になりました。

def test(master=None):
    o = App(master,MODAM); o.pack()

if __name__ == '__main__':
    test(Tkinter.Tk())
    Tkinter.mainloop()

演習

Embed型のイクイップメントとしてみましょう。

def test(master=None):
    o = Equipment(master,MODAM,text='MODAM'); o.pack()

複合コンポーネントのGUIをシンプルにする

複合コンポーネントのGUIをシンプルにして使いやすくしましょう。 頻繁に調整する周波数、振幅、バイアス電圧とトリガボタンを設けましょう。 表示にMIXERコンポーネントのGUIを使用しました。 エントリーパネルは新しく定義しました。 MODAM5.png

  1. from PyLAF import *
  2. from functools import partial
  3. class FGSIN(EasyComponent):
  4. PLOTTER = Oscilloscope
  5. def __init__(self,master=None):
  6. EasyComponent.__init__(self,master)
  7. self.mag = EntryPort(1.,'Magnitude [mV]')
  8. self.freq = EntryPort(0.001,'Frequency [MHz]')
  9. self.phase = EntryPort(0.,'Phase Offset [deg]')
  10. self.bias = EntryPort(0.,'Bias Voltage [mV]')
  11. self.nums = EntryPort(2000,'Number of Samples')
  12. self.trig = TrigPort(False,'Trig').bind(self.trigger)
  13. def trigger(self):
  14. t = arange(self.nums.get())/self.plotter.samplerate.get()
  15. w = sin(2 * pi * self.freq.get() * t + self.phase.get() / 180. * pi)
  16. self.sig_out.set(self.mag.get() * w + self.bias.get())
  17. class MIXER(EasyComponent):
  18. PLOTTER = Oscilloscope
  19. def __init__(self,master=None):
  20. EasyComponent.__init__(self,master)
  21. self.sig_a = Port(array([])).bind(self.trigger)
  22. self.sig_b = Port(array([])).bind(self.trigger)
  23. def trigger(self):
  24. a, b = self.sig_a.get(), self.sig_b.get()
  25. if a.shape == b.shape:
  26. self.sig_out.set(self.sig_a.get() * self.sig_b.get())
  27. class MODAM(EasyComponent):
  28. def __init__(self,master=None):
  29. EasyComponent.__init__(self,master)
  30. self.fga = fga = FGSIN(self)
  31. self.fgb = fgb = FGSIN(self)
  32. self.mix = mix = MIXER(self)
  33. self.lof = EntryPort(.1,'LO Frequency [MHz]'); self.lof.link(fga.freq)
  34. self.iff = EntryPort(.001,'IF Frequency [MHz]'); self.iff.link(fgb.freq)
  35. self.ifm = EntryPort(1.,'IF Magnitude [mV]'); self.ifm.link(fgb.mag)
  36. self.ifb = EntryPort(1.,'IF Bias Voltage [mV]'); self.ifb.link(fgb.bias)
  37. self.trig = TrigPort(False,'Trig')
  38. fga.sig_out.link(mix.sig_a)
  39. fgb.sig_out.link(mix.sig_b)
  40. fga.nums.link(fgb.nums)
  41. self.trig.link(fga.trig,fgb.trig)
  42. self._menu = [['Sub', ['FGA', partial(self.popup,master=master,comp=self.fga)],
  43. ['FGB', partial(self.popup,master=master,comp=self.fgb)]]]
  44. def gui(self,master,cnf={},**kw):
  45. frm = Frame(master)
  46. mix = Embed(frm,self.mix,text='RF'); mix.pack()
  47. ctl = EasyGrid(frm,self); ctl.pack()
  48. return frm
  49. def popup(self,master,comp):
  50. App(Tkinter.Toplevel(master),comp).pack()
  51. comp.trigger()
  52. def test(master=None):
  53. o = App(master,MODAM); o.pack()
  54. if __name__ == '__main__':
  55. test(Tkinter.Tk())
  56. Tkinter.mainloop()

ソースコードの解説

EasyComponentを使って複合コンポーネントを作ります。

class MODAM(EasyComponent):
    def __init__(self,master=None):
        EasyComponent.__init__(self,master)
        self.fga = fga = FGSIN(self)
        self.fgb = fgb = FGSIN(self)
        self.mix = mix = MIXER(self)

EasyPortを使ってコントロールパネルを構築します。

        self.lof = EntryPort(.1,'LO Frequency [MHz]'); self.lof.link(fga.freq)
        self.iff = EntryPort(.001,'IF Frequency [MHz]'); self.iff.link(fgb.freq)
        self.ifm = EntryPort(1.,'IF Magnitude [mV]'); self.ifm.link(fgb.mag)
        self.ifb = EntryPort(1.,'IF Bias Voltage [mV]'); self.ifb.link(fgb.bias)
        self.trig = TrigPort(False,'Trig')

入出力ポート、パラメータポートをリンクします。

        fga.sig_out.link(mix.sig_a)
        fgb.sig_out.link(mix.sig_b)
        fga.nums.link(fgb.nums)
        self.trig.link(fga.trig,fgb.trig)

コンポーネントメニューを定義します。コンポーネントメニューはEasyComponent._menuにリスト形式で次のように定義します。

[
 [ label1,
          [ label1_1, command1_1 ],
          [ label1_2, command1_2 ],
          ...
 ],
 [ label2,
          [ label2_1, command2_1 ],
          ...
 ],
 ...
]
具体的には次のようにしました。
        self._menu = [['Sub', ['FGA', partial(self.popup,master=master,comp=self.fga)],
                              ['FGB', partial(self.popup,master=master,comp=self.fgb)]]]
コマンド部分には関数の部分適用を使って起動関数を簡略化しています。
partial(self.popup,master=master,comp=self.fga)
self.popup()はコンポーネントをAppイクイップメント化して新しいウィンドウで打ち上げるランチャーです。 GUIを描画するためにtrigger()を1回呼んでいます。
    def popup(self,master,comp):
        App(Tkinter.Toplevel(master),comp).pack()
        comp.trigger()

GUIは以下の簡単なコードで記述できます。 グラフ表示部にMIXERクラスのEmbed型イクイップメントを用います。 エントリーパネルはEasyGridクラスで生成できます。

    def gui(self,master,cnf={},**kw):
        frm = Frame(master)
        mix = Embed(frm,self.mix,text='RF'); mix.pack()
        ctl = EasyGrid(frm,self); ctl.pack()
        return frm