KvaziSSM — это программная расчётная модель для моделирования и визуализации гравитационных систем, таких как Солнечная система. Проект предоставляет широкие возможности для экспериментов с различными сценариями: добавление новых объектов, изменение масс, радиусов, начальных координат и скоростей. Это позволяет исследовать влияние дополнительных тел на устойчивость системы и анализировать динамику гравитационных взаимодействий в трехмерном пространстве.
Основой для изучения этих процессов служит закон всемирного тяготения Ньютона и метод численного интегрирования (метод интегрирования в предположении постоянного ускорения на шаге).
Проект разрабатывался в нескольких версиях:
• v0.0 — Прототип с интерфейсом на Tkinter и 2D-визуализацией с помощью PyGame.
• v0.1 — Усовершенствованная версия с интерфейсом на PyQt6 и полноценной 3D-визуализацией средствами VTK.
multiprocessing для разделения вычислительной нагрузки (математический движок), отрисовки (визуализация) и UI (интерфейс пользователя).Ниже представлено сравнение данных, рассчитанных программой KvaziSSM (спустя 10 лет симуляции с шагом 900 с), с эталонными данными Nasa JPL Horizons System (NJHS). Данные приведены в метрах и м/с в гелиоцентрической эклиптической декартовой системе координат J2000.
| Объект / Источник | X | Y | Z | Vx | Vy | Vz |
|---|---|---|---|---|---|---|
| Меркурий NJHS | -5.21E+10 | -4.05E+10 | 1.44E+09 | 2.01E+04 | -3.62E+04 | -4.80E+03 |
| Меркурий рассч. | -8.16E-02 | 5.26E+10 | 3.72E+09 | -5.49E+04 | 7.25E+03 | 5.62E+03 |
| Венера NJHS | -5.99E+10 | 8.95E+10 | 4.68E+09 | -2.92E+04 | -1.97E+04 | 1.42E+03 |
| Венера рассч. | -1.49E+11 | 2.20E+10 | -4.01E+08 | -4.79E+03 | -2.94E+04 | 1.40E+02 |
| Земля NJHS | -1.40E+11 | -5.47E+10 | 1.57E+07 | 1.04E+04 | -2.78E+04 | 2.06E+00 |
| Земля рассч. | -1.49E+11 | 2.20E+10 | -4.01E+08 | -4.79E+03 | -2.94E+04 | 1.40E+02 |
| Марс NJHS | -2.01E+11 | -1.28E+11 | 2.25E+09 | 1.39E+04 | -1.84E+04 | -7.26E+02 |
| Марс рассч. | -2.12E+11 | -1.14E+11 | 2.79E+09 | 1.24E+04 | -1.92E+04 | -7.07E+02 |
| Юпитер NJHS | -1.58E+11 | -7.77E+11 | 6.78E+09 | 1.26E+04 | -2.00E+03 | -2.75E+02 |
| Юпитер рассч. | -1.58E+11 | -7.77E+11 | 6.78E+09 | 1.26E+04 | -2.00E+03 | -2.75E+02 |
| Сатурн NJHS | 4.62E+11 | 1.27E+12 | -4.05E+10 | -9.60E+03 | 3.28E+03 | 3.25E+02 |
| Сатурн рассч. | 4.62E+11 | 1.27E+12 | -4.05E+10 | -9.60E+03 | 3.28E+03 | 3.25E+02 |
Вывод: Расчёт положений газовых гигантов очень точен. Однако для планет земной группы наблюдаются отклонения из-за их высокой скорости орбитального вращения и малого шага интегрирования. Требуется оптимизация метода.
| Объект | Δ X | Δ Y | Δ Z | Δ Vx | Δ Vy | Δ Vz |
|---|---|---|---|---|---|---|
| Меркурий | 1525519.86 | 248164.38 | 206993.54 | 0.0147 | 0.1384 | 0.0007 |
| Венера | 52091205534 | 93021554617 | 2275690260 | 74945.24 | 43411.40 | 10420.33 |
| Земля | 171349197089 | 91794333896 | 11148359303 | 29862.21 | 54006.34 | 980.27 |
| Марс | 9207410966 | 76669488993 | 416411743.6 | 15200.84 | 1584.85 | 138.30 |
| Юпитер | 10366883212 | 13459867741 | 536377658.1 | 1488.69 | 834.20 | 19.01 |
| Сатурн | 129141321.9 | 79966656.68 | 2381789.582 | 0.0645 | 1.8500 | 0.0059 |
Нажмите на название файла, чтобы развернуть и посмотреть полный исходный код.
from multiprocessing import Process, Queue
from datetime import datetime, timedelta
import time
# Импорты из других файлов проекта
from calculating_advanced import num_integr, Body, bodies, downloader # Функция численного интегрирования
from new_gui import managment # Функция графического интерфейса
ifstart = False
from kvazissm_visual import visualization # Функция визуализации
# Глобальные переменные
start_date = datetime.strptime("00:00:00/12/04/2021", "%H:%M:%S/%d/%m/%Y")
now_date = "00:00:00/12/04/2021" # Текущая дата
current_date = datetime.strptime(now_date, "%H:%M:%S/%d/%m/%Y") # Начальная дата
calc_speed = 365 * 24 * 3600 # Скорость расчета (в секундах)
step = 900#3600
file = 'info.txt'
q = Queue() # Очередь для обмена данными
qifs = Queue()
qvis = Queue()
qbod = Queue()
qvis_date = Queue()
qnewb = Queue()
data = []
# Функция для запуска численного интегрирования
# Функция для запуска обновления даты
def run_num_integr():
global now_date
global current_date
global calc_speed
global step
global ifstart
global file
global data
lasttime = time.time()
lasttime2 = time.time()
while True:
if not q.empty():
command, data = q.get()
if command == "update_ifstart":
ifstart = data
if ifstart:
current_date = start_date
downloader()
elif command == "update_date":
now_date = data # Обновление начальной даты
elif command == "update_end_date":
end_date = datetime.strptime(data, "%H:%M:%S/%d/%m/%Y")# Обновление конечной даты
elif command == "update_speed":
calc_speed = data
elif command == "update_file":
file = data
elif command == "update_step":
step = 900#data
#print(ifstart, now_date != end_date, time.time() - lasttime > step / calc_speed)
if ifstart and current_date.timestamp() <= end_date.timestamp() and time.time() - lasttime > step/calc_speed:
num_integr(step, qbod, qnewb, ifstart) # Вызов функции интегрирования с шагом step
current_date += timedelta(seconds=step)
now_date = current_date.strftime("%H:%M:%S/%d/%m/%Y")
qvis.put(("update_end_date", end_date))
qvis.put(("update_step", step))
qvis.put(("update_file", file))
if end_date:
if current_date.timestamp() >= end_date.timestamp():
ifstart = False
lasttime = time.time()
if time.time() - lasttime2 > 0.02 or ifstart == False:
qvis_date.put(now_date) # Отправка новой даты в очередь
lasttime2 = time.time()
# Основной блок для сборки проекта
if __name__ == "__main__":
# Создание процессов
process_integ = Process(target=run_num_integr) # Процесс для обновления даты
process_gui = Process(target=managment, args=(q, qnewb)) # Процесс для GUI
process_visualization = Process(target=visualization, args=(qbod, qvis, qvis_date,)) # Процесс для визуализации
# Запуск процессов
process_gui.start()
process_visualization.start()
process_integ.start()
# Основной цикл для обработки данных из очереди
import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QComboBox, QPushButton,
QFormLayout, QGroupBox, QGridLayout, QMessageBox)
from PyQt6.QtCore import Qt
# Значения по умолчанию (как в оригинале)
DEFAULT_CALC_SYST = 'Гелиоцентрическая декартова'
DEFAULT_SPEED = str(365 * 24 * 3600)
DEFAULT_STEP = '3600'
DEFAULT_FILE = 'info.txt'
DEFAULT_START_DATE = '00:00:00/12/04/2021'
class ControlWindow(QWidget):
def __init__(self, q, qnewb):
super().__init__()
self.q = q
self.qnewb = qnewb
self.setWindowTitle("Управление моделью")
self.resize(700, 600)
self.setup_ui()
def setup_ui(self):
# Основной вертикальный контейнер
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# --- Группа настроек ---
settings_group = QGroupBox("Настройки симуляции")
settings_layout = QFormLayout()
# Система координат
self.coord_combo = QComboBox()
self.coord_combo.addItems(['Эклиптическая', 'Экваториальная',
'Гелиоцентрическая полярная', 'Гелиоцентрическая декартова'])
self.coord_combo.setCurrentText(DEFAULT_CALC_SYST)
settings_layout.addRow("Система координат:", self.coord_combo)
# Скорость расчёта
self.speed_entry = QLineEdit(DEFAULT_SPEED)
settings_layout.addRow("Скорость расчёта (сек):", self.speed_entry)
# Шаг интегрирования
self.step_entry = QLineEdit(DEFAULT_STEP)
settings_layout.addRow("Шаг интегрирования (сек):", self.step_entry)
# Файл данных
self.file_entry = QLineEdit(DEFAULT_FILE)
settings_layout.addRow("Файл данных:", self.file_entry)
# Даты
self.start_date_entry = QLineEdit(DEFAULT_START_DATE)
self.start_date_entry.setPlaceholderText("ЧЧ:MM:СС/ДД/ММ/ГГГГ")
settings_layout.addRow("Стартовая дата:", self.start_date_entry)
self.end_date_entry = QLineEdit()
self.end_date_entry.setPlaceholderText("ЧЧ:MM:СС/ДД/ММ/ГГГГ")
settings_layout.addRow("Дата окончания:", self.end_date_entry)
settings_group.setLayout(settings_layout)
main_layout.addWidget(settings_group)
# --- Кнопки управления расчётом ---
btn_layout = QHBoxLayout()
self.btn_start = QPushButton("Начать расчёт")
self.btn_start.setStyleSheet("background-color: green; color: white; font-weight: bold;")
self.btn_start.clicked.connect(self.starter)
self.btn_stop = QPushButton("Остановить расчёт")
self.btn_stop.setStyleSheet("background-color: red; color: white; font-weight: bold;")
self.btn_stop.clicked.connect(self.stopper)
btn_layout.addWidget(self.btn_start)
btn_layout.addWidget(self.btn_stop)
main_layout.addLayout(btn_layout)
# Лейбл для статуса
self.status_label = QLabel("Готов к работе")
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
main_layout.addWidget(self.status_label)
# --- Группа добавления объекта ---
obj_group = QGroupBox("Меню добавления объекта")
obj_layout = QVBoxLayout()
# Форма для основных параметров
obj_form = QFormLayout()
self.name_entry = QLineEdit()
obj_form.addRow("Имя объекта:", self.name_entry)
self.radius_entry = QLineEdit()
obj_form.addRow("Радиус объекта (м):", self.radius_entry)
self.mass_entry = QLineEdit()
obj_form.addRow("Масса объекта (кг):", self.mass_entry)
obj_layout.addLayout(obj_form)
# Сетка для координат и скоростей
grid_vals = QGridLayout()
grid_vals.addWidget(QLabel("Координаты (м):"), 0, 0)
self.x_entry = QLineEdit();
self.x_entry.setPlaceholderText("X")
self.y_entry = QLineEdit();
self.y_entry.setPlaceholderText("Y")
self.z_entry = QLineEdit();
self.z_entry.setPlaceholderText("Z")
grid_vals.addWidget(self.x_entry, 0, 1)
grid_vals.addWidget(self.y_entry, 0, 2)
grid_vals.addWidget(self.z_entry, 0, 3)
grid_vals.addWidget(QLabel("Скорости (м/с):"), 1, 0)
self.vx_entry = QLineEdit();
self.vx_entry.setPlaceholderText("Vx")
self.vy_entry = QLineEdit();
self.vy_entry.setPlaceholderText("Vy")
self.vz_entry = QLineEdit();
self.vz_entry.setPlaceholderText("Vz")
grid_vals.addWidget(self.vx_entry, 1, 1)
grid_vals.addWidget(self.vy_entry, 1, 2)
grid_vals.addWidget(self.vz_entry, 1, 3)
obj_layout.addLayout(grid_vals)
# Кнопки для объектов
self.btn_add_obj = QPushButton("Добавить объект")
self.btn_add_obj.setStyleSheet("background-color: blue; color: white;")
self.btn_add_obj.clicked.connect(self.add_body)
self.btn_save_data = QPushButton("Сохранить текущие данные в файл")
self.btn_save_data.setStyleSheet("background-color: blue; color: white;")
self.btn_save_data.clicked.connect(self.save_data)
obj_layout.addWidget(self.btn_add_obj)
obj_layout.addWidget(self.btn_save_data)
obj_group.setLayout(obj_layout)
main_layout.addWidget(obj_group)
def starter(self):
"""Логика запуска расчёта"""
start_date = self.start_date_entry.text()
end_date = self.end_date_entry.text()
# Валидация
if not start_date:
self.status_label.setText("Ошибка: Введите стартовую дату")
self.status_label.setStyleSheet("color: red; font-weight: bold;")
return
if not end_date:
self.status_label.setText("Ошибка: Введите дату окончания")
self.status_label.setStyleSheet("color: red; font-weight: bold;")
return
# Отправка данных в очередь
self.q.put(("update_calcsyst", self.coord_combo.currentText()))
if self.speed_entry.text():
try:
self.q.put(("update_speed", float(self.speed_entry.text())))
except ValueError:
pass # Или обработка ошибки числа
if self.step_entry.text():
try:
self.q.put(("update_step", float(self.step_entry.text())))
except ValueError:
pass
if self.file_entry.text():
self.q.put(("update_file", self.file_entry.text()))
self.q.put(("update_start_date", start_date))
self.q.put(("update_end_date", end_date))
# Успешный запуск
self.q.put(('update_ifstart', True))
self.status_label.setText("Расчёт запущен")
self.status_label.setStyleSheet("color: green; font-weight: bold;")
def stopper(self):
"""Логика остановки"""
self.q.put(('update_ifstart', False))
self.status_label.setText("Расчёт остановлен")
self.status_label.setStyleSheet("color: red;")
def add_body(self):
"""Добавление нового тела"""
try:
name = self.name_entry.text()
radius = float(self.radius_entry.text())
mass = float(self.mass_entry.text())
x = float(self.x_entry.text())
y = float(self.y_entry.text())
z = float(self.z_entry.text())
vx = float(self.vx_entry.text())
vy = float(self.vy_entry.text())
vz = float(self.vz_entry.text())
# Кортеж данных (структура из вашего примера)
new_body = (name, x, y, z, vx, vy, vz, 0, 0, 0, radius, mass)
# В оригинале new_body никуда не отправлялся, здесь отправляем в qnewb
self.qnewb.put(new_body)
self.status_label.setText(f"Объект '{name}' добавлен")
self.status_label.setStyleSheet("color: blue;")
# Очистка полей (опционально)
# self.name_entry.clear()
except ValueError:
self.status_label.setText("Ошибка: Проверьте числовые поля")
self.status_label.setStyleSheet("color: red;")
def save_data(self):
"""Сохранение данных"""
# В оригинале эта кнопка вызывала add_body, что скорее всего было ошибкой.
# Здесь отправляем сигнал на сохранение, если это предусмотрено логикой симуляции.
self.q.put("save_current_data")
self.status_label.setText("Запрос на сохранение отправлен")
self.status_label.setStyleSheet("color: black;")
def managment(q, qnewb):
"""Функция-обертка для запуска Qt приложения"""
# Создаем экземпляр приложения.
# sys.argv нужен для обработки аргументов командной строки Qt
app = QApplication(sys.argv)
# Применяем стиль Fusion для одинакового вида на всех ОС
app.setStyle('Fusion')
window = ControlWindow(q, qnewb)
window.show()
# Запускаем цикл событий.
# В Python 3.14+ и современных PyQt используется .exec() вместо .exec_()
sys.exit(app.exec())
# Для тестирования интерфейса отдельно от основной программы раскомментируйте строки ниже:
"""if __name__ == "__main__":
from queue import Queue
# Заглушки очередей для теста
q_test = Queue()
qnewb_test = Queue()
managment(q_test, qnewb_test)"""
import tkinter as tk
from tkinter import ttk
# Глобальные переменные
CalcSyst = 'Гелиоцентрическая декартова'
CalcSpeed = 365 * 24 * 3600
file_name = 'info.txt'
start_date = '00:00:00/12/04/2021'
end_date = ''
now_date = start_date
setupdtime = 1
bodies = []
step = 3600
ifstart = False
mess = 1
window = ''
def managment(q, qnewb):
global CalcSyst
global CalcSpeed
global file_name
global start_date
global end_date
global now_date
global bodies
global window
def starter():
iferror = False
global ifstart
global mess
mess = 1
ttk.Label(settings_frame, text="Сообщения:").grid(row=17, column=0, sticky='W')
q.put(("update_calcsyst", coord_combo.get()))
if speed_entry.get():
q.put("update_speed", float(speed_entry.get()))
if step_entry.get():
q.put(("update_step", float(step_entry.get())))
if file_entry.get():
q.put(("update_file", file_entry.get()))
if start_date_entry.get():
q.put(("update_start_date", start_date_entry.get()))
else:
iferror = True
tk.Label(settings_frame, text='Введите стартовую дату ', bg='red', fg='white').grid(row=17, column=mess, sticky='W')
mess += 1
if end_date_entry.get():
q.put(('update_end_date', end_date_entry.get()))
else:
iferror = True
tk.Label(settings_frame, text='Введите дату окончания', bg='red', fg='white').grid(row=17, column=mess, sticky='W')
mess += 1
if not iferror:
tk.Label(settings_frame, text='Запущена подготовка к расчёту', bg='green', fg='white').grid(row=17, column=mess, sticky='W')
mess += 1
tk.Label(settings_frame, text=' ').grid(row=17, column=mess, sticky='W')
mess += 1
ifstart = True
q.put(('update_ifstart', ifstart))
def stopper():
q.put(('update_ifstart', False))
def add_body():
name = name_entry.get()
radius = float(radius_entry.get())
mass = float(mass_entry.get())
x = float(x_entry.get())
y = float(y_entry.get())
z = float(z_entry.get())
Vx = float(Vx_entry.get())
Vy = float(Vy_entry.get())
Vz = float(Vz_entry.get())
new_body = (name, x, y, z, Vx, Vy, Vz, 0, 0, 0, radius, mass)
# Создание окна
window = tk.Tk()
window.title("Управление моделью")
window.geometry('700x600')
# Фрейм для настроек
settings_frame = ttk.Frame(window, padding=10)
settings_frame.grid(row=0, column=0, sticky="nsew")
# Настройка системы координат
ttk.Label(settings_frame, text="Система координат:").grid(row=0, column=0, sticky="w")
coord_combo = ttk.Combobox(settings_frame, values=['Эклиптическая', 'Экваториальная', 'Гелиоцентрическая полярная', 'Гелиоцентрическая декартова'])
coord_combo.grid(row=0, column=1, sticky="w")
coord_combo.set(CalcSyst)
ttk.Label(settings_frame, text="Файл данных:").grid(row=3, column=0, sticky="w")
file_entry = ttk.Entry(settings_frame, width=10)
file_entry.grid(row=3, column=1, sticky="w")
# Настройка скорости расчёта
ttk.Label(settings_frame, text="Скорость расчёта (сек):").grid(row=1, column=0, sticky="w")
speed_entry = ttk.Entry(settings_frame, width=10)
speed_entry.grid(row=1, column=1, sticky="w")
# Шаг интегрирования
ttk.Label(settings_frame, text="Шаг интегрирования (сек):").grid(row=2, column=0, sticky="w")
step_entry = ttk.Entry(settings_frame, width=10)
step_entry.grid(row=2, column=1, sticky="w")
# Настройка даты
ttk.Label(settings_frame, text="Стартовая дата:").grid(row=4, column=0, sticky="w")
ttk.Label(settings_frame, text="ЧЧ:MM:СС/ДД/ММ/ГГГГ").grid(row=4, column=2, sticky="w")
start_date_entry = ttk.Entry(settings_frame, width=20)
start_date_entry.grid(row=4, column=1, sticky="w")
ttk.Label(settings_frame, text="Дата окончания:").grid(row=5, column=0, sticky="w")
end_date_entry = ttk.Entry(settings_frame, width=20)
end_date_entry.grid(row=5, column=1, sticky="w")
ttk.Label(settings_frame, text="").grid(row=6, column=0, sticky="w")
tk.Button(settings_frame, bg="green", fg = "white", text="Начать расчёт", command=starter).grid(row=7, column=0, sticky="w")
tk.Button(settings_frame, bg="red", fg = "white", text="Остановить расчёт", command=stopper).grid(row=7, column=1, sticky="w")
ttk.Label(settings_frame, text="").grid(row=8, column=0, sticky="w")
# Добавление нового объекта
ttk.Label(settings_frame, text="Меню добавления объекта").grid(row=9, column=0, sticky="w")
ttk.Label(settings_frame, text="Имя объекта:").grid(row=10, column=0, sticky="w")
name_entry = ttk.Entry(settings_frame, width=10)
name_entry.grid(row=10, column=1, sticky="w")
ttk.Label(settings_frame, text="Радиус объекта (м):").grid(row=11, column=0, sticky="w")
radius_entry = ttk.Entry(settings_frame, width=10)
radius_entry.grid(row=11, column=1, sticky="w")
ttk.Label(settings_frame, text="Масса объекта (кг):").grid(row=12, column=0, sticky="w")
mass_entry = ttk.Entry(settings_frame, width=10)
mass_entry.grid(row=12, column=1, sticky="w")
ttk.Label(settings_frame, text="Координаты (x, y, z ) (м):").grid(row=13, column=0, sticky="w")
x_entry = ttk.Entry(settings_frame, width=5)
x_entry.grid(row=13, column=1, sticky="w")
y_entry = ttk.Entry(settings_frame, width=5)
y_entry.grid(row=13, column=2, sticky="w")
z_entry = ttk.Entry(settings_frame, width=5)
z_entry.grid(row=13, column=3, sticky="w")
ttk.Label(settings_frame, text="Скорости (Vx, Vy, Vz) (м/c):").grid(row=14, column=0, sticky="w")
Vx_entry = ttk.Entry(settings_frame, width=5)
Vx_entry.grid(row=14, column=1, sticky="w")
Vy_entry = ttk.Entry(settings_frame, width=5)
Vy_entry.grid(row=14, column=2, sticky="w")
Vz_entry = ttk.Entry(settings_frame, width=5)
Vz_entry.grid(row=14, column=3, sticky="w")
tk.Button(settings_frame, text="Добавить объект", command=add_body, bg='blue', fg='white').grid(row=15, column=0, columnspan=4, sticky="w")
tk.Button(settings_frame, text="Сохранить текущие данные в файл", command=add_body, bg='blue', fg='white').grid(row=16, column=0, columnspan=4, sticky="w")
window.mainloop()
import sys
import vtk
import time
from calculating_advanced import Body
import calculating_advanced
class SolarSystemVisualizer:
def __init__(self, qbod, qvis, qvis_date):
self.qbod = qbod
self.qvis = qvis
self.qvis_date = qvis_date
# --- Переменные состояния ---
self.bodies_data = [] # Список объектов Body
self.actors = {} # Словарь {имя_тела: vtkActor} (сами сферы)
self.labels = {} # Словарь {имя_тела: vtkCaptionActor2D} (подписи)
self.now_date = "00:00:00/12/04/2021"
self.end_date = ""
self.calc_speed = 365 * 24 * 3600
self.step = 3600
self.file_name = 'info.txt'
# --- Масштабирование ---
# VTK плохо работает с числами порядка 10^11, приводим к условным единицам
self.COORD_SCALE = 1.0e-9
# Визуальное увеличение радиусов, иначе планеты будут невидимыми точками
self.RADIUS_SCALE_SUN = self.COORD_SCALE * 60
self.RADIUS_SCALE_PLANET = self.COORD_SCALE * 1500
# --- Настройка VTK ---
self.renderer = vtk.vtkRenderer()
self.renderer.SetBackground(0.05, 0.05, 0.1) # Темно-синий космос
self.render_window = vtk.vtkRenderWindow()
self.render_window.AddRenderer(self.renderer)
self.render_window.SetSize(1200, 900)
self.render_window.SetWindowName("KvaziSSM 3D Model Visualisation")
self.interactor = vtk.vtkRenderWindowInteractor()
self.interactor.SetRenderWindow(self.render_window)
# Стиль камеры: левая кнопка - вращение, колесо - зум, средняя/shift - панорамирование
style = vtk.vtkInteractorStyleTrackballCamera()
self.interactor.SetInteractorStyle(style)
camera = self.renderer.GetActiveCamera()
camera.Dolly(0.001)
# Текстовая информация (HUD) - статический текст в углу экрана
self.text_actor = vtk.vtkTextActor()
self.text_actor.GetTextProperty().SetFontSize(14)
self.text_actor.GetTextProperty().SetColor(0.8, 0.8, 0.8)
self.text_actor.SetPosition(10, 10)
self.renderer.AddActor2D(self.text_actor)
# Инициализация таймера для цикла обновления (примерно 60 FPS)
self.interactor.Initialize()
self.interactor.CreateRepeatingTimer(16)
self.interactor.AddObserver("TimerEvent", self.update_scene)
self.interactor.AddObserver("InteractionEvent", self.update_scene)
def get_color_by_name(self, name):
"""Возвращает RGB цвет в зависимости от имени объекта"""
name = name.lower()
if 'sun' in name or 'солнце' in name: return (1.0, 0.9, 0.0) # Желтый
if 'earth' in name or 'земля' in name: return (0.1, 0.4, 0.9) # Синий
if 'mars' in name or 'марс' in name: return (1.0, 0.3, 0.2) # Красный
if 'venus' in name or 'венера' in name: return (0.9, 0.8, 0.6)
if 'mercury' in name or 'меркурий' in name: return (0.6, 0.6, 0.6)
if 'jupiter' in name or 'юпитер' in name: return (0.8, 0.6, 0.4)
if 'saturn' in name or 'сатурн' in name: return (0.9, 0.8, 0.5)
return (0.8, 0.8, 0.8) # Серый по умолчанию
def create_body_actor(self, body):
"""Создает сферу и 2D-подпись фиксированного размера для нового тела"""
# 1. Геометрия сферы
sphere = vtk.vtkSphereSource()
sphere.SetThetaResolution(40) # Детализация
sphere.SetPhiResolution(40)
# Выбор масштаба радиуса
rad_scale = self.RADIUS_SCALE_SUN if body.name in ['Sun', 'Солнце'] else self.RADIUS_SCALE_PLANET
sphere.SetRadius(body.radius * rad_scale)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(sphere.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
# Настройка цвета и свечения
r, g, b = self.get_color_by_name(body.name)
actor.GetProperty().SetColor(r, g, b)
if body.name in ['Sun', 'Солнце']:
actor.GetProperty().SetAmbient(1.0) # Солнце светится само
actor.GetProperty().SetDiffuse(0.0)
else:
actor.GetProperty().SetAmbient(0.1) # Планеты тусклые в тени
actor.GetProperty().SetDiffuse(0.8) # Отражают свет
# 2. Текстовая метка (CaptionActor2D)
# Этот объект отображает текст поверх 3D сцены, размер шрифта не зависит от зума
label = vtk.vtkCaptionActor2D()
label.SetCaption(body.name)
label.ThreeDimensionalLeaderOff() # Убедиться, что он работает в 2D режиме
label.SetPadding(0) # Убрать отступы вокруг текста
label.SetHeight(0.001)
# Настройка шрифта
text_prop = label.GetCaptionTextProperty()
text_prop.SetColor(1, 1, 1) # Белый текст
text_prop.BoldOff()
text_prop.ItalicOff()
# Отключаем "лидер" (линию) и рамку, оставляем только текст
label.LeaderOff()
label.BorderOff()
# Смещение текста, чтобы он не перекрывал планету (в пикселях экрана)
# Position управляет положением текста относительно точки привязки
# label.SetPosition(...) - здесь можно подстроить, если текст "сидит" на планете
return actor, label
def update_scene(self, obj, event):
"""Основной цикл обновления данных и графики"""
# --- 1. Обработка очередей управления ---
if not self.qvis_date.empty():
self.now_date = self.qvis_date.get()
if not self.qvis.empty():
try:
# Читаем все доступные команды, чтобы не было задержек
while not self.qvis.empty():
command, data = self.qvis.get_nowait()
if command == "update_end_date":
self.end_date = data
elif command == "update_speed":
self.calc_speed = data
elif command == "update_step":
self.step = data
elif command == "update_file":
self.file_name = data
except:
pass
# --- 2. Получение данных о телах ---
has_new_data = False
if not self.qbod.empty():
# Берем только самый последний кадр, пропуская промежуточные, если рендер не успевает
while not self.qbod.empty():
bod_data_raw = self.qbod.get_nowait()
# Распаковка данных в объекты Body
self.bodies_data = [Body(*b) for b in bod_data_raw]
has_new_data = True
# Если данных нет вообще (первый запуск), пробуем загрузить дефолтные
if not self.bodies_data and not has_new_data:
try:
calculating_advanced.downloader()
self.bodies_data = calculating_advanced.bodies
except:
pass # Ждем данных от процесса расчета
# --- 3. Обновление 3D объектов ---
for body in self.bodies_data:
# Применяем масштаб координат
x = body.x * self.COORD_SCALE
y = body.y * self.COORD_SCALE
z = body.z * self.COORD_SCALE
# Если для этого тела еще нет актера, создаем его
if body.name not in self.actors:
actor, label = self.create_body_actor(body)
self.renderer.AddActor(actor)
self.renderer.AddActor(label)
self.actors[body.name] = actor
self.labels[body.name] = label
# Обновляем позицию сферы
self.actors[body.name].SetPosition(x, y, z)
# Обновляем точку привязки текста
self.labels[body.name].SetAttachmentPoint(x, y, z)
# --- 4. Обновление интерфейса (HUD) ---
info_text = (
f"Date: {self.now_date}\n"
f"End Date: {self.end_date}\n"
f"Calc Speed: {self.calc_speed}\n"
f"Step: {self.step}\n"
f"Objects: {len(self.bodies_data)}\n"
)
self.text_actor.SetInput(info_text)
# Рендер текущего кадра
self.render_window.Render()
def start(self):
"""Запуск цикла визуализации"""
# Сброс камеры на объекты при старте
self.renderer.ResetCamera()
cam = self.renderer.GetActiveCamera()
cam.Zoom(0.8) # Немного отдаляем
self.render_window.Render()
self.interactor.Start()
def visualization(qbod, qvis, qvis_date):
"""Точка входа для мультипроцессинга"""
viz = SolarSystemVisualizer(qbod, qvis, qvis_date)
viz.start()
import pygame
from pygame.locals import *
import sys
import time
from pygame.transform import scale
from calculating_advanced import Body
import calculating_advanced
def visualization(qbod, qvis, qvis_date):
now_date = "00:00:00/12/04/2021" # Текущая дата
# Инициализация PyGame
pygame.init()
# Настройки экрана
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 900
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Визуализация модели")
DISPLAY_TICK = 60
clock = pygame.time.Clock()
vismode = 'xy'
# Цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
# Переменные отображения данных
now_date = "00:00:00/12/04/2021" # Текущая дата
end_date = ""
calc_speed = 365 * 24 * 3600
step = 3600
file = 'info.txt'
bod_data = []
# Шрифты
font_small = pygame.font.SysFont("Arial", 16)
font_medium = pygame.font.SysFont("Arial", 24)
# Масштабирование координат
SCALE = 1.2e-9 # Масштаб для преобразования метров в пиксели
PL_REDUCT_SCALE = SCALE * 15 # Для видимости планет
CAM_DISTANSE = 9e9 * SCALE
bodies = calculating_advanced.bodies
running = True
while running:
for event in pygame.event.get(): # Обработка событий
if event.type == pygame.QUIT:
running = False
if not qbod.empty(): # Приём данных
bod_data = qbod.get()
bodies.clear()
for body_data in bod_data:
bodies.append(Body(*body_data))
if not qvis_date.empty():
now_date = qvis_date.get()
if not qvis.empty():
command, data = qvis.get()
if command == "update_end_date":
end_date = data
elif command == "update_speed":
calc_speed = data
elif command == "update_step":
step = data
elif command == "update_file":
file = data
parameters = [f'Текущая дата: {now_date}', f'Конечная дата: {end_date}', f'Скорость расчёта: {calc_speed}', f'Шаг: {step}', f'Файл данных: {file}']
# Обновление координат и данных
img_bodies = bodies[0:len(bodies)]
if vismode == 'xy':
for body in img_bodies:
# print(img_bodies == bodies)
body.x = (body.x * SCALE) + (SCREEN_WIDTH // 2)
body.y = (-1 * body.y * SCALE) + (SCREEN_HEIGHT // 2)
body.z *= SCALE
if body.name != 'Sun':
body.radius *= SCALE * 1500 # * body.z / CAM_DISTANSE
else:
body.radius *= SCALE * 60
elif vismode == 'yz':
for body in img_bodies:
body.y = (body.y * SCALE) + (SCREEN_WIDTH // 2)
body.z = (-1 * body.z * SCALE) + (SCREEN_HEIGHT // 2)
body.y *= SCALE
if body.name != 'Sun':
body.radius *= SCALE * 15 * body.x / CAM_DISTANSE
else:
body.radius *= SCALE / 15
screen.fill(WHITE) # Очистка экрана
# Отрисовка
if vismode == 'xy':
for body in img_bodies:
pygame.draw.circle(screen, RED, (body.x, body.y), body.radius)
text_surface = font_medium.render(body.name, True, BLACK) # Создаем текстовый маркер
text_rect = text_surface.get_rect()
text_rect.center = (body.x, body.y - body.radius - 10)
screen.blit(text_surface, text_rect)
elif vismode == 'yz':
for body in img_bodies:
pygame.draw.circle(screen, RED, (body.y, body.z), body.radius)
text_surface = font_medium.render(body.name, True, BLACK) # Создаем текстовый маркер
text_rect = text_surface.get_rect()
text_rect.center = (body.y, body.z - body.radius - 10)
screen.blit(text_surface, text_rect)
if bod_data:
bodies.clear()
for body_data in bod_data:
bodies.append(Body(*body_data))
else:
calculating_advanced.downloader()
bodies = calculating_advanced.bodies
sch = 0
for p in parameters:
text_date = font_small.render(p, True, BLACK)
text_date_rect = text_date.get_rect()
text_date_rect.topleft = (0, sch*text_date_rect.height)
screen.blit(text_date, text_date_rect)
sch += 2
for bod in bodies:
text_name = font_small.render(f'Имя объекта: {bod.name}', True, WHITE, BLUE)
text_name_rect = text_name.get_rect()
text_name_rect.topleft = (0, sch * text_name_rect.height)
screen.blit(text_name, text_name_rect)
sch += 1
text_mass = font_small.render(f'Масса: {bod.mass}', True, BLACK)
text_mass_rect = text_mass.get_rect()
text_mass_rect.topleft = (0, sch * text_mass_rect.height)
screen.blit(text_mass, text_mass_rect)
sch += 1
text_rad = font_small.render(f'Радиус: {bod.radius}', True, BLACK)
text_rad_rect = text_rad.get_rect()
text_rad_rect.topleft = (0, sch * text_rad_rect.height)
screen.blit(text_rad, text_rad_rect)
sch += 2
text_x = font_small.render(f'x: {bod.x} Vx: {bod.Vx} ax: {bod.ax}', True, BLACK)
text_x_rect = text_x.get_rect()
text_x_rect.topleft = (0, sch * text_x_rect.height)
screen.blit(text_x, text_x_rect)
sch += 1
text_y = font_small.render(f'y: {bod.y} Vy: {bod.Vy} ay: {bod.ay}', True, BLACK)
text_y_rect = text_y.get_rect()
text_y_rect.topleft = (0, sch * text_y_rect.height)
screen.blit(text_y, text_y_rect)
sch += 3
text_z = font_small.render(f'z: {bod.z} Vz: {bod.Vz} az: {bod.az}', True, BLACK)
text_z_rect = text_z.get_rect()
text_z_rect.topleft = (0, sch * text_z_rect.height)
screen.blit(text_z, text_z_rect)
sch += 2
#Тест
for h in bodies:
print(h.name, h.x, h.y, h.z, h.Vx, h.Vy, h.Vz, '\n', sep=' ')
pygame.display.flip() # Обновление экрана
# Ограничение частоты обновления
clock.tick(DISPLAY_TICK)
pygame.quit()
sys.exit()
def datasearch(file_name, objectl, parameter):
file = open(file_name, 'r')
info = file.read()
i = info.index(objectl) + len(objectl)
while info[i+1: i+len(parameter)+2] != parameter+' ':
i = i+1
if info[i: i+len('object_end')] == 'object_end':
print(f'Параметр {parameter} объекта {objectl} не найден в файле {file_name}. Продолжение работы программы невозможно')
exit(1)
j = i+len(parameter)+2
while info[j] == ' ' or info[j] == '=':
j = j + 1
k = j
while info[k] != '\n':
k = k + 1
return info[j:k]
General
system_name = Солнечная система
NUM_BODIES = 9
GRAVITY_CONSTANT = 6.67430e-11
STAR = Sun
TIME = 00:00:00/12/04/2021
Body_0
name = Sun
radius = 6.9634e8
mass = 1.9885e30
x = -1.092961103527230E+09
y = 8.038274795669809E+08
z = 1.896419146749307E+07
Vx = -1.049862721076522E+1
Vy = -1.159390400344258E+1
Vz = 3.414155173086606E-01
Body_1
name = Mercury
radius = 2.4397e6
mass = 3.302e23
x = 5.264555000230405E+10
y = -5.694980330962850E+09
z = -5.441551243950481E+09
Vx = -3.584014950066584E+03
Vy = 5.052872269283028E+04
Vz = 4.458128927712359E+03
Body_2
name = Venus
radius = 6.0518e6
mass = 48.685e23
x = 9.047269464662506E+10
y = 5.851336080208322E+10
z = -4.472877141980678E+09
Vx = -1.879172734890263E+04
Vy = 2.946396608965331E+04
Vz = 1.488636224625598E+03
Body_3
name = Earth
radius = 6.371e6
mass = 5.97219e24
x = -1.401034900195547E+11
y = -5.542031473237328E+10
z = 2.203423339415714E+07
Vx = 1.067330726523228E+04
Vy = -2.773887952618739E+04
Vz = 1.625568732631422E+00
Body_4
name = Mars
radius = 3.3895e6
mass = 6.4171e23
x = -1.050799109983333E+11
y = 2.204600324444063E+11
z = 7.172939531547755E+09
Vx = -2.0994657445110788E+04
Vy = -8.320121933089915E+03
Vz = 3.409811280300108E+02
Body_5
name = Jupiter
radius = 6.9911e7
mass = 1.8982e27
x = 5.385412407001685e11
y = -5.305339771309409e11
z = -9.847608346906245e9
Vx = 9.008969888225652e3
Vy = 9.924822355174250e3
Vz = -2.427157105139859e2
Body_6
name = Saturn
radius = 5.8232e7
mass = 5.6834e26
x = 8.847887203091053e11
y = -1.199070595431435e12
z = -1.437678365171337e10
Vx = 7.233161355133544e3
Vy = 5.712737392345746e3
Vz = -3.873722116053260e2
Body_7
name = Uranus
radius = 2.5362e7
mass = 8.6813e25
x = 2.256978216394547e12
y = 1.908111087045525e12
z = -2.215277767491233e10
Vx = -4.446689911063689e3
Vy = 4.883247899821490e3
Vz = 7.574419726570225e1
Body_8
name = Neptune
radius = 2.4622e7
mass = 1.0241e26
x = 4.413959524250453e12
y = -7.347457715532233e11
z = -8.659351770194119e10
Vx = 8.564501236858040e2
Vy = 5.393770243128461e3
Vz = -1.308114620191572e2