Infoforall

Blender JEIA - 06 : DES FONCTIONS pour BLENDER

Voyons comment créer vos propres fonctions si l'action que vous voulez effectuer n'est pas gérée par l'une des innombrables fonctions ou méthodes de Blender-Python.

Si vous maitrisez bien les fonctions, vous pouvez survoler très rapidement ou totalement zapper les parties 1-2-3. A partir de la partie 4, je présente quelques fonctions pratiques à créer pour animer vos objets.

1 - Premieres fonctions créées : les procédures

Une fonction est un ensemble d'instructions qui seront effectuées lorsqu'on va appeler la fonction. Aucune ligne de code de la fonction ne sera exécutée tant que la fonction n'aura pas été rendue active par son appel. Un petit exemple pour la forme. Il permet d'afficher des cubes :

import bpy

import random


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -

def test_fonction() :

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    x = random.randint(-10,10)

    objet3DSelectionne.location = (x,0,0)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -

for objet in bpy.data.objects :

    bpy.data.objects.remove(objet, True)


test_fonction()


for objet in bpy.data.objects :

    objet.delta_location = (0, 0, 2)


test_fonction()


for objet in bpy.data.objects :

    objet.scale = (0.5, 0.5, 0.5)

CLIQUEZ ICI POUR VOIR L'ORDRE DES INSTRUCTIONS EXECUTEES :

Que fait ce programme ?

  1. On importe des bibliothèques :
  2. import bpy

    import random

  3. On déclare une fonction nommée test_fonction qui crée un objet à la hauteur 0.
  4. def test_fonction() :

        bpy.ops.mesh.primitive_cube_add()

        objet3DSelectionne = bpy.context.object

        x = random.randint(-10,10)

        objet3DSelectionne.location = (x,0,0)

  5. On efface les objets de la scène.
  6. for objet in bpy.data.objects :

        bpy.data.objects.remove(objet, True)

  7. On appelle test_fonction.
  8. test_fonction()

  9. On agit sur les objets de la scène en les déplacant de 1 vers le haut.
  10. for objet in bpy.data.objects :

        objet.delta_location = (0, 0, 2)

  11. On appelle une seconde fois test_fonction.
  12. test_fonction()

  13. On agit sur les objets de la scène en les rendant plus petits.
  14. for objet in bpy.data.objects :

        objet.scale = (0.5, 0.5, 0.5)

01° Avant de tester le bouton, répondre aux questions ci-dessous. Vérifier ensuite la réalité de votre réponse en cliquant sur le bouton puis en lancant le programme dans Blender.

  1. Va-t-on avoir 2 ou 3 cubes créés ?
  2. Combien de cubes à la hauteur 0 et combien à la hauteur 1 ?

...CORRECTION...

On aurait pu penser qu'il y aurait 3 cubes. Mais non, il n'y en a que deux. Pourquoi ?

Simplement parce qu'on exécute pas la fonction lorsqu'on la déclare mais uniquement lorsqu'on l'appelle réellement.

On appelle donc une première fois la fonction et on déclale le cube créé par le haut.

Ensuite on appelle une deuxième fois la fonction et un second cube apparait.

On va finalement rendre les deux cubes plus petits.

Et voici l'explication de ce programme ligne par ligne :

Les deux premières lignes sont habituelles : on importe les deux modules nécessaires.

import bpy

import random

Nous arrivons ensuite à une chose totalement nouvelle : la zone de déclaration des fonctions. On commence ici par la ligne def test_fonction(): où tous les éléments sont importants :

  1. def test_fonction(): : On commence par le mot-clé def qui indique qu'on va donner le nom d'une fonction.
  2. def test_fonction(): : On place un espace entre def et le nom.
  3. def test_fonction(): : On donne le nom de la fonction, test_fonction ici, suivi de parenthèses ().
  4. def test_fonction(): : On finit la déclaration du nom par les deux points : pour signaler que la suite va être composée des actions à effectuer (c'est le même principe qu'avec le if, le while, le for...)
  5. On tabule pour indiquer que l'instruction est incluse dans la fonction.
  6. La fin des instructions est indiquée par le fait qu'on revienne au même niveau que le texte def ... du départ.
  7. def test_fonction() :

        bpy.ops.mesh.primitive_cube_add()

        objet3DSelectionne = bpy.context.object

        x = random.randint(-10,10)

        objet3DSelectionne.location = (x,0,0)

Le résultat attendu :

les deux cubes

Point important : lorsque vous voulez utiliser la fonction, il ne faut pas juste donner son nom : il faut rajouter les parenthèses après son nom. Il faut donc utiliser test_fonction().

On peut donc utiliser de telles fonctions pour effectuer des tâches répétitives (et exactement identiques) qu'on devrait taper sinon à plusieurs endroits dans le code. On notera que si on devait les faire à la suite directe les unes des autres, les boucles FOR ou WHILE conviennent également.

Et si on veut faire des choses un peu différentes ? On peut aussi ?

Oui, on peut transmettre des arguments (des données) aux fonctions de façon à ce qu'elles utilisent ces contenus venant de l'extérieur. Lors de la déclaration de la fonction, il suffit de placer entre les parenthèses les variables nommées paramètres qui devront récupérer les arguments envoyés.

def test_fonction(y, z) :

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    x = random.randint(-10,10)

    objet3DSelectionne.location = (x, y, z)

On devra maintenant fournir les données lors de l'appel de test_fonction : j'ai nommé mon premier paramètre y car il s'agit de la coordonnée y et le second z.

Pour faire appel à la fonction dans le programme, il faut taper par exemple :

test_fonction(5,5)

Le cube sera alors placé en x aléatoire mais en y = 5 et z = 5.

02° Modifier test_fonction comme ci-dessus et en faire l'appel deux fois avec des arguments (valeurs transmises) différents. Par exemple :

test_fonction(4,0)

test_fonction(0,2)

...CORRECTION...

import bpy

import random


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -

def test_fonction(y, z) :

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    x = random.randint(-10,10)

    objet3DSelectionne.location = (x, y, z)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -

test_fonction(4,0)

test_fonction(0,2)

Alors, ça fonctionne comment ?

C'est relativement simple : lors du premier appel, on envoie les valeurs suivantes dans l'ordre (4,0) :

  1. L'argument 4 va donc être relié au paramètre de réception y.
  2. L'argument 0 va donc être relié au paramètre de réception z.

Vous avez ci-dessous une animation permettant de voir les affectations successives des 2 paramètres (les 2 boites bleues) :

CLIQUEZ ICI POUR VOIR L'ORDRE DES INSTRUCTIONS EXECUTEES :

y :

z :

import bpy

import random


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -

def test_fonction(y, z) :

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    x = random.randint(-10,10)

    objet3DSelectionne.location = (x, y, z)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


# Premier appel de la fonction avec les arguments 4 et 0

test_fonction(4,0)


# Deuxième appel de la fonction avec les arguments 0 et 2

test_fonction(0,2)

C'est donc comme si on avait tapé en début de fonction :

y = 4

z = 0

Puis, ceci lors du deuxième appel :

y = 2

z = 0

L'intérêt, c'est que cette affectation va se faire automatiquement en fonction des arguments fournis lors de l'apppel de la fonction.

03° Faire l'appel de test_fonction(2,"0").

Vous devriez obtenir quelque chose comme :

Traceback (most recent call last):

TypeError: bpy_struct: item.attr = val: expected sequence items of type float, not str

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

Le code utilisé est :

# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


# Premier appel de la fonction avec les arguments 4 et 0

test_fonction("4",0)


# Deuxième appel de la fonction avec les arguments 0 et 2

test_fonction(0,2)

Donc, si vous voulez un code solide, il faudra penser à utiliser regulièrement les blocs try: expect: que nous avons vu lors de l'activité TANT QUE.

2 - Utilisation d'une fonction

La force des fonctions vient de la facilité avec laquelle on peut les utiliser sans savoir comment elles fonctionnent. Tout ce qu'on a besoin de connaître, c'est :

A titre d'exemple, nous allons utiliser ici une fonction qui crée un cube à une position donnée sur une certaine frame et le déplace pour que le cube soit à une autre position sur une autre frame.

Nous nommerons cette fonction animerCube et elle aura besoin des informations suivantes :

Si vous voulez placer un cube en (0,0,0) sur la frame 0 puis le faire se déplacer en (1,5,10) en frame 100, il faudra donc simplement taper ceci :

animerCube( 0, (0,0,0), 100, (1,5,10) )

04° Sans chercher à lire le contenu de la fonction au delà des commentaires d'explications, créer un programme pour réaliser une animation similaire à celle proposée :

import bpy


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def animerCube(frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 4 paramètres :

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object


    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


# Premier appel de la fonction avec les arguments 4 et 0

animerCube( 0, (0,0,0), 40, (5,5,5) )

...CORRECTION...

animerCube( 0, (0,0,0), 40, (5,5,5) )

animerCube( 40, (5,5,5), 80, (5,10,10) )

L'une des forces des fonctions est qu'on doit simplement savoir quels arguments transmettre et avoir une idée du but de la fonction. Il est inutile de connaitre et comprendre le code interne.

D'ailleurs, on pourrait modifier la façon dont on réalise l'animation à l'intérieur de la fonction. Pourvu que le résultat soit le même, personne ne pourra savoir que vous avez fait une mise à jour. C'est un principe important en programmation car cela permet de modifier un bout de code sans mettre en péril le reste de l'édifice.

Deuxième gros intérêt : on peut centraliser une méthode d'exécution. Cela évite de faire une tâche en appliquant un algorithme différent à différents endroits.

Fonctions, procédures et méthodes

Voilà la limite des "fonctions basiques" : on ne sait rien de l'exécution interne et on ne récupère aucune information de la fonction. Ce type de fonction qui ne renvoie ni information ni valeur vers le programme principal se nommme PROCEDURE.

procédure

Si on parvient à faire communiquer la fonction, on la nommera effectivement FONCTION.

procédure

Dans certains langages, fonctions et procédures ne se déclarent pas de la même manière. Ce n'est pas le cas de Python. C'est donc la dernière fois que nous noterons ici le mot procédure.

Et les méthodes ? Il s'agit tout simplement du nom qu'on donne à une fonction appartenant à une classe d'objets et pouvant donc agir sur un objet. La plupart du temps, elles sont utilisées sous la forme objet.methode(argument). Vous vous souvenez ?

Nous avons donc vu les fonctions sans retour : les procédures.

Il nous reste à voir les fonctions renvoyant une information vers le programme qui en ont fait l'appel.

3 - Fonctions avec retour d'information

Imaginons maintenant qu'on veuille créer une animation permettant de comparer certains mouvements.

On voudrait comparer un mouvement uniforme et un mouvement uniformément accéléré.

Mouvement uniforme :

La vitesse du cube est constante. On la nommera v0.

La position initiale de l'objet est notée x0.

La position x finale au temps t sera alors : x = v0 * t + x0.

Mouvement uniformément accéléré :

L'accélération du cube est constante. On la nommera a0.

La vitesse du cube est constante. On la nommera v0.

La position initiale de l'objet est notée x0.

La position x finale au temps t sera alors : x = 0.5 * a0 * t2 + v0 * t + x0.

Plutôt que de faire le calcul dans le programme, nous voudrions simplement qu'une fonction le fasse et récupérer le résultat de ce calcul.

Pour renvoyer ce que pointe une variable reponse dans la partie de code qui a fait appel à la fonction, il suffit d'utiliser le code suivant return (reponse) si reponse = a*x+b. Mais on pourrait même faire plus court en utilisant directement return(a*x+b)

Un petit exemple avec cette fonction affine :

# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions #

# - - - - - - - - - - - - - - - - - -

def affine(a,x,b) :

    y = a*x+b

    return(y)


# - - - - - - - - - - - - - - - - - -

# Corps du programme #

# - - - - - - - - - - - - - - - - - -

f1 = affine(2,10,4)

print(f1)

print("FIN")

Comme vous le voyez, on stocke le résultat de la fonction (qu'elle renvoie avec le return) dans une variable f1 pour garder le résultat en mémoire.

05° Avant d'utiliser le code, tentez de découvrir ce que doit afficher normalement le code print(f1).

...CORRECTION...

On voit qu'on veut stocker dans f1 le résultat de la fonction affine(2,10,4).

Lors de l'appel, on a donc a = 2, x = 10 et b = 4.

La fonction va alors stocker dans y le résultat du calcul 2*10+4, soit 24.

Le return fait référence à y et, de retour dans le programme principal, f1 contiendra 24.

CLIQUEZ ICI POUR VOIR LE CONTENU DES VARIABLES :

Variables y, a, x et y de la fonction :

y :

a :

x :

b :

Variable f du programme principal :

f1 :

ATTENTION : lorsque la fonction rencontre return, elle calcule le résultat et sort ensuite du codage de la fonction : c'est comme un break : le reste du code ne sera pas analysé.

On peut également donner le contenu d'une variable comme argument : vous n'êtes pas obligé de donner une valeur en "dur" ce qui est très pratique.

06° Et ce code, que renvoie-t-il ? Faire le calcul à la main puis lancer le programme pour vérifier.

# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions #

# - - - - - - - - - - - - - - - - - -

def affine(a,x,b) :

    y = a*x+b

    return(y)


# - - - - - - - - - - - - - - - - - -

# Corps du programme #

# - - - - - - - - - - - - - - - - - -

f1 = affine(2,3,4)

f2 = affine(2,f1,4)

print(f1)

print(f2)

print("FIN")

...CORRECTION...

f1 va contenir le retour de la fonction affine(2,3,4), donc 2*3+4, soit 10.


f2 va contenir le retour de la fonction affine(2,f1,4)

Comme la variable f1 fait référence à l'integer 10, le paramètre x de la fonction affine va recevoir 10.

On calcule donc 2*10+4, soit 24.

Nous savons maintenant créer des fonctions qui renvoient un résultat.

Comment en renvoyer plusieurs ?

Il faut renvoyer une liste, un tuple ou autre. Bref, une structure de données contenant les données que vous voulez renvoyer.

Revenons maintenant à notre cube qui bouge.

Mouvement uniforme :

La vitesse du cube est constante. On la nommera v0.

La position initiale de l'objet est notée x0.

La position x finale au temps t sera alors : x = v0 * t + x0.

import bpy


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def animerCube(frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 4 paramètres :

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object


    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


def mouvement_uniforme(duree, x0) :

    ''' Cette fonction renvoie la distance parcourue sur l'axe pendant la durée

    Le paramètre duree doit contenir la durée en seconde du mouvement à calculer

    Le paramètre x0 doit contenir la position initiale du cube en unité blender

    On considère donc ici une équivalence entre mètre et unité blender

    '''

    v0 = 3


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x0 = 0

t = 2 # unité : seconde

frames = 24*t # on considère un réglage de 24 frames par seconde


x = mouvement_uniforme(t,x0)


animerCube( 0, (x0,0,0), frames, (x,0,0) )

07° Lire le code incomplet. Comment se nomme la fonction qui doit fournir la position finale du cube ? Quels sont les paramètres qu'elle attend lors de son appel ?

08° Compléter le code de la fonction pour qu'elle calcule correctement le déplacement effectué et renvoie cette valeur à l'aide d'un return.

...CORRECTION...

import bpy


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def animerCube(frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 4 paramètres :

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object


    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


def mouvement_uniforme(duree, x0) :

    ''' Cette fonction renvoie la distance parcourue sur l'axe pendant la durée

    Le paramètre duree doit contenir la durée en seconde du mouvement à calculer

    Le paramètre x0 doit contenir la position initiale du cube en unité blender

    On considère donc ici une équivalence entre mètre et unité blender

    '''

    v0 = 3

    return(x0+v0*duree)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x0 = 0

t = 2 # unité : seconde

frames = 24*t # on considère un réglage de 24 frames par seconde


x = mouvement_uniforme(t,x0)


animerCube( 0, (x0,0,0), frames, (x,0,0) )

Vous devriez donc parvenir à créer une animation linéaire.

Comme vous pouvez le voir, ce n'est pas si facile : l'algorithme de Blender tente de rendre le mouvement réaliste et crée une rapide phase d'accélération et de décéleration.

Il reste à voir le mouvement uniformément accéléré.

Mouvement uniformément accéléré :

L'accélération du cube est constante. On la nommera a0.

La vitesse du cube est constante. On la nommera v0.

La position initiale de l'objet est notée x0.

La position x finale au temps t sera alors : x = 0.5 * a0 * t2 + v0 * t + x0.

09° Supprimer le cube via l'interface puis créer une fonction permettant de créer le mouvement uniformément accéléré. Vous pourriez prendre une accélaration de 1 m.s-2 et une vitesse initiale v0 nulle.

4 - Création de fonctions pratiques pour Blender

C'est assez pénible de devoir supprimer nos anciens cubes à chaque fois. Voici le code que nous avons utilisé pour supprimer les objets 3D basés sur un mesh :

# Destruction des objet liés à un maillage

collecObjets = bpy.data.objects

for refObj in collecObjets :

    if refObj.type == 'MESH' :

        collecObjets.remove( refObj, True)

Comme les objets 3D de l'interface ont plusieurs types, il serait possible de créer une fonction permettant de supprimer les objets dont le type est :

10° Créer une fonction qui supprime les éléments dont le type correspond au string qu'on doit fournir en argument. Ainsi, pour supprimer tous les objets de type 'MESH', il faudrait faire l'appel de cette façon :

supprimer('MESH')

...CORRECTION...

def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)

11° Créer une fonction qui crée un cube et qui en renvoie la référence :

refNouveauCube = creerCube()

...CORRECTION...

def creerCube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)

12° Créer une fonction qui anime un objet en se basant sur notre précédente fonction. On devra par contre cette fois lui fournir en paramètre la référence d'un objet 3D à animer.

animer( refObjet, 0, (x0,0,0), frames, (x,0,0) )

...CORRECTION...

def animer(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)

13° Il ne vous reste plus qu'à créer l'animation suivante : un cube part de (0,0,0) et parvient en (5,5,0) en 2 secondes puis en (10,5,5) encore 2s plus tard.

...CORRECTION...

import bpy


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)


def creerCube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)


def animer(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


supprimer('MESH')

monObjet = creerCube()

animer( monObjet, 0, (0,0,0), 48, (5,5,0) )

animer( monObjet, 48, (5,5,0), 96, (0,5,5) )

Il nous reste à revoir les rotations et les couleurs par exemple.

Commençons par les rotations : pour rappel, l'attribut se nomme rotation_euler et il contient un tuple contenant les angles de rotations autour des axes absolus Ox, Oy et Oz. Attention, les angles devront être fournis en degrés.

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

On va donc imposer une rotation de 2 radians autour de Ox, 3 radians autour de Oy et 4 radians autour de Oz.

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

La variable mesInfos pointera vers un tuple contenant (2,3,4).

Comme je pense que les radians ne vous parlent pas plus que cela, nous allons commencer pour créer une fonction convertirDvR qui va convertir le contenu d'un tuple exprimé en dégrés en radians.

Elle va nécessiter un certain nombre de fonctions du module math, il faudra donc les importer au préalable par exemple.

from math import sqrt, pi, sin, cos, copysign

La déclaration donnerait quelque chose comme :

def convertirDvR(tupleAnglesD) :

    ''' Cette fonction renvoie un tuple après avoir converti les valeurs transmises en degrés en radians '''

    angleX, angleY, angleZ = tupleAnglesD

    angleX = angleX/180*pi

    angleY = angleY/180*pi

    angleZ = angleZ/180*pi

    tupleAnglesR = ( angleX, angleY, angleZ )

    return(tupleAnglesR)

14° Créer un script qui efface les objets Mesh 3D, crée un cube et lui fait faire une rotation de 45° selon l'axe Oz.

...CORRECTION...

import bpy

from math import sqrt, pi, sin, cos, copysign


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def convertirDvR(tupleAnglesD) :

    ''' Cette fonction renvoie un tuple après avoir converti les valeurs transmises en degrés en radians '''

    angleX, angleY, angleZ = tupleAnglesD

    angleX = angleX/180*pi

    angleY = angleY/180*pi

    angleZ = angleZ/180*pi

    tupleAnglesR = ( angleX, angleY, angleZ )

    return(tupleAnglesR)


def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)


def creerCube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)


def animer(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


supprimer('MESH')

monObjet = creerCube()

tupleRotationR = convertirDvR((0,0,45))

monObjet.rotation_euler = tupleRotationR

Si on veut créer des animations, c'est le même principe qu'avec les translations. Pour simplifier le programme, nous allons simplement rajouter une fonction animer_rotation basée sur la même structure que la fonction animer. D'ailleurs, nous allons renommer cette fonction animer_position.

def animer_position(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)

def animer_rotation(objet3DSelectionne, frame_initiale, rot_initiales, frame_finale, rot_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * rot_initiales : tuple (x,y,z) des angles initiaux

    * frame_finale : numéro de la frame finale de l'animation

    * rot_finales : tuple (x,y,z) des angles finaux

    '''

    objet3DSelectionne.rotation_euler = rot_initiales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_initiale)


    objet3DSelectionne.rotation_euler = rot_finales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_finale)

15° Créer un script permet de faire tourner un cube sur l'axe Oz.

...CORRECTION...

import bpy

from math import sqrt, pi, sin, cos, copysign


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def convertirDvR(tupleAnglesD) :

    ''' Cette fonction renvoie un tuple après avoir converti les valeurs transmises en degrés en radians '''

    angleX, angleY, angleZ = tupleAnglesD

    angleX = angleX/180*pi

    angleY = angleY/180*pi

    angleZ = angleZ/180*pi

    tupleAnglesR = ( angleX, angleY, angleZ )

    return(tupleAnglesR)


def animer_rotation(objet3DSelectionne, frame_initiale, rot_initiales, frame_finale, rot_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * rot_initiales : tuple (x,y,z) des angles initiaux

    * frame_finale : numéro de la frame finale de l'animation

    * rot_finales : tuple (x,y,z) des angles finaux

    '''

    objet3DSelectionne.rotation_euler = rot_initiales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_initiale)


    objet3DSelectionne.rotation_euler = rot_finales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_finale)


def animer_position(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)


def creerCube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


supprimer('MESH')


monObjet = creerCube()


tupleRotationR1 = convertirDvR((0,0,0))

tupleRotationR2 = convertirDvR((0,0,90))

tupleRotationR3 = convertirDvR((0,0,180))

tupleRotationR4 = convertirDvR((0,0,270))


animer_rotation( monObjet, 0, tupleRotationR1, 48, tupleRotationR2)

animer_rotation( monObjet, 48, tupleRotationR2, 96, tupleRotationR3)

animer_rotation( monObjet, 96, tupleRotationR3, 144, tupleRotationR4)

En réalité, on peut réaliser ces transitions de caractéristiques avec presque tous les attributs de vos objets. Le tout est de savoir sur quoi appliquer la modification.

Regardons ainsi comment faire pour créer une animation liée aux couleurs.

Dans un premier temps, je vous fournis quelques fonctions liées aux materials.

Si vous regardez bien, les fonctions sont créées avec des noms qui partent dans tous les sens. Parfois avec des underscores, parfois avec des majuscules de séparation ... Il vaut mieux choisir une bonne fois pour toutes votre propre méthode.

Comme les méthodes de Blender utilisent l'underscore, j'ai redéfini les noms des fonctions avec cette façon de faire. Attention donc : certaines fonc

import bpy

from math import sqrt, pi, sin, cos, copysign


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions liées aux materials

# - - - - - - - - - - - - - - - - - -


def animer_couleur(materialSelectionne, frame_initiale, tuple_couleur_init, frame_finale, tuple_couleur_final) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * materialSelectionne : référence du material à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * tuple_couleur_init : tuple (r,g,b) des couleurs initiales

    * frame_finale : numéro de la frame finale de l'animation

    * tuple_couleur_final : tuple (r,g,b) des couleurs finales

    '''

    materialSelectionne.diffuse_color = tuple_couleur_init

    materialSelectionne.keyframe_insert(data_path="diffuse_color", frame=frame_initiale)


    materialSelectionne.diffuse_color = tuple_couleur_final

    materialSelectionne.keyframe_insert(data_path="diffuse_color", frame=frame_finale)


def associer_mat_objet(materialSelectionne, objet3DSelectionne) :

    ''' Cette fonction associe le material à l'objet fourni.

    Un material peut être associé à plusieurs objets.

    '''

    objet3DSelectionne.active_material = materialSelectionne


def supprimer_mat() :

    ''' Cette fonction supprime les materials '''

    collecMats = bpy.data.materials

    for refMat in collecMats :

        collecMats.remove( refMat, True)


def creer_material() :

    ''' Cette fonction crée un material et renvoie sa référence '''

    refMaterial = bpy.data.materials.new('Material') # Vous pouvez mettre n'importe quel nom en réalité

    return(refMaterial)


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions liées aux objets

# - - - - - - - - - - - - - - - - - -


def convertirDvR(tupleAnglesD) :

    ''' Cette fonction renvoie un tuple après avoir converti les valeurs transmises en degrés en radians '''

    angleX, angleY, angleZ = tupleAnglesD

    angleX = angleX/180*pi

    angleY = angleY/180*pi

    angleZ = angleZ/180*pi

    tupleAnglesR = ( angleX, angleY, angleZ )

    return(tupleAnglesR)


def animer_rotation(objet3DSelectionne, frame_initiale, rot_initiales, frame_finale, rot_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * rot_initiales : tuple (x,y,z) des angles initiaux

    * frame_finale : numéro de la frame finale de l'animation

    * rot_finales : tuple (x,y,z) des angles finaux

    '''

    objet3DSelectionne.rotation_euler = rot_initiales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_initiale)


    objet3DSelectionne.rotation_euler = rot_finales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_finale)


def animer_position(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)


def creer_cube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


supprimer('MESH')


monObjet = creer_cube()


tupleRotationR1 = convertirDvR((0,0,0))

tupleRotationR2 = convertirDvR((0,0,90))

tupleRotationR3 = convertirDvR((0,0,180))

tupleRotationR4 = convertirDvR((0,0,270))


animer_rotation( monObjet, 0, tupleRotationR1, 48, tupleRotationR2)

animer_rotation( monObjet, 48, tupleRotationR2, 96, tupleRotationR3)

animer_rotation( monObjet, 96, tupleRotationR3, 144, tupleRotationR4)


supprimer_mat()

material1 = creer_material()

associer_mat_objet(material1, monObjet)

animer_couleur(material1, 0, (1,0,0), 145, (0,0,1) )

16° Tester le code ci-dessous avant d'en analyser le contenu. Vous devriez obtenir une animation proche de ceci :

17° Créer une animation comportant au moins trois objets différents. Si vous voulez faire des trajectoires circulaires, vous pouvez toujours reprendre le code qui nous avions utilisé lors d'une autre activité :

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))

Cela devrait vous permettre de créer une jolie fonction, non ?

5 - Importation de modules de fonctions

Nous avons déjà vu de nombreuses bibliothèques (ou modules). Par les importer, il suffit d'utiliser l'instruction import :

Pour voir toutes les fonctions de la bibliothèque math : DOC PYTHON

Bon, alors autant toujours utiliser, from XXX import * si cela évite d'écrire un truc en plus à chaque fois qu'on utilise une fonction ?

Non. Si vous n'importez qu'un seul module, vous pouvez faire un import total et complet, oui.

Mais si vous faites cela avec deux, trois ou quatre modules, les chances sont grandes qu'ils possèdent des fonctions qui portent le même nom. Et là, bonne chance pour retrouver la bonne !

A titre d'exemple, voici un code qui vous permet d'estimer le temps nécessaire à un ensemble de calcul.

Il intègre le module time et utilise la fonction time qui va permettre d'obtenir le temps écoulé depuis le temps zéro de l'ordinateur (certainement le 1er janvier 1970 à 00h00). Il suffit de stocker le temps initial au commencement d'un calcul et le temps final après calcul et nous aurons la durée du calcul.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from time import time


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions #

# - - - - - - - - - - - - - - - - - -

def foisDeux(entree) :

    reponse = entree*2

    return(reponse)


# - - - - - - - - - - - - - - - - - -

# Corps du programme #

# - - - - - - - - - - - - - - - - - -


valeur = float(input("Valeur initiale : "))

t0 = time() # On stocke le temps initial avant les différents calculs


for i in range(101) :

    print("Le terme de rang ",i," = ",valeur)

    valeur = foisDeux(valeur)


tf = time() # On stocke le temps final après les différents calculs

duree = tf-t0


print("Temps écoulé lors du calcul : ",duree," s")

18° Tester le programme ci-dessus. Faire varier le nombre de calculs pour voir l'évolution du temps de calculs avec le nombre de calculs. Vous devriez parvenir à faire cela sans savoir ce que fait exactement la fonction time, c'est l'avantage de tout ceci : on importe et on utilise.

Pour créer une bibliothèque maison, il suffit d'enregistrer un fichier contenant les fonctions qui vous intéressent.

19° Créer un fichier python nommé mesfonctionsblender.py avec le code ci-dessous. Cliquer sur REGISTER (à droite de RUN SCRIPT) de façon à permettre à Blender de comprendre qu'il s'agit d'un module.

import bpy

from math import sqrt, pi, sin, cos, copysign


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions liées aux materials

# - - - - - - - - - - - - - - - - - -


def animer_couleur(materialSelectionne, frame_initiale, tuple_couleur_init, frame_finale, tuple_couleur_final) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * materialSelectionne : référence du material à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * tuple_couleur_init : tuple (r,g,b) des couleurs initiales

    * frame_finale : numéro de la frame finale de l'animation

    * tuple_couleur_final : tuple (r,g,b) des couleurs finales

    '''

    materialSelectionne.diffuse_color = tuple_couleur_init

    materialSelectionne.keyframe_insert(data_path="diffuse_color", frame=frame_initiale)


    materialSelectionne.diffuse_color = tuple_couleur_final

    materialSelectionne.keyframe_insert(data_path="diffuse_color", frame=frame_finale)


def associer_mat_objet(materialSelectionne, objet3DSelectionne) :

    ''' Cette fonction associe le material à l'objet fourni.

    Un material peut être associé à plusieurs objets.

    '''

    objet3DSelectionne.active_material = materialSelectionne


def supprimer_mat() :

    ''' Cette fonction supprime les materials '''

    collecMats = bpy.data.materials

    for refMat in collecMats :

        collecMats.remove( refMat, True)


def creer_material() :

    ''' Cette fonction crée un material et renvoie sa référence '''

    refMaterial = bpy.data.materials.new('Material') # Vous pouvez mettre n'importe quel nom en réalité

    return(refMaterial)


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions liées aux objets

# - - - - - - - - - - - - - - - - - -


def convertirDvR(tupleAnglesD) :

    ''' Cette fonction renvoie un tuple après avoir converti les valeurs transmises en degrés en radians '''

    angleX, angleY, angleZ = tupleAnglesD

    angleX = angleX/180*pi

    angleY = angleY/180*pi

    angleZ = angleZ/180*pi

    tupleAnglesR = ( angleX, angleY, angleZ )

    return(tupleAnglesR)


def animer_rotation(objet3DSelectionne, frame_initiale, rot_initiales, frame_finale, rot_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * rot_initiales : tuple (x,y,z) des angles initiaux

    * frame_finale : numéro de la frame finale de l'animation

    * rot_finales : tuple (x,y,z) des angles finaux

    '''

    objet3DSelectionne.rotation_euler = rot_initiales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_initiale)


    objet3DSelectionne.rotation_euler = rot_finales

    objet3DSelectionne.keyframe_insert(data_path="rotation_euler", frame=frame_finale)


def animer_position(objet3DSelectionne, frame_initiale, coord_initiales, frame_finale, coord_finales) :

    ''' Cette fonction crée une animation à partir de 5 paramètres :

    * objet3DSelectionne : référence de l'objet à animer

    * frame_initiale : numéro de la frame de départ de l'animation

    * coord_initiales : tuple (x,y,z) des coordonnées initiales

    * frame_finale : numéro de la frame finale de l'animation

    * coord_finales : tuple (x,y,z) des coordonnées finales

    '''

    objet3DSelectionne.location = coord_initiales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_initiale)


    objet3DSelectionne.location = coord_finales

    objet3DSelectionne.keyframe_insert(data_path="location", frame=frame_finale)


def supprimer(typeStr) :

    ''' Cette fonction supprime les objects 3d blender d'un certain type

    Le paramètre typeStr peut valoir 'MESH','LAMP','CAMERA'...

    '''

    collecObjets = bpy.data.objects

    for refObj in collecObjets :

        if refObj.type == typeStr :

            collecObjets.remove( refObj, True)


def creer_cube() :

    ''' Cette fonction crée un mesh 3D de cube'''

    bpy.ops.mesh.primitive_cube_add()

    objet3DSelectionne = bpy.context.object

    return(objet3DSelectionne)


20° Enregistrer un second fichier Python au même endroit et lancer le script :

Attention : il arrive que Blender ne parvienne pas à faire le lien entre les fichiers .py. Il convient donc d'enregistrer le fichier Blender .blend et les deux fichiers .py dans le même dossier. Ensuite, si cela ne fonctionne toujours pas, vous pouvez briser le lien avec la croix unlink. Il faudra ensuite ouvrir ce fichier python en utilisant le bouton juste à gauche, celui qui ressemble à un fichier.

unlink

Si le lancement du script pose problème en allant chercher les fonctions du script, il faut donc faire ce qui est indiqué ici.

from mesfonctionsblender import *

supprimer('MESH')


monObjet = creer_cube()


tupleRotationR1 = convertirDvR((0,0,0))

tupleRotationR2 = convertirDvR((0,0,90))

tupleRotationR3 = convertirDvR((0,0,180))

tupleRotationR4 = convertirDvR((0,0,270))


animer_rotation( monObjet, 0, tupleRotationR1, 48, tupleRotationR2)

animer_rotation( monObjet, 48, tupleRotationR2, 96, tupleRotationR3)

animer_rotation( monObjet, 96, tupleRotationR3, 144, tupleRotationR4)


supprimer_mat()

material1 = creer_material()

associer_mat_objet(material1, monObjet)

animer_couleur(material1, 0, (1,0,0), 145, (0,0,1) )

Voilà. Vous avez vu beaucoup de choses sur cette activité. Sachez qu'il reste encore beaucoup à dire mais vous allez maintenant pouvoir faire plein de choses qui auraient été compliquées à faire sans les fonctions.

Pour finir, sachez qu'il existe de muliples façons de simuler dans Blender. Vous n'êtes pas obligé de coder le poids par exemple. On peut activer directement cela dans Blender.

On peut également gérer les chocs, les jets de particules ou même les écoulements.

Comme les autres parties de Blender, on peut les gérer avec l'interface ou via un code Python.


Si vous voulez voir une application pratique de quelques unes de ces notions, vous pouvez aller voir les activites Blender Meca présentes à la fin de la présentation Blender de ce site.