Infoforall

BLENDER PYTHON - 04 - Gestion des objets 3D via Python

Nous allons voir aujourd'hui

  • comment créer des objets 3D (autres que des cubes)
  • comment les déplacer sur la vue 3D (comme avec translate)
  • comment modifier la taille (comme avec scale
  • comment les faire tourner (comme avec rotate).
  • comment gérer les matériaux qu'on applique sur les objets

Nous allons du coup devoir voir comment stocker les références de ces objets et comment utiliser les méthodes qui s'y appliquent. Du coup, vous allez réussir à modifier les objets créés par le script Python mais aussi modifier les objets que vous avez créé directement en utilisant l'interface.

Au final, on pourrait faire des animations précises de ce type : (c'est une animation créée pour expliquer le principe de fonctionnement des codeurs pour la robotique : des moteurs qui savent dire combien de tours ils ont fait et dans quel sens !)

On pourrait ainsi créer un script qui uniformise les couleurs des différents objets créés sans avoir à le faire à la main, objet par objet. Avec 200 objets, ça devient vite un must have !

Rappel : Nous allons scripter avec Python dans cette partie.

Parfois, le code plante et il est toujours bon de savoir pourquoi. Or, les erreurs n'apparaissent pas directement dans Blender mais dans la console Système du logiciel Blender. Comment la voir ?

Menu INFO - WINDOW - TOOGLE SYSTEM CONSOLE. Le chemin en image :

afficher les erreurs

AGRANDIR LA ZONE DE TEXTE : Si vous voulez facilement voir tout votre script, vous pouvez maximiser la taille de l'écran en plaçant la souris dessus et en utlisant la combinaison de touches CTRL + ↑.

AFFICHAGE DU SCRIPT : Vous pouvez afficher le menu d'affichage du script en appuyant sur le petit plus (+) juste à sa gauche :

Menu du script

On obtient alors un second menu. Dans ce menu, je vous conseille de sélectionner Line Numbers pour afficher les numéros de lignes, Synthax Highlight pour mettre les éléments synthaxiques en valeur et et de désactiver Live Edit qui va lancer le script dès que vous lui faites subir la moindre modification.

Menu

1 - Stockage des informations sur les objets 3D

Pour pouvoir accéder à un objet3D depuis un script Python, il suffit en réalité de stocker son 'adresse' dans une variable.

Pour l'instant, vous avez fait des créations d'objets 3D du type :

import bpy # On importe la bibliothèque Blender Python


distance = 2

bpy.ops.mesh.primitive_cube_add( location = (0,0,0))

bpy.ops.mesh.primitive_cube_add( location = (distance,distance,0))

Et cela donne deux cubes situés ainsi :

Création initiale

01° Supprimer le cube initial de Blender puis lancer ce script. Comment se nomme le premier cube créé ? Et le second ? Pour cela, sélectionnez un cube dans la vue 3D et allez voir dans la fenêtre qui liste les objets. Il sera mis en valeur en blanc .

Noms des cubes

On voit donc que le premier cube se nomme Cube et que le second se nomme Cube.001.

Autant, on garde dans Blender un accés à ce cube, autant dans le script on ne stocke nulle part sa référence. On a placé le nombre 2 dans la variable distance mais nous n'avons pas créé de variables permettant d'accéder aux cubes. Nous parvenons donc à les créer, les modifier et les poser. Mais on ne peut pas les modifier après coup avec le code Python.

C'est ce que nous allons faire.

1.1 Récupérer la référence d'un objet qu'on vient de créer

Pour connaitre la référence de l'objet Blender actuellement sélectionné, il suffit de taper ceci (mais il faut le savoir !) :

objet3DSelectionne = bpy.context.object

Or, lorsqu'on crée un objet, il est automatiquement en mode sélectionné. Si vous voulez mémoriser la référence d'un objet que vous venez de créer avec une instruction Python, il faut donc stocker ce résultat dans une variable.

Si on veut mémoriser les références des deux cubes qu'on vient de créer, on peut donc faire ceci :

import bpy # On importe la bibliothèque Blender Python


bpy.ops.mesh.primitive_cube_add( location = (0,0,0))

monCube1 = bpy.context.object


bpy.ops.mesh.primitive_cube_add( location = (2,2,0))

monCube2 = bpy.context.object


CODE COULEUR : dans la suite des activités Blender-Python, je n'utiliserai plus le bleu pour représenter les variables contenant les références d'un objet 3D : désormais, ces variables particulière seront en orange. Cela permettra plus facilement de comprendre les variables qui sont directement en lien avec des données contenues dans Blender.

02° Supprimer les objets 3D. Placez en mode SCRIPT si ce n'est pas encore fait. Utiliser alors le script suivant :

import bpy # On importe la bibliothèque Blender Python


distance = 2


bpy.ops.mesh.primitive_cube_add( location = (0,0,0))

monCube1 = bpy.context.object


bpy.ops.mesh.primitive_cube_add( location = (distance,distance,0))

monCube2 = bpy.context.object


Ca n'a l'air de rien. Vous devriez arriver à une configuration proche de celle présentée ci-dessous. Vous allez pouvoir voir la puissance de ce qu'on vient de faire.

Configuration

Maintenant, nous allons passer à un ensemble de commande dans la console Python.

1.2 Récupérer les coordonnées d'un objet

03° Tapez les instructions suivantes. Observez les informations qu'elles transmettent depuis Blender ou les modifications qu'elles engendrent dans l'interface Blender.

>>> bpy.ops.mesh.primitive_cube_add( location = (4,0,0))

>>> monCube3 = bpy.context.object

>>> monCube3

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

On voit que la variable monCube3 fait bien référence à un objet 3D contenu dans Blender. On voit son nom dans l'interface : Cube.002.

04° Tentez maintenant de récupérer des informations géométriques sur votre objet :

>>> monCube3.location

Vector((2.0, 0.0, 0.0))


>>> monCube3.scale

Vector((1.0, 1.0, 1.0))


>>> monCube3.rotation_euler

Euler((0.0, 0.0, 0.0), 'XYZ')

On constate bien que les informations sur la localisation de l'objet sont fournies par groupe de 3 et l'ensemble est fourni entre parenthèses. Il s'agit des coordonnées X,Y,Z mais fournis dans un tuple.

Vector((2.0, 0.0, 0.0))

Pareil pour les informations sur les échelles d'aggrandissement en X,Y et Z ou les inclinaisons de l'objet par rapport aux axes X,Y et Z.

Comment récupérer les informations une à une ? Imaginons qu'on veuille uniquement connaitre la position en X de l'objet, on fait comment ?

05° Utilisez les commandes ci-dessous pour vérifier que l'on obtient bien deux fois la même chose :

1er méthode : on récupère le tuple et on lit son contenu dans ses trois premières 'cases'. Attention, la première porte le numéro 0 !

>>> coordonnees = monCube3.location

>>> coordonnees[0]

2.0

>>> coordonnees[1]

0.0

>>> coordonnees[2]

0.0

2e méthode : on stocke directement les trois valeurs dans 3 variables.

>>> x,y,z = monCube3.location

>>> x

2.0

>>> y

0.0

>>> z

0.0

Vous devriez dans les deux cas obtenir les coordonnées X,Y et Z.

1.3 Récupérer les coefficients d'échelle

Pour connaitre l'attribut à noter, il suffit d'ouvrir avec N la fenêtre des propriétés dans la vue 3D. En restant immobile avec la souris au dessus de la valeur qu'on veut récupérer, on obtient le code Python permettant de récupérer la donnée : ici, je reste immobile au dessus de la rotation en Y.

Récupération d'info

06° Utilisez les informations disponibles pour obtenir les changements d'échelle (scale) effectués un objet. Si vous n'avez pas utilisé l'effet Scale sur l'objet, vous devriez obtenir 1.0 dans les trois directions XYZ.

...CORRECTION...

En laissant la souris au dessus des 3 valeurs Scale, on obtient scale[0], scale[1] et scale[2].

On obtient donc deux façons d'obtenir ces valeurs :


>>> changementEchelle = monCube3.scale

>>> changementEchelle[0]

1.0

>>> changementEchelle[1]

1.0

>>> changementEchelle[2]

1.0


>>> ex,ey,ez = monCube3.scale

>>> ex

1.0

>>> ey

1.0

>>> ez

1.0

Comme vous l'avez vu, les noms des attributs ne sont pas choisi non plus totalement au hasard. En programmation, on tente toujours de donner des noms qui aient un sens immédiatement.

Pour trouver les coordonnées, on utilise donc  monObjet3D.location .

Pour trouver les modifications d'échelle, on utilise donc

monObjet3D.scale

1.4 Récupérer les dimensions de la boîte qui contient l'objet

Cette fois, il suffit d'utiliser l'attribut dimensions.

monObjet3D.dimensions

Comme un cube est de largeur 2 par défaut, on pourra obtenir ceci si vous n'avez pas modifié votre cube :

>>> monCube3.dimensions

Vector((2.0, 2.0, 2.0))


>>> dimX,dimY,dimZ = monCube3.dimensions

>>> dimX

2.0

1.5 Récupérer les angles des rotations qu'a subi l'objet

Encore une fois, en se renseignant dans la vue 3D, on voit que l'attribut se nomme rotation_euler.

monObjet3D.rotation_euler

Pour le cube précédent, si vous n'avez pas fait tourner :

>>> monCube3.rotation_euler

Euler( (0.0, 0.0, 0.0), 'XYZ' )


>>> rx,ry,rz = monCube3.rotation_euler

>>> rx

0.0

1.6 Récupérer la référence d'un objet déjà présent

Pour l'instant, nous travaillons avec la console et en travaillant sur une variable obtenu à partir de l'objet sélectionné à un moment :

>>> monCube3 = bpy.context.object

Mais on peut faire bien mieux. Vous pouvez créer des objets avec l'interface, ou les importer depuis un fichier. Et ensuite, vous pourrez créer des variables permettant de les manipuler, pourvu que vous connaissiez leurs noms dans l'interface.

07° Créez deux cylindres avec l'interface. Normalement, ils devraient se nommer Cylinder et Cylinder.001. Déplacer les cylindres, faites leur subir une rotation ect ... Bref, modifiez les.

Maintenant, nous voudrions modifiez ces deux objets à l'aide d'un script. Il va donc falloir récupérer leurs références. Comment ? Comme ça (attention, il s'agit d'un script, pas de commandes à taper directement dans la console :

import bpy # On importe la bibliothèque Blender Python


monObjet1 = bpy.data.objects['Cylinder']


monObjet2 = bpy.data.objects['Cylinder.001']

Comme vous le voyez, on va chercher dans le module bpy.data pour y chercher un objet objects, avec un s attention. Cette objet est en réalité une collection de toutes les références des objets contenus dans votre fichier 3D. Pour y récupérer la référence d'un objet en particulier, il faut ouvrir et fermer des crochets et fournir à l'intérieur le string contenant le nom de l'objet voulu. J'aurai pu lui garder une couleur bleue : il s'agit d'une variable. Mais elle est un peu particulière : on a pas besoin de la créer. C'est Blender qui la remplit tout seul. D'où sa couleur marron.

Maintenant, nous allons pouvoir récupérer les informations de nos deux objets à l'aide des variables-objets monObjet1 et monObjet2.

08° Rajouter des lignes à ce script pour qu'il affiche les coordonnées du premier objets et ses dimensions.

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python


# Recherche des références aux objets

monObjet1 = bpy.data.objects['Cylinder']

monObjet2 = bpy.data.objects['Cylinder.001']


# Affichage des coordonnées

print('Coordonnées :')

for x in range(3) :

    print(monObjet1.location[x])

# Affichage des dimensions

print('Dimensions :')

for x in range(3) :

    print(monObjet1.dimensions[x])

Console : n'oubliez pas qu'un print affiche le résultat dans la console Système, pas la console Python intégrée à Blender.

1.7 Quelques autres propriétés

Pour finir, voyons encore quelques dernières propriétés :

09° Tapez ceci dans la console Python de façon à mémoriser le premier cylindre puis tapez le reste de l'exemple :

>>> monObjet1 = bpy.data.objects['Cylinder']

Pour voir si l'objet est caché ou visible, vous pouvez chercher l'attribut hide

Si l'objet est visible (le petit oeil de l'interface à côté de son nom est sélectionné), cela va renvoyer False car l'objet n'est pas caché :

>>> monObjet1.hide

False

10° Cacher l'objet à l'aide de l'oeil dans le menu de droite et retaper les mêmes instructions. Vous devriez obtenir une réponse True cette fois : l'objet est caché.

>>> monObjet1.hide

True

Nous allons voir deux derniers attributs assez pratiques :

>>> monObjet1.type

'MESH'

Le type vous permet de savoir que vous avez bien affaire à un objet fait avec des vertex, pas une caméra ou une lumière.

Voici la commande à taper pour obtenir le type de l'objet sélectionné :

>>> bpy.context.object.type

11° Sélectionner différents objets et faire un essai avec l'instruction à chaque fois. Sélectionnez des objets, la caméra, une lumière ect... CTRL C et CTRL V sont vos amis.

Quelques exemples de réponses. A vous de deviner ce que j'ai sélectionné à chaque fois !

'CAMERA'

'LAMP'

Et ça sert à quoi ? Nous verrons plus tard qu'on peut tester une valeur. On pourra ainsi éviter de tenter d'appliquer un matériau à la caméra par exemple !

Dernière information pratique à récupérer parfois : le nom de l'objet. C'est facile, c'est l'attribut name.

Exemple :

>>> monObjet1.name

'Cylinder'

Un test sur cette valeur peut notamment vous permettre de savoir si l'objet est un Cube, une Sphere ... Nous verrons cela lors de l'activité sur les tests IF.

2 - Modifier un objet Blender

Passons aux choses sérieuses : nous allons pouvoir changer les paramètres d'un objet. Par exemple, nous pourrions décider de créer un script qui éloigne l'objet actif en cours du centre du plateau. On veut multiplier chaque coordonnée par 2 par exemple. Et lui faire subir une rotation de 30° selon l'axe z au passage tiens.

Et comment fait-on cela ? C'est simple, on utilise l'affectation.

Voici des exemples sur un objet qu'on nommera ici monObjet :

Agit sur Attribut Exempe
Coordonnées location

monObjet.location = (1,2,3)

Déplace l'objet aux coordonnées X=1, Y=2 et Z = 3.

Echelle scale

monObjet.scale = (2,3,4)

Multiplie la taille de l'objet par 2 sur X, 3 sur Y et 4 sur Z.

Dimensions dimensions

monObjet.dimensions = (2,3,4)

L'objet est large de 2 selon X, 3 selon Y et 4 selon Z.

Rotation rotation_euler

monObjet.rotation_euler = (2,3,4)

On fait tourner l'objet de 2 rad sur l'axe X, 3 rad sur l'axe Y ect ...

L'objet tourne de un tour avec 2 pi radians (soit 360°).

Cela correspond en gros à

  • 6,28 radians pour 360°.
  • 3,14 radians pour 180°.
  • 1,57 radians pour 90°.
  • 0,52 radians pour 30°.
  • ect ...

Nous reprendrons plus bas l'exercice permettant de convertir proprement les degrés en radians.

Visible ou non hide

monObjet.hide = True

Dissimule l'objet à la vue

monObjet.hide = False

Reactive la visibilté de l'objet

Nom Blender name

monObjet.name = "nouveauNom"

Change le nom de l'objet dans l'interface

A vous de jouer.

12° Créer un script qui multiplie la dimension d'un objet par 1.1 mais qui l'éloigne du centre en multipliant les distances x,y et z par 1,3. On rajoutera une rotation de 30° selon l'axe X à chaque fois. Vérifier le fonctionnement en sélectionnant différents objets.

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python


# Recupération de l'objet sélectionné

monObjet = bpy.context.object


# Récupération et modification des coordonnées

x,y,z = monObjet.location

monObjet.location = (x*1.3,y*1.3,z*1.3)


# Récupération et modification des dimensions

dx,dy,dz = monObjet.dimensions

monObjet.dimensions = (dx*1.1,dy*1.1,dz*1.1)


# Récupération et modification de la rotation selon Z

rx,ry,rz = monObjet.rotation_euler

monObjet.rotation_euler = (rx+0.52,0,0)

Voici l'exemple de la rotation de l'une des activités précédentes.

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


rayon = 10


for angle_degre in range(-0,360,36):

    angle = math.radians(angle_degre) # math.radians permet de convertir en radians

    x = rayon*math.cos(angle)

    y = rayon*math.sin(angle)

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

13° Question : pourriez-vous parvenir à créer un script qui fait subir à l'objet actif une rotation de 10° autour de sa position actuelle à chaque fois qu'on lance le script ?

Remarque super pratique : si la souris est sur la fenêtre des scripts, vous pouvez utilisez ALT+P (comme python) pour lancer automatiquement le script en cours. Sympa non ? Ca évite de taper sur RUN SCRIPT.

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


# Recherche de 10° en radians

angle = math.radians(10) # math.radians permet de convertir en radians


# Recupération de l'objet sélectionné

monObjet = bpy.context.object


# Récupération et modification de la rotation selon Z

rx,ry,rz = monObjet.rotation_euler

monObjet.rotation_euler = (rx+angle,0,0)

3 - Documentations des autres objets 3D

Comment créer un cone et modifier sa taille ? Il suffit d'aller voir la documentation : aller sur le menu CREATION de Blender, faire un clic-droit et sélectionner la documentation Python en ligne :

bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, size=1.0, calc_uvs=False, view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), layers=(False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))

Construct a UV sphere mesh.


    Parameters:

  •     segments (int in [3, 100000], (optional)) – Segments
  •     ring_count (int in [3, 100000], (optional)) – Rings
  •     size (float in [0, inf], (optional)) – Size
  •     calc_uvs (boolean, (optional)) – Generate UVs, Generate a default UV map
  •     view_align (boolean, (optional)) – Align to View, Align the new object to the view
  •     enter_editmode (boolean, (optional)) (optional)) – Enter Editmode, Enter editmode when adding this object
  •     location (float array of 3 items in [-inf, inf], (optional)) – Location, Location for the newly added object
  •     rotation (float array of 3 items in [-inf, inf], (optional)) – Rotation, Rotation for the newly added object
  •     layers (boolean array of 20 items, (optional)) – Layer

bpy.ops.mesh.primitive_cube_add(radius=1.0, calc_uvs=False, view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), layers=(False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))

Construct a cube mesh.


    Parameters:

  •     radius (float in [0, inf], (optional)) – Radius
  •     calc_uvs (boolean, (optional)) – Generate UVs, Generate a default UV map
  •     view_align (boolean, (optional)) – Align to View, Align the new object to the view
  •     enter_editmode (boolean, (optional)) – Enter Editmode, Enter editmode when adding this object
  •     location (float array of 3 items in [-inf, inf], (optional)) – Location, Location for the newly added object
  •     rotation (float array of 3 items in [-inf, inf], (optional)) – Rotation, Rotation for the newly added object
  •     layers (boolean array of 20 items, (optional)) – Layer

bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=1.0, radius2=0.0, depth=2.0, end_fill_type='NGON', calc_uvs=False, view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), layers=(False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))

Construct a conic mesh.


    Parameters:

  •     vertices (int in [3, 10000000], (optional)) – Vertices
  •     radius1 (float in [0, inf], (optional)) – Radius 1
  •     radius2 (float in [0, inf], (optional)) – Radius 2
  •     depth (float in [0, inf], (optional)) – Depth
  •     end_fill_type (enum in ['NOTHING', 'NGON', 'TRIFAN'], (optional)) – Depth
  •     calc_uvs (boolean, (optional)) – Generate UVs, Generate a default UV map
  •     view_align (boolean, (optional)) – Align to View, Align the new object to the view
  •     enter_editmode (boolean, (optional)) – Enter Editmode, Enter editmode when adding this object
  •     location (float array of 3 items in [-inf, inf], (optional)) – Location, Location for the newly added object
  •     rotation (float array of 3 items in [-inf, inf], (optional)) – Rotation, Rotation for the newly added object
  •     layers (boolean array of 20 items, (optional)) – Layer

14° Créer un script agit sur une sphère et un cone créé à l'aide de l'interface. Lorsqu'on lance le script, la taille du cube doit doubler, la taille du cone doit tripler.

Voici une première version du code que vous devriez tenter de faire. Attention aux noms de vos objets. Je vous rassure, ça ne fonctionne pas. C'est normal !

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python


# Recupération des objets sélectionnés

maSphere = bpy.data.objects['Sphere']

monCone = bpy.data.objects['Cone']


# Récupération et modification de la taille de la sphère

rayon = maSphere.size

maSphere.size = rayon*2


# Récupération et modification du cone

rayon1 = monCone.radius1

rayon2 = monCone.radius2

hauteur = monCone.depth

monCone.radius1 = rayon1*2

monCone.radius2 = rayon2*2

monCone.depth = hauteur*2

Si vous lancez le script, vous pourrez voir une jolie erreur apparaitre dans la console système :

AttributeError: 'Object' object has no attribute 'size'

Error: Python script fail, look in the console for now...

D'où vient le problème ? Pas de la documentation mais de son utilisation. La méthode présentée est une méthode dite constructeur. Elle sert à créer l'objet 3D en lui fournissant certains paramètres.

Mais une fois l'objet créé, il réagit comme n'importe quel objet : c'est juste un ensemble de vertex. Pour l'agrandir, il va donc falloir utiliser soit scale, soit dimensions.

15° Créer un script qui fonctionne cette fois.

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python


# Recupération des objets sélectionnés

maSphere = bpy.data.objects['Sphere']

monCone = bpy.data.objects['Cone']


# Récupération et modification de la sphère

maSphere.dimensions = (2*maSphere.dimensions[0], 2*maSphere.dimensions[1], 2*maSphere.dimensions[2])


# Récupération et modification du cone

monCone.dimensions = (3*monCone.dimensions[0], 3*monCone.dimensions[1], 3*monCone.dimensions[2])

Attention, un code qui tenterai de modifier un à un le conteun du tuple ne fonctionnera pas :

import bpy # On importe la bibliothèque Blender Python


# Recupération des objets sélectionnés

maSphere = bpy.data.objects['Sphere']

monCone = bpy.data.objects['Cone']


# Récupération et modification de la sphère

maSphere.dimensions[0] = maSphere.dimensions[0]*2

maSphere.dimensions[1] = maSphere.dimensions[1]*2

maSphere.dimensions[2] = maSphere.dimensions[2]*2


# Récupération et modification du cone

monCone.dimensions[0] = monCone.dimensions[0]*3

monCone.dimensions[1] = monCone.dimensions[1]*3

monCone.dimensions[2] = monCone.dimensions[2]*3

Pourquoi ? Parce qu'on ne peut pas modifier les éléments d'un tuple. Il faut recréer l'intégralité du tuple et le réaffecter à dimensions. Une fois créé, le contenu d'un tuple est immuable. Si vous voulez le modifier, il faut récréer un autre tuple. Les strings réagissent de la même manière.

A cause de cela, ce n'est pas facile de manipuler un tuple avec un boucle for par exemple.

Comme vous le voyez, modifier des tuples, c'est un peu long. Heureusement, les concepteurs de ce objet de stockage d'informations ont pensé à certaines fonctionnalités bien pratiques. Ainsi le tuple supporte par exemple la multiplication. On pourra se contenter d'écrire ceci :

import bpy # On importe la bibliothèque Blender Python


# Recupération des objets sélectionnés

maSphere = bpy.data.objects['Sphere']

monCone = bpy.data.objects['Cone']


# Récupération et modification de la sphère

maSphere.dimensions = 2*maSphere.dimensions


# Récupération et modification du cone

monCone.dimensions = 3*monCone.dimensions

4 - Gestion des matériaux avec Python

Vous savez maintenant créer des objets 3D de base avec Python.

Vous savez mettre les références en mémoire et les utiliser pour déplacer ou déformer ces objets.

Nous allons continuer à utiliser dans le cadre de la création et gestion des matériaux avec Blender et Python.

Commençons par apprendre à créer les matériaux avec du code

4-1 Création et/ou modification du matériau

La ligne de code suivante revient à sélectionner le menu Material(1), cliquer sur l'icone permettant de créer un nouveau matériau (2) puis à lui donner un nom (3) :

# Création du matériau-objet "couleur_1

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

Menu Matériau

Pourquoi avoir mis materials en marron ? Car il s'agit d'une collection des références aux différents matériaux. Comme objects. En cliquant sur la collection des matériaux disponibles, vous devriez voir celui que nous venons de créer en Python :

Menu Matérials

On remarquera également que new est en rouge foncée : il s'agit d'une méthode intégrée à materials. Nous verrons plus tard qu'on fournit des informations aux méthodes à l'aide des parenthèses.

Comme vous pouvez le voir, nous avons bien créer un nouveau matériau. Par contre, il n'est pas affecté à notre objet en cours (on voit 0 devant son nom) et les couleurs ... sont celles par défaut.

Une fois le matériau créé, il faut donc définir sa couleur "diffuse" par exemple : (rouge, vert et bleu devront être remplacés par des valeurs ou des variables contenant les valeurs voulues.

Pour accéder à l'un des éléments d'une collection de données, il faut utiliser les crochets [ et ] et fournir via un STRING le nom de l'élément qu'on veut récupérer.

Voici le code pour modifier la couleur diffuse d'un matériau existant nommé 'couleur_1' :

# Affectation des couleurs "diffuse" en format RGB

bpy.data.materials['couleur_1'].diffuse_color = (rouge, vert, bleu)

Ainsi, si on tape la ligne suivante, cela revient à règler manuellement les trois couleurs à la valeur de 0.8 :

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

Menu Matériau

Et comment je sais qu'il faut utiliser diffuse_color ? Grace à la documentation : cliquez droit sur le diffuse de l'interface et sélectionnez Online Python References. Vous y trouverez le nom de l'attribut à utiliser sur la référence de votre matériau : diffuse_color.

16° Créer un script qui crée un matériau nommé couleur_1 dont la couleur diffuse RGB est réglée sur (1,0.5 et 0.25). Cela veut dire que l'intensité rouge est reglée à 1, le vert à 0.5 et le bleu à 0.25.

Attention : si vous lancez le script plusieurs fois, vous allez créer un premier matériau nommé couleur_1, puis un matériau nommé couleur_1_001 puis couleur_1_002. Il faudra donc être vigilant et ne pas confondre la création et la modification. Nous n'avons pas encore vu les tests, le mieux est encore de simplement créer les objets et matériaux via l'interface et les modifier via Python.

Première solution : on ne stocke pas la référence du matériau. On va juste le chercher à chaque fois dans le menu Material de Blender.

...CORRECTION SANS STOCKAGE...

import bpy # On importe la bibliothèque Blender Python


# Création du matériau-objet "couleur_1

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

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

Deuxième solution : on stocke la référence du matériau. Cela permettra d'y faire référence plus facilement.

...CORRECTION AVEC STOCKAGE...

import bpy # On importe la bibliothèque Blender Python


# Création et stockage du matériau-objet "test_1" dans Blender, monMaterial dans Python

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

monMaterial = bpy.data.materials['couleur_1']


# Modification de monMaterial dans Python

monMaterial.diffuse_color = (0.8, 0.8, 0.8)

4-2 Affectation du matériau à un objet

Néanmoins, ce matériau n'est relié à aucun de nos objets.

Pour cela, nous allons aujourd'hui apprendre à le faire sur notre objet ACTIF, l'objet sélectionné par un clic droit sur l'interface 3D :

bpy.context.object.active_material = bpy.data.materials[ 'couleur_1' ]

Ou encore à le relier à un objet Blender dont on connait le nom dans l'interface :

bpy.data.objects[ 'Sphere' ].active_material = bpy.data.materials[ 'couleur_1' ]

Ou simplement à le relier à un objet Blender dont on connait la référence dans Python :

bpy.context.object.active_material = bpy.data.materials[ 'couleur_1' ]

Comme vous pouvez le voir, il suffit d'utiliser l'attribut active_material.

Cela permet d'aillers de l'imposer (avec un =) ou simplement de lire ce qu'il contient.

Matériau rattaché à l'objet

Comme vous pouvez le voir sur l'image, notre matériau a bien été affecté à l'objet en cours de sélection qu'on obtient à l'aide de bpy.context.object. J'ai encore utilisé de l'orange pour le dernier car il s'agit de l'objet que vous venez de sélectionner. On peut donc interagir avec lui depuis l'interface très facilement.

Et en quoi c'est si bien ce truc ?

Vous devez voir souvenir du script qui permet de créer des cubes placé en cercle en fonction de l'altitude :

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


rayon = 10


for rayon in range (6,23,4):

    for angle_degre in range(0,360,int(36*6/rayon)):

        angle = math.radians(angle_degre) # math.radians permet de convertir en radians

        x = rayon*math.cos(angle)

        y = rayon*math.sin(angle)

        altitude = -rayon//2

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

Sans couleur, on obtient ceci :

Les cubes sans matériaux

17° Modifier le script pour que chaque cube possède le matériau que nous avons déjà concu, couleur_1. Il suffit de rajouter une ligne au bon endroit !

Et maintenant, on obtient ceci :

Les cubes sans matériaux

Vous imaginez si vous aviez dû affecter les matériaux un à un !

...CORRECTION AVEC STOCKAGE...

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


rayon = 10


for rayon in range (6,23,4):

    for angle_degre in range(0,360,int(36*6/rayon)):

        angle = math.radians(angle_degre) # math.radians permet de convertir en radians

        x = rayon*math.cos(angle)

        y = rayon*math.sin(angle)

        altitude = -rayon//2

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

        bpy.context.object.active_material = bpy.data.materials[ 'couleur_1' ]

Finissons cette partie par deux simples remarques :

Obtention de la référence d'un matériau : comment obtenir l'accés à la référence d'un matériau d'un objet monObjet ? C'est facile :

leMateriau = monObjet.active_material

Obtention du nom du matériau : comment connaitre le nom d'un matériau affecté à un objet ? C'est facile :

nomMateriau = monObjet.active_material.name

5 - La gestion des keyframes

Pour finir, nous allons voir rapidement comment créer des keyframes avec Python. Nous n'allons faire que survoler cette fonction car la gestion complète va nécessiter une activité entière.

Résumé des épisodes précédents :

Nous avons vu comment créer un nouveau objet :

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

Nous avons vu comment créer un matériau et lui imposer une couleur :

bpy.data.materials.new(nom)

bpy.data.materials[nom].diffuse_color = (couleur, 0.04, 0.04)

Nous avons égalememt vu deux collections : la collection des objets 3D et la collection des matériaux.

bpy.data.objects

bpy.data.materials

Nous avons vu également qu'on pouvait récuper l'objet sélectionné actuellement et en profiter pour changer des choses, son nom par exemple ou lui fournir un nouveau matériau :

bpy.context.object.name

bpy.context.object.active_material = bpy.data.materials[nom]

Il reste un nombre important de commandes bien entendu. Nous en rencontrerons beaucoup dans les activités Blender Python suivantes.

Pour l'instant, prenons un cas compliqué mais intéressant demandant de gérer beaucoup de keyframes sur beaucoup d'objets : la rotation des cubes précédents par exemple.

Pour placer le curseur temporel des animations à une certaine position, nous allons utiliser bpy.context.scene.frame_set(numero_de_la_frame_voulue).

Si vous tapez bpy.context.scene.frame_set(20), cela revient à vous placer sur 20 sur l'interface Timeline :

Un exemple en image :

allez à la frame 20

Et si on veut aller à la 40 ?

allez à la frame 20

Ensuite, il faut modifier l'attribut de l'objet désiré pour le fixer à la valeur voulue. Ensuite, on insère une keyframe avec la méthode keyframe_insert().

Au vu de la documentation monstrueuse de Blender, vous devriez commencer à vous dire "mais comment il sait tout ça lui ! Il passe ses nuits à lire la documentation ou quoi ?!?". Et la réponse est ... non. Il suffit d'ouvrir la zone du haut en info et d'étendre la zone :

info

Et voilà. Si vous cherchez la façon de réaliser une action, il suffit

  • Soit d'ouvrir ce menu info et de l'agrandir
  • Soit de trouver les instructions dans l'interface elle-même (beaucoup d'instructions s'affichent en plaçant la souris dessus
  • Soit de partir à l'aventure dans la documentation technique. Prenez des vivres !

Le problème de la première méthode, c'est qu'il s'agit des instructions pour passer par le menu. Mais ça donne au moins une première idée de ce qu'il faut faire.

Reprenons :

Pour fixer à la frame 10 (reglée par frameset) une keyframe de position sur un objet 3D dont la référence est stockée dans une variable monObjet, on tape par exemple :

bpy.context.scene.frame_set(10)

monObjet.keyframe_insert(data_path="location")

On peut même faire plus simple :

monObjet.keyframe_insert(data_path="location", frame=10)

Pour enregistrer les grandeurs de rotation sur la frame en cours :

monObjet.keyframe_insert(data_path="rotation_euler")

Pour enregistrer les grandeurs de scale sur la frame 50 :

monObjet.keyframe_insert(data_path="scale", frame=50)

Et voilà, c'est tout.

18° Commencons par un petit exercice : tentons de créer un script qui permet de créer une animation : l'objet sélectionné par l'utilisateur (l'objet actif donc) doit subir plusieurs keyframe :

A vous de jouer.

A titre d'exemple, voici ce que cela donne sur l'un des cubes précédent :

...CORRECTION...

import bpy # On importe la bibliothèque Blender Python


# Recherche de la référence et enregistrement de sa position au moment du départ du script

monObjet = bpy.context.object

x, y ,z = monObjet.location


# Création des keyframes et déplacement de l'objet

monObjet.keyframe_insert(data_path="location", frame=0)


monObjet.location = ( x, y, z+3 )

monObjet.keyframe_insert(data_path="location", frame=40)


monObjet.location = ( x, y, z-2.5 )

monObjet.keyframe_insert(data_path="location", frame=80)


monObjet.location = ( x, y, z+2 )

monObjet.keyframe_insert(data_path="location", frame=120)


monObjet.location = ( x, y, z-1.5 )

monObjet.keyframe_insert(data_path="location", frame=160)


monObjet.location = ( x, y, z+1 )

monObjet.keyframe_insert(data_path="location", frame=200)


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

monObjet.keyframe_insert(data_path="location", frame=240)


monObjet.location = ( x, y, z )

monObjet.keyframe_insert(data_path="location", frame=280)

19° Il ne reste plus qu'à appliquer ceci à tous les cubes de notre pyramide des cubes.

20° Dernière subtilité : je voudrais que le sens de déplacement varie d'un cercle de rayon sur l'autre : l'un commence par le haut, l'autre par le bas.

Le plus facile est certainement de créer une variable sens hors de la boucle.

On pourrait changer son signe en la multipliant par 1 dans la première bouche.

Si elle est positive, on doit commencer par monter.

Si elle est négative, on doit commencer par descendre.

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


rayon = 10

sens = 1


for rayon in range (6,23,4):

    sens = sens*(-1)

    for angle_degre in range(0,360,int(36*6/rayon)):

        angle = math.radians(angle_degre) # math.radians permet de convertir en radians

        x = rayon*math.cos(angle)

        y = rayon*math.sin(angle)

        altitude = -rayon//2

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

        bpy.context.object.active_material = bpy.data.materials[ 'couleur_1' ]

        # A VOUS DE CODER ICI !

Cela pourrait donner ceci :

...CORRECTION ...

import bpy # On importe la bibliothèque Blender Python

import math # On importe la bibliothèque math pour calculer les cos...


rayon = 10

sens = 1


for rayon in range (6,23,4):

    sens = sens*(-1)

    for angle_degre in range(0,360,int(36*6/rayon)):

        angle = math.radians(angle_degre) # math.radians permet de convertir en radians

        x = rayon*math.cos(angle)

        y = rayon*math.sin(angle)

        altitude = -rayon//2

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

        bpy.context.object.active_material = bpy.data.materials[ 'couleur_1' ]

        # A VOUS DE CODER ICI !

        monObjet = bpy.context.object

        x, y ,z = monObjet.location


        # Création des keyframes et déplacement de l'objet

        monObjet.keyframe_insert(data_path="location", frame=0)


        monObjet.location = ( x, y, z+3*sens )

        monObjet.keyframe_insert(data_path="location", frame=40)


        monObjet.location = ( x, y, z-2.5*sens )

        monObjet.keyframe_insert(data_path="location", frame=80)


        monObjet.location = ( x, y, z+2*sens )

        monObjet.keyframe_insert(data_path="location", frame=120)


        monObjet.location = ( x, y, z-1.5*sens )

        monObjet.keyframe_insert(data_path="location", frame=160)


        monObjet.location = ( x, y, z+1*sens )

        monObjet.keyframe_insert(data_path="location", frame=200)


        monObjet.location = ( x, y, z-0.5*sens )

        monObjet.keyframe_insert(data_path="location", frame=240)


        monObjet.location = ( x, y, z )

        monObjet.keyframe_insert(data_path="location", frame=280)

Comme vous pouvez le constater, faire ceci avec juste l'interface risque d'être long. On peut toujours travailler avec les armatures mais travailler avec le code est bien pratique aussi.

Dans les prochaines activités, nous allons découvrir encore d'autres outils qui vont décupler ce que vous venez de découvrir :

Et avec tous cela, les possibilités d'animation sont infinies...