Infoforall

BLENDER PYTHON - 06 - Les boucles TANT QUE

Vous savez déjà utiliser des boucles FOR bornées, c'est à dire exécuter la boucle un nombre de fois précis.

Nous allons réutiliser ce bouclage pour créer un escalier.

Une fois ce script créé, nous en verons les limites ainsi que les moyens de les dépasser.

Rappel sur la sélection : pour sélectionner tous les éléments dans la vue 3D, il suffit d'appuyer sur la touche A.

1 - Création d'un escalier avec un FOR numérique

Commençons par le script de base. Dans ce script :

01° Tester le code complet ci-dessous. Vous pouvez déplacer le Cube initial avant de lancer le script de façon à tracer des escaliers à différents endroits. Où indique-t-on la hauteur des marches et la profondeur des marches ?

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

for marche in range(10):

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)

    x = x + 0.5

    z = z - 0.25

...CORRECTION...

Chaque marche est tracée à des coordonnées correspondant aux contenus des variables x, y et z.

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

La marche possède une hauteur de 0,25 carreau Blender puisqu'on utilise ceci à chaque fin de boucle avant de tracer la marche suivante :

    z = z - 0.25

De la même façon, chaque marche possède une profondeur de 0,5 carrau Blender puisqu'on utilise ceci :

    x = x + 0.5

Au final, on obtient donc plusieurs escaliers de 10 marches assez facilement.

escalier 10 marches

Mais mais mais ... Imaginons que nous voudrions des escaliers qui descendent systématiquement jusqu'à une altitude z = 0. Avec ce script, il faudrait modifier à la main le nombre de fois que la boucle doit se faire. Ce serait un peu long.

Nous pourrions également lui dire de le faire un million de fois et utiliser un if et un break pour sortir de la boucle lorsqu'on atteint z=0.

Oui mais :

  1. C'est un peu lourd
  2. Et s'il faut 1 million et une marche ?
  3. Il existe déjà une boucle qui fait ça : la boucle while.

2 - La boucle While ou tant que

Une boucle WHILE permet de demander au programme de faire plusieurs fois la même chose tant qu’une condition précise est respectée.

En français, on dira TANT QUE la condition est vraie.

Tant que la condition donnée est vraie, le programme va lancer les instructions à l’intérieur de la structure. Il est donc possible que cette instruction ne se fasse jamais si la condition n’est pas vérifiée initialement.

Cette boucle s'utilise comme ceci :

while condition :

    Instruction 1 à répéter

    Instruction 2 à répéter

Tout ce qui est tabulé sera répété tant que la condition est vérifiée (True). Attention donc au copier-coller : si l’une des tabulations est mal encodée, ça peut modifier tout le programme…

Voilà un premier exemple qui vous montre comment demander une note tant que la note est supérieure à 20.

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

note1 = 21

while (note1>20) :

    note1 = input("Quelle est votre note (entière) ? ")

    note1 = int(note1)


print("Votre note est ", note1)

input("pause")

CLIQUEZ SUR LE PREMIER BOUTON :

Réponse Tapée >>>

note1 :

02° Utiliser la simulation ci-dessus ou utiliser le programme pour comprendre le principe du while. Attention, comme on utilise des prints et des inputs, il vaut mieux utiliser l'IDLE de Python plutôt que de passer par la console Système de Blender.

Si vous restez sous Blender pour cette question : n'oubliez pas d'ouvrir la console sytème de Python : menu INFO - Window - Toggle System Console. N'oubliez pas d'appuyer sur Entrée à la fin du script pour redonner la main à Blender.

03° Pourquoi rentre-t-on forcément au moins une fois dans la boucle ? Quelle est la fonction qui transforme la chaîne de caractères en integer ? Modifier le programme pour qu’il vérifie en plus que la note soit bien supérieure à 0 : il faut rajouter une condition dans la condition du while en utilisant or).

...CORRECTION...

On rentre dans la boucle puisqu'on place arbitrairement 21 dans la variable note1. La condition du WHILE est donc vraie : 21 est bien strictement supérieur à 20.

La fonction int() permet de transformer le string de la réponse en entier.

Pour la condition double :

while (note1>20 or note1<0) :

Une autre façon de faire : on part d’un test True et on le rend False lorsqu’on estime qu’on peut sortir :

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

test = True

while test :

    note1 = input("Quelle est votre note (entière) ? ")

    note1 = int(note1)

    test = (note1<0 or note1>20) # Boolean


print("Votre note est ", note1)

input("pause")

Et si on veut vérifier que la chaîne tapée par l’utilisateur est bien un chiffre ?

Il faudrait utiliser la méthode isnumeric() : note1.isnumeric() renvoie True si tous les caractères de la chaîne note1 correspondent à des chiffres. Sinon (c’est à dire si au moins un caractère n’est pas un chiffre), elle renvoie False.

04° Utiliser le code donné juste au dessus en tapant n’importe quoi comme A ou b. Le Modifier en utilisant la méthode isnumeric pour éviter les erreurs et recommencer la saisie. Il faudra modifier test par exemple.

Voici pour la présentation du while. Revenons à notre problème d'escalier qui doit aller jusqu'à z = 0.

3 - Retour à l'escalier

05° Reprendre vos nouvelles connaissances sur while et le code initial avec un for pour remplacer la boucle for par un while : nous voudrions maintenant tracer des marches tant qu'on a pas atteint l'altitude 0.

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

for marche in range(10):

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)

    x = x + 0.5

    z = z - 0.25

Si vous relancer le script, vous devriez cette fois avoir des marches jusqu'en bas (une fois le code modifié !)

escalier jusqu'en bas

...CORRECTION...

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

while z > 0 :

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)

    x = x + 0.5

    z = z - 0.25

Continuons : on voudrait rajouter des petits pilliers sur les côtes des marches, pour symboliser une barrière :

# Création de l'escalier

while z > 0 :


    # Création de la marche

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)


    # Création des rambardes

    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y-1.8, z+0.5))

    refRambardeG = bpy.context.object

    refRambardeG.dimensions = (0.1,0.1,1)


    # Modification des coordonnées pour la prochaine création

    x = x + 0.5

    z = z - 0.25

06° Analyser et tester ce nouveau code. Rajouter ensuite les lignes permettant d'obtenir la rambarde centrale et celle de droite.

escalier avec rambardes

...CORRECTION...

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

while z > 0 :


    # Création de la marche

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)


    # Création des rambardes

    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y-1.8, z+0.5))

    refRambardeG = bpy.context.object

    refRambardeG.dimensions = (0.1,0.1,1)


    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y+1.8, z+0.5))

    refRambardeD = bpy.context.object

    refRambardeD.dimensions = (0.1,0.1,1)


    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y, z+0.5))

    refRambardeM = bpy.context.object

    refRambardeM.dimensions = (0.1,0.1,1)


    # Modification des coordonnées pour la prochaine création

    x = x + 0.5

    z = z - 0.25

Et si on ne désire qu'un pilier sur 3 exemples ?

Et bien, il suffit d'utiliser l'opérateur % qui permet d'obtenir le reste d'une division entière :

Ainsi :

>>> 0%3

0

>>> 1%3

1

>>> 2%3

2

>>> 3%3

0

>>> 4%3

1

>>> 5%3

2

>>> 6%3

0

>>> 7%3

1

07° Utiliser un test IF associé à cet opérateur % pour n'afficher qu'un pilier sur 3. Il faudra créer une variable numeroMarche qui part de 0 et augmente de un à chaque fois qu'on crée une marche supplémentaire. Voici le code modifié avec cette variable. A vous de l'utiliser pour ne pas afficher tous les pilliers.

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

numeroMarche = 0

while z > 0 :


    # Création de la marche

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)


    # Création des rambardes

    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y-1.8, z+0.5))

    refRambardeG = bpy.context.object

    refRambardeG.dimensions = (0.1,0.1,1)


    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y+1.8, z+0.5))

    refRambardeD = bpy.context.object

    refRambardeD.dimensions = (0.1,0.1,1)


    bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y, z+0.5))

    refRambardeM = bpy.context.object

    refRambardeM.dimensions = (0.1,0.1,1)


    # Modification des coordonnées pour la prochaine création

    x = x + 0.5

    z = z - 0.25


    # Incrémentation de 1 du numéro de marche

    numeroMarche += 1

...CORRECTION...

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

numeroMarche = 0

while z > 0 :


    # Création de la marche

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)


    if numeroMarche%3 == 0 :

        # Création des rambardes

        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y-1.8, z+0.5))

        refRambardeG = bpy.context.object

        refRambardeG.dimensions = (0.1,0.1,1)


        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y+1.8, z+0.5))

        refRambardeD = bpy.context.object

        refRambardeD.dimensions = (0.1,0.1,1)


        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y, z+0.5))

        refRambardeM = bpy.context.object

        refRambardeM.dimensions = (0.1,0.1,1)


    # Modification des coordonnées pour la prochaine création

    x = x + 0.5

    z = z - 0.25


    # Incrémentation de 1 du numéro de marche

    numeroMarche += 1

4 - TRY EXCEPT

Imaginons maintenant que nous désirions colorier l'ensemble.

Nous pouvons associer des matériaux aux différents éléments sans aucun problème sauf qu'il faudrait tester l'existence des matériaux pour éviter d'associer un matériau inexistant à un objet. Cela créerait une erreur et le script échouerait.

Nous pouvons donc utiliser la méthode choisie avec le cube : on teste l'existence et s'il n'existe pas, on le crée.

Néanmoins, on peut aussi faire autrement. On peut tenter de faire une série d'action (try en anglais). Si c'est possible, les actions seront faites. Si l'une d'entre elles échoue, l'interpréteur va lever une erreur. On peut alors attraper cette erreur avec le mot-clé except et gérer le code en fonction du type d'erreur.

Voici un bout de code qui tente d'associer un objet 3D (dont on aurait stocké la référence dans refObjet) à un matériau hypothétique dont la référence est stockée dans refMaterial.

. . . .

# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


try :

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

except :

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

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


refMaterial.diffuse_color = (0.4, 0.8, 0.4)

refCube.active_material = refMaterial

. . . .

08° Utilisez cette nouvelle technique pour créer des marches bleutées et des piliers dorés.

...CORRECTION...

# -*-coding:Utf-8 -*


import bpy # On importe la bibliothèque Blender Python


# Création d'un objet Cube s'il n'existe pas

if (not('Cube' in bpy.data.objects)) :

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

    refTemporaire = bpy.context.object

    refTemporaire.name = 'Cube'


# Récupération ou création des Materials

try :

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

except :

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

    matMarche = bpy.data.materials[ 'couleur_marche' ]


try :

    matPilier = bpy.data.materials[ 'couleur_pilier' ]

except :

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

    matPilier = bpy.data.materials[ 'couleur_pilier' ]


matMarche.diffuse_color = (0.3, 0.3, 0.8)

matPilier.diffuse_color = (0.8, 0.8, 0.3)

# Récupération du cube et des coordonnées du cube

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

x, y, z = refCube.location


# Création de l'escalier

numeroMarche = 0

while z > 0 :


    # Création de la marche

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

    refMarche = bpy.context.object

    refMarche.dimensions = (2,4,0.25)

    refMarche.active_material = matMarche


    if numeroMarche%3 == 0 :

        # Création des rambardes

        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y-1.8, z+0.5))

        refRambardeG = bpy.context.object

        refRambardeG.dimensions = (0.1,0.1,1)

        refRambardeG.active_material = matPilier


        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y+1.8, z+0.5))

        refRambardeD = bpy.context.object

        refRambardeD.active_material = matPilier

        refRambardeD.dimensions = (0.1,0.1,1)

        refMarche.active_material = matMarche


        bpy.ops.mesh.primitive_cylinder_add( location = (x+0.75, y, z+0.5))

        refRambardeM = bpy.context.object

        refRambardeM.dimensions = (0.1,0.1,1)

    refRambardeM.active_material = matPilier


    # Modification des coordonnées pour la prochaine création

    x = x + 0.5

    z = z - 0.25


    # Incrémentation de 1 du numéro de marche

    numeroMarche += 1

09° Et si vous tentiez de faire une marche sur deux un peu plus foncée que l'autre ? N'oubliez pas l'opérateur %.

On pourrait aller encore bien plus loin mais vous avez les bases. Voici les différentes erreurs possibles :

Quelques exemples et types d'erreur

>>> 5+"e"

TypeError: unsupported operand type(s) for +: 'int' and 'str'


>>> type(a)

NameError: name 'a' is not defined


>>> bpy.data.objects['toto le taureau']

KeyError: 'bpy_prop_collection[key]: key "toto le taureau" not found'

Si vous voulez réagir à une erreur particulière, vous pourriez taper :

except ValueError:

A titre d'exemple, voici une cycloïde dont l'intensité varie avec l'altitude :

cycloide

On commence par définir une variable hauteur à 0 et on modifie la couleur et la position des suivants, tant que la hauteur du cube est bien inférieur au contenu de la variable hauteur_max.

import bpy # On importe la bibliothèque Blender Python

import math


rayon = 6

hauteur_max = 20

nbr_par_tour = 15

hauteur_par_tour = 7

angle_degre = 0

hauteur = 0

couleur = 0.3

delta_couleur = 0.05


while hauteur < hauteur_max:

    # Préparation des valeurs en cours

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

    x = rayon*math.cos(angle)

    y = rayon*math.sin(angle)

    nom = 'couleur' + str(couleur)# Permet de générer un nom à la texture en fonction de l'intensité

    # Création du matériau et de l'objet

    bpy.data.materials.new(nom)# Génère un nouveau matériau nommé nom

    bpy.data.materials[nom].diffuse_color = (couleur, 0.04, 0.04)# Fixe la couleur du matériau nommé nom

    bpy.ops.mesh.primitive_cube_add(location=(x,y,hauteur))# On crée un nouveau cube qui devient l'objet actif

    bpy.context.object.active_material = bpy.data.materials[nom]# On affecte notre texture à notre cube sélectionné

    # Préparation des valeurs suivantes

    hauteur = hauteur + ( hauteur_par_tour / nbr_par_tour )

    angle_degre = angle_degre + 360 / nbr_par_tour

    angle_degre = angle_degre % 360 # L'angle est défini modulo 360° : 370° deviendrait 10°

    couleur = couleur + delta_couleur

10° Utiliser le script donné avec des cubes pour créer une image proche de la suivante : une chaîne de sphères de plus en plus petites.

chaine qui tourne