Licence CC BY-NC-SA

Tracer et dessiner

Nous allons pouvoir entrer dans le vif du sujet ! À travers cette partie nous verrons comment tracer et dessiner des figures.

Se repérer et tracer

Pour pouvoir se repérer dans la fenêtre, turtle met en place un repère à deux dimensions. Par défaut, celui-ci est centré dans la fenêtre. Ce repère nous permet de nous déplacer aisément de x sur l’axe des abscisses et de y sur l’axe des ordonnées. Le centre du repère, c’est-à-dire le point (x = 0, y = 0), est l’endroit où le curseur apparaît, sa maison en quelque sorte. Cela n’est pas bien compliqué à comprendre, il faut juste s’habituer à penser dans le plan. L’image suivante permet de mieux visualiser ce que nous venons de dire et comporte quelques exemples de points :

Le repère.
Le repère.
Nettoyer l’écran

Au cours de notre utilisation de turtle, il est possible que nous ayons besoin de nettoyer l’écran. Pour cela, nous pouvons utiliser clear qui permet d’effacer ce que nous avons dessiné. En plus, nous pouvons aussi utiliser reset qui fait la même chose et réinitialise les valeurs du curseur à leurs valeurs par défaut (le crayon retourne à l’origine du repère, retrouve son orientation originale, sa largeur de trait par défaut, etc…). Ces deux fonctions ne modifient donc pas les configurations liées à la fenêtre, comme le titre ou la couleur de fond par exemple. Enfin, elles ne prennent aucun paramètre.

turtle.clear()  #Efface les dessins du crayon
turtle.reset()  #Fait de même et réinitialise le crayon
Avancer et reculer

Pour tracer, il faut se déplacer. Et pour déplacer le curseur, turtle nous offre plusieurs fonctions, comme forward et backward, respectivement pour avancer et reculer d’une distance que l’on passe en paramètre. Elles ont aussi chacune leur version abrégée, respectivement fdet bk.

turtle.forward(turtle.window_width()/3)  #Avance d'un tiers de la largeur de la fenêtre
turtle.backward(turtle.window_width()/2)  #Recule de la moitié de la largeur de la fenêtre
turtle.bk(50)  #Recule de 50px
turtle.fd(0)  #Avance de 0px, donc n'avance pas
Se déplacer à des coordonnées données

Avec goto, en lui fournissant une coordonnée x et une coordonnée y, nous pouvons nous rendre directement à un point (x, y) donné. De plus, nous pouvons aussi modifier uniquement la position en abscisse du curseur avec setx et la position en ordonnée avec sety, en leur passant la nouvelle valeur. Enfin, puisque nous avons parlé du centre du repère, notons que la fonction home permet d’y retourner.

turtle.goto(100, 100)  #Position (100, 100)
turtle.setx(20)  #Position(20, 100)
turtle.sety(-80)  #Position(20, -80)
turtle.home()  #Position(0, 0) (équivalent à turtle.goto(0, 0))
Lever ou baisser le crayon

Si nous ne pouvions pas nous déplacer dans la fenêtre sans laisser de trace, ce ne serait pas très amusant. Or, la fonction up nous permet de lever le crayon tandis que la fonction down nous permet de l’abaisser. Elles ne prennent aucun paramètre. Grâce à elles, nous pouvons choisir de tracer ou non :

turtle.up()  #Lève le crayon
turtle.forward(150)  #Avance de 150px sans tracer
turtle.down()  #Abaisse le crayon
turtle.backward(50)  #Recule de 50px en traçant
Changer la taille du traçage

Faire des traits, c’est bien, mais pouvoir choisir la taille, c’est encore mieux. En passant la nouvelle largeur de nos traits à pensize, nous pouvons le faire. En ne passant rien, la fonction nous renvoie la taille actuelle.

print(turtle.pensize())  #Affiche '1'
turtle.pensize(5.5)  #Modifie la largeur du traçage
print(turtle.pensize())  #Affiche '5.5'

Pour le moment, ce sont des fonctions plutôt basiques. Ce serait plus intéressant de pouvoir faire des figures, en assemblant les traits, et c’est ce que nous allons faire dans la section suivante !

Dessiner des figures simples

Jusqu’à présent, nous avons vu comment ouvrir une fenêtre et comment nous déplacer dans celle-ci. À présent, nous allons aller encore plus loin en dessinant nos premières figures.

Changer l’angle

Pour dessiner aisément, il nous manque tout de même quelque chose, et je pense que vous vous en êtes rendu compte : il faut que l’on puisse choisir l’inclinaison de notre trait, c’est-à-dire l’angle. En effet, jusqu’à présent nous avons été limités dans nos déplacements.

Or, turtle nous permet justement de faire varier la direction du curseur. Par défaut, lorsque l’on ouvre une fenêtre avec turtle, le crayon est orienté vers l’Est : l’angle est de 0 (ou 360). Pour jouer avec les angles, nous avons les fonctions right et left qui permettent de tourner respectivement vers la droite ou vers la gauche d’un angle passé. Parfois, il est plus simple d’utiliser setheading qui change directement l’angle avec la valeur passée. Vous pouvez aussi connaître la direction actuelle de votre crayon en utilisant la fonction heading qui ne prend aucun paramètre. Toutes ces explications sont illustrées avec l’image ci-dessous ainsi que par l’exemple qui la suit :

L'orientation.
L’orientation.
turtle.setup()  #Initialise la fenêtre 
print(turtle.heading())  #Affiche 0.0 : le crayon pointe vers le point bleu : Est
turtle.left(90)  #Pointe vers le point jaune : Nord
turtle.right(270)  #Pointe vers le point vert : Ouest
turtle.setheading(0)  #Pointe de nouveau vers le point bleu
turtle.setheading(-90)  #Pointe à l'opposé du point jaune : Sud
print(turtle.heading())  #Affiche '270.0'

Concrètement, vous conviendrez que nous sommes désormais beaucoup plus libres. N’hésitez pas à essayer et à vous approprier ces notions, car elles seront vraiment utiles pour la suite. Voici un exemple d’utilisation de ce que l’on vient d’apprendre :

Code :

import turtle

LARGEUR, HAUTEUR = 640, 480

if __name__ == "__main__":
    turtle.forward(LARGEUR/3)  #Avance de d'un tiers de la largeur
    turtle.left(80)  #Tourne de 80° à gauche
    turtle.up()  #Lève le curseur
    turtle.forward(HAUTEUR/4)  #Avance d'un quart de la hauteur
    turtle.down()  #Baisse le curseur
    turtle.right(180)  #Tourne à 180 à droite
    turtle.backward(HAUTEUR/4)  #Recule d'un quart de la hauteur
    turtle.pensize(3)  #Change l'épaisseur du tracé
    turtle.home()  #Retourne à la maison
    turtle.exitonclick()  #Clique gauche pour fermer 
Résultat exemple d'utilisation des angles.
Résultat exemple d’utilisation des angles.

Notons au passage que home réinitialise aussi l’orientation en plus de la position, c’est pourquoi nous voyons que le curseur a un angle de 0 à la fin puisque nous terminons le traitement par cela.

Pour terminer sur les angles, nous pouvons brièvement parler de la fonction towards qui prend en paramètre les coordonnées d’un point et nous retourne l’angle qu’il faudrait pour aller à ce point. Ainsi, ces deux morceaux de code donnent quasiment le même résultat (dans le second cas, l’angle final est celui d’avant l’appel à la fonction)

angle = turtle.towards(0, 90)
print(angle)  #Affiche '90.0'
turtle.setheading(angle)  #Angle : 90.0
turtle.forward(90)  #Position : (0, 90); Angle : 90.0
turtle.goto(0, 90)  #Position : (0, 90); Angle : 0.0
Dessiner des figures simples

Voilà, nous sommes désormais totalement capable de tracer nos propres figures grâce à ce que nous avons appris. Nous allons donc nous exercer en faisant quelques polygones. Triangle équilatérale, carré et octogone régulier seront nos invités. Avant que vous lisiez la suite, je vous encourage à essayer de dessiner par vous-mêmes ces figures.

Commençons avec le triangle équilatéral. Pour rappel, un triangle équilatéral est un triangle dont les côtés ont la même longueur ce qui implique que chaque angle a une valeur de 60° (par définition, la somme des angles d’un triangle vaut 180°). Une fois que l’on a cela en tête, nous pouvons implémenter une solution explicite :

#Un exemple de triangle équilatéral
longueur_cote = 200
turtle.forward(longueur_cote)  #1er côté
turtle.left(360/3)  #Angle
turtle.forward(longueur_cote)  #2ème côté
turtle.left(360/3)  #Angle
turtle.forward(longueur_cote)  #3ème côté

Pour le carré et l’octogone, nous appliquerons le même principe. Pour le carré, nous avons quatre côtés de même longueur ainsi que quatre angle de 90°. Voici une solution :

#Un exemple de carré
longueur_cote = 200
for i in range(4):
    turtle.forward(longueur_cote)  #Côté
    turtle.left(90)  #Angle

L’octogone a quant à lui 8 côtés et des angles de 45° (360° divisé par 8 côtés). Une solution est :

#Un exemple d'octogone
longueur_cote = 100
for i in range(8):
    turtle.forward(longueur_cote)  #Côté
    turtle.left(360/8)  #Angle
Résultats pour ces figures.
Résultats pour ces figures.

Voilà, pour pratiquer, vous pouvez essayer de dessiner des figures plus compliquées voire de généraliser au cas d’un polygone, ou encore de dessiner à d’autres endroits que depuis le centre du repère. Il est temps de parler d’une dernière figure dont nous n’avons pas encore parlée : le cercle.

Utiliser les cercles

Pour les cercles, nous pouvons éviter de réinventer la roue puisqu’il existe une fonction déjà toute prête : circle. Au minimum, nous devons passer à celle-ci le rayon de notre cercle. De plus, nous pouvons aussi lui passer un angle (extent) qui permet de tracer uniquement une partie du cercle, ainsi qu’un nombre (steps) qui correspond au nombre d’étapes pour tracer. L’orientation du crayon a des conséquences sur la manière dont le cercle sera tracé. Pour mieux comprendre, voyons ce que ça peut donner :

turtle.circle(120)  #Trace un cercle de rayon 120px
turtle.circle(70, 180)  #Trace un demi-cercle de rayon 70px
turtle.circle(90, steps = 8)  #Trace un octogone de longueur 90px
turtle.circle(40, 180, 4)  #Trace la moitié d'un octogone de longueur 40px

Voici un exemple de code pour afficher cinq cercles du plus petit au plus grand, avec pour centre l’origine du repère :

Code :

import turtle

if __name__ == "__main__":
    rayon, ecart = 50, 20
    for i in range(5):
        turtle.up()
        turtle.goto(0, -rayon)
        turtle.down()
        turtle.circle(rayon)
        rayon += ecart  #Augmente la valeur de rayon
    turtle.up()
    turtle.home()
    turtle.exitonclick()
Résultat exemple cercles.
Résultat exemple cercles.

Voilà, nous savons désormais dessiner des figures simples. Dans la section suivante, nous allons complexifier nos figures.

Dessiner des choses plus complexes

Un dessin plus complexe (1)

Grâce à ce que nous venons d’apprendre, nous pouvons désormais réaliser des figures plus complexes, comme le montre l’image suivante par exemple. Pour réaliser cela, j’ai utilisé deux fonctions que nous n’avons pas encore vues. Tout d’abord, la fonction position, qui ne prend aucun paramètre et retourne, comme son nom l’indique, la position du crayon. Ensuite, j’ai aussi fait appel à la fonction distance qui prend en paramètre les coordonnées x et y d’un point et qui retourne la distance entre le curseur et ce point. De cette manière, j’ai pu connaître facilement la distance entre le centre du dessin, le point (0, 0) et le coin des petits carrés par lesquels je souhaitais faire passer le cercle : c’est-à-dire le rayon. Remarquez que le dessin a pour centre le centre du repère.

Un dessin un peu plus complexe.
Un dessin un peu plus complexe.

Concernant le code, nous commençons par tracer le grand carré, puis les quatre petits, et nous terminons par le cercle. L’ensemble a pour centre le point d’origine du repère. Pour tracer chaque carré, nous nous plaçons à son coin bas gauche. Les commentaires vous aideront à comprendre. Si vous avez du mal, n’hésitez pas à prendre une feuille de papier et un crayon pour vous aider à visualiser.

Code :

import turtle

def deplacer_sans_tracer(x, y = None):
    """Fonction pour se déplacer à un point sans tracer"""
    turtle.up()
    if (isinstance(x, tuple) or isinstance(x, list)) and len(x) == 2:
        turtle.goto(x)
    else:
        turtle.goto(x, y)
    turtle.down()

def triangle_dans_carre(long_carre):
    """Fonction pour tracer un triangle à l'intérieur du carré"""
    #On prend la position du curseur qui est sur le coin bas gauche
    pos_coin_bg = turtle.position()
    #On trace les deux traits restants, la base étant déjà faite
    turtle.goto(pos_coin_bg[0]+long_carre/2, pos_coin_bg[1]+long_carre)
    turtle.goto(pos_coin_bg[0]+long_carre, pos_coin_bg[1])

def carre_avec_triangle(longueur):
    """Fonction pour tracer un carré avec un triangle à l'intérieur"""
    for i in range(4):
        turtle.forward(longueur)
        turtle.left(90)
    triangle_dans_carre(longueur)

if __name__ == "__main__":
    #On initialise les longueurs du grand carré et des petits carrés
    longueur_1, longueur_2 = 150, 75
    #On se positionne au coin bas gauche de notre futur grand carré
    deplacer_sans_tracer(-longueur_1/2, -longueur_1/2)
    #On le dessine
    carre_avec_triangle(longueur_1)
    #On prépare les valeurs des coin bas gauche des petits carrés
    coins = [(longueur_1/2, longueur_1/2),
             (-longueur_1/2-longueur_2, longueur_1/2),
             (-longueur_1/2-longueur_2, -longueur_1/2-longueur_2),
             (longueur_1/2, -longueur_1/2-longueur_2)]
    #On dessine notre quatre petits carrés
    for coin in coins:
        deplacer_sans_tracer(coin)
        carre_avec_triangle(longueur_2)
    #On retourne au centre de notre dessin
    deplacer_sans_tracer(0, 0)
    #On prend la distance entre le centre et le coin par lequel le cercle passera
    rayon = turtle.distance(longueur_1/2+longueur_2, longueur_1/2+longueur_2)
    #On se déplace et on trace notre cercle
    deplacer_sans_tracer(0, -rayon)
    turtle.circle(rayon)
    #On retourne à la maison, et on prend un angle de 90°
    deplacer_sans_tracer(0, 0)
    turtle.left(90)
    turtle.exitonclick()
Les points

La fonction dot nous permet d’afficher un point dans le canvas. Pour cela, nous pouvons passer en paramètre le diamètre du point, et nous pouvons aussi, si le cœur nous en dit, passer une couleur. Si nous ne lui passons rien, le point aura un diamètre par défaut et la couleur de traçage du curseur (noir par défaut). Nous étudierons le coloriage plus en détails dans la partie suivante.

turtle.dot(100, 'red')  #Imprime un point rouge d'un diamètre de 100px
turtle.dot(50, 'yellow')  #Imprime un point jaune d'un diamètre de 50px
turtle.dot(25)  #Imprime un point noir d'un diamètre de 25px

Remarquons que si nous avions imprimé les points dans l’ordre inverse, nous n’aurions vu que le rouge puisque celui aurait masqué le jaune qui lui-même aurait masqué le noir. Nous pouvons aussi noter qu’un point sera tracé même si le crayon est levé.

Puisque comme d’habitude, rien ne nous empêche de jouer avec ce que nous apprenons, voici un programme qui imprime dix points de plus petit en plus grand en allant de gauche à droite et qui ont une couleur différente :

Code :

import turtle
from random import randint

COULEURS = ['black', 'grey', 'brown', 'orange', 'pink', 'purple',
            'red', 'blue', 'yellow', 'green']

if __name__ == "__main__":
    turtle.setup(650, 100)
    diametre = 15
    turtle.up(); turtle.setx(-turtle.window_width()/2+2*diametre); turtle.down()
    #Pour le nombre de couleurs disponibles
    for i in range(len(COULEURS)):
        #On choisit un couleur aléatoirement
        index_choisi = randint(0, len(COULEURS)-1)
        #On imprime un point de cette couleur
        turtle.dot(diametre, COULEURS[index_choisi])
        #On supprime la couleur choisie pour éviter de la rechoisir
        del COULEURS[index_choisi]
        #On met à jour le diamètre et on se déplace pour le prochain point
        diametre += 5; turtle.up(); turtle.fd(1.5*diametre); turtle.down()
    turtle.exitonclick()
Résultat exemple de points.
Résultat exemple de points.
Les tampons

Les points, ce n’est pas tout ! Nous pouvons aussi, de la même manière, imprimer la forme du curseur avec stamp. Cette fonction ne prend aucun paramètre et retourne l’identifiant du tampon. Ce dernier nous sert à supprimer le tampon de la fenêtre en le passant à clearstamp. Nous pouvons aussi supprimer plusieurs tampons de l’écran en fournissant un nombre à clearstamps, voire tous en ne lui passant aucune valeur ou None. Voici ci-dessous un exemple d’utilisation illustré :

id_tampons = []
#L'opération suivante est répétée à maintes reprises tout en se déplaçant
id_tampons.append(turtle.stamp())  #Tamponne et on enregistre l'identifiant
turtle.clearstamp(id_tampons[14])  #Supprime le 15ème tampon
turtle.clearstamps(9)  #Supprime les 9 premiers tampons
turtle.clearstamps(-10)  #Supprime les 10 derniers tampons
turtle.clearstamps()  #Supprime tous les tampons restants
Résultat exemple tampon aux différentes étapes.
Résultat exemple tampon aux différentes étapes.
Un dessin plus complexe (2)

Voici un autre exemple un peu plus complexe. Ici, nous dessinons un ciel étoilé. Pour ce faire, nous avons codé une fonction etoile qui se charge de tracer une étoile d’une longueur donnée, et nous allons nous en servir dans la boucle principale, tout en veillant à ce que l’étoile ne soit pas dessinée hors de notre fenêtre. Nous pourrions améliorer le code pour éviter qu’une étoile soit tracée par dessus une autre par exemple.

import turtle
from random import randint

LARGEUR, HAUTEUR = 640, 480
LONGUEUR_MIN, LONGUEUR_MAX = 5, 20

def deplacer_sans_tracer(x, y = None):
    """Fonction pour se déplacer à un point sans tracer"""
    turtle.up()
    if (isinstance(x, tuple) or isinstance(x, list)) and len(x) == 2:
        turtle.goto(x)
    else:
        turtle.goto(x, y)
    turtle.down()
        
def etoile(longueur):
    """Fonction pour dessiner une étoile"""
    turtle.setheading(180-2*72)
    for i in range(5):
        turtle.forward(longueur)
        turtle.left(180-180/5)
        
if __name__ == "__main__":
    turtle.setup(LARGEUR, HAUTEUR)
    turtle.speed(0) #Met la vitesse de traçage la plus rapide
    nb_etoiles, longueur_etoile = 20, 0
    for i in range(nb_etoiles):
        longueur_etoile = randint(LONGUEUR_MIN, LONGUEUR_MAX)
        deplacer_sans_tracer(randint(-LARGEUR//2+LONGUEUR_MAX//2, LARGEUR//2-LONGUEUR_MAX),
                             randint(-HAUTEUR//2+LONGUEUR_MAX//2, HAUTEUR//2-LONGUEUR_MAX))
        etoile(longueur_etoile)
    deplacer_sans_tracer(0, 0)
    turtle.exitonclick()
Un ciel étoilé.
Un ciel étoilé.

Voilà, j’espère que vous avez suivi avec attention, car c’est le moment de mettre tout cela en pratique !

TP : De bien jolis dessins

Nous voilà aux travaux pratiques de cette partie ! C’est le moment de vérifier que vous savez dessiner avec ce que nous avons appris. Pour ce faire, il y aura deux exercices où il faudra coder le programme permettant de reproduire le dessin (ne vous souciez pas des longueurs). Si vous êtes bloqué, n’hésitez pas à retourner voir ce que nous avons précédemment pour vous aider et à procéder par étapes. Enfin, lors du troisième exercice, nous nous intéresserons au Flocon de Von Koch.

Exercice 1

Voici la première figure à reproduire :

Exercice 1.
Exercice 1.

La correction :

import turtle

def deplacer_sans_tracer(x, y = None):
    """Fonction pour se déplacer à un point sans tracer"""
    turtle.up()
    if (isinstance(x, tuple) or isinstance(x, list)) and len(x) == 2:
        turtle.goto(x)
    else:
        turtle.goto(x, y)
    turtle.down()
        
def carre(longueur):
    """Fonction pour tracer un carré depuis le coin bas gauche"""
    for nb_cote in range(4):
        turtle.forward(longueur)
        turtle.left(90)

def point_octogone(longueur, diametre = 5, couleur = 'black'):
    """Fonction pour faire les points des sommets d'un octogone"""
    for nb_cote in range(8):
        turtle.dot(diametre, couleur)
        turtle.up(); turtle.forward(longueur); turtle.down()
        turtle.left(360/8)
        
if __name__ == "__main__":
    #Deux carrés
    longueurs_carre = [90, 180]
    for longueur in longueurs_carre:
        deplacer_sans_tracer(-longueur/2,-longueur/2)
        carre(longueur)
    #Cercle
    rayon = turtle.distance(0,0)  #nous sommes alors sur le coin bas gauche
    deplacer_sans_tracer(0, -rayon)
    turtle.circle(rayon)
    #Octogone de point
    deplacer_sans_tracer(-1.25*rayon/2, -1.25*rayon-30)
    point_octogone(1.25*rayon, 25, 'red')
    #Retour maison
    deplacer_sans_tracer(0, 0)
    turtle.exitonclick()
Exercice 2

Voici la seconde figure à reproduire :

Exercice 2.
Exercice 2.

La correction :

import turtle

LARGEUR, HAUTEUR = 640, 480
        
if __name__ == "__main__":
    turtle.setup(LARGEUR, HAUTEUR)
    turtle.speed("fast") #Met la vitesse de traçage à rapide
    ecart = 4
    for i in range(30):
        turtle.stamp()
        turtle.left(30)
        turtle.up(); turtle.forward(ecart); turtle.down()
        ecart += 3
    turtle.exitonclick()
Exercice 3 : Flocon de Von Koch

Un flocon de Von Koch est l’ensemble de trois courbes de Von Koch constituant chaque côté du triangle équilatéral initial. Vous pouvez trouver plus d’explication à propos de cette figure et de sa construction ici.

À travers cet exercice, il va falloir faire une fonction pour dessiner un flocon de Von Koch en fonction de la longueur des côtés du triangle ainsi que du nombre d’étapes permettant choisir le nombre de pics de notre flocon (par exemple, avec 0 et une longueur non nulle, nous avons juste un triangle équilatéral). Cet exercice étant un peu plus complexe, je vous conseille de découper votre progression ainsi : tout d’abord, essayez de programmer une fonction réalisant une courbe de Von Koch selon les paramètres précédemment mentionnés, puis, dans un second temps, réalisez une fonction pour tracer un flocon selon les paramètres.

Un exemple de flocon de Von Koch avec 3 étapes.
Un exemple de flocon de Von Koch avec 3 étapes.

La correction :

import turtle
 
def courbe_koch(longueur, etape):
    """Fonction récursive pour dessiner une courbe de Von Koch
    (une fonction récursive étant une fonction s'appelant elle-même"""
    if etape == 0:
        turtle.forward(longueur)
    else:
        courbe_koch(longueur/3, etape-1)
        turtle.left(60)
        courbe_koch(longueur/3, etape-1)
        turtle.right(120)
        courbe_koch(longueur/3, etape-1)
        turtle.left(60)
        courbe_koch(longueur/3, etape-1)
 
def flocon_koch(longueur, etape):
    """Fonction pour dessiner un flocon de Von Koch
    depuis le coin haut gauche"""
    for i in range(3):  #Pour chaque côté du triangle initial
        courbe_koch(longueur, etape)  #Courbe de Von Koch
        turtle.right(120)

if __name__ == "__main__":
    flocon_koch(100, 3)

Voilà, la récursivité s’appliquant bien à ce genre de figure, nous avons une fonction récursive pour tracer une courbe de Von Koch, puis une fonction traçant un flocon de Von Koch à l’aide de cette première.


Désormais, nous savons tracer et dessiner en utilisant turtle. Si vous trouvez que ça manque encore de couleurs, vous allez être ravis, car c’est justement le but de la partie suivante !