1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import math
from PyQt4 import QtGui, QtCore

# Перед тем, как изучать код, рекомендую запустить программу
# и увидеть, как она работает. Так будет понятнее.
# Не забудьте ввесити во все поля численные значения. Хотя бы нули.


class TrajectoryDrawer(QtGui.QWidget):

    def __init__(self):
        super(TrajectoryDrawer, self).__init__()

        self.initUI()

    def initUI(self):
        grid = QtGui.QGridLayout() # Выравнивание виджетов по элементам сетки.

        variables = ['x1', 'y1', 'vx1', 'vy1', 'm1',
                     'x2', 'y2', 'vx2', 'vy2', 'm2']  # Имена параметров.
        self.editDict = {}
        for ind, v in enumerate(variables): # ind и v -- номер и индекс параметра
            grid.addWidget(QtGui.QLabel(v), ind, 0) # Добавляет в сетку текстовую метку с именем парметра
                        #ind -- номер строки в сетке, 0 -- номер колонки.
            self.editDict[v] = QtGui.QLineEdit() # Сохраняем в словаре виджет текстового ввода для параметра v.
            self.editDict[v].setText('0')  # Заполним все поля ввода начальным значением 0.
            grid.addWidget(self.editDict[v], ind, 1) # и добавляем его на сетку с коориднатами (ind, 1)

        self.button = QtGui.QPushButton("Run!", self) # Кнопка запуска с надписью "Run!"
        grid.addWidget(self.button, ind+1, 0, 1, 2) # добавлем на сетку с координтами (ind+1, 0) и размером в
                        # 1 строку и 2 колонки.
        self.button.clicked.connect(self.run) # при нажатии кнопки будет выполяться метод run.

        hbox = QtGui.QHBoxLayout() # Горизотнальное выравнивание.
        hbox.addStretch(1) # Добавляем stretch, который отожмёт табичку с именами параметров и полями ввода вправо.
        hbox.addLayout(grid) # Затем добавить выравниваение по сетке внутрь горизотнального.
        self.setLayout(hbox) # Гравным выравниваем в окне будет hbox.

        self.setGeometry(200, 150, 700, 600)
        self.setWindowTitle('Trajectories')
        self.show()

        self.traj = [] # Траекторий пока нет.
        self.timer = QtCore.QBasicTimer() # Таймер позволяет нам делать анимацию.

    def run(self, b): # Эта функция подкючена к назатию кнопки с надписью "Run!" и вызывается при нажатии.
        d = self.editDict
        x1 = float(d['x1'].text()) # достаём значения переменных из полей ввода.
                    # d['x1'] -- поле ввода для x1, d['x1'].text() -- содержащийся в нём текст.
        y1 = float(d['y1'].text())
        vx1 = float(d['vx1'].text())
        vy1 = float(d['vy1'].text())
        m1 = float(d['m1'].text())
        x2 = float(d['x2'].text())
        y2 = float(d['y2'].text())
        vx2 = float(d['vx2'].text())
        vy2 = float(d['vy2'].text())
        m2 = float(d['m2'].text())
        self.traj = self.makeTrajectory(x1, y1, vx1, vy1, m1, x2, y2, vx2, vy2, m2) # Генерируем новые траектории.
        self.timer.start(100, self) # запускаем таймер с интервалом 100мс (= 0.1с).
                    # по договорённости, таймер будет вызывать в переданном ему объекте (в данном случае self)
                    # метод timerEvent.
        self.step = 0 # Индекс, до которого рисуется траектория на данном шаге анимации. Вначале 0, потом растёт
                    # c шагом 1.

    def timerEvent(self, e):
        if self.step >= len(self.traj[0]) - 1: # Если шагов по траектории больше, чем точек в траектории,
            self.timer.stop() # ... то остановим таймер.
        else:
            self.step += 1; # иначе, увеличим шаг
            self.update() # метод update поставить в очередь перерисовку окна с помощью paintEvent

    def closeEvent(self, e):
        self.timer.stop()  # Останавливаем таймер при выходе из программы.
        # Нужно при запуске программы через IDLE.

    def makeTrajectory(self, x1, y1, vx1, vy1, m1, x2, y2, vx2, vy2, m2):
        # Здесь должен быть расчёт движения планет, и нужно вернуть две траектории.
        # Пока возвращаются 2 тракетории: окружности радиусами 1 и 0.6.
        # !!!! Только этот метод и нужно трогать, всё остальное будет сделано автоматически. !!!!!
        tr1 = [ (math.cos(math.pi * i / 50), math.sin(math.pi * i / 50)) for i in range(0, 100) ]
        tr2 = [ (0.6 * math.cos(math.pi * i / 50), 0.6 * math.sin(math.pi * i / 50)) for i in range(0, 100) ]
        return tr1, tr2

    def paintEvent(self, e):
        p = QtGui.QPainter()
        p.begin(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing, True) # Включаем сглаживание линий при рисовании.
        p.translate(250, 250) # Сдвиг координат.
        p.scale(200, -200) # Масштабирование координат.
        # Сдвинули и отмасштабировал систему координат так, чтобы можно было рисовать в прямоугольнике
        # от -1 до 1 по X и от -1 до 1 по Y.
        if self.traj: # Вначале self.traj -- пустой список, и мы не войдём в тело if'a.
            p.setPen(QtGui.QColor(255, 0, 0)) # Можно не создавать QPen, а просто передавать цвет.
            trajectory_part = self.traj[0][0:self.step + 1] # Часть нулевой траектории, которую мы
                    # будем рисовать. На каждом шаге self.step увеличивается и
                    # получается, что количество точек в срезе растёт и
                    # получается анимация.
            trajectory_points = [ QtCore.QPointF(pnt[0], pnt[1]) for pnt in trajectory_part]
                    # Преобразуем список кортежей с координатами в список
                    # объектов QPointF, как этого требует метод drawPolyline.
            p.drawPolyline(*trajectory_points)  # элементы списка становятся агрументами.
                    # drawPolyline рисует линию от своего первого аргумента до
                    # последнего.
            p.setPen(QtGui.QColor(150, 0, 0)) # более тёмный красный цвет.
            x, y = self.traj[0][self.step] # Центр кружка.
            p.drawEllipse(QtCore.QPointF(x, y), 0.01, 0.01) # Рисуем кружок с центром в (x, y) и радиусом 0.01.

            # И то же самое для второй траектории.
            p.setPen(QtGui.QColor(0, 255, 0))
            trajectory_part = self.traj[1][0:self.step + 1] # Аналогично тому, что раньше, но с индексом 1.
            trajectory_points = [ QtCore.QPointF(pnt[0], pnt[1]) for pnt in trajectory_part]
            p.drawPolyline(*trajectory_points)
            p.setPen(QtGui.QColor(0, 150, 0))
            x, y = self.traj[1][self.step]
            p.drawEllipse(QtCore.QPointF(x, y), 0.01, 0.01)

        p.end()


def main():
    app = QtGui.QApplication(sys.argv)
    ex = TrajectoryDrawer()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()