From c811196a5363bb6949222e2ff2d4d444d4b0815c Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 18 Feb 2022 22:09:50 +0100 Subject: [PATCH] doc --- DOC.md | 284 ++-------------------------------------------------- main.md | 229 ++++++++++++++++++++++++++++++++++++++++++ main.py | 1 - mainLoop.md | 156 +++++++++++++++++++++++++++++ 4 files changed, 396 insertions(+), 274 deletions(-) create mode 100644 main.md create mode 100644 mainLoop.md diff --git a/DOC.md b/DOC.md index 1fe417a..3cf7184 100644 --- a/DOC.md +++ b/DOC.md @@ -19,278 +19,16 @@ Le tracé de la route est defini par la propriètée "shape" qui est composée d Une jonction entre plusieurs routes. Possède un ID, une position (x,y), une liste des voies qui s'y intersecte, une liste des voies internes qui la compose, un shape qui l'englobe -## Implémentation -### main.py -Le fichier principal - -#### Imports -imports "classiques" : - -```python -import os, sys -import time -``` - -imports pour sumo, 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 -from PySide6.QtWidgets import QApplication, QMainWindow -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 - -```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, donc on execute le code d'initialisation de la classe QMainWindow - -```python - super(MainWindow, self).__init__() -``` - -On genere l'interface à partir du fichier générée par QT Designer - -```python - self.ui = Ui_MainWindow() - self.ui.setupUi(self) -``` - -On initialise la boucle de mise à jour - -```python - self.mainLoop = mainLoop(self) -``` - -On donne à la boucle principale le widget qui nous sert à afficher les infos (l'interface avec position, vitesse,... à droite de l'écran). -Après il est transmis à chaque voiture qui décident de ce quelles vont afficher - -```python - self.mainLoop.addInfosDisplay(self.ui.infos) -``` - -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.mainLoop.openNetwork) - openMenu.addAction(openNet) - - openVeh = QAction("&Open Vehicles",self) - openVeh.setStatusTip("Open Vehicle description (.rou.xml)") - openVeh.triggered.connect(self.mainLoop.openVehicles) - openMenu.addAction(openVeh) -``` - -On crée un timer qui va executer toutes les 1/60s (60fps) la fonction update de notre boucle principale - -```python - timer = QTimer(self) - timer.timeout.connect(self.mainLoop.update) - timer.start(1.0/60) -``` - -##### keyPressEvent -Cette fonction est appelée à chaque appui sur le clavier, ici si la touche est Esc ou Q on arrète le programme - -```python - def keyPressEvent(self, e): - if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Q: - self.close() -``` - -#### 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, comme ça Qt se demerde pour gérer l'event loop, le context,... - -```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()) -``` - -### mainLoop.py -Le fichier contenant notre boucle de maj principale, appelé par un QTimer défini dans [main.py](#Classe-MainWindow) - -#### imports -Nos deux classes définies dans [Map.py](#Map.py) et [CarController.py](#CarController.py) chargées de gérer respectivement le reseau (chargement, affichage,...) et les voitures (chargement, calcul de trajet, affichage, deplacements, ...) - -```python -from Map import Map -from CarController import CarController -``` - -import de QFileDialog pour l'ui d'ouverture des fichiers de reseau et de vehicules - -```python -from PySide6.QtWidgets import QFileDialog -``` - -#### classe mainLoop -Notre classe (On remarquera qu'elle ne suit pas la même convention pour le nom que MainWindow) - -```python -class mainLoop(): -``` - - -##### \_\_init\_\_ -La fonction d'initialisation - -Ici parent fait reference à notre MainWindow parce que QFileDialog veut absolument un QWidget. -painter est la surface d'affichage principale, définie dans le fichier [mainPainter.py](#mainPainter.py), on s'en sert pour dessiner. - -On crée nos objet pour le réseau (Map) et pour les voitures (CarController), pour l'instant ils sont vides, on les remplis après quand l'utilisateur nous donne l'emplacement des fichiers ([openNetwork](#openNetwork) et [openVehicles](openVehicles)) - -```python -def __init__(self, parent): - self.parent = parent - self.painter = parent.ui.mainSurf - - self.map = Map() - - self.controller = CarController(self.map) - - self.painter.addMap(self.map) - self.painter.addCarController(self.controller) -``` - -##### update -La fonction dans laquelle on fait les maj, elle est appelée periodiquement par le Timer defini dans le [`__init__`](#mainInit) de la fenêtre principale - -On fait d'abord la maj des voitures (deplacement + conduite). -Puis on fait appel à update sur notre painter qui va prévoir un [paintEvent](#paintEvent) dans lequel on fait appel à [Map.draw](#mapDraw) et [CarController.draw](#CCDraw) qui va redessiner les elements de l'affichage - -```python -def update(self): - self.controller.update() - self.painter.update() -``` - -##### openNetwork -Fonction appelé quand l'utilisateur click sur le bouton open-\>network file - -```python -def openNetwork(self): -``` - -On crée un dialogue d'ouverture de fichier - -```python - fileName = QFileDialog.getOpenFileName(self.parent,"Open Network", "./", "Network File (*.net.xml)") -``` - -Si le resultat est vide (i.e l'utilisateur à annulé) on abandonne - -```python - if(fileName[0] == ''): - return -``` - -Sinon on crée le reseau à partir u chemin récupéré - -```python - self.map.fromPath(fileName[0]) -``` - -On génére la matrice de transformation entre coordonnées du réseau-\>coordonées de l'écran - -```python - self.painter.generateTransform() -``` - -Maintenant qu'on as le réseau on peut generer les itineraires pour les voitures -(si on as pas encore chargé les voitures cette fonction n'a pas d'effet) - -```python - self.controller.prepareRoute() -``` - -##### openVehicles -Très similaire à la fonction [openNetwork](#openNetwork) mais ce coup ci pour charger les vehicules - -```python -def openVehicles(self): - fileName = QFileDialog.getOpenFileName(self.parent,"Open Vehicle trip description", "./", "Route File (*.rou.xml)") - if(fileName[0] == ''): - return - self.controller.fromPath(fileName[0]) -``` - -Si le reseau est déjà chargé on peut directement calculer les trajet ici - -```python - if(self.map.isLoaded()): - self.controller.prepareRoute() -``` - ## Compilation +### Pour generer les classes de l'ui à partir des .ui de Qt Designer +Pour la fenêtre principale : + +`uic-qt6 -g python window.ui > window.py` + +Pour la sous-fenêtre d'affichage des infos de voiture : + +`uic-qt6 -g python carInfo.ui > carInfo.py` + + ### Pour faire un executable -nuitka --onefile --enable-plugin=pyside6 main.py +`nuitka --onefile --enable-plugin=pyside6 main.py` diff --git a/main.md b/main.md new file mode 100644 index 0000000..9581450 --- /dev/null +++ b/main.md @@ -0,0 +1,229 @@ +# 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()) +``` + + diff --git a/main.py b/main.py index 8f3ed3f..101ea3d 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,6 @@ from window import Ui_MainWindow from mainLoop import mainLoop class MainWindow(QMainWindow): - quickLoadSignal = Signal() def __init__(self): super().__init__() self.ui = Ui_MainWindow() diff --git a/mainLoop.md b/mainLoop.md new file mode 100644 index 0000000..6622bd0 --- /dev/null +++ b/mainLoop.md @@ -0,0 +1,156 @@ +# mainLoop.py +Le fichier contenant notre boucle de maj principale, appelé par un QTimer défini dans [main.py](#Classe-MainWindow) + +## imports +Nos deux classes définies dans [Map.py](#Map.py) et [CarController.py](#CarController.py) chargées de gérer respectivement le reseau (chargement, affichage,...) et les voitures (chargement, calcul de trajet, affichage, deplacements, ...) + +```python +from Map import Map +from CarController import CarController +``` + +Comme d'hab les modules de Qt necessaires + +```python +from PySide6.QtWidgets import QFileDialog, QToolBox +from PySide6.QtCore import QElapsedTimer, QTimer, QElapsedTimer, QObject, Signal, Slot +``` + +## classe mainLoop +Notre classe (On remarquera qu'elle ne suit pas la même convention pour le nom que MainWindow) + +```python +class mainLoop(): +``` + +On crée un signal pour la maj de l'affichage des fps. +Il y as une bonne raison pour laquelle il ne se trouve pas dans le `__init__` mais je m'en souvient plus + +```python + updateFPS = Signal(int) +``` + +### \_\_init\_\_ +La fonction d'initialisation + +Ici parent fait reference à notre MainWindow. +painter est la surface d'affichage principale, définie dans le fichier [mainPainter.py](mainPainter.py), on s'en sert pour dessiner. + +On crée nos objet pour le réseau (Map) et pour les voitures (CarController), pour l'instant ils sont vides, on les remplis après quand l'utilisateur nous donne l'emplacement des fichiers ([openNetwork](#openNetwork) et [openVehicles](openVehicles)) + +```python +def __init__(self, parent): + self.parent = parent + self.painter = parent.ui.mainSurf + + self.map = Map() + + self.controller = CarController(self.map) + + self.painter.addMap(self.map) + self.painter.addCarController(self.controller) +``` + +On crée le timer qui va s'occuper de maj les deplacements (mais ce coup ci on le demarre pas tout de suite, on va attendre que le thread ai demarré ([start](#start))) +On instancie aussi le timer qui va nous servir à mesurer le fps + +```python + self.timer = QTimer() + self.timer.timeout.connect(self.update) + self.timer.timeout.connect(self.updateFps) + self.timer.setInterval(1000/60) + + self.fpsTimer = QElapsedTimer() + self.updateFPS.connect(self.parent.updatePhysicsFps) +``` + +### start +Fonction appelée quand le thread est demarré (on l'a connecté dans [main.py](main.py#__init__)) + +```python + def start(self): + self.fpsTimer.start() +``` + +### update +La fonction dans laquelle on fait les maj, elle est appelée periodiquement par le Timer defini dans precedemment [\_\_init\_\_](#\_\_init\_\_) + +On fait la maj des voitures (deplacement + conduite). + +```python + def update(self): + self.controller.update() +``` + +##### openNetwork +Fonction appelé quand l'utilisateur click sur le bouton open-\>network file par le code dans [MainWindow](main.md#openNetwork) + +```python + def openNetwork(self): +``` + +Si le resultat est vide (i.e l'utilisateur à annulé) on abandonne + +```python + if(fileName[0] == ''): + return +``` + +Sinon on crée le reseau à partir u chemin récupéré + +```python + self.map.fromPath(fileName[0]) +``` + +On génére la matrice de transformation entre coordonnées du réseau-\>coordonées de l'écran + +```python + self.painter.generateTransform() +``` + +Maintenant qu'on as le réseau on peut generer les itineraires pour les voitures +(si on as pas encore chargé les voitures cette fonction n'a pas d'effet) + +```python + self.controller.prepareRoute() +``` + +### openVehicles +Très similaire à la fonction [openNetwork](#openNetwork) mais ce coup ci pour charger les vehicules + +```python + def openVehicles(self): + if(fileName[0] == ''): + return + self.controller.fromPath(fileName[0]) +``` + +Si le reseau est déjà chargé on peut directement calculer les trajet ici + +```python + if(self.map.isLoaded()): + self.controller.prepareRoute() +``` + +### quickLoad + +Fonction pour charger rapidement les reseaux, j'avais la flemme d'aller chercher dans le menu à chque fois + +```python + def quickLoad(self): + self.map.fromPath("test2.net.xml") + self.painter.generateTransform() + self.controller.fromPath("test5.rou.xml") + self.controller.prepareRoute() +``` + +### updateFps + +Fonction pour mesurer/maj le fps. +On recupere le temps depuis la dernier maj puis on la passe au Slot dans [main.py](main.md#updatePhysicsFps) + +```python + def updateFps(self): + newFps = self.fpsTimer.restart() + self.updateFPS.emit(newFps) +```