Оценка усталостной долговечности при изгибе однопролетной балки

Для корректной работы python-файла, сначало нужно установить библиотеки scipy, PyQt5: pip install scipy и pip install pyQt5==5.15.1.

Создайте новый файл python. Для этого запустите среду разработки IDLE из меню «Пуск» или папки, где установлен Python. Выберите File - New File.

Cкопируйте в открывшееся пустое окно следующий скрипт:

import  meshio, os, sys
import numpy as np
from scipy.interpolate import interp1d
from scipy.linalg import solve

BASE_DIR = os.getcwd()
JOIN = os.path.join
RESULT_FILENAME = 'Static'
STATIC_RESULT_PATH = f'{BASE_DIR}/{RESULT_FILENAME}'
FIDESYS_PREP_DIR = 'C:/Program Files/Fidesys/CAE-Fidesys-6.0/preprocessor/bin' 
prep_paths = [
FIDESYS_PREP_DIR,
os.path.join(FIDESYS_PREP_DIR, 'plugins'),
os.path.join(FIDESYS_PREP_DIR, 'acis', 'code', 'bin')
] # директории, где препроцессор
calc_path = os.path.join(FIDESYS_PREP_DIR, 'FidesysCalc.exe') # директория, где решатель
for path in prep_paths:
    os.environ['PATH'] += os.pathsep + path # Добавление пути к препроцессору в PATH.
    os.add_dll_directory(path)
    sys.path.append(path) # Добавление пути к препроцессору в PATH.
try:
    import cubit, fidesys
except ModuleNotFoundError:
    print(f'Модули fydesys, cubit не найдены в директории {FIDESYS_PREP_DIR}')  
    sys.exit(1)
FC = fidesys.FidesysComponent()      
class Fatique:       
    def __init__(self,fatique_params):
        self.fatique_params = fatique_params  
   # Поиск минимума и максимума по циклам      
    def get_max_and_min_cycles_from_curve(self):
        points = self.fatique_params['sn_points_dict']
        N = np.array(points['cycles'])
        maxN = np.max(N)
        minN = np.min(N)
        return minN,maxN
    # Поиск коэффициента k,b для линейной логарифмической интерполяции кривой        
    def get_coeffs_from_log_interpolation(self):
        points = self.fatique_params['sn_points_dict']
        log_n = np.log10(points['cycles'])
        log_s = np.log10(points['stress'])
        max_log_n = np.max(log_n)
        min_log_n = np.min(log_n)
        log_function = interp1d(log_n,log_s,kind = 'linear')
        matrix = np.array([[min_log_n,1],[max_log_n,1]])
        vector = np.array([log_function(min_log_n),log_function(max_log_n)])
        coeffs = solve(matrix,vector)
        return coeffs
    # Подсчет скрипта фидесиса 
    def calculate_fidesys_script(self):
        cubit.init(['cubit','-nojournal'])
        FC.init_application(FIDESYS_PREP_DIR) # инициализация
        FC.start_up_no_args() # запуск обязательного компонента Фидесис fc
        fidesys.cmd('reset')
        fidesys.cmd('brick x 0.006 y 0.012 z 0.25')
        fidesys.cmd('graphics axis on')
        fidesys.cmd('volume all size auto factor 4')
        fidesys.cmd('mesh volume all')
        fidesys.cmd('create material 1')
        fidesys.cmd('modify material 1 name \'alum\'')
        fidesys.cmd('modify material 1 set property \'MODULUS\' value 7.31e+10')
        fidesys.cmd('modify material 1 set property \'POISSON\' value 0')
        fidesys.cmd('set duplicate block elements off')
        fidesys.cmd('block 1 add volume 1')
        fidesys.cmd('block 1 material 1 cs 1 element solid order 1')
        fidesys.cmd('create displacement  on surface 2 dof all fix 0 ')
        fidesys.cmd('create distributed force on surface 1  force value 50 moment value 0 direction 0 -10 0 equivalent')
        fidesys.cmd('analysis type static elasticity dim3')
        fidesys.cmd('output nodalforce on energy off midresults off record3d off material off modelprops off')   
        fidesys.cmd(f"calculation start path '{STATIC_RESULT_PATH}.pvd'")
    # вытаскиваем координаты узлов и массив связи элементов и узлов
    def get_elements_nodes_connect_and_nodal_coords(self):
        self.calculate_fidesys_script()
        nodal_coords = [] # массив координат узлов
        nodes_id = [] # массив id узлов
        elements_nodes_connect = [] # массив связи узлов и элементов
        node_count = cubit.get_node_count() # количество узлов
        element_count = cubit.get_element_count() # количество элементов
        for node_id in range(1,node_count+1):
            nodes_id.append(node_id) # заполнение массива id узлов
            x,y,z = cubit.get_nodal_coordinates(node_id) # получение координат для каждого узла
            nodal_coords.append([x,y,z]) # заполнение массива координат узлов
        for element_id in range(1,element_count+1):
            node_id_list = cubit.get_expanded_connectivity("hex", element_id)  #поиск кортежа узлов для каждого гексаэдрального элемента
            node_id_list = list(node_id_list)   # конвертация кортежа в массив 
            elements_nodes_connect.append(list(map(lambda el: el-1, node_id_list)))  # в каждом массиве узлов от каждого элемента необходимо отнять единицу  
        FC.delete_application() # удаление задачи из памяти
        return  elements_nodes_connect, nodal_coords
    # получение массива компоненты напряжений из вту файла                     
    def get_stress_array_from_vtu(self):
        stress_column_index = self.fatique_params['stress_column_index']
        mesh = meshio.read(f'{STATIC_RESULT_PATH}/case1_step0001_substep0001.vtu')
        stress_array = mesh.point_data['Stress'][:,stress_column_index]
        return stress_array
    # получение массива id узлов из вту файла 
    def get_node_id_array_from_vtu(self):
        mesh = meshio.read(f'{STATIC_RESULT_PATH}/case1_step0001_substep0001.vtu')
        node_id_array = mesh.point_data['Node ID']
        node_count = len(node_id_array) 
        return node_id_array,  node_count
    # сортировка массива напряжений 
    def get_stress_array_to_write(self):
        stress_array = self.get_stress_array_from_vtu()
        node_id_array, node_count = self.get_node_id_array_from_vtu()
        stress_array_to_write = np.array([])
        for id in range(1,node_count + 1):
            index = np.where(node_id_array == id)
            stress_array_to_write = np.insert(stress_array[index],0, stress_array_to_write)
        return  stress_array_to_write
    # получение результатов размаха напряжений и среднего напряжения в соответствии с типом цикла нагружения
    def get_alternating_and_mean_stress(self):
        # максимальное напряжение цикла - sigma_max (равно статике), минимальное  напряжение цикла sigma_min
        # размахи напряжений - alternating_sigma, # среднее напряжение - mean_sigma
        # r - коэффициент ассиметрии цикла (в случае ассимитричного цикла)
        sigma_max = self.get_stress_array_to_write()
        cicle_type = self.fatique_params['cicle_type']['type']
        if cicle_type == 'Symmetrical':
            sigma_min = - sigma_max
        if cicle_type == 'Zero_Based':
            sigma_min = 0
        if cicle_type == 'Asymmetrical':
            r = self.fatique_params['cicle_type']['ratio']
            sigma_min = sigma_max*r
        alternating_sigma = abs(sigma_max - sigma_min)/2
        mean_sigma  = abs(sigma_max + sigma_min)/2
        return alternating_sigma,mean_sigma
    # получение результатов размаха напряжений/ коэффициент усталости  в соответствии с выбором теории коррекции среднего напряжения (Гудман, Содерберг, Гербер, Элиптический) 
    def get_alternating_stress_with_mean_stress_theory(self):
        alternating_sigma,mean_sigma = self.get_alternating_and_mean_stress()
        mean_stress_theory = self.fatique_params['mean_stress_theory']
        fatique_coeff = self.fatique_params['fatique_coeff']
        ultimate_stress = self.fatique_params['ultimate_stress']
        yield_stress = self.fatique_params['ultimate_stress']
        if mean_stress_theory == None:
            return alternating_sigma/fatique_coeff 
        if mean_stress_theory == 'Godman':
            res = mean_sigma/ultimate_stress
            alternating_sigma = alternating_sigma/abs(1-res)  
        if mean_stress_theory == 'SoderBerg':
            res = mean_sigma/yield_stress
            alternating_sigma = alternating_sigma/abs(1-res)
        if mean_stress_theory == 'Gerber':
            res = (mean_sigma/ultimate_stress)**2
            alternating_sigma = alternating_sigma/abs(1-res)
        if mean_stress_theory == 'Eleptic':
            res1 = (yield_stress - mean_sigma)**0.5
            res2 = (yield_stress + mean_sigma)**0.5
            res = res1*res2
            alternating_sigma = alternating_sigma*yield_stress/res
        return  alternating_sigma/fatique_coeff  
    # Получение количества циклов, сооветсвующее полученному размаху напряжений из кривой 
    def get_cycles_from_stress_array(self):
        [k,b] = self.get_coeffs_from_log_interpolation()
        stress_array = self.get_alternating_stress_with_mean_stress_theory()
        cycles = 10**((np.log10(stress_array) - b)/k)
        return cycles
    # Получение предельного напряжения из кривой (сооветсвующее заданному количеству циклов нагружения design_life) 
    def get_limit_stress_from_cycle(self):
        design_cycles = self.fatique_params['design_cycles']
        [k,b] = self.get_coeffs_from_log_interpolation()
        limit_stress = 10**(k*np.log10(design_cycles)+b)
        return limit_stress
    # Получение Коэффициента запаса усталости по размахам напряжений в соотвествествии с выбором теории коррекции среднего напряжения
    def get_safety_factor_with_mean_stress_theory(self):
        mean_stress_theory = self.fatique_params['mean_stress_theory']
        fatique_coeff = self.fatique_params['fatique_coeff']
        ultimate_stress = self.fatique_params['ultimate_stress']
        yield_stress = self.fatique_params['ultimate_stress']
        alternating_sigma,mean_sigma = self.get_alternating_and_mean_stress()
        alter_stress = self.get_alternating_stress_with_mean_stress_theory()
        limit_stress = self.get_limit_stress_from_cycle()
        safety_factor = limit_stress/alter_stress
        if mean_stress_theory == None:
            return safety_factor
        if mean_stress_theory == 'Godman': 
            res1 = (alternating_sigma/fatique_coeff )/limit_stress
            res2 =  (mean_sigma)/ultimate_stress
            safety_factor = 1/(res1+res2)
        if mean_stress_theory == 'SoderBerg':
            res1 = (alternating_sigma/fatique_coeff )/limit_stress
            res2 =  (mean_sigma)/yield_stress
            safety_factor = 1/(res1+res2)    
        if mean_stress_theory == 'Gerber':
            res1 = (ultimate_stress/mean_sigma)**2
            res2 = (alternating_sigma/fatique_coeff )/limit_stress
            res3 = (2*(mean_sigma*limit_stress)/(ultimate_stress*alternating_sigma/fatique_coeff ))**2
            safety_factor = 0.5 * res1*res2*(-1 +(1+ res3)**0.5)
        if mean_stress_theory == 'Eleptic': 
            res1 = (alternating_sigma/fatique_coeff )/limit_stress
            res2 =  (mean_sigma)/yield_stress
            safety_factor = (1/(res1**2 + res2**2))**0.5   
        return safety_factor 
    # Получение массивов размаха напряжений, коэффициента запаса усталости по напряжениям,   коэффициента запаса усталости по циклам, количества циклов до разрушения
    # Вся ниже приведенная логика реализована по аналогии с Ansys WB

    # 1.Если полученные значения для life меньше минимального значения циклов в кривой S-N  (в S-N_curve.csv  это 1000), ставим значение life - 0, 
    # 2.если полученные значения для life больше мамксимального значения циклов в кривой S-N  (в S-N_curve.csv  это 1e8), ставим последнее значение ,приведенное в таблице (в S-N_curve.csv  это 1e8)
    # 3.если полученные значения life находятся в пределах значений заданной кривой (в нашем случае [1000,1e8])
    # 4.по дефолту массив damage заполняется очень большим значением (1e32), далее идет корректировка: если life = 0, то damage остается как есть, иначе damage равно количество заданных циклов/life
    # 5.массив safety_factor ограничен значением 15. Все значения больше 15 удаляются и ставятся 15.
    # 
    def calculate_fatique(self):
        design_cycles = self.fatique_params['design_cycles']
        alter_stress = self.get_alternating_stress_with_mean_stress_theory()
        life = self.get_cycles_from_stress_array() 
        minN,maxN = self.get_max_and_min_cycles_from_curve()        
        damage = np.empty(len(alter_stress))
        damage.fill(1e+32)
        safety_factor = self.get_safety_factor_with_mean_stress_theory()
        for index in range(0,len(alter_stress)):
            life[index] = 0 if life[index] < minN else life[index]
            life[index] = maxN if life[index] > maxN else life[index]
            damage[index] = design_cycles/life[index] if life[index] != 0 else damage[index]
            safety_factor[index] = 15 if safety_factor[index] > 15 else safety_factor[index]
        return alter_stress,life,damage,safety_factor
    # Строим полученный vtu- файл
    def plot_vtu_mesh_file(self):
        elements_nodes_connect, nodal_coords = self.get_elements_nodes_connect_and_nodal_coords()
        alter_stress ,life,damage, safety_factor = self.calculate_fatique()                
        cells = [
        ("hexahedron", elements_nodes_connect)] # ячейки в виде тетраэдров первого порядка
        mesh = meshio.Mesh(
    nodal_coords,
    cells,
    point_data= {
        "Alternating Stress":alter_stress,
        "Damage":damage,
        "Life" :life,
        "Safety Factor":safety_factor
        }
) # формирование сетки и значений в узлах заданных массивов
        mesh.write(
    JOIN(BASE_DIR ,"fatique.vtu")
) # запись vtu - файла

Сохраните его с названием calculate_fatique.py.

Затем создайте новый файл python в той же папке, где вы сохранили предыдущий файл:

from PyQt5 import  QtWidgets,QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu,QMenuBar, QFileDialog,QPushButton,QTableWidget,QTableWidgetItem,QComboBox, QSlider,QLineEdit,QLabel
from PyQt5.QtCore import QThread,Qt
import sys,csv,os
from calculate_fatique import Fatique

BASE_DIR = os.getcwd()
JOIN = os.path.join
RESULT_FILENAME = 'Static'
STATIC_RESULT_PATH = f'{BASE_DIR}/{RESULT_FILENAME}'
print(QtCore.PYQT_VERSION_STR)
class Window(QMainWindow): 
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Fatique')
        self.setFixedHeight(500)
        self.setFixedWidth(900)
        self.setWindowIcon(QtGui.QIcon('fidesys.ico'))

        ## Add select csv file button
        self.select_file_btn = QPushButton(self)
        self.select_file_btn.setText('Select File')
        self.select_file_btn.move(50,350)
        self.select_file_btn.clicked.connect(self.select_csv_file)

        # Add csv table
        self.table = QTableWidget(self)
        self.table.setGeometry(50,20,300,300)
        self.table.setRowCount(5)
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(('Cycles','Stress')) 

        # Add cycleTypeList
        self.cycle_types_list = QComboBox(self)
        self.cycle_types_list.addItems(['Symmetrical','Zero_Based','Asymmetrical'])
        self.cycle_types_list.move(500,60)
        self.cycle_types_list.activated.connect(self.is_visible_asymmetrical_coefficient)
        # Add Label for  cycle Type
        self.cycle_type_label = QLabel(self)
        self.cycle_type_label .setText('cycle type')
        self.cycle_type_label .move(400,60)

        # Add Label for  meanStressTheory
        self.mean_stress_theory_label = QLabel(self)
        self.mean_stress_theory_label.setText('Mean Stress Theory')
        self.mean_stress_theory_label.move(400,20)
        # Add mean_stress_theory_list
        self.mean_stress_theory_list = QComboBox(self)
        self.mean_stress_theory_list.addItems(['None','Godman','SoderBerg','Gerber','Eleptic'])
        self.mean_stress_theory_list.move(500,20)  

        # Add LineTextArea for assymmetrical cycle
        self.asymmetrical_ratio_text_area = QLineEdit(self)
        self.asymmetrical_ratio_text_area.setText('-1')
        self.asymmetrical_ratio_text_area.move(750,60)
        self.asymmetrical_ratio_text_area.textChanged.connect(self.correct_asymmetrical_ratio_text)
        self.asymmetrical_ratio_text_area.hide() 

        # Add assymmetrical cycle coefficient Label
        self.asymmetrical_ratio_label = QLabel(self)
        self.asymmetrical_ratio_label.setText('cycleCoefficient')
        self.asymmetrical_ratio_label.move(650,60)
        self.asymmetrical_ratio_label.hide()

         # Add assymmetrical  coefficient Slider
        self.assymmetrical_coefficient_slider = QSlider(Qt.Orientation.Horizontal,self)
        self.assymmetrical_coefficient_slider.move(750,100)
        self.assymmetrical_coefficient_slider.setRange(-1000,1000)
        self.assymmetrical_coefficient_slider.setValue(-1000)
        self.assymmetrical_coefficient_slider.valueChanged.connect(self.change_asymmetrical_ratio_slider_value)
        self.assymmetrical_coefficient_slider.hide()

        # Add LineTextArea yieldStress
        self.yield_stress_text_area = QLineEdit(self)
        self.yield_stress_text_area.setText('2e8')
        self.yield_stress_text_area.move(500,100)   

        # Add yieldStress label
        self.yield_stress_label = QLabel(self)
        self.yield_stress_label.setText('yield stress')
        self.yield_stress_label.move(400,100)

        # Add LineTextArea ultimate Stress
        self.ultimate_stress_text_area = QLineEdit(self)
        self.ultimate_stress_text_area.setText('5.642e8')
        self.ultimate_stress_text_area.move(500,140)  

        # Add ultimateStress label
        self.ultimate_stress_label = QLabel(self)
        self.ultimate_stress_label.setText('ultimate stress')
        self.ultimate_stress_label.move(400,140)      

        # Add LineTextArea fatiqueCoefficient
        self.fatique_coefficient_text_area = QLineEdit(self)
        self.fatique_coefficient_text_area.setText('1')
        self.fatique_coefficient_text_area.move(500,195)
        self.fatique_coefficient_text_area.textChanged.connect(self.correct_fatique_coefficient_text) 
    
        # Add fatiqueCoefficient label
        self.fatique_coefficient_label = QLabel(self)
        self.fatique_coefficient_label.setText('Fatique Coefficient')
        self.fatique_coefficient_label.move(400,195)

         # Add fatiqueCoefficient Slider
        self.fatique_Coefficient_slider = QSlider(Qt.Orientation.Horizontal,self)
        self.fatique_Coefficient_slider.move(650,195)
        self.fatique_Coefficient_slider.setRange(10,1000)
        self.fatique_Coefficient_slider.setValue(1000)
        self.fatique_Coefficient_slider.valueChanged.connect(self.change_fatique_coefficient_slider_value)

        # Add designLife Label
        self.design_life_label = QLabel(self)
        self.design_life_label.setText('Design Life')
        self.design_life_label.move(400,250)

        # Add designLife TextArea
        self.design_life_text_area = QLineEdit(self)
        self.design_life_text_area.setText('1e8')
        self.design_life_text_area.move(500,250)

       # Add stressComponent Label
        self.stress_component_label = QLabel(self)
        self.stress_component_label.setText('stress component')
        self.stress_component_label.move(400,290)

        #Add stressComponent List
        self.stress_component_list = QComboBox(self)
        self.stress_component_list.addItems(['Sxx','Syy','Szz','Sxy','Syz','Sxz','Seq','S1','S2','S3'])
        self.stress_component_list.move(500,290)

       # Add calculate button
        self.calculate_btn = QPushButton(self)
        self.calculate_btn.setText('Calculate')
        self.calculate_btn.move(700,350)
        self.calculate_btn.clicked.connect(self.calculate_fatique)   

    def select_csv_file(self):
        csv_line_index = 0
        try:
            fname = QFileDialog.getOpenFileName()[0]
            with open(fname,'r') as file:
                reader = list(csv.reader(file))
                while csv_line_index < len(reader):
                    row_count = self.table.rowCount()
                    line = reader[csv_line_index][0].split(';')
                    if csv_line_index == row_count:
                        self.table.insertRow(row_count)   
                    self.table.setItem(csv_line_index,0,QTableWidgetItem(line[0]))
                    self.table.setItem(csv_line_index,1,QTableWidgetItem(line[1]))       
                    csv_line_index += 1
        except FileNotFoundError:
            return
                         
    def is_visible_asymmetrical_coefficient(self):
       text_area = self.asymmetrical_ratio_text_area
       label = self.asymmetrical_ratio_label
       slider = self.assymmetrical_coefficient_slider
       cycle_list =  self.cycle_types_list 
       if cycle_list.currentText() ==  'Asymmetrical':
            text_area.show()
            label.show()
            slider.show()            
       else:  
            text_area.hide()
            label.hide()
            slider.hide()
           
    def change_fatique_coefficient_slider_value(self,value):
        if self.fatique_coefficient_text_area.text() == '':
            return            
        if float(self.fatique_coefficient_text_area.text()) == 0:
            return
        self.fatique_coefficient_text_area.setText(str(value/1000))                           
    def correct_fatique_coefficient_text(self):
        try:
            self.fatique_Coefficient_slider.setValue(int(float(self.fatique_coefficient_text_area.text())*1000))
        except ValueError:
            return
    def correct_asymmetrical_ratio_text(self):
        try:
            self.assymmetrical_coefficient_slider.setValue(int(float(self.asymmetrical_ratio_text_area.text())*1000))
        except ValueError:
            return
    def change_asymmetrical_ratio_slider_value(self,value):
        if self.asymmetrical_ratio_text_area.text() == '':
            return 
        if  float(self.asymmetrical_ratio_text_area.text()) in [-1000,0,1000]:
            self.asymmetrical_ratio_text_area.setText(str(int(value/1000)))  
        else:
            self.asymmetrical_ratio_text_area.setText(str(value/1000))                                      
    def get_dict_from_table(self):
        table =  self.table
        row_count = table.rowCount()
        dictionary = {'cycles':[],'stress':[]}
        for row in range(0,row_count):
                cycle_row = table.item(row,0).text()
                stress_row = table.item(row,1).text()
                dictionary['cycles'].append(float(cycle_row))
                dictionary['stress'].append(float(stress_row))
        return dictionary
    def calculate_fatique(self):
        sn_points_dict = self.get_dict_from_table()
        stress_column_index = int(self.stress_component_list.currentIndex())
        design_cycles = float(self.design_life_text_area.text())
        fatique_coeff = float(self.fatique_coefficient_text_area.text())
        cicle_type = self.cycle_types_list.currentText()
        assymetry_coefficient = float(self.asymmetrical_ratio_text_area.text())
        mean_stress_theory = self.mean_stress_theory_list.currentText()
        yield_stress = float(self.yield_stress_text_area.text())
        ultimate_stress = float(self.ultimate_stress_text_area.text())
        fatique_params = {
'sn_points_dict' : sn_points_dict,  
'stress_column_index':stress_column_index,
'design_cycles' : design_cycles,
'fatique_coeff' : fatique_coeff,
'cicle_type':{'type':cicle_type,'ratio':assymetry_coefficient},
'mean_stress_theory': mean_stress_theory,
'yield_stress':yield_stress,
'ultimate_stress' :  ultimate_stress
}
        fatique_instance = Fatique(fatique_params)
        fatique_instance.plot_vtu_mesh_file() 


def application():
    app = QApplication(sys.argv)
    window = Window()
    window.show()  
    sys.exit(app.exec_())       
if __name__ == '__main__':
    application()    
    
    
    '''
    def get_max_and_min_cycles_from_table(self):
        points = self.get_dict_from_table()
        N = np.array(points['cycles'])
        maxN = np.max(N)
        minN = np.min(N)
        return minN,maxN
    def get_coeffs_from_log_interpolation(self):
        points = self.get_dict_from_table()
        logN = np.log10(points['cycles'])
        logS = np.log10(points['stress'])
        maxLogN = np.max(logN)
        minLogN = np.min(logN)
        logFunction = interp1d(logN,logS,kind = 'linear')
        matrix = np.array([[minLogN,1],[maxLogN,1]])
        vector = np.array([logFunction(minLogN),logFunction(maxLogN)])
        coeffs = solve(matrix,vector)
        return coeffs
    def calculate_fidesys_script(self):
        cubit.init([""])
         # cоздание обязательного компонента Фидесис fc
        FC.init_application(FIDESYS_PREP_DIR) # инициализация
        FC.start_up_no_args() # запуск обязательного компонента Фидесис fc
        fidesys.cmd('reset')
        fidesys.cmd('brick x 0.006 y 0.012 z 0.25')
        fidesys.cmd('graphics axis on')
        fidesys.cmd('volume all size auto factor 4')
        fidesys.cmd('mesh volume all')
        fidesys.cmd('create material 1')
        fidesys.cmd('modify material 1 name \'alum\'')
        fidesys.cmd('modify material 1 set property \'MODULUS\' value 7.31e+10')
        fidesys.cmd('modify material 1 set property \'POISSON\' value 0')
        fidesys.cmd('set duplicate block elements off')
        fidesys.cmd('block 1 add volume 1 ')
        fidesys.cmd('block 1 material 1 cs 1 element solid order 1')
        fidesys.cmd('create displacement  on surface 2 dof all fix 0 ')
        fidesys.cmd('create distributed force on surface 1  force value 50 moment value 0 direction 0 -10 0 equivalent')
        fidesys.cmd('analysis type static elasticity dim3')
        fidesys.cmd('output nodalforce on energy off midresults off record3d off material off modelprops off')
        fidesys.cmd(f"calculation start path '{STATIC_RESULT_PATH}.pvd'")
    def get_elements_nodes_connect_and_nodal_coords(self):
        self.calculate_fidesys_script()
        nodal_coords = [] # массив координат узлов
        nodes_id = [] # массив id узлов
        elements_nodes_connect = [] # массив связи узлов и элементов
        node_count = cubit.get_node_count() # количество узлов
        element_count = cubit.get_element_count() # количество элементов
        for node_id in range(1,node_count+1):
            nodes_id.append(node_id) # заполнение массива id узлов
            x,y,z = cubit.get_nodal_coordinates(node_id) # получение координат для каждого узла
            nodal_coords.append([x,y,z]) # заполнение массива координат узлов
        for element_id in range(1,element_count+1):
            node_id_list = cubit.get_expanded_connectivity("hex", element_id)  #поиск кортежа узлов для каждого гексаэдрального элемента
            node_id_list = list(node_id_list)   # конвертация кортежа в массив 
            elements_nodes_connect.append(list(map(lambda el: el-1, node_id_list)))  # в каждом массиве узлов от каждого элемента необходимо отнять единицу  
        FC.delete_application() # удаление задачи из памяти
        return  elements_nodes_connect, nodal_coords
    def get_stress_array_from_vtu(self):
        mesh = meshio.read(f'{STATIC_RESULT_PATH}/case1_step0001_substep0001.vtu')
        stress_array = mesh.point_data['Stress'][:, self.stressColumnIndex] ## Сделать поле для индекса напряжений
        return stress_array

    def get_node_id_array_from_vtu(self):
        mesh = meshio.read(f'{STATIC_RESULT_PATH}/case1_step0001_substep0001.vtu')
        node_id_array = mesh.point_data['Node ID']
        node_count = len(node_id_array) 
        return node_id_array, node_count 
    
    def get_stress_array_to_write(self):
        stress_array = self.get_stress_array_from_vtu()
        node_id_array, node_count = self.get_node_id_array_from_vtu()
        stress_array_to_write = np.array([])
        for id in range(1,node_count + 1):
            index = np.where(node_id_array == id)
            stress_array_to_write = np.insert(stress_array[index],0,stress_array_to_write)
        return stress_array_to_write
    def get_alternating_and_mean_stress(self):
        sigmaMax = self.get_stress_array_to_write()
        cycleList =  self.cycle_types_list.currentText()
        assym_coeff =  self.asymmetrical_ratio_text_area.text()
        if cycleList == 'Symmetrical':
            sigmaMin = - sigmaMax
        if cycleList == 'Zero_Based':
            sigmaMin = 0
        if cycleList == 'Asymmetrical':
            r = assym_coeff
            sigmaMin = sigmaMax*r
        alternatingSigma = abs(sigmaMax - sigmaMin)/2
        meanSigma  = abs(sigmaMax + sigmaMin)/2
        return alternatingSigma,meanSigma

    def get_alternating_stress_with_mean_stress_theory(self):
        alternatingSigma,meanSigma = self.get_alternating_and_mean_stress()
        mean_stress_theory = self.mean_stress_theory_list.currentText()
        fatique_coeff =  self.fatique_coefficient_text_area.text()
        yield_stress = float(self.yield_stress_text_area.text())
        ultimate_stress = float(self.ultimate_stress_text_area.text())
        if mean_stress_theory == 'None':
            return alternatingSigma/fatique_coeff  
        if mean_stress_theory == 'Godman':
            res = meanSigma/ultimate_stress
            alternatingSigma = alternatingSigma/abs(1-res)  
        if mean_stress_theory == 'SoderBerg':
            res = meanSigma/yield_stress
            alternatingSigma = alternatingSigma/abs(1-res)
        if mean_stress_theory == 'Gerber':
            res = (meanSigma/ultimate_stress)**2
            alternatingSigma = alternatingSigma/abs(1-res)
        if  mean_stress_theory == 'Eleptic':
            res1 = (yield_stress - meanSigma)**0.5
            res2 = (yield_stress + meanSigma)**0.5
            res = res1*res2
            alternatingSigma = alternatingSigma*yield_stress/res
        return  alternatingSigma/fatique_coeff
    def get_cycles_from_stress_array(self):
        [k,b] = self.get_coeffs_from_log_interpolation()
        stress_array = self.get_alternating_stress_with_mean_stress_theory()
        cycles = 10**((np.log10(stress_array) - b)/k)
        return cycles

    def get_limit_stress_from_cycle(self):
        [k,b] = self.get_coeffs_from_log_interpolation()
        limit_stress = 10**(k*np.log10(self.designCycles)+b) #Ввести поле для количества циклов
        return limit_stress 
    def get_safety_factor_with_mean_stress_theory(self):
        mean_stress_theory = self.mean_stress_theory_list.currentText()
        fatique_coeff =  self.fatique_coefficient_text_area.text()
        yield_stress = float(self.yield_stress_text_area.text())
        ultimate_stress = float(self.ultimate_stress_text_area.text())
        alternating_sigma,mean_sigma = self.get_alternating_and_mean_stress()
        alter_stress = self.get_alternating_stress_with_mean_stress_theory()
        limit_Stress = self.get_limit_stress_from_cycle() 
        safety_factor = limit_Stress/alter_stress
        if mean_stress_theory == 'None':
            return safety_factor
        if mean_stress_theory == 'Godman': 
            res1 = (alternating_sigma/fatique_coeff)/limit_Stress
            res2 =  (mean_sigma)/ultimate_stress
            safety_factor = 1/(res1+res2)
        if mean_stress_theory == 'SoderBerg':
            res1 = (alternating_sigma/fatique_coeff)/limit_Stress
            res2 =  (mean_sigma)/yield_stress
            safety_factor = 1/(res1+res2)    
        if mean_stress_theory == 'Gerber':
            res1 = (ultimate_stress/mean_sigma)**2
            res2 = (alternating_sigma/fatique_coeff)/limit_Stress
            res3 = (2*(mean_sigma*limit_Stress)/(ultimate_stress*alternating_sigma/fatique_coeff))**2
            safety_factor = 0.5 * res1*res2*(-1 +(1+ res3)**0.5)
        if  mean_stress_theory == 'Eleptic': 
            res1 = (alternating_sigma/fatique_coeff)/limit_Stress
            res2 =  (mean_sigma)/yield_stress
            safety_factor = (1/(res1**2 + res2**2))**0.5   
        return safety_factor
    def calculate_fatique(self):
        alter_stress = self.get_alternating_stress_with_mean_stress_theory()
        life = self.get_cycles_from_stress_array()
        minN,maxN = self.get_max_and_min_cycles_from_table()
        damage = np.empty(len(alter_stress))
        damage.fill(1e+32)
        safetyFactor = self.get_safety_factor_with_mean_stress_theory()
        for index in range(0,len(alter_stress)):
            life[index] = 0 if life[index] < minN else life[index]
            life[index] = maxN if life[index] > maxN else life[index]
            damage[index] = self.designCycles/life[index] if life[index] != 0 else damage[index] ## количество циклов поставить
            safetyFactor[index] = 15 if safetyFactor[index] > 15 else safetyFactor[index]
        return alter_stress,life,damage,safetyFactor
    
    def plotVtuMeshFile(self):
        elements_nodes_connect, nodal_coords = self.calculate_fidesys_script()
        alter_stress ,life,damage, safety_factor = self.calculate_fatique()       
        cells = [
        ("hexahedron", elements_nodes_connect)] # ячейки в виде тетраэдров первого порядка
        mesh = meshio.Mesh(
    nodal_coords,
    cells,
    point_data= {
        "Alternating Stress":alter_stress,
        "Damage":damage,
        "Life" :life,
        "Safety Factor":safety_factor
        }
) # формирование сетки и значений в узлах заданных массивов
        mesh.write(
    JOIN(BASE_DIR ,"fatique.vtu")
) # запись vtu - файла   
 '''   

Сохраните файл с названием Window.py.

Запустите на расчет. Выберите Run - Run Module.

После того как расчет прошел появится диалоговое окно для расчета:

В диалоговом окне нажмите Select File и выберите образец кривой "напряжения - циклы" (Кривая Веллера). Нажмите Open.

Задайте коэффициент усталости (Fatique Coefficient) и компоненту напряжения (stress component). Нажмите Calculate.

В папке с файлом скрипта будут лежать файлы с результатами расчета. Откройте vtu-файл с названием fatique.

Появится окно FidesysViewer, в котором вы сможете ознакомиться с результатами расчёта.

На верхней панели выберите данные результата расчета для отображения. Из первого выпадающего списка выберите Alternating Stress. В результате на модели отобразятся размахи напряжений.

Затем из первого выпадающего списка выберите Damage. В результате на модели отобразится коэффициент запаса по циклам.

Далее из первого выпадающего списка выберите Life. В результате на модели отобразится количество циклов до разрушения.

Из первого выпадающего списка выберите Safety Factor. В результате на модели отобразится коэффициент запаса по размахам напряжений.