Infoforall

BLENDER PYTHON - 07 - Collections et for nominatif

Nous allons voir aujourd'hui comment accéder à tous les éléments d'une collection sans avoir à passer par un index numérique.

Vous avez déjà vu les collections contenant :

Nous allons voir aujourd'hui comment lire et agir sur les objets de ces collections.

1 - Les collections : révisions

Les collections sont omniprésentes dans Blender et vous les avez déjà utilisé à de multiples occasions. Vous avez d'ailleurs déjà réalisé les premières questions.

1/5 Obtenir les collections et les listes de leurs clés ou contenus

01° Fermer puis ouvrir Blender. Se mettre en affichage SCRIPTING. Tapez les instructions suivantes dans la console Python de Blender (celle contenant les 3 chevrons >>>) :

>>> bpy.data.objects

>>> bpy.data.objects.items()

Vous devriez voir apparaitre ceci :

>>> bpy.data.objects

<bpy_collection[3], BlendDataObjects>


>>> bpy.data.objects.items()

[('Camera', bpy.data.objects['Camera']), ('Cube', bpy.data.objects['Cube']), ('Lamp', bpy.data.objects['Lamp'])]

On pourrait écrire la dernière ligne de cette façon pour rendre plus visible qu'on obtient une liste (délimitée par les crochets) contenant des éléments qui sont des tuples (délimités par des parenthèses) :

[

    ( 'Camera', bpy.data.objects['Camera'] ),

    ( 'Cube' , bpy.data.objects['Cube'] ),

    ( 'Lamp' , bpy.data.objects['Lamp'] )

]

Pourquoi obtient-on cela ? Réponse :

[

    ( 'Camera', bpy.data.objects['Camera'] )

    ( 'Cube' , bpy.data.objects['Cube'] )

    ( 'Lamp' , bpy.data.objects['Lamp'] )

]

Si vous voulez lire d'autres collections, il faut aller voir la documentation de Blender :

Les collections dispo Lien vers la documentation Blender 2.79 sur les différentes collections accessibles.

02° Créer un nouveau Material. Lire la colletion contenant les matériaux en tapant la bonne instruction dans la console. Si vous n'avez plus le souvenir de l'instruction, allez voir le lien ci-dessus.

...CORRECTION...

On peut obtenir le contenu de materials en tapant :

>>> bpy.data.materials.items()


On obtient par exemple :

[ ('Material', bpy.data.materials['Material']), ('Material.001', bpy.data.materials['Material.001']) ]


On voit que la collection contient deux tuples (ensembles de données delimités par les parenthèses) et qu'on pourrait représenter le contenu ainsi :

('Material', bpy.data.materials['Material']),

('Material.001', bpy.data.materials['Material.001'])

03° Créer des nouveaux objets 3D de type maillage, mesh en anglais (cube, sphère...). Lire la colletion contenant uniquement les objets à maillage (et pas la caméra et l'éclairage par exemple). Allez voir le lien ci-dessus.

...CORRECTION...

On peut obtenir le contenu des maillages (meshes) en tapant :

>>> bpy.data.meshes.items()


On obtient par exemple :

[ ('Circle', bpy.data.meshes['Circle']), ('Cube', bpy.data.meshes['Cube']), ('Cube.001', bpy.data.meshes['Cube.001']), ('Icosphere', bpy.data.meshes['Icosphere']), ('Sphere', bpy.data.meshes['Sphere']) ]


On voit que la collection contient deux tuples (ensembles de données delimités par les parenthèses) et qu'on pourrait représenter le contenu ainsi :

('Circle', bpy.data.meshes['Circle']),

('Cube', bpy.data.meshes['Cube']),

('Cube.001', bpy.data.meshes['Cube.001']),

('Icosphere', bpy.data.meshes['Icosphere']),

('Sphere', bpy.data.meshes['Sphere'])

On peut donc récupérer des collections de beaucoup de choses avec bpy.data.quelquechose:

bpy.data.objects pour la collection des objets 3D (tous)
bpy.data.meshes pour la collection des maillages (constitués de vertex)
bpy.data.texts pour la collection des objets 3D de type texte uniquement
bpy.data.materials pour la collection des materials disponibles

Il y en a bien d'autres. Allez voir la documentation.

Différence entre la collection OBJECTS et la collection MESHES

Attention, on pourrait croire que les éléments de MESHES sont contenus dans OBJECTS. Mais c'est plus compliqué : les objets 3D maillés sont dans OBJECTS avec comme clé le nom de l'objet. Le maillage de l'objet est lui dans MESHES avec comme clé le nom du maillage. Dans la plupart des cas, le nom du maillage correspond au nom de l'objet. Mais pas toujours. Exemple : objet et maillage

Nous avons vu qu'appliquer la méthode items permet d'obtenir l'association key-contenu.

Si vous ne voulez uniquement voir les clés, vous pouvez utiliser la méthode keys qui renvoie la liste (crochets) des clés présentes dans la collection. Ainsi avec les objets que j'avais crée juste avant :

>>> bpy.data.meshes.keys()

['Circle', 'Cube', 'Cube.001', 'Icosphere', 'Sphere']


>>> bpy.data.objects.keys()

['Camera', 'Circle', 'Cube', 'Cube.001', 'Icosphere', 'Lamp', 'Sphere']

Si vous ne voulez voir que les références des contenus, vous pouvez utiliser la méthode values. Cette méthode va vous renvoyer la liste des références utilisables. Nous verrons comme les utiliser un peu plus bas.

>>> bpy.data.meshes.values()

[bpy.data.meshes['Circle'], bpy.data.meshes['Cube'], bpy.data.meshes['Cube.001'], bpy.data.meshes['Icosphere'], bpy.data.meshes['Sphere']]

2/5 Obtenir l'un des éléments à partir de la clé

Rien de nouveau : on peut récupérer l'un des contenus en fournissant entre crochets la clé sous forme de string :

>>> refSphere = bpy.data.objects['Icosphere']

>>> refSphere.hide = True

>>> refSphere.hide = False

04° Après avoir créer une Icosphere, testez le code pour vérifier qu'on parvient bien à récupérer sa référence pour la rendre invisible puis visible.

3/5 Tester la présence d'une clé

A quoi ça sert ? A éviter les scripts qui déclarent des erreurs ! Si vous tentez de récupérer un objet inexistant, le script va s'arrêter.

Rien de nouveau non plus : pour tester la présence d'une clé, on doit faire ceci par exemple :

>>> if ( not('Nom_de_la_forme_3D' in bpy.data.objects ) ) :

Si la collection bpy.data.objects contient une clé nommée 'Nom_de_la_forme_3D', le test du in va renvoyer True et la présence du not va inverse la réponse qui devient False. On ne va donc effectuer les actions sous le if que si l'objet n'existe pas. Nous avions utilisé ceci pour créer un objet ou un material si la référence n'était pas encore créé.

05° Créer au moins deux cubes dans votre interface. Le premier devrait se nommer Cube et le deuxième Cube.001. Lancer les instructions suivantes :

>>> bpy.data.objects.keys()

['Camera', 'Circle', 'Cube', 'Cube.001', 'Icosphere', 'Lamp', 'Sphere']


>>> 'Cube' in bpy.data.objects

>>> refCube = bpy.data.meshes['Cube']

True


>>> bpy.data.objects.remove( refCube, True)

>>> 'Cube' in bpy.data.objects

False

06° Blender cherche-t-il un objet ayant exactement la bonne clé ou cherche-t-il un objet dont la clé contient le string qu'on lui demande de tester ?

...CORRECTION...

Avec ce test, on vérifie qu'une clé correspond EXACTEMENT au string fourni. Ainsi la clé Cube.001 renvoie False alors que Cube est bien dans le nom. En faisant une confiance aveugle à ce test, on pourrait docn croire qu'il n'y a pas d'autres cubes...

4/5 Lecture d'un élément de collection à l'aide de son index

Nous avons vu qu'on peut accéder à un élément si on connait sa clé. Exemple :

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

Mais on peut aussi accéder à l'un des éléments d'une collection si on connait son index :

>>> refObjet = bpy.data.objects[0]

>>> refObjet.name


>>> refObjet = bpy.data.objects[1]

>>> refObjet.name

07° Utiliser les lignes ci-dessus pour découvrir les objets qui se cachent derrière les index 0 et 1.

08° Que va faire la ligne suivante à votre avis ?

>>> refObjet = bpy.data.objects[200]

>>> refObjet.name

...CORRECTION...

Une erreur. Pourquoi ? On veut accéder à un élément sur un index qui n'existe pas. Sur mon fichier Blend, j'obtiens ceci par exemple :

IndexError: bpy_prop_collection[index]: index 200 out of range, size 6

Les index possibles sont donc 0 - 1 - 2 - 3 - 4 - 5.

Comment éviter ce type d'erreur ? En connaissant la taille de votre collection ! Si vous savez qu'elle contient 25 éléments, vous saurez qu'on peut chercher les éléments d'index 0 à 24.

Il suffit d'utiliser la fonction len que nous avions déjà utilisé avec les strings :

>>> len(bpy.data.objects)

6

Ici, la collection possède donc ici 6 éléments. Les index utilisables sont donc les nombres de 0 à 5.

5/5 Lecture des éléments d'une collection avec un FOR numérique

Une collection est une structure de donnée séquentielle : on peut la lire en utilisant une boucle FOR numérique.

# Importation du module Blender Python

import bpy


collecObjets = bpy.data.objects


for numeroIndex in range( len(collecObjets) ) :

    print( collecObjets[ numeroIndex ] )

09° Utilisez ce script en ouvrant la console System de Blender. Observez les éléments obtenus. Parvient-on bien à obtenir toutes les références des objets 3D ?

2 - La boucle FOR nominative

Le for numérique est donc capable d'énumérer un à un les éléments d'une collection. Mais c'est lourd.

Il existe un moyen bien plus facile de faire cela : for nominatif.

# Importation du module Blender Python

import bpy


collecObjets = bpy.data.objects


for refObj in collecObjets :

    print( refObj )

10° Utilisez ce script en ouvrant la console System de Blender. Observez les éléments obtenus. Parvient-on bien à obtenir toutes les références des objets 3D ?

Comme vous le voyez, c'est beaucoup plus rapide qu'avec une boucle for numérique.

11° Modifier le script pour afficher plutôt le nom (name) de l'objet pointé par la variable refObj.

...CORRECTION...

# Importation du module Blender Python

import bpy


collecObjets = bpy.data.objects


for refObj in collecObjets :

    print( refObj.name )

Qu'allons-nous pouvoir faire de cela ? Et bien, cela permet d'avoir accès à tous les éléments de la scène. Pour l'instant, nous avions réussi à récupérer les références d'un objet 3D :

A titre d'exemple, voici un code qui va nous permettre de faire disparaitre les objets maillés présents dans la liste des meshes. Il ne restera donc que les lampes, les caméras ...

# Importation du module Blender Python

import bpy


# Destruction des anciens Meshes

collecMeshes = bpy.data.meshes

for refObj in collecMeshes :

    collecMeshes.remove( refObj, True)

12° Tester ce script après avoir créé quelques meshes. Vous devriez constater que l'exécution n'est pas immédiate : il faut revenir sur la vue 3D et l'interface met un certain temps avant de faire disparaitre les objets : nous venons de faire disparaitre les maillages, pas les objets. Lors de vérification périodique, Blender se rend compte alors qu'il possède des objets maillés n'ayant pas de maillages. Il les supprime alors à ce moment là.

On peut aussi agir sur la liste des objets et vérifier un à un lesquels sont des objets maillés.

13° Sur quelle collection agit le script ci-dessous ? Comment parvient-il à ne supprimer que les objets liés à un maillage ?

# Importation du module Blender Python

import bpy


# Destruction des objet liés à un maillage

collecObjets = bpy.data.objects

for refObj in collecObjets :

    if refObj.type == 'MESH' :

        collecObjets.remove( refObj, True)

3 - Mini-projet

Vous allez maintenant réaliser deux scripts.

Le premier script pourra :

  1. faire disparaitre tous les éléments 3D meshes.
  2. créer un ensembe de 10 sphères ayant une position aléatoire
  3. créer un ensemble de 10 cubes ayant une position aléatoire

14° Compléter le script ci-dessous pour qu'il réponde parfaitement aux 3 attentes.

# Importation des modules Blender Python et random

import bpy

import random


# Destruction des objet liés à un maillage

collecObjets = bpy.data.objects

for refObj in collecObjets :

    if refObj.type == 'MESH' :

        collecObjets.remove( refObj, True)


# Création des 10 sphères

for i in range(10):

    x = random.randint(-10,10)

    y = random.randint(-10,10)

    z = random.randint(-10,10)

    bpy.ops.mesh.primitive_uv_sphere_add(location=(x,y,z))


...CORRECTION...

# Création des 10 cubes

for i in range(10):

    x = random.randint(-10,10)

    y = random.randint(-10,10)

    z = random.randint(-10,10)

    bpy.ops.mesh.primitive_cube_add(location=(x,y,z))


Le deuxième script devra ensuite déplacer les sphères de 0.5 vers le haut et les cubes de 0.5 vers le bas. C'est maintenant que vous allez pouvoir voir la puissance de l'accès aux collections sans avoir eu besoin de stocker les références ou de connaitre exactement les noms des objets.

Pour cela, nous allons avoir besoin de savoir si les objets maillés sont des cubes ou des sphères. Or, l'information sur la forme n'apparait pas directement dans le maillage.

Dans notre cas particulier, nous pouvons néanmoins le savoir de deux façons :

  1. On pourrait compter les vertex des maillages : il y en a beaucoup plus dans les sphères.
  2. On pourrait chercher la présence de Cube ou Sphere dans le nom de l'objet.

Nous choississons ici le deuxième méthode.

Cela donnera un test du type : ( la variable refObjet contenant la référence d'un des objets 3D)

if 'Cube' in refObjet.name :

15° Compléter largement le script ci-dessous pour qu'il déplace correctement les sphères vers le haut et les cubes vers le bas à chaque fois qu'on lance le script.

# Importation des modules Blender Python et random

import bpy


# Recherche de la référence Python permettant l'axxès à la collection des objets

collecObjets = bpy.data.objects


# Lecture un à un des objets contenus dans la collection des objets 3D

for refObjet in collecObjets :


    # Recherche des objets liés à un cube

    if 'Cube' in refObjet.name :

        pass


    # Recherche des objets liés à une sphere

    if 'Sphere' in refObjet.name :

        pass

Pour rappel, voici un extrait des informations qu'on peut trouver dans l'onglet Recap. de Blender sur ce site.

Récupérer les données d'un objet monObjet

Récupérer le TUPLE des coordonnées : mesInfos = monObjet.location

  • Récupérer la coordonnée x : xObjet = monObjet.location[0]
  • Récupérer la coordonnée y : yObjet = monObjet.location[1]
  • Récupérer la coordonnée z : zObjet = monObjet.location[2]

Récupérer le TUPLE des échelles : mesInfos = monObjet.scale

Récupérer le TUPLE des rotations : mesInfos = monObjet.rotation_euler

Récupérer le TUPLE des dimensions : mesInfos = monObjet.dimensions

Savoir si l'objet est visible : mesInfos = monObjet.hide

Récupérer le type de l'objet : mesInfos = monObjet.type qui peut renvoyer : MESH, CAMERA, LAMP ...

Récupérer le nom de l'objet dans l'interface: mesInfos = monObjet.name


Modifier les données d'un objet monObjet

Modifier le TUPLE des coordonnées : monObjet.location = (1,2,3)

Modifier le TUPLE des échelles : monObjet.scale = (2,3,4)

Modifier le TUPLE des rotations : monObjet.rotation_euler = (2,3,4)

Récupérer le TUPLE des dimensions : monObjet.dimensions = (2,3,4)

Rendre un objet non visible : monObjet.hide = True

Changer le nom de l'objet dans l'interface: monObjet.name = "nouveauNom"

Comme à chaque fois, n'allez pas voir la correction possible immédiatement. N'allez voir que si vous bloquez réellement pendant plusieurs looooooongues minutes.

...CORRECTION...

# Importation des modules Blender Python et random

import bpy


# Recherche de la référence Python permettant l'axxès à la collection des objets

collecObjets = bpy.data.objects


# Recherche des objets liés à un cube

for refObjet in collecObjets :


    # Recherche des objets liés à un cube

    if 'Cube' in refObjet.name :

        x, y, z = refObjet.location

        refObjet.location = ( x, y, z+0.5 )


    # Recherche des objets liés à une sphere

    if 'Sphere' in refObjet.name :

        x, y, z = refObjet.location

        refObjet.location = ( x, y, z-0.5 )

Conclusion : vous savez maintenant agir sur les objets qu'un utilisateur peut avoir créé à la main : il suffit d'aller les chercher dans la collection et de les étudier un à un. Mais nous allons voir qu'on peut encore faire mieux : on peut stocker les références qui nous intéressent plutôt que de les filtrer à chose fois. C'est l'objet de la prochaine activité qui traite des listes.

4 - FAQ

Question : comment connaitre le nom du mesh lié à un objet ?

C'est assez simple : on doit aller chercher l'attribut data de l'objet. Si l'objet de base est de type MESH, son data correspond justement à ce maillage. Pour obtenir le nom du maillage, il suffit donc de lire l'attribut name de ce data. Illustration :

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


>>> type(monObjet)

<class 'bpy_types.Object'>


>>> monObjet.name

'Cube'


>>> monObjet.type

'MESH'


>>> leMaillage = monObjet.data


>>> type(leMaillage)

<class 'bpy_types.Mesh'>


>>> leMaillage.name

'Cube.001'