diff --git a/README.md b/README.md index a542148c..902ffe2e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # solar_project -Модель Солнечной системы на языке Python +Модель Солнечной системы на языке Python. diff --git a/solar_input.py b/solar_input.py index d45e0401..fdfd3ffb 100644 --- a/solar_input.py +++ b/solar_input.py @@ -5,7 +5,7 @@ def read_space_objects_data_from_file(input_filename): - """Cчитывает данные о космических объектах из файла, создаёт сами объекты + """Считывает данные о космических объектах из файла, создаёт сами объекты и вызывает создание их графических образов Параметры: @@ -19,12 +19,14 @@ def read_space_objects_data_from_file(input_filename): if len(line.strip()) == 0 or line[0] == '#': continue # пустые строки и строки-комментарии пропускаем object_type = line.split()[0].lower() - if object_type == "star": # FIXME: do the same for planet + if object_type == "star": star = Star() parse_star_parameters(line, star) objects.append(star) else: - print("Unknown space object") + planet = Planet() + parse_planet_parameters(line, planet) + objects.append(planet) return objects @@ -43,8 +45,15 @@ def parse_star_parameters(line, star): **line** — строка с описание звезды. **star** — объект звезды. """ + words = line.split() + star.R = float(words[1]) + star.color = words[2] + star.m = float(words[3]) + star.x = float(words[4]) + star.y = float(words[5]) + star.Vx = float(words[6]) + star.Vy = float(words[7]) - pass # FIXME: not done yet def parse_planet_parameters(line, planet): """Считывает данные о планете из строки. @@ -61,7 +70,14 @@ def parse_planet_parameters(line, planet): **line** — строка с описание планеты. **planet** — объект планеты. """ - pass # FIXME: not done yet... + words = line.split() + planet.R = float(words[1]) + planet.color = words[2] + planet.m = float(words[3]) + planet.x = float(words[4]) + planet.y = float(words[5]) + planet.Vx = float(words[6]) + planet.Vy = float(words[7]) def write_space_objects_data_to_file(output_filename, space_objects): @@ -77,8 +93,9 @@ def write_space_objects_data_to_file(output_filename, space_objects): """ with open(output_filename, 'w') as out_file: for obj in space_objects: - print(out_file, "%s %d %s %f" % ('1', 2, '3', 4.5)) - # FIXME: should store real values + print(obj.type, " ", obj.R, " ", obj.color, " ", obj.m, " ", obj.x, + " ", obj.y, " ", obj.Vx, " ", obj.Vy, file=out_file) + # FIXME: хорошо бы ещё сделать функцию, сохранающую статистику в заданный файл... diff --git a/solar_main.py b/solar_main.py index d77bddca..c39b1035 100644 --- a/solar_main.py +++ b/solar_main.py @@ -2,92 +2,78 @@ # license: GPLv3 import tkinter -from tkinter.filedialog import * -from solar_vis import * -from solar_model import * -from solar_input import * +import tkinter.filedialog as filedialog +import solar_vis as vis +import solar_model as model +import solar_input as inp -perform_execution = False +perform_execution = [False] """Флаг цикличности выполнения расчёта""" -physical_time = 0 -"""Физическое время от начала расчёта. -Тип: float""" - -displayed_time = None -"""Отображаемое на экране время. -Тип: переменная tkinter""" - -time_step = None -"""Шаг по времени при моделировании. -Тип: float""" - -space_objects = [] +space_objects = [[]] """Список космических объектов.""" -def execution(): +def execution(perform, _physical_time, _displayed_time, _time_step, time_speed, space): """Функция исполнения -- выполняется циклически, вызывая обработку всех небесных тел, а также обновляя их положение на экране. Цикличность выполнения зависит от значения глобальной переменной perform_execution. При perform_execution == True функция запрашивает вызов самой себя по таймеру через от 1 мс до 100 мс. """ - global physical_time - global displayed_time - recalculate_space_objects_positions(space_objects, time_step.get()) - for body in space_objects: - update_object_position(space, body) - physical_time += time_step.get() - displayed_time.set("%.1f" % physical_time + " seconds gone") - - if perform_execution: - space.after(101 - int(time_speed.get()), execution) + model.recalculate_space_objects_positions(space_objects[0], _time_step.get()) + for body in space_objects[0]: + vis.update_object_position(space, body) + _physical_time[0] += _time_step.get() + _displayed_time[0].set("%.1f" % _physical_time[0] + " seconds gone") + if perform[0]: + space.after(101 - int(time_speed.get()), + lambda: execution(perform, _physical_time, _displayed_time, _time_step, time_speed, space)) -def start_execution(): +def start_execution(perform, button, _physical_time, _displayed_time, _time_step, time_speed, space): """Обработчик события нажатия на кнопку Start. Запускает циклическое исполнение функции execution. """ - global perform_execution - perform_execution = True - start_button['text'] = "Pause" - start_button['command'] = stop_execution - execution() + perform[0] = True + button['text'] = "Pause" + button['command'] = lambda: stop_execution(perform, button, _physical_time, _displayed_time, _time_step, time_speed, + space) + + execution(perform, _physical_time, _displayed_time, _time_step, time_speed, space) print('Started execution...') -def stop_execution(): +def stop_execution(perform, button, _physical_time, _displayed_time, _time_step, time_speed, space): """Обработчик события нажатия на кнопку Start. Останавливает циклическое исполнение функции execution. """ - global perform_execution - perform_execution = False - start_button['text'] = "Start" - start_button['command'] = start_execution + perform[0] = False + button['text'] = "Start" + button['command'] = lambda: start_execution(perform, button, _physical_time, _displayed_time, _time_step, + time_speed, + space) print('Paused execution.') -def open_file_dialog(): +def open_file_dialog(perform, space, _space_objects): """Открывает диалоговое окно выбора имени файла и вызывает функцию считывания параметров системы небесных тел из данного файла. Считанные объекты сохраняются в глобальный список space_objects """ - global space_objects - global perform_execution - perform_execution = False - for obj in space_objects: + perform[0] = False + for obj in _space_objects[0]: space.delete(obj.image) # удаление старых изображений планет - in_filename = askopenfilename(filetypes=(("Text file", ".txt"),)) - space_objects = read_space_objects_data_from_file(in_filename) - max_distance = max([max(abs(obj.x), abs(obj.y)) for obj in space_objects]) - calculate_scale_factor(max_distance) + in_filename = filedialog.askopenfilename(filetypes=(("Text file", ".txt"),)) + _space_objects[0] = inp.read_space_objects_data_from_file(in_filename) + max_distance = max([max(abs(obj.x), abs(obj.y)) for obj in _space_objects[0]]) + vis.calculate_scale_factor(max_distance, vis.scale_factor) - for obj in space_objects: + for obj in _space_objects[0]: if obj.type == 'star': - create_star_image(space, obj) + vis.create_star_image(space, obj) elif obj.type == 'planet': - create_planet_image(space, obj) + vis.create_planet_image(space, obj) else: raise AssertionError() @@ -97,36 +83,40 @@ def save_file_dialog(): функцию считывания параметров системы небесных тел из данного файла. Считанные объекты сохраняются в глобальный список space_objects """ - out_filename = asksaveasfilename(filetypes=(("Text file", ".txt"),)) - write_space_objects_data_to_file(out_filename, space_objects) + out_filename = filedialog.asksaveasfilename(filetypes=(("Text file", ".txt"),)) + inp.write_space_objects_data_to_file(out_filename, space_objects[0]) def main(): """Главная функция главного модуля. Создаёт объекты графического дизайна библиотеки tkinter: окно, холст, фрейм с кнопками, кнопки. """ - global physical_time - global displayed_time - global time_step - global time_speed - global space - global start_button print('Modelling started!') - physical_time = 0 + physical_time = [0] + """Физическое время от начала расчёта. + Тип: float""" root = tkinter.Tk() # космическое пространство отображается на холсте типа Canvas - space = tkinter.Canvas(root, width=window_width, height=window_height, bg="black") + space = tkinter.Canvas(root, width=vis.window_width, height=vis.window_height, bg="black") space.pack(side=tkinter.TOP) # нижняя панель с кнопками frame = tkinter.Frame(root) frame.pack(side=tkinter.BOTTOM) - start_button = tkinter.Button(frame, text="Start", command=start_execution, width=6) - start_button.pack(side=tkinter.LEFT) + displayed_time = [tkinter.StringVar()] + """Отображаемое на экране время. + Тип: переменная tkinter""" + + displayed_time[0].set(str(physical_time[0]) + " seconds gone") + time_label = tkinter.Label(frame, textvariable=displayed_time[0], width=30) + time_label.pack(side=tkinter.RIGHT) time_step = tkinter.DoubleVar() + """Шаг по времени при моделировании. + Тип: float""" + time_step.set(1) time_step_entry = tkinter.Entry(frame, textvariable=time_step) time_step_entry.pack(side=tkinter.LEFT) @@ -135,18 +125,21 @@ def main(): scale = tkinter.Scale(frame, variable=time_speed, orient=tkinter.HORIZONTAL) scale.pack(side=tkinter.LEFT) - load_file_button = tkinter.Button(frame, text="Open file...", command=open_file_dialog) + start_button = tkinter.Button(frame, text="Start", + command=lambda: start_execution(perform_execution, start_button, physical_time, + displayed_time, time_step, time_speed, space), + width=6) + start_button.pack(side=tkinter.LEFT) + + load_file_button = tkinter.Button(frame, text="Open file...", + command=lambda: open_file_dialog(perform_execution, space, space_objects)) load_file_button.pack(side=tkinter.LEFT) save_file_button = tkinter.Button(frame, text="Save to file...", command=save_file_dialog) save_file_button.pack(side=tkinter.LEFT) - displayed_time = tkinter.StringVar() - displayed_time.set(str(physical_time) + " seconds gone") - time_label = tkinter.Label(frame, textvariable=displayed_time, width=30) - time_label.pack(side=tkinter.RIGHT) - root.mainloop() print('Modelling finished!') + if __name__ == "__main__": main() diff --git a/solar_model.py b/solar_model.py index 7cb63d65..4133cf71 100644 --- a/solar_model.py +++ b/solar_model.py @@ -18,9 +18,9 @@ def calculate_force(body, space_objects): for obj in space_objects: if body == obj: continue # тело не действует гравитационной силой на само себя! - r = ((body.x - obj.x)**2 + (body.y - obj.y)**2)**0.5 - body.Fx += 1 # FIXME: нужно вывести формулу... - body.Fy += 2 # FIXME: нужно вывести формулу... + r = ((body.x - obj.x) ** 2 + (body.y - obj.y) ** 2) ** 0.5 + body.Fx += gravitational_constant * body.m * obj.m / r ** 3 * (obj.x - body.x) + body.Fy += gravitational_constant * body.m * obj.m / r ** 3 * (obj.y - body.y) def move_space_object(body, dt): @@ -31,10 +31,13 @@ def move_space_object(body, dt): **body** — тело, которое нужно переместить. """ - ax = body.Fx/body.m - body.x += 42 # FIXME: не понимаю как менять... - body.Vx += ax*dt - # FIXME: not done recalculation of y coordinate! + ax = body.Fx / body.m + body.x += body.Vx * dt + ax * dt * dt / 2 + body.Vx += ax * dt + + ay = body.Fy / body.m + body.y += body.Vy * dt + ay * dt * dt / 2 + body.Vy += ay * dt def recalculate_space_objects_positions(space_objects, dt): diff --git a/solar_objects.py b/solar_objects.py index 0c73668b..2c083d4c 100644 --- a/solar_objects.py +++ b/solar_objects.py @@ -79,4 +79,4 @@ class Planet: """Цвет планеты""" image = None - """Изображение планеты""" \ No newline at end of file + """Изображение планеты""" diff --git a/solar_vis.py b/solar_vis.py index ae972ba4..5b9a1a30 100644 --- a/solar_vis.py +++ b/solar_vis.py @@ -3,29 +3,28 @@ """Модуль визуализации. Нигде, кроме этого модуля, не используются экранные координаты объектов. -Функции, создающие гaрафические объекты и перемещающие их на экране, принимают физические координаты +Функции, создающие графические объекты и перемещающие их на экране, принимают физические координаты """ header_font = "Arial-16" """Шрифт в заголовке""" -window_width = 800 +window_width = 600 """Ширина окна""" -window_height = 800 +window_height = 600 """Высота окна""" -scale_factor = None +scale_factor = [0] """Масштабирование экранных координат по отношению к физическим. Тип: float Мера: количество пикселей на один метр.""" -def calculate_scale_factor(max_distance): +def calculate_scale_factor(max_distance, _scale_factor): """Вычисляет значение глобальной переменной **scale_factor** по данной характерной длине""" - global scale_factor - scale_factor = 0.4*min(window_height, window_width)/max_distance - print('Scale factor:', scale_factor) + _scale_factor[0] = 0.4 * min(window_height, window_width) / max_distance + print('Scale factor:', _scale_factor[0]) def scale_x(x): @@ -39,7 +38,7 @@ def scale_x(x): **x** — x-координата модели. """ - return int(x*scale_factor) + window_width//2 + return int(x * scale_factor[0]) + window_width // 2 def scale_y(y): @@ -54,7 +53,7 @@ def scale_y(y): **y** — y-координата модели. """ - return y # FIXME: not done yet + return int(y * scale_factor[0]) + window_height // 2 def create_star_image(space, star): @@ -80,7 +79,10 @@ def create_planet_image(space, planet): **space** — холст для рисования. **planet** — объект планеты. """ - pass # FIXME: сделать как у звезды + x = scale_x(planet.x) + y = scale_y(planet.y) + r = planet.R + planet.image = space.create_oval([x - r, y - r], [x + r, y + r], fill=planet.color) def update_system_name(space, system_name): @@ -108,7 +110,7 @@ def update_object_position(space, body): r = body.R if x + r < 0 or x - r > window_width or y + r < 0 or y - r > window_height: space.coords(body.image, window_width + r, window_height + r, - window_width + 2*r, window_height + 2*r) # положить за пределы окна + window_width + 2 * r, window_height + 2 * r) # положить за пределы окна space.coords(body.image, x - r, y - r, x + r, y + r)