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