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 :
bpy.data.objects
) etbpy.data.materials
).Nous allons voir aujourd'hui comment lire et agir sur les objets de ces collections.
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.
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 :
bpy.data.objects
renvoie la collection des objets 3D présents dans l'interface Blender. La console n'affiche malheureusement pas le contenu de cette structure de données.bpy.data.objects.items()
permet d'utiliser la méthode items sur cette collection. On obtient alors la liste [...]
des éléments contenus : des tuples (..) contenant la clé permettant d'accéder à chaque item et l'item correspond :[
( '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.
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 :
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']]
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.
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...
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.
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 ?
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 :
objet3DSelectionne = bpy.context.object
monObjet = bpy.data.objects['Cylinder.001']
monObjet = bpy.data.objects[2]
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)
Vous allez maintenant réaliser deux scripts.
Le premier script pourra :
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 :
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
xObjet = monObjet.location[0]
yObjet = monObjet.location[1]
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.
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'