# main.py Le fichier principal, charge et contrôle tout les autres éléments du programme ## Imports imports "classiques" : ```python import os, sys ``` ajout de sumolib dans le PATH, honnêtement je suis pas sur qu'on en ai besoin si la librairie est installée avec pip mais bon c'était le code recommandé [ici](https://sumo.dlr.de/docs/Tools/Sumolib.html) : ```python if 'SUMO_HOME' in os.environ: tools = os.path.join(os.environ['SUMO_HOME'], 'tools') sys.path.append(tools) else: print("please declare environment variable 'SUMO_HOME'") ``` imports pour Qt : ```python from PySide6.QtCore import Qt, QTimer, QElapsedTimer, QThread, Slot, Signal from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QFileDialog from PySide6.QtGui import QSurfaceFormat, QAction ``` import du fichier contenant la description de interface, generée par uic : ```python from window import Ui_MainWindow ``` le fichier contenant la boucle principale de maj : ```python from mainLoop import mainLoop ``` ## Classe MainWindow On définit la classe MainWindow qui hérite de QMainWindow, C'est la classe quio contient notre affichage principal ```python class MainWindow(QMainWindow): ``` ### `__init__` La fonction `__init__` est la fonction qui est appellée quand on crée l'objet (le constructeur) ```python def __init__(self): ``` On fait appel à la fonction `__init__` de la superclass, i.e on execute le code d'initialisation de la classe QMainWindow ```python super().__init__() ``` On genere l'interface à partir du fichier générée par QT Designer ```python self.ui = Ui_MainWindow() self.ui.setupUi(self) ``` De base il met le focus sur les QSpinBox, ducoup on ne recupere pas les events sur le touches ([keyPressEvent](#keyPressEvent)) ducoup pour eviter ça on autorise la fenetre principale à recuperer le focus si elle est cliquée et par defaut on lui donne le focus ```python self.setFocusPolicy(Qt.StrongFocus) self.setFocus() ``` On crée le Qthread qui va s'occuper de faire la mise à jour des déplacements, on crée un instance de notre classe qui gére la maj ([mainLoop](mainLoop.md)) et on la deplace dans le thread fraichement créé. On relie la fonction start de notre fonction au signal started du thread, pour demarrer le timer de la classe après que le thread ai demarré. Finalement on demarre le Thread ```python self.updateThread = QThread() self.mainLoop = mainLoop(self) self.mainLoop.moveToThread(self.updateThread) self.updateThread.started.connect(self.mainLoop.start) self.updateThread.start() ``` On génere le menu (l'interface en haut de l'écran). Avec le menu general, qui as un sous-menu (File), qui as un sous-menu (Open), qui as deux sous-actions (ouvrir le réseau et ouvrir les vehicules). Pour chaque action on les relient (connect) à un fonction qui sera executée quand on cliquera sur le bouton. ```python fileMenu = self.menuBar().addMenu("&File") openMenu = fileMenu.addMenu("&Open") openNet = QAction("&Open Network",self) openNet.setStatusTip("Open Network file (.net.xml)") openNet.triggered.connect(self.openNetwork) openMenu.addAction(openNet) openVeh = QAction("&Open Vehicles",self) openVeh.setStatusTip("Open Vehicle description (.rou.xml)") openVeh.triggered.connect(self.openVehicles) openMenu.addAction(openVeh) ``` On crée le timer qui va se charger d'executer peridiodiquement la fct de maj de notre affichage (1000ms/60 = 60fps) On la relie aussi à la fct [updateFPS](#updateFPS) qui s'occupe juste de mesurer et de maj le fps suir l'affichage ```python timer = QTimer(self) timer.timeout.connect(self.ui.mainSurf.update) timer.timeout.connect(self.updateFPS) timer.start(1000/60) ``` On relie les différents bouton et entrées à leurs actions (start, stop, ...) ```python self.ui.startButton.clicked.connect(self.mainLoop.timer.start) self.ui.stopButton.clicked.connect(self.mainLoop.timer.stop) self.ui.mainFPS_set.valueChanged.connect(self.drawTimer.setInterval) self.ui.physicsFPS_set.valueChanged.connect(self.mainLoop.timer.setInterval) ``` Ce timer nous sert juste à mesurer le temps passé entre chaque maj de l'affichage (pour récuperer le fps) ```python self.fpsTimer = QElapsedTimer() self.fpsTimer.start() ``` ### keyPressEvent Cette fonction est appelée à chaque appui sur le clavier, ici si la touche est Esc ou Q on arrète le programme, si la touche est S on charge le reseau et les vehicules à partir des fichiers dans ./ (si ils existent) ```python if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Q: self.close() elif e.key() == Qt.Key_S: self.mainLoop.quickLoad() ``` ### updateFPS La fonction chargée de maj l'affichage des fps, appelée par le drawTimer. On récupère le QLabel pour l'affichage, on change le texte avec la nouvelle valeur récuperé dans le fpsTimer et finalment on marque le widget pour la maj ```python def updateFPS(self): widget = self.findChild(QLabel,"mainFps") widget.setText(f"fps : {1000/self.fpsTimer.restart():.1f}") widget.update() ``` ### updatePhysicsFps Comme pour [updateFps](#updateFPS) mais ce coup ci pour la boucle de maj des déplacements. Comme on est dans un thread diférent pour ce timer on fait appel au slot directement avec la valeur mais sinon c'est pareil ``` @Slot(int) def updatePhysicsFps(self,t): widget = self.findChild(QLabel,"physicsFPS") widget.setText(f"ph fps : {1000/t:.1f}") ``` ### openNetwork La fonction qui ouvre le dialogue d'ouverture de fichier quand on clique sur le menu, on est obligé de la mettre dans ce thread là parce que Qt supporte mal de demarrer le dialoque dans un Thread différent que celui d'affichage. Donc on ouvre le dialogue, on récupère le résultat et on le passe à [la fonction d'ouverture dans mainLoop](mainLoop.md#openNetwork) ```python def openNetwork(self): filename = QFileDialog.getOpenFileName(self,"Open Network", "./", "Network File (*.net.xml)") self.mainLoop.openNetwork(filename[0]) ``` ### openVehicle Idem à [openNetwork](#openNetwork) mais pour l'ouverture du fichier de description des voyages pour le vehicules ```python def openVehicles(self): filename = QFileDialog.getOpenFileName(self,"Open Vehicle trip description", "./", "Route File (*.rou.xml)") self.mainLoop.openVehicles(filename[0]) ``` ## main `__name__` est defini à `"__main__"` seulement si on est dans le fichier principal (i.e pas dans un module). Donc cette portion de code est celle executée quand on lance main.py ```python if __name__ == "__main__": ``` On crée une QApplication, bah c'est Qt ```python app = QApplication() ``` On définie les propriètés par defaut de la surface, j'avais mis ça pour activer l'AA mais ça à l'air foiré ([cf](https://doc.qt.io/qtforpython/PySide6/QtOpenGLWidgets/QOpenGLWidget.html#PySide6.QtOpenGLWidgets.PySide6.QtOpenGLWidgets.QOpenGLWidget.resized)) ```python format = QSurfaceFormat() format.setDepthBufferSize(24) format.setVersion(3, 2) format.setSamples(4) format.setProfile(QSurfaceFormat.CoreProfile) QSurfaceFormat.setDefaultFormat(format) ``` On crée notre objet de fenetre principale défini [juste au dessus](#Classe-MainWindow) ```python window = MainWindow() window.show() ``` Finalement on lance l'event loop, et on quitte dès qu'elle retourne ```python sys.exit(app.exec()) ```