В данной статье показан пример того, как можно создать пользовательскую утилиту с графическим интерфейсом для расчета критических скоростей вращения системы вал-ротор с учетом гироскопического эффекта и построения диаграммы Кэмпбелла.
Для корректной работы python-файла, сначало нужно установить библиотеки pyQt5=5.15.1 (именно 5.15.1, поскольку выше версии предпроцессор фидесиса не поддерживает), numpy, scipy, meshio, pyqtgraph: pip install pyQt5==5.15.1, pip install numpy, pip install scipy, pip install meshio, pip install pyqtgraph.
Создайте новый файл python. Для этого запустите среду разработки IDLE из меню «Пуск» или папки, где установлен Python. Выберите File - New File.
Cкопируйте в открывшееся пустое окно следующий скрипт:
import shutil,os
JOIN = os.path.join
# function clear working folder
def clear_working_folder(dir,file_name):
path_working_folder = os.listdir(dir)
for element in path_working_folder:
element_path = JOIN(dir, element)
if element.startswith(file_name):
if os.path.isfile(element_path):
os.remove(element_path)
if os.path.isdir(element_path):
shutil.rmtree(element_path)
else:
continue
Сохраните его с названием clear_working_folder.py
Затем создайте новый файл python в той же папке, где вы сохранили предыдущий файл:
# VM 247
import numpy as np
import os,sys,time
from clear_working_folder import JOIN
FIDESYS_PREP_DIR = 'C:/Program Files/Fidesys/CAE-Fidesys-6.0/preprocessor/bin'
prep_paths = [
FIDESYS_PREP_DIR,
JOIN(FIDESYS_PREP_DIR, 'plugins'),
JOIN(FIDESYS_PREP_DIR, 'acis', 'code', 'bin')
] # Preprocessor directory
calc_path = JOIN(FIDESYS_PREP_DIR, 'FidesysCalc.exe') # solution directory
for path in prep_paths:
os.environ['PATH'] += os.pathsep + path # ADD directories to PATH.
os.add_dll_directory(path)
sys.path.append(path) # ADD directories to PATH.
try:
import cubit, fidesys
except ModuleNotFoundError:
print(f'Модули fydesys, cubit не найдены в директории {FIDESYS_PREP_DIR}')
sys.exit(1)
BASE_DIR = os.getcwd()
RESULT_FILENAME = 'modal'
modes_count = 6
# diametres of circle sections
section_diametr_array = [
1.02E-2,
2.04E-2,
1.52E-2,
4.06E-2,
4.06E-2,
6.6E-2,
6.6E-2,
5.08E-2,
5.08E-2,
2.54E-2,
2.54E-2,
3.04E-2,
3.04E-2,
2.54E-2,
2.54E-2,
7.62E-2,
4.06E-2,
4.06E-2
]
# block ids with pipe section
tube_section_ids = [7,8,18]
keypoints_beam_coords = [
0,
1.27E-2,
5.08E-2,
7.62E-2,
8.89E-2,
10.16E-2,
10.67E-2,
11.43E-2,
12.7E-2,
13.46E-2,
16.51E-2,
19.05E-2,
22.86E-2,
26.67E-2,
28.7E-2,
30.48E-2,
31.5E-2,
34.54E-2,
35.5E-2
]
keypoints_spring_coords = [
16.51E-2,16.51E-2,28.7E-2,28.7E-2
]
def create_beam_blocks():
fidesys.cmd('create material 1')
fidesys.cmd('modify material 1 name \'stal\'')
fidesys.cmd('modify material 1 set property \'MODULUS\' value 2.078e+11')
fidesys.cmd('modify material 1 set property \'SHEAR_MODULUS\' value 1e+12')
fidesys.cmd('modify material 1 set property \'DENSITY\' value 7806')
for keypoint_coord in keypoints_beam_coords:
fidesys.cmd(f'create vertex {keypoint_coord} 0 0')
for keypoint_id in range(1,len(keypoints_beam_coords)+1):
if keypoint_id < len(keypoints_beam_coords):
fidesys.cmd(f'create curve vertex {keypoint_id} {keypoint_id+1}')
else:
break
fidesys.cmd('merge all')
fidesys.cmd('curve all interval 1')
fidesys.cmd('curve all scheme equal')
fidesys.cmd('mesh curve all')
fidesys.cmd('merge all')
for index in range(len(section_diametr_array)):
diametr = section_diametr_array[index]
fidesys.cmd(f'block {index + 1} add curve {index + 1}')
fidesys.cmd(f'create beam properties {index + 1}')
fidesys.cmd(f'modify beam properties {index + 1} type \'Ellipse\'')
fidesys.cmd(f'modify beam properties {index + 1} angle 0.0')
fidesys.cmd(f'modify beam properties {index + 1} ey 0.0')
fidesys.cmd(f'modify beam properties {index + 1} ez 0.0')
fidesys.cmd(f'modify beam properties {index + 1} geom_a {diametr}')
fidesys.cmd(f'modify beam properties {index + 1} geom_b {diametr}')
fidesys.cmd(f'modify beam properties {index + 1} mesh_quality 6')
fidesys.cmd(f'modify beam properties {index + 1} warping_dof off')
fidesys.cmd(f'block {index + 1} material 1 cs 1 element beam order 1')
fidesys.cmd(f'block {index + 1} beam properties {index + 1}')
# modify pipe sections
for tube_section_id in tube_section_ids:
internal_diametr = 0.0304
fidesys.cmd(f'modify beam properties {tube_section_id} type \'Circle With Offset Hole\'')
fidesys.cmd(f'modify beam properties {tube_section_id} angle 0')
fidesys.cmd(f'modify beam properties {tube_section_id} ey 0')
fidesys.cmd(f'modify beam properties {tube_section_id} ez 0')
fidesys.cmd(f'modify beam properties {tube_section_id} geom_d1 {section_diametr_array[tube_section_id - 1]}')
internal_diametr = 0.0356 if tube_section_id == 8 else internal_diametr
fidesys.cmd(f'modify beam properties {tube_section_id} geom_d2 {internal_diametr}')
fidesys.cmd(f'modify beam properties {tube_section_id} geom_e 0')
fidesys.cmd(f'modify beam properties {tube_section_id} mesh_quality 6')
fidesys.cmd(f'modify beam properties {tube_section_id} warping_dof off')
fidesys.cmd('create displacement on vertex all dof 1 dof 4 fix 0')
def create_combin_blocks():
increment = 0
length = len(keypoints_beam_coords)
for keypoints_spring_coord_index in range(len(keypoints_spring_coords)):
if keypoints_spring_coord_index % 2 == 0:
fidesys.cmd(f'create curve location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 0 location {keypoints_spring_coords[keypoints_spring_coord_index]} 1e-03 0')
else:
fidesys.cmd(f'create curve location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 0 location {keypoints_spring_coords[keypoints_spring_coord_index]} 0 1e-03')
increment += 1
combin_line_id = length - 1 + increment
fidesys.cmd('curve all interval 1')
fidesys.cmd('curve all scheme equal')
fidesys.cmd(f'mesh curve {combin_line_id}')
fidesys.cmd(f'block {length } add curve {combin_line_id}')
fidesys.cmd('merge all')
fidesys.cmd('create spring properties 1')
fidesys.cmd('modify spring properties 1 type \'linear_spring\'')
fidesys.cmd('modify spring properties 1 stiffness 4.37e7')
fidesys.cmd('modify spring properties 1 spring_constant_damping 0')
fidesys.cmd('modify spring properties 1 spring_linear_damping 0')
fidesys.cmd('modify spring properties 1 spring_mass 0')
fidesys.cmd(f'block {length} element spring')
fidesys.cmd(f'block {length} spring properties 1')
fidesys.cmd('create displacement on vertex 42 38 44 40 dof all fix')
def create_point_mass_block():
length = len(keypoints_beam_coords)
fidesys.cmd(f'block { length + 1 } add vertex 5')
fidesys.cmd('create lumpmass properties 1')
fidesys.cmd('modify lumpmass properties 1 mass_x 1.401')
fidesys.cmd('modify lumpmass properties 1 mass_y 1.401')
fidesys.cmd('modify lumpmass properties 1 mass_z 1.401')
fidesys.cmd('modify lumpmass properties 1 mass_inertia_x 0.002')
fidesys.cmd('modify lumpmass properties 1 mass_inertia_y 0.00136')
fidesys.cmd('modify lumpmass properties 1 mass_inertia_z 0.00136')
fidesys.cmd(f'block { length + 1 } cs 1 element lumpmass')
fidesys.cmd(f'block { length + 1 } lumpmass properties 1')
def calculate(omega):
modal_result_path = f'{BASE_DIR}/{RESULT_FILENAME}_{round(omega/0.1047,3) }'
cubit.init([''])
fc = fidesys.FidesysComponent()
fc.init_application(FIDESYS_PREP_DIR) # initialization
fc.start_up_no_args() # run fidesys component fc
fidesys.cmd('reset')
create_beam_blocks()
create_combin_blocks()
create_point_mass_block()
fidesys.cmd('create angular velocity global')
fidesys.cmd(f'modify angular velocity 1 dof 1 value {omega}')
fidesys.cmd('analysis type eigenfrequencies dim3 preload off')
fidesys.cmd(f'eigenvalue find {modes_count} smallest')
fidesys.cmd('damping off mass_matrix 0 stiffness_matrix 0')
fidesys.cmd('coriolis on')
fidesys.cmd(f"calculation start path '{modal_result_path}.pvd'")
fc.delete_application()
Сохраните файл с названием calculate_modal.py
Еще раз создайте новый файл python в той же папке, где вы сохранили предыдущий файл:
from PyQt5 import QtCore,QtTest
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QTableWidget,QTableWidgetItem,QWidget,QGridLayout,QLineEdit,QLabel,QMessageBox,QPlainTextEdit
import sys,os,meshio
import numpy as np
import pyqtgraph as pg
from clear_working_folder import clear_working_folder
from calculate_modal import modes_count,BASE_DIR,RESULT_FILENAME,calculate
from scipy.interpolate import interp1d
from scipy.linalg import solve
from scipy.optimize import root
from random import randrange
class Window(QMainWindow):
rotational_velocities_rpm = np.array([0,35000,70000,105000])
rotational_velocities_radian = rotational_velocities_rpm * 0.1047
rotational_velocities_hz = rotational_velocities_rpm * 0.017
def __init__(self):
super().__init__()
self.setupUi() # Create Widgets
def setupUi(self):
self.setWindowTitle('Campell diagram')
self.setFixedHeight(1000)
self.setFixedWidth(1000)
self.pre_processor_title_create()
self.btn_calculate_create()
self.table_list_rotational_velocities_create()
self.post_processor_title_create()
self.graph_Widget_create()
self.table_list_critical_speeds_create()
self.text_box_ratio_edit_create()
self.text_title_ratio_create()
self.button_replot_graph_and_recalculate_critical_speeds_create()
self.terminal_create()
self.toggle_visibility_widgets()
# empty Function
def empty_function(self,omega):
pass
# BLOCK OF CREATING WIDGETS BEGINS!!
# create table list of rotational velocities
def table_list_rotational_velocities_create(self):
length = len(self.rotational_velocities_rpm)
self.table_list_rotational_velocities = QTableWidget(self)
self.table_list_rotational_velocities.setGeometry(50,50,220,120)
self.table_list_rotational_velocities.setRowCount(length)
self.table_list_rotational_velocities.setColumnCount(1)
self.table_list_rotational_velocities.setHorizontalHeaderLabels(['Rotational velocity (rpm)'])
self.table_list_rotational_velocities.horizontalHeader().setDefaultSectionSize(200)
self.table_list_rotational_velocities.verticalHeader().setDefaultSectionSize(15)
self.insert_rotational_velocities_values_into_table()
#create preprocessor title
def pre_processor_title_create(self):
self.pre_processor_title = QLabel(self)
self.pre_processor_title.setText('PREPROCESSOR (4 VELOCITIES -> 4 STEPS)')
self.pre_processor_title.setStyleSheet('font-size:30px')
self.pre_processor_title.setGeometry(200, 0, 700, 40)
# create button calculate
def btn_calculate_create(self):
self.btn_calculate = QPushButton('Calculate fidesys script',self)
self.btn_calculate.move(50,350)
self.btn_calculate.setGeometry(500, 80, 200, 40)
self.btn_calculate.clicked.connect(self.btn_calculate_handler)
# create postprocessor title
def post_processor_title_create(self):
self.post_processor_title = QLabel(self)
self.post_processor_title.setText('POSTPROCESSOR')
self.post_processor_title.setStyleSheet('font-size:30px')
self.post_processor_title.setGeometry(400, 380, 300, 40)
# create graph widget
def graph_Widget_create(self):
self.plot_graph = pg.PlotWidget()
self.plot_graph.setBackground('w')
self.plot_graph.addLegend(offset=(-650, 5))
self.plot_graph.setStyleSheet("border : none;")
self.widget = QWidget(self)
self.layout = QGridLayout()
self.layout.addWidget(self.plot_graph)
self.widget.setLayout(self.layout)
self.widget.setStyleSheet("border : 5px solid blue;")
self.widget.setGeometry(100,420, 800, 400)
# create table list of critical speeds
def table_list_critical_speeds_create(self):
self.table_list_critical_speeds = QTableWidget(self)
self.table_list_critical_speeds.setGeometry(50,850,220,120)
self.table_list_critical_speeds.setColumnCount(1)
self.table_list_critical_speeds.setHorizontalHeaderLabels(['Critical speeds (rpm)'])
self.table_list_critical_speeds.horizontalHeader().setDefaultSectionSize(200)
self.table_list_critical_speeds.verticalHeader().setDefaultSectionSize(15)
# create text box edit ratio of critical line
def text_box_ratio_edit_create(self):
self.text_box_ratio_edit = QLineEdit(self)
self.text_box_ratio_edit.setGeometry(450,925,50,20)
self.text_box_ratio_edit.setAlignment(QtCore.Qt.AlignCenter)
validator = QRegExpValidator(QRegExp("\d+\.?\d*e?-?\d+"))
self.text_box_ratio_edit.setValidator(validator)
self.text_box_ratio_edit.setText('1')
# create button replot campell's diagram and recalculate critical speeds
def button_replot_graph_and_recalculate_critical_speeds_create(self):
self.button_replot_graph_and_recalculate_critical_speeds = QPushButton('Replot',self)
self.button_replot_graph_and_recalculate_critical_speeds.setGeometry(550,920,150,30)
self.button_replot_graph_and_recalculate_critical_speeds.clicked.connect(self.button_replot_graph_and_recalculate_critical_speeds_handler)
# create text title 'RATIO'
def text_title_ratio_create(self):
self.text_title_ratio = QLabel(self)
self.text_title_ratio.setGeometry(400,925,50,20)
self.text_title_ratio.setText('RATIO')
# create terminal
def terminal_create(self):
self.terminal = QPlainTextEdit(self)
self.terminal.setGeometry(100,180, 800, 200)
# BLOCK OF CREATING WIDGETS FINISH!!
# control visibility of Widgets
def toggle_visibility_widgets(self,is_visible = False):
self.button_replot_graph_and_recalculate_critical_speeds.setVisible(is_visible)
self.text_box_ratio_edit.setVisible(is_visible)
self.text_title_ratio.setVisible(is_visible)
self.table_list_critical_speeds.setVisible(is_visible)
# plot diagram campell
def plot_campell_diagramm(self,modes_dict,ratio = 1):
def create_random_color(): # random hex color
symbols = [1,2,3,4,5,6,7,8,9,'A','B','C','D','E']
color = '#'
for i in range(0,6):
random_index = randrange(0,len(symbols))
random_symbol = symbols[random_index]
color = color + str(random_symbol)
return color
self.plot_graph.clear()
for mode_index in range(1,modes_count+1):
line_color = create_random_color()
self.plot_graph.plot(self.rotational_velocities_rpm,modes_dict[f'{mode_index} mode'], fillLevel = 10,pen = pg.mkPen(color = line_color,width = 3),name = f'Mode_{mode_index}')
self.plot_graph.plot(self.rotational_velocities_rpm,self.rotational_velocities_hz/float(ratio),pen = pg.mkPen(color = "black",width = 4))
# insert_rotational_velocities_values_into_table
def insert_rotational_velocities_values_into_table(self):
table = self.table_list_rotational_velocities
length = len(self.rotational_velocities_rpm)
for index in range(length):
table.setItem(index,0,QTableWidgetItem(str(self.rotational_velocities_rpm[index])))
table.item(index,0).setFlags(QtCore.Qt.ItemIsEditable)
table.item(index,0).setTextAlignment(QtCore.Qt.AlignHCenter)
# get damping frequencies from vtu
def get_frequencies_from_vtu(self,calculate_fidesys_script_function):
modes_dict = {}
modes_dict_for_plotting = {}
for omega in self.rotational_velocities_radian:
modal_result_path_result_vtu = f'{BASE_DIR}/{RESULT_FILENAME}_{round(omega/0.1047,3)}/case1_step0001_substep0001.vtu'
calculate_fidesys_script_function(omega)
try:
mesh = meshio.read(f'{modal_result_path_result_vtu}')
except meshio._exceptions.ReadError:
self.terminal.insertPlainText(f'Error!!!.Vtu file from {modal_result_path_result_vtu} is not Found\n')
clear_working_folder(BASE_DIR,RESULT_FILENAME)
self.btn_calculate.setEnabled(True)
raise meshio._exceptions.ReadError('Ошибка! Vtu-Файл не найден!')
damping_freqs = mesh.field_data['Eigen Values Extended']
damping_freqs = list(damping_freqs[0])
modes_dict[f'{omega} rad/s'] = damping_freqs
for mode_index in range(1,modes_count+1):
modes_dict_for_plotting[f'{mode_index} mode'] = []
for key in modes_dict:
element = round(modes_dict[key][mode_index-1],3)
modes_dict_for_plotting[f'{mode_index} mode'].append(element)
for key,value in modes_dict_for_plotting.items():
modes_dict_for_plotting[key] = np.array(value)
return modes_dict_for_plotting
# calculate damping Frequencies
def calculate_damping_frequencies(self,omega):
self.terminal.insertPlainText(f'Calculation with {round(omega/0.1047,3)} rpm begin\n')
calculate(omega)
QtTest.QTest.qWait(3000)
self.terminal.insertPlainText(f'Calculation with {round(omega/0.1047,3)} rpm finished succesfully\n')
# calculate critical speeds
def critital_speeds_calculate(self,modes_dict,ratio = 1):
self.toggle_visibility_widgets(True)
critical_line_matrix = np.array([[self.rotational_velocities_rpm[0],1],[self.rotational_velocities_rpm[-1],1]])
critical_line_vector = np.array([self.rotational_velocities_hz[0]/float(ratio),self.rotational_velocities_hz[-1]/float(ratio)])
[k1,b1] = solve(critical_line_matrix,critical_line_vector) # equation of line y = k1*x+ b1. Find coeffs k1,b1
def intersect_function(x): # point of intersection of two linear functions
return k*x + b - k1*x - b1
critical_speeds = []
for key in modes_dict:
array = modes_dict[key]
for index in range(0,len(array)-1):
current_rotational_velocity = self.rotational_velocities_rpm[index]
next_rotational_velocity = self.rotational_velocities_rpm[index + 1]
current_speed = array[index]
next_speed = array[index + 1]
interval_rotational_velocity = np.array([current_rotational_velocity,next_rotational_velocity])
interval_speed = np.array([current_speed,next_speed])
linear_interpolation =interp1d(interval_rotational_velocity,interval_speed,kind = 'linear')
matrix = np.array([[current_rotational_velocity,1],[next_rotational_velocity,1]])
vector = np.array([current_speed,next_speed])
[k,b] = solve(matrix,vector) # equation of line y = k*x+b. Find coeffs k,b
sol = root(intersect_function, x0=0) # find intersect of two functions in current_interval
if current_rotational_velocity <= sol.x[0] <= next_rotational_velocity:
critical_speed = round(float(linear_interpolation(sol.x[0])),3)
critical_speeds.append(critical_speed)
else:
continue
critical_speeds = np.array(critical_speeds)/0.017 # critical speeds in rpm
return np.round(critical_speeds,3)
# clear critical speeds table
def table_list_critical_speeds_clear(self):
table = self.table_list_critical_speeds
while (table.rowCount() > 0):
table.removeRow(0)
# insert critical speeds into table
def insert_critical_speeds_into_table(self,modes_dict,ratio = 1):
self.table_list_critical_speeds_clear()
table = self.table_list_critical_speeds
critical_speeds = self.critital_speeds_calculate(modes_dict,ratio)
length = len(critical_speeds)
table.setRowCount(length)
for index in range(length):
table.setItem(index,0,QTableWidgetItem(str(critical_speeds[index])))
table.item(index,0).setFlags(QtCore.Qt.ItemIsEditable)
table.item(index,0).setTextAlignment(QtCore.Qt.AlignHCenter)
# BLOCK OF CREATING HANDLERS BUTTONS BEGIN!!
# create button calculate handler
def btn_calculate_handler(self):
self.terminal.clear()
os.system('cls')
self.btn_calculate.setEnabled(False)
self.toggle_visibility_widgets(False)
self.text_box_ratio_edit.setText('1')
self.plot_graph.clear()
QtTest.QTest.qWait(2000)
self.terminal.insertPlainText('Calculation start\n')
QtTest.QTest.qWait(2000)
self.terminal.insertPlainText('Clear Working Folder\n')
clear_working_folder(BASE_DIR,RESULT_FILENAME)
QtTest.QTest.qWait(3000)
self.terminal.insertPlainText('Clear Working Folder finished\n')
modes_dict = self.get_frequencies_from_vtu(self.calculate_damping_frequencies)
self.plot_campell_diagramm(modes_dict)
self.insert_critical_speeds_into_table(modes_dict)
self.btn_calculate.setEnabled(True)
self.terminal.insertPlainText('Calculation finished\n')
# create button replot_graph_and_recalculate_critical_speeds handler
def button_replot_graph_and_recalculate_critical_speeds_handler(self):
try:
self.terminal.insertPlainText(f'plot and calculate critical speeds with ratio {self.text_box_ratio_edit.text()} of critical line \n')
self.plot_graph.clear()
modes_dict = self.get_frequencies_from_vtu(self.empty_function)
self.plot_campell_diagramm(modes_dict,float(self.text_box_ratio_edit.text()))
self.insert_critical_speeds_into_table(modes_dict,float(self.text_box_ratio_edit.text()))
except ValueError:
self.message_box = QMessageBox(self)
self.message_box.setText('Invalid ratio')
self.message_box.exec_()
# BLOCK OF CREATING HANDLERS BUTTONS END!!
def application():
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
application()
Сохраните его с названием Window.py
Запустите на расчет файл Window.py. Выберите Run - Run Module.
После того как расчет прошел появится диалоговое окно для расчета:
Нажмите на Calculate fidesys script. В результате построится диаграмма Кемпбелла и посчитаются критические скорости для коэффициента критической линии 1:
В поле RATIO можно поменять коэффициент критической линии и диаграмма Кэмпбелла с табличкой расчета критических скоростей перерасчитается заново (с помощью кнопки Replot).
fidesys