Infoforall

BLENDER PYTHON - 08 - Les listes

Nous allons voir aujourd'hui comment créer des zones mémoires capables de stocker des références sans avoir besoin de créer autant de variables que de choses à contenir.

1 - Description des listes

Qu'est qu'une liste ? C'est une structure de données :

Les éléments d'une liste sont séparés par des virgules et la déclaration de la liste commence avec [ et s'arrête avec ].

ma_liste = [1,'a',45.2,"bonjour",'b']

Cela crée une liste de 5 éléments contenant l'integer 1, le caractère 'a', le float 45.2, le string "bonjour" et le char "b".

Ouvrez la console Python et tapez les instructions suivantes pour voir comment cela fonctionne :

Si on veut afficher une liste, print(ma_liste) fonctionne :

>>> ma_liste = [ 1, 'a', 45.2, "bonjour", 'b' ]

>>> print(ma_liste)

[1,'a',45.2,'bonjour','b"]

Si on veut connaitre le nombre d'éléments dans une liste, len(ma_liste) fonctionne :

>>> ma_liste = [ 1, 'a', 45.2, "bonjour", 'b' ]

>>> len(ma_liste)

5

Par contre, attention : le premier élément a un index de 0 et le dernier un index de 4.

Si on veut accéder à l'un des éléments d'une liste (le 3e dans l'exemple : 45.2), on cherchera l'index 2 et on tapera ma_liste[2]. Pourquoi ? Simplement car le premier élément 1 est l'élément 0.

Si on demande print(ma_liste[2]) avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient :

>>> ma_liste = [ 1,'a',45.2,"bonjour",'b' ]

>>> print(ma_liste[2])

45.2

Si on veut accéder à un ensemble d'éléments d'une liste, on tapera ma_liste[1:4], et on aura les éléments 1,2 et 3 (car on a une commande du type i<4).

Si on demande print(ma_liste[1:4]) avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient :

>>> ma_liste = [ 1,'a',45.2,"bonjour",'b' ]

>>> print(ma_liste[1:4])

['a',45.2,'bonjour' ]

2 - Liste et boucle FOR

Pour parcourir une liste, il suffit d'utiliser un for numérique ou nominatif :

for truc in ma_liste :

    print(truc)

for numero in range(len(ma_liste)) :

    print(ma_liste[numero])

Avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient alors dans les deux cas :

1

a

45.2

bonjour

b

Avant de continuer, regardez le petit encart ci-dessous.

Liste, Dictionnaire et Collection

Il existe une différence fondamentale entre les listes et les collections : une liste et une collection peuvent être lue via un index numéroté. Par contre, on ne peut pas trouver un élément d'une liste à l'aide d'une clé, contrairement aux collections.

La liste est donc une structure de données plus simple qu'une collection : on y accède uniquement en utilisant l'index des éléments.

>>> maListe = ['a','b','c']


>>> type(maListe)

<class 'list'>


>>> maListe[0]

'a'


>>> maListe['a']

Traceback (most recent call last):

File "<blender_console>", line 1, in <module>

TypeError: list indices must be integers or slices, not str

Il existe une autre strucure de donnée qui possède des clés de lecture mais pas d'index chiffré : les dictionnaires. Nous ne le aborderons pas dans cette activité. Contrairement aux listes, on les crée en utilisant des accolades { }.

Voici un court exemple pour comprendre la différence avec les listes :

>>> monDictionnaire = { 'arbres':20, 'carottes':400}


>>> type(monDictionnaire)

<class 'dict'>


>>> monDictionnaire['arbres']

20


>>> monDictionnaire[0]

Traceback (most recent call last):

File "<blender_console>", line 1, in <module>

KeyError: 0

Les collections de Blender sont en réalité un mélange des listes et des dictionnaires : on peut accéder aux éléments à la fois par l'index numéroté et par la clé.

>>> maCollec = bpy.data.objects


>>> type(maCollec)

<class 'bpy_prop_collection'>


>>> maCollec[0]

bpy.data.objects['Camera']


>>> maCollec['Cube']

bpy.data.objects['Cube']

3 - Liste et Blender

Je voudrais maintenant rentrer des couleurs dans une liste de façon à pouvoir changer à l'envie les couleurs de mes objets 3D.

Par exemple :

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]

Ainsi le premier objet d'index 0 devra être 'red', le second d'index 1 devra être de couleur 'blue' ...

Pour cela, nous désirons qu'un script aille chercher les références des 18 premiers objets.

Le script devra vérifier d'abord la présence de 3 materials nommés 'red', 'blue' et 'yellow'. Si l'un d'entre eux n'existe pas, le script le crée.

# Importation du module Blender Python

import bpy


# Récupération de la collection des matériaux et objets

collecMaterials = bpy.data.materials

collecObjects = bpy.data.objects


# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


# Vérification de la présence d'un matérial nommé 'blue' et création si besoin

if not('blue' in collecMaterials) :

    bpy.data.materials.new( 'blue' )


# Vérification de la présence d'un matérial nommé 'yellow' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'yellow' )


# Initialisation des couleurs des matériaux

bpy.data.materials['blue'].diffuse_color = (0.1, 0.1, 0.8)

bpy.data.materials['yellow'].diffuse_color = (0.8, 0.8, 0.1)

01° Lancer le script. Vous devriez pouvoir constater l'apparation de deux nouveaux matériaux dans le menu en cliquant au bon endroit :

Menu materials

02° Compléter le script pour gérer également le material 'red'.

Il est temps de passer à la réalisation de notre script. Commençons par la mise en scène.

03° Créer un ensemble de 10 cubes ou sphères en les plaçant les unes à côté des autres dans l'ordre de création.

Les 10 objets

04° Compléter maintenant le script pour qu'il affecte les matériaux dans l'ordre des objets stockés dans la collection.

# Importation du module Blender Python

import bpy


# Récupération de la collection des matériaux et objets

collecMaterials = bpy.data.materials

collecObjects = bpy.data.objects


# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


# Vérification de la présence d'un matérial nommé 'blue' et création si besoin

if not('blue' in collecMaterials) :

    bpy.data.materials.new( 'blue' )


# Vérification de la présence d'un matérial nommé 'yellow' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'yellow' )


# Vérification de la présence d'un matérial nommé 'red' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'red' )


# Initialisation des couleurs des matériaux

bpy.data.materials['blue'].diffuse_color = (0.1, 0.1, 0.8)

bpy.data.materials['yellow'].diffuse_color = (0.8, 0.8, 0.1)

bpy.data.materials['red'].diffuse_color = (0.8, 0.1, 0.1)


# Affectation des materials sur les 10 premiers objets

for index in range(10) :

    pass

Pour rappel, vous pouvez trouver un récapitulatif des choses vues dans les activités dans l'onglet Recap. de Blender sur ce site.

Vous devriez obtenir ceci : un truc qui marche presque mais pas jusqu'au bout. Nous verrons pourquoi juste après.

Les 10 objets colorés

Voici une correction possible. La structure de la correction tente d'être claire, pas forçement optimale ou courte.

...CORRECTION...

# Affectation des materials sur les 10 premiers objets

for index in range(10) :

    objet3D = collecObjects[index]

    nomMaterial = liste_des_couleurs[index]

    refMaterial = collecMaterials[nomMaterial]

    objet3D.active_material = refMaterial

Alors, pourquoi est-ce que cela ne fonctionne pas ? En réalité, c'est simple : on demande au script d'agir sur les objets 3D, pas uniquement sur les objets basés sur un mesh !

05° Tapez ceci dans la console Python pour comprendre de quoi je parle. Si vous avez déjà géré le problème dans votre script, bravo.

>>> maCollec = bpy.data.objects

>>> maCollec.keys()

['Camera', 'Cube', 'Cube.001', 'Cube.002', 'Cube.003', 'Cube.004', 'Cube.005', 'Cube.006', 'Cube.007', 'Cube.008', 'Cube.009', 'Lamp']

Et voici donc pourquoi le 10e cube n'a pas de couleurs : l'index 0 pointe la caméra et pas un cube. Il faudrait donc rajouter un test pour ne prendre que les objets basés sur un mesh.

Voyons comment remplir une liste à partir des données qu'on veut y placer. C'est l'objet de la partie suivante.

4 - Créer des listes en rajoutant peu à peu des éléments

Nous allons maintenant faire mieux : nous allons stocker les références de nos objets 3D dans une liste. Cela nous permettra de modifier la couleur des objets si leurs références sont contenus dans une liste : en ne plaçant dans cette liste que des objets de type MESH nous seront certain d'avoir 10 objets colorés.

Néanmoins, pour faire cela, nous avons besoin d'en savoir un peu plus sur les listes : comment rajouter des éléments à la volée et comment détruire le contenu par exemple.

Une fiche regroupant les notions importantes sur les Listes est disponible dans la partie FICHES du site. En voici un extrait :

Rajouter des éléments dans une liste

L'une des possibilités est d'utiliser la méthode append qui rajoute des éléments en fin de liste.

>>> a = [1,2,3]

>>> a

[1, 2, 3]

>>> a.append('4')

>>> a

[1, 2, 3, '4']

>>> b = [7,8,9]

>>> a.append(b)

>>> a

[1, 2, 3, '4', [7, 8, 9]]

Cette méthode append permet donc de considérer les listes comme des piles de livres : on rajoute les éléments au dessus des derniers éléments.

append() rajoute au dessus de la pile

Pas d'affectation ! : on remarque que a est modifiée par la méthode, sans présence du signe =.

Il ne faut pas noter quelque chose comme

a = a.append(b)

Sinon, cela veut dire que vous voulez stocker le résultat de la méthode. Méthode qui ne revoit rien. Vous obtenez alors une variable a ne contenant plus rien !

Attention également : avec append, on rajoute l'élément b, on ne rajoute pas les éléments de b si x est itérable. Ici, on a bien rajouté une liste b dans la liste a et pas les élements de b dans a.

Dans l'exemple, lorsqu'on rajoute la liste [7,8,9], on obtient ainsi :

[ 1, 2, 3, '4', [7, 8, 9] ]

et pas

[ 1, 2, 3, '4', 7, 8, 9 ]

La fiche complète est disponible si vous en avez besoin. Sinon, Internet permet de se documenter sans aucune difficulté.

06° Tapez ceci dans la console Python pour comprendre comment mémoriser les différents mesh dans une liste. La première ligne vous montre comment créer une liste initialement vide.

>>> mesCubes = []

>>> mesCubes

[]


>>> monPremierCube = bpy.data.objects['Cube']

>>> mesCubes.append(monPremierCube)

>>> mesCubes

[bpy.data.objects['Cube']]

Pour l'instant, nous n'avons pas qu'une seule référence stockée dans notre liste. Et encore ... Nous avons dû donner le nom de l'objet. Nous allons pouvoir passer aux choses sérieuses. Il faudra :

  1. Récupérer dans une variable la collection Objects
  2. Créer une liste vide nommée mesObjets
  3. Lire un à un les objets de la collection :
    • S'il s'agit d'un MESH, rajouter cette référence à la liste
    • Si la liste contient 10 éléments, sortir avec un break de la boucle de lecture
  4. Appliquer le programme précédent (qui applique les couleurs) aux objets dont les références sont dans la liste

07° Créer le programme qui permettra de réaliser ce qui est indiqué juste au dessus.

Les 10 objets colorés

...CORRECTION...

# Importation du module Blender Python

import bpy


# Récupération de la collection des matériaux et objets

collecMaterials = bpy.data.materials

collecObjects = bpy.data.objects


# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


# Vérification de la présence d'un matérial nommé 'blue' et création si besoin

if not('blue' in collecMaterials) :

    bpy.data.materials.new( 'blue' )


# Vérification de la présence d'un matérial nommé 'yellow' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'yellow' )


# Vérification de la présence d'un matérial nommé 'red' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'red' )


# Initialisation des couleurs des matériaux

bpy.data.materials['blue'].diffuse_color = (0.1, 0.1, 0.8)

bpy.data.materials['yellow'].diffuse_color = (0.8, 0.8, 0.1)

bpy.data.materials['red'].diffuse_color = (0.8, 0.1, 0.1)


# Création de la liste mesObjets

mesObjets = []

for objet in collecObjects :

    if objet.type == 'MESH' :

        mesObjets.append(objet)

    if len(mesObjets) == 10 :

        break


# Affectation des materials sur les 10 premiers objets

for index in range(len(mesObjets)) :

    objet3D = mesObjets[index]

    nomMaterial = liste_des_couleurs[index]

    refMaterial = collecMaterials[nomMaterial]

    objet3D.active_material = refMaterial

08° Dernière modification : on ne désire travailler qu'avec la liste suivante. Débrouillez vous pour faire tourner le script malgré tout. Vous avez carte blanche.

# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow' ]

Il y a tellement de façons de faire que vous allez bien en trouver une.

...CORRECTION...

    nomMaterial = liste_des_couleurs[ index%3 ]

5 - pop

On ne parlera par de musique ici mais de l'inverse de la méthode append.

Avec la méthode pop, on parvient à extraire le dernier élément de la liste. On le fait donc disparaitre de la liste et on peut le récupérer dans une variable. Exemple :

pop

09° Tester les lignes suivantes pour visualiser le fonctionnement de la méthode pop.

Nous avons donc une liste qui contient les éléments suivants :

IndexElément
5'4'
43
3[7,8,9]
22
11
0'0'

>>> maListe = [ '0', 1, 2, [7,8,9], 3, '4' ]

>>> recuperation = maListe.pop()

>>> recuperation

'4'


>>> maListe

[ '0', 1, 2, [7,8,9], 3]


>>> recuperation = maListe.pop()

>>> recuperation

3


>>> maListe

[ '0', 1, 2, [7,8,9]]

10° Tester les lignes suivantes où on utilise pop(0). Quelle est la différence avec un simple pop() ?

>>> maListe = [ '0', 1, 2, [7,8,9], 3, '4' ]

>>> recuperation = maListe.pop(0)

>>> recuperation

'0'


>>> maListe

[ 1, 2, [7,8,9], 3, '4']


>>> recuperation = maListe.pop()

>>> recuperation

1


>>> maListe

[ 2, [7,8,9], 3, '4']

...CORRECTION...

Avec pop(), on extrait le dernier élément de la liste.

Avec pop(0), on extrait le premier élément de la liste (celui d'index 0).

11° Modifier le code : juste avant de commencer la boucle sur les objets, si la materiel du premier objet est déja 'red', on doit :

  1. Extraire ce 'red' de la liste des couleurs avec pop(0)
  2. Le rajouter en fin de liste des couleurs avec append()

...CORRECTION...

# Importation du module Blender Python

import bpy


# Récupération de la collection des matériaux et objets

collecMaterials = bpy.data.materials

collecObjects = bpy.data.objects


# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow' ]


# Vérification de la présence d'un matérial nommé 'blue' et création si besoin

if not('blue' in collecMaterials) :

    bpy.data.materials.new( 'blue' )


# Vérification de la présence d'un matérial nommé 'yellow' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'yellow' )


# Vérification de la présence d'un matérial nommé 'red' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'red' )


# Initialisation des couleurs des matériaux

bpy.data.materials['blue'].diffuse_color = (0.1, 0.1, 0.8)

bpy.data.materials['yellow'].diffuse_color = (0.8, 0.8, 0.1)

bpy.data.materials['red'].diffuse_color = (0.8, 0.1, 0.1)


# Création de la liste mesObjets

mesObjets = []

for objet in collecObjects :

    if objet.type == 'MESH' :

        mesObjets.append(objet)

    if len(mesObjets) == 10 :

        break


# Modification éventuelle de la liste des couleurs

if mesObjets[0].active_material == bpy.data.materials['red'] :

    rouge = liste_des_couleurs.pop(0)

    liste_des_couleurs.append(rouge)


# Affectation des materials sur les 10 premiers objets

for index in range(len(mesObjets)) :

    objet3D = mesObjets[index]

    nomMaterial = liste_des_couleurs[index%3]

    refMaterial = collecMaterials[nomMaterial]

    objet3D.active_material = refMaterial

Soyons honnête : utiliser cette technique pop-append sur cet exercice, n'est pas le plus adapté. C'était surtout pour vous faire manipuler les éléments de la liste et vous permettre de vous rendre compte qu'on parvient facilement à extraire et injecter des éléments dans une liste.

12° Tester ce dernier code qui vous permettra de modifier les couleurs au fur et à mesure qu'on active le script.

# Importation du module Blender Python

import bpy


# Récupération de la collection des matériaux et objets

collecMaterials = bpy.data.materials

collecObjects = bpy.data.objects


# Création de la liste contenant les materials à appliquer aux objets

liste_des_couleurs = [ 'red', "blue", 'yellow' ]


# Vérification de la présence d'un matérial nommé 'blue' et création si besoin

if not('blue' in collecMaterials) :

    bpy.data.materials.new( 'blue' )


# Vérification de la présence d'un matérial nommé 'yellow' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'yellow' )


# Vérification de la présence d'un matérial nommé 'red' et création si besoin

if not('yellow' in collecMaterials) :

    bpy.data.materials.new( 'red' )


# Initialisation des couleurs des matériaux

bpy.data.materials['blue'].diffuse_color = (0.1, 0.1, 0.8)

bpy.data.materials['yellow'].diffuse_color = (0.8, 0.8, 0.1)

bpy.data.materials['red'].diffuse_color = (0.8, 0.1, 0.1)


# Création de la liste mesObjets

mesObjets = []

for objet in collecObjects :

    if objet.type == 'MESH' :

        mesObjets.append(objet)

    if len(mesObjets) == 10 :

        break


# Modification éventuelle de la liste des couleurs

if mesObjets[0].active_material == bpy.data.materials['red'] :

    liste_des_couleurs = [ 'blue', 'yellow', 'red' ]

elif mesObjets[0].active_material == bpy.data.materials['blue'] :

    liste_des_couleurs = [ 'yellow', 'red', 'blue' ]


# Affectation des materials sur les 10 premiers objets

for index in range(len(mesObjets)) :

    objet3D = mesObjets[index]

    nomMaterial = liste_des_couleurs[index%3]

    refMaterial = collecMaterials[nomMaterial]

    objet3D.active_material = refMaterial

6 - copy

Avant d'en finir avec cette activité, il est temps de parler d'un des problèmes récurrents que rencontre les gens avec les listes : la copie d'une liste.

Pour l'instant, vous avez beaucoup travaillé avec des variables qu'on pourrait nommer "simples", c'est à dire des variables qui font référence à un contenu simple : un entier, un réel ou un string.

Voyons si vous savez répondre à cela : que donne b si on tape ceci dans les deux cas suivants ?

Cas 1 : avec des entiers

>>> a = 3

>>> b = a

>>> a = a+1

>>> a

4


>>> b

Cas 2 : avec des listes

>>> a = [1,2,3]

>>> b = a

>>> a.append(4)

>>> a

[1,2,3,4]


>>> b

13° Tentez de deviner ce que va afficher la console maintenant qu'on lui demande de fournir le contenu de b.

...CORRECTION...

Dans le premier cas, b n'est pas modifié par la modification sur a.


Ce n'est pas le cas avec la liste : le contenu de b est bien identique au contenu de a alors que a a été modifié après l'affectation b = a.

On aurait pu croire que b fasse encore référence à [1,2,3].

Comment est-ce possible ?

En réalité, c'est aussi simple à comprendre : dans le cas de la liste, a et b sont simplement deux alias qui pointent vers le même contenu mémoire. Disons l'adresse mémoire 145 par l'exemple.

Ainsi lorsque vous tapez a.append(4) vous dites en réalité : va à l'adresse mémoire 145 et rajoute un nouvel élément (4) à la liste que tu y trouves.

Lorsqu'on demande ensuite d'afficher le contenu de b, on demande donc d'afficher le contenu de ce qu'il y a à l'adresse 145. A savoir [1,2,3,4].

Attetion donc à ce point de détail : lorsqu'on utilise b = a, on ne crée pas une nouvelle liste. On crée simplement un nouvel alias permettant de voir le contenu de la liste.

référence mémoire

Comment fait-on alors pour obtenir une vraie copie de façon à pouvoir modifier l'une des listes sans pour autant modifier l'autre ?

14° Que constatez-vous de nouveau lorsqu'on utilise l'exemple suivant avec b = a.copy() ?

Cas 3 : avec des listes et la méthode copy

>>> a = [1,2,3]

>>> b = a.copy()

>>> a.append(4)

>>> a

[1,2,3,4]


>>> b

Cette fois, ça fonctionne mieux : b n'est plus une référence vers la même zone mémoire que a. Il s'agit bien d'une copie du contenu. Si on modifie a, on ne modifie plus b.

En fonction des besoins, il faudra donc utiliser l'affectation (avec =) pour copier la référence mémoire ou la méthode copy pour ne copier que le contenu à un instant t.

Voilà. C'est tout pour cette activité. N'oubliez pas d'aller consulter la fiche LISTE si vous avez des problèmes liés aux listes.

L'activité suivante vous permettra de voir une autre grande notion de programmation : les fonctions. Vous deviendrez alors capable de commencer à réaliser des animations très complexes en peu de lignes de codes.