Создание программы с графическим интерфейсом, использующей CAE Fidesys как внешнюю библиотеку

Для создания модели используется модель из данной статьи: Оптимизация диаметра сечения столба рекламного щита с использованием Python API Fidesys

Для добавления картинки в графическое окно программы скачайте эту картинку и разместите ее в папке со скриптом.

Не забудьте проверить верно ли указана версия CAE Fidesys в пути скрипта.

#https://realpython.com/python-gui-tkinter/

import tkinter as tk # Библиотека графического интерфейса
import vtk        # Библиотека работы с выходными данными
from vtk.util.numpy_support import vtk_to_numpy # Модуль для преобразования результатов
import sys        # Cистемная библиотека
import os         # Cистемная библиотека
from PIL import Image, ImageTk # Библиотеки для удобной работы с изображением
        
fidesys_path = r'C:\Program Files\Fidesys\CAE-Fidesys-6.1' # Расположение Фидесиса
base_dir = os.path.dirname(os.path.abspath(__file__))      # Директория где лежит скрипт
pvd_file = os.path.join(base_dir, '1.pvd') # Файл ссылок на результаты
prep_path = os.path.join(fidesys_path, 'preprocessor', 'bin')    # Директория, где препроцессор
os.environ['PATH'] += prep_path  # Добавление пути к препроцессору в PATH
sys.path.append(prep_path)       # Добавление пути к препроцессору в PATH
#Пытаемся импортировать библиотеки Фидесис
try: #Блок попыток
    import cubit                     # Библиотека препроцессинга
    import fidesys                   # Библиотека Фидесиса
except ModuleNotFoundError: #Если не вышло, то выводим в консоль сообщение
    print("В скрипте указан следующий путь к Фидесис: ", fidesys_path)
    print("Укажите в скрипте путь к вашей версии Фидесис. Вероятно ваша версия отличается от указанной или установлена в другую директорию.")
    sys.exit(1)
cubit.init([""])                 # Инициализация препроцессора
fc = fidesys.FidesysComponent()  # Создание обязательного компонента Фидесис fc

window = tk.Tk() # Создание окна
window.title("One-Button Solution for Billboard") # Заголовок окна

# Основной класс
class MainClass(object):

    # Инициализация переменных
    def __init__(self):
        # Строчки давления
        self.lbl1 = tk.Label(window, text="Wind pressure", width=13)
        self.lbl1.place(x=310, y=10)
 
        self.entry1 = tk.Entry(window, width=14)
        self.entry1.place(x=410, y=10)
        self.entry1.insert(1, '230')

        self.lbl2 = tk.Label(window, text="p, Pa", width=5)
        self.lbl2.place(x=497, y=10)

        # Строчки длины столба
        self.lbl3 = tk.Label(window, text="Pillar lenght", width=13)
        self.lbl3.place(x=305, y=55)
 
        self.entry2 = tk.Entry(window, width=14)
        self.entry2.place(x=410, y=55)
        self.entry2.insert(1, '25')

        self.lbl4 = tk.Label(window, text="L, m", width=4)
        self.lbl4.place(x=500, y=55)

        # Строчки диаметра
        self.lbl5 = tk.Label(window, text="Pillar diameter", width=15)
        self.lbl5.place(x=305, y=100)
 
        self.entry3 = tk.Entry(window, width=14)
        self.entry3.place(x=410, y=100)
        self.entry3.insert(1, '0.5')

        self.lbl6 = tk.Label(window, text="D, m", width=4)
        self.lbl6.place(x=500, y=100)

        # Строчки высоты щита
        self.lbl7 = tk.Label(window, text="Billboard width", width=15)
        self.lbl7.place(x=306, y=145)
 
        self.entry4 = tk.Entry(window, width=14)
        self.entry4.place(x=410, y=145)
        self.entry4.insert(1, '20')

        self.lbl8 = tk.Label(window, text="H, m", width=4)
        self.lbl8.place(x=500, y=145)

        # Строчки ширины щита
        self.lbl9 = tk.Label(window, text="Billboard height", width=16)
        self.lbl9.place(x=305, y=190)
 
        self.entry5 = tk.Entry(window, width=14)
        self.entry5.place(x=410, y=190)
        self.entry5.insert(1, '10')

        self.lbl10 = tk.Label(window, text="W, m", width=4)
        self.lbl10.place(x=500, y=190)

        # Прочностной прогноз
        self.lbl11 = tk.Label(window, text="Strength status", width=15)
        self.lbl11.place(x=305, y=235)
 
        self.entry6 = tk.Entry(window, width=14)
        self.entry6.place(x=410, y=235)
        self.entry6.insert(1, ' Unknown')

        self.pict = Image.open(os.path.join(str(base_dir)+r"\Billboard.png")) # Добавление картинки
        try: # Обработка ошибки на случай разных версий библиотеки PIL
            # Ресайзинг картинки под нужный размер
            self.pict = self.pict.resize((300, 250), Image.Resampling.LANCZOS) 
        except AttributeError:
            # Ресайзинг картинки под нужный размер
            self.pict = self.pict.resize((300,250),resample=Image.BICUBIC) 
        picture = ImageTk.PhotoImage(self.pict)
        self.label1 = tk.Label(image=picture)
        self.label1.image = picture
        self.label1.place(x=10, y=10)

        self.btn2 = tk.Button(master=window, text = 'Calculate', command=self.calculate)
        self.btn2.place(x=260, y=270)

        self.tbx = tk.Text(window, width = 65, height = 5, background ='White')
        self.tbx.place(x=10, y=305)
        self.tbx.insert('1.0', '...')

        self.ext=tk.Button(master=window, text="Close", command=window.destroy)
        self.ext.place(x=495, y=395)

        self.btn3 = tk.Button(master=window, text = 'Open in FidesysViewer', command=self.openView, state=['disabled'])
        self.btn3.place(x=225, y=395)


    def convert(self):
        self.tbx.delete(1.0, 3.0) # Вычищаем поле
        p = self.entry1.get()
        self.appendToField('1.0', "Wind pressure is "+ str(p) + " Pa")

        return 

    def calculate(self):
        fc.init_application(prep_path)  # !!!!!Инициализация для версий 5.1+ (для 5.0 и ниже заменить на fc.initApplication(prep_path))
        fc.start_up_no_args()            # Запуск обязательного компонента Фидесис fc

        self.tbx.delete(1.0, 100.0) # Вычищаем поле

        p = self.entry1.get()
        L = self.entry2.get()
        D = self.entry3.get()
        W = self.entry4.get()
        H = self.entry5.get()

        self.appendToField('1.0', "Calculation has started. \n")

        overstressed = [] # Создаем пустой массив для заполнения перенапряженными узлами
        
        # ---------Начало вставляемого скрипта из Фидесис-------------
        fidesys.cmd("reset")
        fidesys.cmd("brick x "+str(W)+" y 0.5 z "+str(H)+"")
        fidesys.cmd("move Volume 1  x 0 y 0 z 30 include_merged ")
        fidesys.cmd("create frustum height "+str(L)+" radius "+str(float(D)/2)+"top 0.25")
        fidesys.cmd("move Surface 9  location surface 2  include_merged ")
        fidesys.cmd("undo group begin")
        fidesys.cmd("imprint volume all ")
        fidesys.cmd("merge volume all ")
        fidesys.cmd("undo group end")
        fidesys.cmd("volume all scheme tetmesh")
        fidesys.cmd("mesh volume all")
        fidesys.cmd("create material 1 from 'Углеродистая сталь'")
        fidesys.cmd("set duplicate block elements off")
        fidesys.cmd("block 1 add volume all")
        fidesys.cmd("block 'Block 1' material 1 cs 1 element solid order 1")
        fidesys.cmd("create displacement  on surface 8  dof all fix  ")
        fidesys.cmd("create distributed force on surface 3  force value "+str(p)+" moment value 0 direction 0 1 0 specific")
        fidesys.cmd("create gravity global")
        fidesys.cmd("modify gravity 1 dof 3 value -9.81")
        fidesys.cmd("analysis type static elasticity dim3")
        # ---------Конец вставляемого скрипта из Фидесис-------------
        
        output_pvd_path = os.path.join(base_dir + "\\" + "1.pvd") # Объявляем директорию и файл сохранения
        print("strarting calculation to " + output_pvd_path)            # Выводим в консоль директорию и файл сохранения
        fidesys.cmd("calculation start path '" + output_pvd_path + "'")  # Просим Фидесис начать расчет в указанную директорию
        
        self.appendToField('2.0', "Calculation has finished. \n")
        self.appendToField('3.0', "Reading results. \n")
        reader = vtk.vtkXMLUnstructuredGridReader()                             #Подключаем читалку
        print("Читаем результаты из ",str(base_dir)+r"\1\case1_step0001_substep0001.vtu") # Пишет откуда берем результаты
        self.filename = os.path.join(str(base_dir)+r"\1\case1_step0001_substep0001.vtu") # Указываем путь к файлу
        reader.SetFileName(self.filename)                                            # Подключаем путь к читалке и читаем
        reader.Update()                                                         # Needed because of GetScalarRange
        grid = reader.GetOutput()                                               # Забираем выходные данные
        point_data = grid.GetPointData()                                        # Забираем данные для точек

        self.appendToField('4.0', "Analysing results. \n")
        arrayOfStress = vtk_to_numpy(point_data.GetArray("Stress")) # Считываем напряжения из массива результатов
        node_id = vtk_to_numpy(point_data.GetArray("Node ID"))      # Считываем номера узлов из массива результатов

        for point in range(len(arrayOfStress)):
                if arrayOfStress[point][6] > 106e6:     # Проверяем напряжения по Мизесу в узлах
                    overstressed.append(node_id[point]) # Заполняем массив номерами перенапряженных узлов

        if len(overstressed) == 0:  # Проверяется размер массива перенапряженных узлов, если он 0 то
            self.entry6.configure(bg='green')
            self.entry6.delete(1,30)
            self.entry6.insert(1, ' Good design')
            print("Good")
            self.appendToField('5.0', "Design is good. \n")
            self.btn3.config(state="active")
        else:
            self.entry6.configure(bg='red')
            self.entry6.delete(1,30)
            self.entry6.insert(1, ' Bad design')
            print("Bad")
            self.appendToField('6.0', "Design is bad. Try another parameters. \n")
            self.btn3.config(state="active")

        fc.delete_application() # Команда удаления задачи из памяти для версии 5.1 (для 5.0 и ниже заменить на fc.deleteApplication())

        return
    
    def openView(self):
        os.startfile(pvd_file)
        return

    def appendToField(self, pos, txtData):
        self.tbx.insert(pos, txtData)
        self.tbx.update()
    
    def main(self): 
        window.minsize(545,430)
        window.mainloop()

if __name__ == "__main__":
    MainClass().main()

Попробуйте запустить программу. В случае успешного запуска вы увидите следующее окно:

Попробуйте нажать "Calculate" и посмотреть результат, затем изменить диаметр столба с 0.5 на 0.6 и еще раз запустить расчет.