Для корректной работы 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. В результате на модели отобразится коэффициент запаса по размахам напряжений.
