プログラミングメモ

2025年4月20日

以下ソースコード

# ライブラリインポート
import tkinter as tk
from tkinter import *
from tkinter import ttk, filedialog, messagebox
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import math,os,sys

# GUI作成 ここから↓
root = tk.Tk()
root.title("Thermal Relay Setting Assist Tools ver 1.0")

# コンボボックス1(Relay Type)の値
relay_type = ["HGMP N60 (Hyundai)", "Terasaki"]

# コンボボックス2(設定値その1)のラベル
label1 = {
    "HGMP N60 (Hyundai)": "CLASS : ",
    "Terasaki": "class : "
}

# コンボボックス2(設定値その1)の設定可能値
variable1 = {
    "HGMP N60 (Hyundai)": [i for i in range(1, 61)],
    "Terasaki": [i for i in range(1, 31)]
}

# コンボボックス3(設定値その2)のラベル
label2 = {
    "HGMP N60 (Hyundai)": "In : ",
    "Terasaki": "I_n : "
}

# コンボボックス3(設定値その2)の設定可能値
# ※values1,2をそのまま使用すると小数点以下がおかしくなる(1.000001等)ため、roundで丸めた値を使用。
values1 = [i*0.1 for i in range(5, 16)]
values2 = [i*0.1 for i in range(5, 31)]
variable2 = {
    "HGMP N60 (Hyundai)": [round(values1[n], 1) for n in range(len(values1))],
    "Terasaki":[round(values2[n], 1) for n in range(len(values2))]
}

# 軸範囲指定用コンボボックスの値
# xは定格電流の倍数、yは時間(対数)
x_axis_max= [5,6,7,8,9,10,11,12]
x_axis_min= [1]
y_axis_max= [1000,10000,100000]
y_axis_min= [1]

# 選択したリレータイプに応じて変数を変化させる関数
def update_combo(event):
    # リレータイプをコンボボックスから取得
    selected = relay.get()
    # リレータイプに応じて選べる値を変更
    variable_1['values'] = variable1[selected]
    variable_1.set('')  # 初期値をリセット
    # リレータイプに応じて変数名称を変更
    label_1 = label1[selected]
    variable1_label["text"] = label_1

    #以下、上記と同じ内容をVariable2にも適用
    variable_2['values'] = variable2[selected]
    variable_2.set('')  # 初期値をリセット
    label_2 = label2[selected]
    variable2_label["text"] = label_2

def update_graph(): 
    # 更新時にグラフ・画像を一旦クリア
    ax.cla()
    ay.cla()

    # 選択された軸の最大値・最小値を取得。選択されていなければエラー表示。
    if x_min.get() == "" or x_max.get() == "" or y_min.get() == "" or y_max.get() == "":
        messagebox.showerror('エラー', 'x軸、y軸の最大値、最小値を選択してください。')
    else:
        xmin = int(x_min.get())
        xmax = int(x_max.get())
        ymin = int(y_min.get())
        ymax = int(y_max.get())

    ax.set_ylim(ymin, ymax)  # y軸の表示範囲を指定
    ax.set_xlim(xmin, xmax)  # x軸の範囲を設定
    ax.set_yscale('log')  # y軸を対数スケールに設定

    # 入力されたグラフタイトルを取得し、グラフ上部に表示
    graph_title = entry.get()
    ax.set_title(graph_title)

    # 背景画像の表示
    if 'background_image' in globals():
        if background_image is not None:
            # 画像の表示範囲を軸の範囲に合わせる
            def update_image_extent():
                # x軸とy軸の現在の範囲を取得
                xlim = ax.get_xlim()
                ylim = ay.get_ylim()  # 右側のy軸の範囲を使用
                # extentを設定
                return [xlim[0], xlim[1], ylim[0], ylim[1]]
            # 画像の表示
            extent = update_image_extent()
            im = ay.imshow(background_image, aspect='auto', extent=extent, alpha=0.5) # プロットと重ねて表示するため、半透明にて表示
    else:
        messagebox.showerror('エラー', 'Reference ボタン押下し、画像を選択してください。')
    
    # 画像はグラフと別の軸(ay)を設定しプロットしている。見た目上、2軸あるとややこしいのでayはグラフ目盛り削除
    ay.tick_params(axis='y', which='both', length=0) 
    ay.set_yticklabels([])  # 目盛りラベルを空にする
    
    # グラフ描画。背景画像選択後、表示させるため。
    canvas.draw()

    # コンボボックスの値を取得。未入力の場合エラー表示
    if relay.get() == "" or variable_1.get() == "" or variable_2.get() == "":
        messagebox.showerror('エラー', 'Relay Type, 各変数を選択してください。')
    else:
        var1 = float(variable_1.get())
        var2 = float(variable_2.get())
        relaytype = relay.get()

    if relaytype == "HGMP N60 (Hyundai)":
        x = np.linspace(1.11, 10, 100) 
        y = 29.5*int(var1)*(np.log(x**2/( x**2 - (1.1*var2)**2)))

    else:
        x = np.linspace(1.11, 10, 100) 
        y = 60*int(var1)*(np.log(x**2/( x**2 - (1.1*var2)**2)))
    
    # サーマルリレーカーブを対数軸でプロット
    ax.plot(x, y, 'b-', label='Thermal Relay Curve') 
    ax.tick_params(axis='y', labelcolor='b')  # 青色でカーブをプロット

    # 右上に設定値(プルダウンで選択した値)・リレータイプを表示
    ax.text(0.95, 1.1, f'Relay Type : {relaytype}', transform=ax.transAxes, fontsize=6,
        verticalalignment='top', horizontalalignment='right',)
    ax.text(0.95, 1.06, f'{variable1_label["text"]}{var1}', transform=ax.transAxes, fontsize=6,
        verticalalignment='top', horizontalalignment='right',)
    ax.text(0.95, 1.03, f'{variable2_label["text"]}{var2}', transform=ax.transAxes, fontsize=6,
        verticalalignment='top', horizontalalignment='right',)

    ay.tick_params(axis='y', which='both', length=0)  # 目盛りを非表示にする
    ay.set_yticklabels([])  # 目盛りラベルを空にする
    
    # グラフ描画
    canvas.draw()

# 背景を指定する関数
def select_background():
    global background_image
    file_path = filedialog.askopenfilename()
    bg_entry.set(file_path)
    if file_path:
        img = Image.open(file_path)
        background_image = np.array(img)
#        update_graph()


# プロットしたグラフを画像として保存する関数
def save_graph():
    file_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG files", "*.png"), ("All files", "*.*")])
    if file_path:
        fig.savefig(file_path)

# 背景画像・グラフをクリアする関数
def clear_all():
    # 更新時にグラフ・画像を一旦クリア
    ax.cla()
    ay.cla()
    ay.tick_params(axis='y', which='both', length=0)  # 目盛りを非表示にする
    ay.set_yticklabels([])  # 目盛りラベルを空にする

    # グラフ描画
    canvas.draw()


# 背景画像選択ボタンの作成
bg_title = tk.Label(text=u'Background Image Setting', bg = "light gray", width = 25)
bg_title.grid(columnspan=4, row=1,column=19)

bg_label = tk.Label(root, text="File Path :")
bg_label.grid(columnspan=1, row=2,column=19,pady=(3, 3), padx=(10, 10))

bg_button = tk.Button(root, text="Reference", command=select_background, width = 8)
bg_button.grid(columnspan=1, row=2,column=22,pady=(3, 3), padx=(10, 10))

bg_entry = StringVar()
bg_dir = tk.Entry(textvariable=bg_entry, width=30)
bg_dir.grid(columnspan=2, row=2,column=20)

# リレー変数設定
relay_title = tk.Label(text=u'Relay Setting', bg = "light gray", width = 25)
relay_title.grid(columnspan=4, row=6,column=19)

# 1つ目のコンボボックス
relay_label = tk.Label(root, text="Relay Type :")
relay_label.grid(columnspan=2, row=7,column=19,pady=(3, 3), padx=(10, 10))
relay = ttk.Combobox(root, values=relay_type, width=18)
relay.grid(columnspan=2,row=7,column=21,pady=(3, 3), padx=(10, 10),sticky=tk.W)
relay.bind("<<ComboboxSelected>>", update_combo)

# 2つ目のコンボボックス
variable1_label = tk.Label(root, text="")
variable1_label.grid(columnspan=2, row=8, column=19,pady=(3, 3), padx=(10, 10))
variable_1 = ttk.Combobox(root, width=18)
variable_1.grid(columnspan=2,row=8,column=21,pady=(3, 3), padx=(10, 10),sticky=tk.W)

# 3つ目のコンボボックス
variable2_label = tk.Label(root, text="")
variable2_label.grid(columnspan=2,row=9,column=19,pady=(3, 3), padx=(10, 10))
variable_2 = ttk.Combobox(root, width=18)
variable_2.grid(columnspan=2,row=9,column=21,pady=(3, 3), padx=(10, 10),sticky=tk.W)

# グラフ描画ボタンの作成
gp_title = tk.Label(text=u'Graph', bg = "light gray", width = 25)
gp_title.grid(columnspan=2, row=14,column=20)

entry_label = tk.Label(root, text="Graph Title :")
entry_label.grid(columnspan=2, row=16,column=19,pady=(3, 3), padx=(10, 10))
entry = tk.Entry(text="Graph Title", width = 23)
# entry.insert(tk.END, u'Input Graph Title')
entry.grid(columnspan=2, row=16, column=21,sticky=tk.W)

gh_button = tk.Button(root, text="Update Graph", command=update_graph, width = 10)
gh_button.grid(columnspan =2, row=19,column=19,pady=(3, 3), padx=(10, 10))

# クリアボタンの作成
save_button = tk.Button(root, text="Clear All", command=clear_all, width = 10)
save_button.grid(columnspan =2, row=19,column=21,pady=(3, 3), padx=(10, 10))

# グラフ保存ボタンの作成
save_button = tk.Button(root, text="Save Graph", command=save_graph, width = 10)
save_button.grid(columnspan=2, row=20,column=21,pady=(3, 3), padx=(10, 10))

# グラフ上下限設定のコンボボックス
xmin_label = tk.Label(root, text="x軸最小値 : ")
xmin_label.grid(columnspan=1, row=32, column=3,pady=(3, 3), padx=(10, 10))
x_min = ttk.Combobox(root, values=x_axis_min, width=7)
x_min.current(0)
x_min.grid(row=33,column=3,pady=(3, 3), padx=(10, 10))

xmax_label = tk.Label(root, text="x軸最大値 : ")
xmax_label.grid(columnspan=1, row=32, column=17,pady=(3, 3), padx=(10, 10))
x_max = ttk.Combobox(root, values=x_axis_max, width=7)
x_max.current(3)
x_max.grid(row=33,column=17,pady=(3, 3), padx=(10, 10))

ymin_label = tk.Label(root, text="y軸最小値 : ")
ymin_label.grid(columnspan=1, row=27, column=0,pady=(3, 0), padx=(10, 10))
y_min = ttk.Combobox(root, values=y_axis_min, width=7)
y_min.current(0)
y_min.grid(row=28,column=0,pady=(0, 3), padx=(10, 10))

ymax_label = tk.Label(root, text="y軸最大値 : ")
ymax_label.grid(columnspan=1, row=1, column=0,pady=(3, 0), padx=(10, 10))
y_max = ttk.Combobox(root, values=y_axis_max, width=7)
y_max.current(2)
y_max.grid(row=2, column=0,pady=(0, 3), padx=(10, 10))

# Matplotlibのグラフ作成
fig, ax = plt.subplots()
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(columnspan=16, rowspan=32, row=0,column=2)

# 右側のy軸を作成
ay = ax.twinx()
# 右側のy軸の目盛りを非表示にする
ay.tick_params(axis='y', which='both', length=0)  # 目盛りを非表示にする
ay.set_yticklabels([])  # 目盛りラベルを空にする

root.mainloop()

商品紹介

Posted by Rinemo