====== Commit 2017-01-06 ====== Ce commit consiste à modifier le code source de Metafor (et keygen) pour qu'il soit compilable avec VTK 6 et Qt 5. C'est une des dernières étapes pour pouvoir utiliser le Visual Studio 2015. Le code reste cependant compatible avec VTK 5 et Qt 4. Les batteries sont d'ailleurs passées avec les anciennes versions de ces libs. Le code python (vtk/PyQt) n'a pas encore été traduit. Il n'est donc pas encore possible de passer les batteries avec VTK 6 (à cause de geniso et biomec qui utilisent vtk). Enfin, Luc n'a pas encore intégré ces 2 nouyelles versions dans ses libs. ===== Portage VTK 6 ===== ==== Contexte ==== Continuer à utiliser VTK 5 devient de plus en plus difficile avec les OS et les compilateurs qui évoluent ainsi que la "nouvelle" norme C++ qui nous fait de l'oeil. En effet, * VTK 5 compile difficilement sous Mac OS X et ne supporte pas Qt 5.x (Qt 4 n'est officiellement plus supporté sur Mac OS X Sierra). J'ai vraiment galéré pour compiler Qt4+VTK5 sous Mac et, une fois que ça a marché, on observe des bugs d'affichage. * VTK 5 n'est pas compilable avec Visual Studio 2015 (du moins j'ai pas réussi à la faire en quelques heures), ce qui nous empêche de migrer vers ce nouveau compilateur qui nous permettrait d'utiliser du C++11 de manière généralisée dans le code (et donc nettoyer pas mal de choses) ou d'utiliser les nouvelles versions de Parasolid (v29). * VTK 6 date déjà de début 2013 et la version actuelle de VTK est aujourd'hui la 7.x! On est donc en retard de 2 versions. ==== Essai #1: Portage brutal ==== Malheureusement VTK 6 n'est pas tout à fait compatible avec VTK 5. Le pipeline VTK distingue maintenant les connexions entre "données et filtres" et les connexions entre "ports de filtres". Par exemple, si ''dataset'' est un ''vtkDataSet'' (un maillage de type ''vtkPolyData'' ou ''vtkUnstructuredGrid'') et qu'on veut filtrer ce ''dataset'' avec ''filter'', on pouvait écrire avec VTK 5: filter->SetInput(dataset); Il faudrait maintenant écrire ceci avec VTK 6: filter->SetInputData(dataset); En effet, ''dataset'' est un ensemble de données et pas un filtre. La fonction ''setInput()'' n'existe plus! Mais ce n'est pas tout: la connexion qui, sous VTK 5, était souvent (pas toujours) relativement intelligente (le filtre testait régulièrement si ''dataset'' était modifié pour remouliner les données lorsque le mapper s'exécutait), ne l'est plus jamais. Un portage brutal VTK 5 vers VTK 6 consisterait à écrire: #if (VTK_MAJOR_VERSION <= 5) filter->SetInput(dataset); #else filter->SetInputData(dataset); filter->Update(); // on force la mise à jour! #endif C'est la première méthode que j'ai utilisée... Ca marche... mais j'ai fait mieux finalement. Les connexions de type Filtre<=>Filtre pouvaient s'écrire sous VTK 5: filtre2->SetInput(filtre1->GetOutput()); Ce type de connexion était encore un fois relativement intelligent dans VTK 5. Ça ne marche plus dans VTK 6: il faut faire: filtre2->SetInputConnection(filtre1->GetOutputPort()); En d'autres mots, on ne connecte plus les données des 2 filtres mais plutôt leur "ports" (de type ''vtkAlgorithmOutput'', quel que soit le type de dataset qui transite à travers la connexion). Par contre, bonne nouvelle, dans ce cas-ci, cette écriture est compatible avec VTK 5 et elle est toujours intelligente (''filtre2'' teste toujours si l'output de ''filtre1'' est à jour avant de s'exécuter à la demande du mapper en aval). Même genre de problème d'écriture pour les mappers. Il était possible de faire un ''SetInput(data)'' qui peut être directement écrit sous forme d'un ''setInputConnection(port)'' (du moins si l'entrée est bien un filtre... ce qui n'est pas toujours le cas). On a aussi le problème avec les "sources VTK" où le ''SetSource(data)'' peut être remplacé par un ''SetSourceConnection(port)'' dans le cas où l'entrée est bien un filtre. Dans la visu de Metafor, il y a beaucoup de filtres et certaines classes ont été écrites pour rassembler certaines opérations assez complexes. Par exemple, l'objet ''Slices'' crée une série de tranches à partir d'un maillage 3D. Il contient tout ce qu'il faut pour construire un maillage à partir d'un autre (des objets filtres, un mapper et un actor). Dans cet exemple, l'entrée de ''Slices'' est donc un ''vtkUnstructuredGrid''. Cependant, dans Metafor, ce maillage d'entrée est soit le maillage initial, soit un maillage qui sort d'un filtre et qui a déjà subi des opérations de duplication pour tenir compte des symétries X, Y, Z. Dans le cas de l'ancienne écriture VTK 5, on faisait un ''setInput()'' du ''vtkUnstructuredGrid'' sortant de l'éventuel filtre de symétrie. Pour VTK 6, on voit bien qu'on a deux cas différents (avec ou sans symétrie) qui vont se traduire par des appels différents, c'est-à-dire des ''if()'' et des ''Update()'' additionnels dans le code (plus des ''#ifdef''/''#endif'' liés au changement d'API). Je n'aimais pas du tout le résultat obtenu même s'il était fonctionnel et j'ai donc décidé de faire les choses plus proprement... ==== Essai #2: Nettoyage du pipeline VTK ==== Après quelques heures de "portage brutal", j'ai donc eu un code compilable avec VTK 5 et VTK 6. A l'exécution, certains filtres n'étaient plus actifs en version VTK 6 et j'ai pu forcer le bon fonctionnement de la visu en ajoutant des ''filter->Update()'' un peu partout. Néanmoins ça ne me plaisait pas beaucoup plus plusieurs raisons: * J'avais introduit beaucoup de ''#ifdef'' / ''#endif''. Ces directives pourraient être évitées par l'utilisation de connexions propres entre ports de filtres. * J'avais l'impression que tous ces ''Update()'' ajoutés allaient mettre à jour parfois inutilement le pipeline (et donc consommer inutilement du CPU). Je n'ai pas vérifié. * La modification du code source m'a interpellé: beaucoup de moments "mais j'ai déjà modifié ce code?", c'est-à-dire beaucoup de copier/coller dans le travail de Marco (et un peu moins Gaëtan et le mien)... Certaines parties de code me semblaient complètement inutiles ou redondantes. J'ai donc décidé de modifier le source pour qu'il compile avec VTK 5 et VTK 6 avec un minimum de directives de préprocesseur ''#ifdef'' / ''#endif''. L'idée est donc d'éviter comme la peste les connections de type ''filter->SetInputData(dataset)''. Comme je l'ai déjà dit, ce type de connexion n'est pas dynamique et ''filter->Update()'' doit être appelé explicitement si le contenu du ''dataset'' change. VTK 6 propose une nouvelle méthode nommée ''SetInputDataObject()'' qui permet une connexion dynamique... mais cette fonction n'est pas disponible en VTK 5 et je voulais garder la compatibilité VTK 5 le plus longtemps possible. La solution m'est venue en regardant l'implémentation de ''SetInputDataObject()'' dans VTK 6: la méthode utilise le filtre ''vtkTrivialProducer'' qui est un filtre "vide" pour lequel on peut prescrire la sortie. Gros intérêt: la sortie est observée et les filtres connectés en aval s'exécutent automatiquement lorsque le ''dataset'' est modifié! On retrouve donc le comportement des vieilles versions de VTK en utilisant des commandes compatibles avec toutes les versions de VTK. J'ai donc modifié tous les ''datasets'' de Metafor en leur ajoutant un ''vtkTrivialProducer'' et en connectant tous les filtres avec des ''SetInputConnection()''. C'est une modification assez grosse puisque cela signifie qu'on ne manipule plus aucun pointeur vers des ''vtkPolyData'' ou ''vtkUnstructuredGrid'' mais plutôt vers des ''vtkAlgorithmOutput'' (des entrées/sorties de filtres). J'ai traité la plupart des classes sauf: * ''mtWear'': vu la quantité de ''ifdef''/''endif'' déjà présents dans le source, je me suis dit que ces classes étaient toujours en chantier. La suppression des ''setInputData()'' que je viens d'introduire ne devrait pas poser de problème. * ''geniso'': j'ai une mauvaise vision globale de cette partie du code. De plus, les cas-tests utilisent également l'interface python de VTK que je n'ai pas installée sur mon PC. Ce sera donc à tester dans un deuxième temps. ==== Modification de la visu ==== Ayant dû lancer manuellement des tas de cas-tests pour déboguer mes modifs, je suis évidemment tombé sur des mini bugs ou des petits trucs à faire pour améliorer les choses dans la visu: * Correction de la taille des balls vis à vis de la taille des tubes. * Prise en compte du specular effect sur les balls & tubes. * Changement de certains paramètres par défaut (il ne faut plus cocher 8 cases pour afficher les "contact territories" - le scaling des vecteurs est mis sur "auto" par défaut). * Suppression de l'exportation vers des formats exotiques (''SAVE_IV'', ''SAVE_OBJ'', ''SAVE_OOGL'', ''SAVE_RIB'') qu'on utilise jamais (et qui ne marchaient plus, même dans la version officielle). * Prise en compte correcte de la couleur du label de scalar bar sur background sombre (c'était noir sur fond noir dans la version officielle). ==== Nettoyage du code source ==== C'est une des choses qui m'a frappé quand j'ai modifié le code lors du "portage brutal": il y a, dans le nouveau code, beaucoup de copier/coller et très peu de commentaires. J'ai décidé de démarrer une opération de nettoyage quand j'ai vu que les classes ''Elem0DDataSet'', ''Elem1DDataSet'', ''Elem2DDataSet'' étaient tout à fait IDENTIQUES! (depuis toujours). Voila donc mes modifs: * Ajout de commentaires là où j'ai pu et un peu de doc DoxyGen. * Suppression de nombreux copier/coller (il en reste...) * Indentation. * Alignement des opérations "=", des arguments de fonction. * Espaces entre types, '*' ou '&' et variables: ''double* a'' => ''double *a'' * Aération du source et regroupement des opérations en blocs logiques ou en blocs de fonctions virtuelles pour les déclarations de fonctions. * Uniformisation du nombre d'espaces entre définition de fonctions. * Modification de noms de variables (cfr. noms à rallonge de Gaëtan ou d'anciens noms à moi provenant de classes splittées qui contenaient beaucoup d'objets). * Retours à la ligne pour obtenir une vision claire du source en ~80 colonnes (ça aide à obtenir un code facile à lire sur papier aussi). * Ajout d'un espace après les virgules. * Suppression des classes suivantes (introduites par Marco): * ''Elem0DDataSet'': classe identique à ''Elem2DDataSet'' (maillage ''vtkPolydata'') * ''Elem1DDataSet'': classe identique à ''Elem2DDataSet'' (maillage ''vtkPolydata'') * ''Elem1DDiscDataSet'': classe identique à ''Elem2DDiscDataSet'' * ''Elem2DDataSet'': remplacé par ''Elem3DDataSet'' (depuis l'affichage des éléments du second degré incompatibles avec un ''vtkPolydata'', les maillages 2D utilisent un ''vtkUnstructuredGrid'' comme en 3D) * ''Elem2DDiscDataSet'': même raison que ''Elem2DDataSet'' * ''Mesh1D'': classe identique à ''Mesh2D'' (permet l'affichage d'un champ scalaire sur ''vtkPolydata'') * ''SymmetryFilter0D'': classe identique à ''SymmetryFilter2D'' (filtre de symétrie de maillage ''vtkPolydata'') * ''SymmetryFilter1D'': classe identique à ''SymmetryFilter2D'' ==== Bugs ==== Je n'ai pas corrigé tous les bugs trouvés... et j'en ai même introduit 1 nouveau que je n'arrive pas à résoudre. Anciens (ça plante dans la version officielle): * contact territory / 3D / "Corner points" explose lorsqu'il est activé. * mtWear: la désactivation/réactivation du ''WearContactTool'' enlève définitivement les couleurs du champ scalaire (le maillage devient blanc). * Le contact territory n'a pas d'épaisseur sur un fac rechargé. * Il est possibile de faire une extrusion axisym et linéaire en même temps. * Le combo box "axes / axis marker / position" est toujours grisé. Nouveau: * Passer d'un affichage des champs scalaires en "texture mapping" à un affichage classique requiert 2 "updates"... Je n'ai pas compris pourquoi. Vu que, d'une part, personne n'utilise cette fonction et que, d'autre part, il suffit de cliquer 2x pour obtenir un affichage correct, je pense que ça ne mérite pas que je passe des jours là dessus. ===== Portage Qt 5 ===== Un avantage d'utiliser VTK 6 est la compatibilité avec Qt 5 (nouvelle version de Qt disponible depuis 2012!). J'avais lu que le code Qt 4 est hautement compatible avec le code Qt 5 et qu'un manuel de portage existait. J'ai donc décidé de passer à Qt 5. La dernière version (5.7.x) n'est plus compilable avec Visual Studio 2012. J'ai donc compilé sur mon PC la dernière version compatible, c'est-à-dire la 5.6.2. C'est déjà un énorme saut en avant. Tout comme celle de VTK 6, la nouvelle architecture Qt 5 est beaucoup plus modulaire qu'auparavant. Il y a énormément de DLLs et un gros travail a été effectué pour permettre une sélection fine des composants utiles à l'application (ceci, j'imagine, pour rendre les exécutables les plus petits possible malgré l'augmentation des fonctionnalités en vue de l'utilisation sur GSM et tablettes). En pratique, le code actuel a compilé quasiment du premier coup. Voici les modifs: * ''TRUE'', ''FALSE'' n'existent plus et doivent être remplacés par ''true'' et ''false''. * ''qApp->argc()'', ''qApp->argv()'' n'existent plus. C'était la manière dont on récupérait les arguments de la ligne de commande pour instancier l'interpréteur python dans un thread secondaire quand l'interface graphique est active. La doc Qt suggère d'utiliser une fonction ''QCoreApplication::arguments()'' mais celle-ci retourne une ''QStringList'' pas très pratique à transmettre à l'API python. J'ai donc simplement réimplémenté ces 2 vieilles fonctions dans ''MetaforApplication''. * ''QAbstractItemModel::reset()'' n'existe plus (c'est utilisé dans l'affichage du ''PYTHONPATH'' sous forme d'arbre. J'ai juste retiré les appels et ça a l'air de marcher. Au niveau de CMake, l'utilisation de Qt 5 nécessite une version 2.8.11 minimum. La compilation Qt 4 est toujours possible. J'ai ajouté un test qui vérifie que la version Qt utilisée par VTK et la version Qt trouvée sont bien identiques. Le problème le plus ennuyeux que j'ai eu est lié à la présence de Qt 5 dans de nombreuses applications qui s'ajoutent dans le ''PATH'' système. Le risque de conflit est très grand et les symptômes sont assez bizarres (j'ai eu du mal à comprendre ce qui se passait alors que c'est pas la première fois qu'on a ce genre de problème. Voila ce que donne une recherche de ''Qt5Gui.dll'' sur mon PC... {{ :commit:2017:qt5dll_partout.png |}} On voit qu'on peut avoir des problèmes avec Matlab, MikTeX, CMake, etc. Certains logiciels (Antidote p expl) ont pris la peine de renommer leurs libs avant de s'imposer dans le ''PATH'' mais ce n'est pas le cas des 3 programmes cités ci-dessus. Les autres programmes ne se mettent pas dans le ''PATH'' et ne posent donc pas de problème en pratique. Comment voit-on qu'on a un conflit? On obtient ce genre d'erreur: {{ :commit:2017:mauvaise_dll_chargee.png |}} La solution est donc de s'assurer que les libs Qt5 fraîchement compilées et avec lesquelles on va linker Metafor sont devant les autres dans le ''PATH''. Voilà un exemple qui pose problème: {{ :commit:2017:cmake_avant_qt5.png |}} La solution est d'intervertir la place des libs Qt5 et de CMake. ''cmake.exe'' marchera toujours puisqu'il utilisera en priorité les ''.dll'' qui sont à côté de l'exécutable. D'une certaine manière on peut se réjouir de ces problèmes puisque cela signifie que Qt 5 est largement utilisé et qu'on ne risque donc pas de le voir disparaître du jour au lendemain. C'était donc un bon choix de bibliothèque graphique. --- //[[r.boman@ulg.ac.be|boman]] 2017/01/05 12:22//