Tutoriel sur les add-ons

Audience visée

Ce tutoriel est conçu pour aider les artistes techniciens ou les développeurs à apprendre à étendre Blender. Une compréhension des bases de Python est exigée de ceux qui mettent en œuvre ce tutoriel.

Prérequis

Avant de poursuivre ce tutoriel, vous devriez…

  • Avoir une connaissance des bases de travail dans Blender.
  • Savoir comment lancer un script dans l’Éditeur de texte de Blender.
  • Avoir une compréhension des types de primitives de Python (int, boolean, string, list, tuple, dictionary, et set).
  • Avoir une connaissance du concept des modules Python.
  • Avoir une compréhension de base des classes (orientation objet) dans Python.

Lectures suggérées avant de commencer ce tutoriel.

To best troubleshoot any error message Python prints while writing scripts, you run Blender from a terminal. See Use The Terminal.

Qu’est-ce qu’un add-on ?

un add-on est un module Python avec quelques exigences supplémentaires que Blender peut l’afficher dans une liste avec des informations utiles.

En guise d’exemple, voici l’add-on le plus simple possible

bl_info = {"name": "My Test Add-on", "category": "Object"}
def register():
    print("Hello World")
def unregister():
    print("Goodbye World")
bl_info
is a dictionary containing add-on metadata such as the title, version and author to be displayed in the Preferences add-on list.
register
est une fonction qui se lance seulement une fois l’add-on activé, ce qui signifie que le module peut être chargé sans activation de l’add-on.
unregister
est une fonction pour décharger tout ce qui a été installé par register, celle-ci est appelée quand l’add-on est désactivé.

Notez que cet add-on ne fait rien concernant Blender (le module blender_api:bpy n’est pas importé par exemple).

Ceci est un exemple artificiel d’un add-on qui sert à illustrer le fait que les exigences de base d’un add-on sont simples.

Un add-on typiquement enregistrera des opérateurs, des panneaux, des éléments de menu, etc, mais il convient de noter que n’importe quel script peut faire cela, quand il a été exécuté depuis l’éditeur texte ou même depuis la console interactive – il y a intrinsèquement aucune différence avec un add-on qui s’autorise à s’intégrer dans Blender, une telle fonctionnalité est simplement offerte par le module blender_api:bpy pour que n’importe quel script puisse y accéder.

Aussi un add-on est simplement une manière d’encapsuler un module Python de manière à ce qu’un utilisateur puisse aisément l’utiliser.

Note

Running this script within the Text editor won’t print anything, to see the output it must be installed through the Preferences. Messages will be printed when enabling and disabling.

Votre premier add-on

L’add-on le plus simple possible ci-dessus est utile comme exemple mais pas plus. L’add-on suivant est simple mais montre comment intégrer un script dans Blender en utilisant un Operator qui est la manière typique de définir un outil accessible depuis les menus, boutons et raccourcis clavier.

Pour le premier exemple, nous allons écrire un script qui déplace simplement tous les objets dans une scène.

Écrire le script

Ajoutez le script suivant à l’éditeur de texte de Blender

import bpy

scene = bpy.context.scene
for obj in scene.objects:
    obj.location.x += 1.0

Cliquez le bouton Run Script, tous les objets dans la scène active sont déplacés d’une unité Blender.

Écrire l’add-on (simple)

Cet add-on prend le corps du script ci-dessus, et l’ajoute à la fonction execute() d’un opérateur.

bl_info = {
    "name": "Move X Axis",
    "category": "Object",
}

import bpy


class ObjectMoveX(bpy.types.Operator):
    """My Object Moving Script"""      # Use this as a tooltip for menu items and buttons.
    bl_idname = "object.move_x"        # Unique identifier for buttons and menu items to reference.
    bl_label = "Move X by One"         # Display name in the interface.
    bl_options = {'REGISTER', 'UNDO'}  # Enable undo for the operator.

    def execute(self, context):        # execute() is called when running the operator.

        # The original script
        scene = context.scene
        for obj in scene.objects:
            obj.location.x += 1.0

        return {'FINISHED'}            # Lets Blender know the operator finished successfully.

def register():
    bpy.utils.register_class(ObjectMoveX)


def unregister():
    bpy.utils.unregister_class(ObjectMoveX)


# This allows you to run the script directly from Blender's Text editor
# to test the add-on without having to install it.
if __name__ == "__main__":
    register()

Note

bl_info est réparti entre plusieurs lignes. C’est simplement une convention de style pour ajouter plus facilement des éléments.

Note

Plutôt que d’utiliser``bpy.context.scene``, nous utilisons l’argument context.scene passé à``execute()``. Dans la plupart des cas, ceux-ci seront les mêmes. Cependant, dans certains cas, un contexte personnalisé sera passé aux opérateurs aussi les auteurs de scripts devraient préférer l’argument context passé aux opérateurs.

Pour tester le script, vous pouvez le copier et le coller dans l’éditeur Texte de Blender et le lancer. Ceci va exécuter le script directement et appeler register immédiatement.

Cependant le lancement du script ne va pas déplacer les objets. Pour cela, vous devez exécuter l’opérateur nouvellement enregistré.

../../_images/advanced_scripting_addon-tutorial_operator-search-menu.png

Menu Operator Search.

Do this by pressing F3 to bring up the operator search menu and type in « Move X by One » (the bl_label), then Return.

Les objets devraient se déplacer comme précédemment.

Gardez cet add-on ouvert dans Blender pour la prochaine étape - installer.

Installer l’add-on

Once you have your add-on within in Blender’s Text editor, you will want to be able to install it so it can be enabled in the Preferences to load on startup.

Même si l’add-on ci-dessus est un test, parcourons néanmoins les étapes et ainsi vous saurez comment faire pour plus tard.

Pour installer le texte Blender comme un add-on, vous devrez d’abord l’enregistrer sur disque. Veuillez suivre les restrictions de nommage qui s’appliquent aux modules Python dont une extension .py.

Une fois le fichier sur disque, vous pouvez l’installer comme vous le feriez pour un add-on téléchargé.

Open the Preferences ‣ Add-ons ‣ Install… and select the file.

Maintenant l’add-on sera listé et vous pourrez l’activer en pressant la case à cocher, si vous voulez l’activer au démarrage, pressez Save as Default.

Note

La destination d’un add-on dépend de votre configuration de Blender. Pendant l’installation d’un add-on les chemins de la source et de la destination sont affichés sur la console. Vous pouvez aussi trouver les emplacements des chemins de l’add-on en lançant ceci dans la console Python.

import addon_utils
print(addon_utils.paths())

More is written on this topic here: Directory Layout.

Votre second add-on

Pour notre second add-on, nous nous focaliserons sur l’instanciation d’objet – ceci est – pour faire des copies liées d’un objet d’une façon semblable à ce que vous avez pu voir avec le modificateur Array.

Écrire le script

Comme précédemment, d’abord nous allons commencer par un script, le développer, puis le convertir en un add-on.

import bpy
from bpy import context

# Get the current scene
scene = context.scene

# Get the 3D cursor location
cursor = scene.cursor.location

# Get the active object (assume we have one)
obj = context.active_object

# Now make a copy of the object
obj_new = obj.copy()

# The new object has to be added to a collection in the scene
scene.collection.objects.link(obj_new)

# Now we can place the object
obj_new.location = cursor

Now try copying this script into Blender and run it on the default Cube. Make sure you click to move the 3D cursor before running as the duplicate will appear at the cursor’s location.

Après le lancement, notez que lorsque vous passez en Mode Édition pour modifier le Cube – toutes les copies changent. Dans Blender, ce point est connu comme Copies liées.

Ensuite nous allons faire cela dans une boucle, pour faire un tableau (array) d’objets entre l’objet actif et le curseur.

import bpy
from bpy import context

scene = context.scene
cursor = scene.cursor.location
obj = context.active_object

# Use a fixed value for now, eventually make this user adjustable
total = 10

# Add 'total' objects into the scene
for i in range(total):
    obj_new = obj.copy()
    scene.collection.objects.link(obj_new)

    # Now place the object in between the cursor
    # and the active object based on 'i'
    factor = i / total
    obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))

Try running this script with the active object and the cursor spaced apart to see the result.

With this script you’ll notice we’re doing some math with the object location and cursor, this works because both are 3D blender_api:mathutils.Vector instances, a convenient class provided by the blender_api:mathutils module which allows vectors to be multiplied by numbers and matrices.

Si vous êtes intéressé par ce point, lisez blender_api:mathutils.Vector – il y a beaucoup de fonctions utilitaires pratiques telles que l’obtention de l’angle entre deux vecteurs, le produit vectoriel, le produit scalaire ainsi que des fonctions plus avancées dans blender_api:mathutils.geometry telles que l’interpolation de courbe de Bézier et l’intersection rayon/triangle.

Pour l’instant nous allons nous focaliser à faire de ce script un add-on, mais il est bon de savoir que ce module de math 3D est disponible et peut vous aider avec une fonctionnalité plus avancée plus tard.

Écrire l’add-on

La première étape est de convertir le script tel quel en un add-on:

bl_info = {
    "name": "Cursor Array",
    "category": "Object",
}

import bpy


class ObjectCursorArray(bpy.types.Operator):
    """Object Cursor Array"""
    bl_idname = "object.cursor_array"
    bl_label = "Cursor Array"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        cursor = scene.cursor.location
        obj = context.active_object

        total = 10

        for i in range(total):
            obj_new = obj.copy()
            scene.collection.objects.link(obj_new)

            factor = i / total
            obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))

        return {'FINISHED'}

def register():
    bpy.utils.register_class(ObjectCursorArray)


def unregister():
    bpy.utils.unregister_class(ObjectCursorArray)


if __name__ == "__main__":
    register()

Tout ici a été couvert dans les étapes précédentes, il se peut que vous vouliez encore essayer de lancer l’add-on et considérer ce qui pourrait être fait pour le rendre plus utile.

Les deux choses les plus évidentes qui manquent sont – avoir le total fixé à 10, et avoir l’accès à l’opérateur depuis la barre d’espace n’est pas très pratique.

Ces deux ajouts sont expliqués ci-après, avec le script final après.

Propriété d’opérateur

il y a une diversité de types de propriétés qui sont utilisés pour les réglages d’outils, les types de propriété habituels comprennent : int, float, vector, color, boolean et string.

Ces propriétés sont gérées différemment des attributs de classe Python typiques parce que Blender a besoin de les afficher dans l’interface, enregistrer leurs réglages dans les configurations de clavier et garder les réglages pour une réutilisation.

Bien que ceci soit géré de manière clairement Pythonique, soyez conscient que vous êtes en fait en train de définir des réglages d’outils qui sont chargés dans Blender et accessibles par d’autres parties de Blender, en dehors de python.

Pour nous passer du littéral 10 pour total, nous allons utiliser une propriété d’opérateur. Les propriétés d’opérateur sont définies via le module bpy.props, ceci est ajouté au corps de la classe:

# moved assignment from execute() to the body of the class...
total: bpy.props.IntProperty(name="Steps", default=2, min=1, max=100)

# and this is accessed on the class
# instance within the execute() function as...
self.total

Ces propriétés de blender_api:bpy.props sont gérées spécialement par Blender quand la classe est enregistrée de sorte qu’elles s’affichent comme des boutons dans l’interface utilisateur. Il y a un grand nombre d’arguments que vous pouvez passer aux propriétés pour définir des limites, modifier les valeurs par défaut et afficher une info-bulle.

Voir aussi

blender_api:bpy.props.IntProperty

Ce document n’entre pas dans les détails sur l’utilisation d’autres types de propriétés. Cependant le lien ci-dessus comprend des exemples plus avancés d’utilisation de propriété .

Configuration de clavier

Dans Blender, les add-ons ont leur propre configuration de clavier de façon à ne pas interférer avec les configurations de clavier intégrées de Blender.

In the example below, a new object mode blender_api:bpy.types.KeyMap is added, then a blender_api:bpy.types.KeyMapItem is added to the key-map which references our newly added operator, using Shift-Ctrl-T as the key shortcut to activate it.

# store keymaps here to access after registration
addon_keymaps = []

def register():

    # handle the keymap
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')

    kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'T', 'PRESS', ctrl=True, shift=True)
    kmi.properties.total = 4

    addon_keymaps.append((km, kmi))


def unregister():

    # handle the keymap
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

Remarquez comment l’élément de configuration de clavier peut avoir un réglage total différent de celui par défaut défini par l’opérateur. Ceci vous permet d’avoir de multiples clés d’accès au même opérateur avec des réglages différents.

Note

While Shift-Ctrl-T is not a default Blender key shortcut, it is hard to make sure add-ons will not overwrite each other’s keymaps, At least take care when assigning keys that they do not conflict with important functionality within Blender.

Pour la documentation de l’API sur les fonctions listées ci-dessus, voir :

  • blender_api:bpy.types.KeyMaps.new,
  • blender_api:bpy.types.KeyMap,
  • blender_api:bpy.types.KeyMapItems.new,
  • blender_api:bpy.types.KeyMapItem.

En les combinant tous ensemble

bl_info = {
    "name": "Cursor Array",
    "category": "Object",
}

import bpy


class ObjectCursorArray(bpy.types.Operator):
    """Object Cursor Array"""
    bl_idname = "object.cursor_array"
    bl_label = "Cursor Array"
    bl_options = {'REGISTER', 'UNDO'}

    total: bpy.props.IntProperty(name="Steps", default=2, min=1, max=100)

    def execute(self, context):
        scene = context.scene
        cursor = scene.cursor.location
        obj = context.active_object

        for i in range(self.total):
            obj_new = obj.copy()
            scene.collection.objects.link(obj_new)

            factor = i / self.total
            obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor))

        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(ObjectCursorArray.bl_idname)

# store keymaps here to access after registration
addon_keymaps = []


def register():
    bpy.utils.register_class(ObjectCursorArray)
    bpy.types.VIEW3D_MT_object.append(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    # Note that in background mode (no GUI available), keyconfigs are not available either,
    # so we have to check this to avoid nasty errors in background case.
    kc = wm.keyconfigs.addon
    if kc:
        km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
        kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'T', 'PRESS', ctrl=True, shift=True)
        kmi.properties.total = 4
        addon_keymaps.append((km, kmi))

def unregister():
    # Note: when unregistering, it's usually good practice to do it in reverse order you registered.
    # Can avoid strange issues like keymap still referring to operators already unregistered...
    # handle the keymap
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

    bpy.utils.unregister_class(ObjectCursorArray)
    bpy.types.VIEW3D_MT_object.remove(menu_func)


if __name__ == "__main__":
    register()
../../_images/advanced_scripting_addon-tutorial_in-menu.png

Dans le menu.

Run the script (or save it and add it through the Preferences like before) and it will appear in the Object menu.

../../_images/advanced_scripting_addon-tutorial_op-prop.png

Propriété d’opérateur.

Après l’avoir sélectionné dans le menu, vous pouvez choisir combien d’instances du cube vous voulez créer.

Note

Directly executing the script multiple times will add the menu each time too. While not useful behavior, there’s nothing to worry about since add-ons will not register themselves multiple times when enabled through the Preferences.

Conclusions

Les add-ons peuvent encapsuler certaines fonctionnalités pour l’écriture d’outils pour améliorer votre flux de travail ou pour écrire des utilitaires pour les autres.

Bien qu’il y ait des limites à ce que Python peut faire dans Blender, il y a certainement beaucoup de choses qui peuvent être réalisées sans avoir à plonger dans le code C/C++ de Blender.

L’exemple du tutoriel est limité, mais montre l’utilisation de l’API Blender dans des tâches courantes que vous pouvez étendre pour écrire vos propres outils.

Lectures recommandées

Blender est fourni avec des modèles commentés qui sont accessibles dans l’entête de l’éditeur de texte. Si vous avez des domaines spécifiques dont vous voulez voir le code exemple, c’est un bon point de départ.

Voici quelques sites que vous pourriez vouloir consulter après la lecture de ce manuel.