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.
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,
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…
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:
#ifdef
/ #endif
. Ces directives pourraient être évitées par l'utilisation de connexions propres entre ports de filtres.Update()
ajoutés allaient mettre à jour parfois inutilement le pipeline (et donc consommer inutilement du CPU). Je n'ai pas vérifié.
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.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:
SAVE_IV
, SAVE_OBJ
, SAVE_OOGL
, SAVE_RIB
) qu'on utilise jamais (et qui ne marchaient plus, même dans la version officielle).
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:
double* a
⇒ double *a
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
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):
WearContactTool
enlève définitivement les couleurs du champ scalaire (le maillage devient blanc).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.
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