Metafor

ULiege - Aerospace & Mechanical Engineering

User Tools

Site Tools


commit:2017:01_06

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* adouble *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…

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:

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:

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.

boman 2017/01/05 12:22

commit/2017/01_06.txt · Last modified: 2017/01/06 11:45 by boman