Comment: Utiliser POV-Ray avec Blender (Page Developpeurs)
Go To User's page ► |
- POV-Ray Site
- Exporter Quick Start
- POV-Ray Documentation
- Sample Materials
- POV-Ray centric default workspace
- ---
- ---
- ---
- ---
- ---
- ---
- ---
- ---
- design ideas
- regression files
Le module complémentaire Blender vers POV-Ray et vice versa (alias "POV@Ble") a commencé comme un seul fichier Python.
Pour être conforme aux critères d'extensions livrables avec Blender, ce module complémentaire devait :
- fournir quelques informations générales sur le module complémentaire : son nom et sa version,
- définir du code pour effectuer des actions, principalement via des « opérateurs » ou des « fonctions »
- et s'assurer qu'ils sont inscrits au registre afin qu'ils puissent être utilisés.
bpy
Blender dispose d'un interpréteur Python intégré qui est chargé au démarrage de Blender et reste actif pendant que Blender est en cours d'exécution. Cet interpréteur exécute des scripts pour dessiner l’interface utilisateur et est également utilisé pour certains outils internes de Blender. L'interpréteur intégré de Blender fournit un environnement Python typique, donc le code des didacticiels sur la façon d'écrire des scripts Python peut également être exécuté avec l'interpréteur de Blender. Blender fournit ses modules Python, tels que bpy et mathutils, à l'interpréteur intégré afin qu'ils puissent être importés dans un script et donner accès aux données, classes et fonctions de Blender. Les scripts qui traitent les données de Blender devront faire un import de module(s) pour fonctionner. Ainsi, de tous les modules, s'il n'y en a aucun autre, bpy est généralement toujours importé dans les scripts Blender. (sauf dans la console interactive où cette importation se fait automatiquement pour plus de commodité)
Metadonnées de l'add-on
bl_info = {
"name": "Persistence of Vision",
"author": "Campbell Barton, "
"Maurice Raybaud, "
"Leonid Desyatkov, "
"Bastien Montagne, "
"Constantin Rahn, "
"Silvio Falcinelli",
"version": (0, 1, 1),
"blender": (2, 81, 0),
"location": "Render Properties > Render Engine > Persistence of Vision",
"description": "Persistence of Vision integration for blender",
"doc_url": "{BLENDER_MANUAL_URL}/addons/render/povray.html",
"category": "Render",
"warning": "Under active development, seeking co-maintainer(s)",
}
Avant Blender 4.2, les informations générales sur le module complémentaire étaient définies dans un dictionnaire nommé
bl_info
Chaque clé de ce dictionnaire fournissait à Blender des informations spécifiques sur le module complémentaire, même si toutes n'étaient pas d'égale importance. La plupart des informations étaient utilisées dans la boîte de dialogue des préférences utilisateur et aidaient l'utilisateur à trouver et sélectionner un module complémentaire.
name
Une sorte de nom de code pour l'addon, par lequel il est référencé dans les sources de Blender... (le nom de l'interface utilisateur peut afficher autre chose)
author
Si vous contribuez, vous serez crédité ici !
version
Numéro de version de l'outil. Tout schéma de numérotation est valide, à condition qu'il s'agisse d'un tuple de trois nombres entiers. De préférence, ne déplacez que la dernière position, mais nous pourrions opter pour un schéma plus structuré plus tard.
blender
La version minimale de Blender requise par ce module complémentaire. Encore un tuple de trois entiers. Même si vous vous attendez à ce que les choses fonctionnent avec des versions plus anciennes et plus récentes, cela peut être une bonne idée de répertorier la version la plus ancienne avec laquelle vous avez réellement testé votre module complémentaire ! Il est important de développer avec quelque chose d'assez stable, puis de tester avec la dernière version juste avant la validation, mais ne mettez généralement pas à jour le numéro de version compatible, uniquement lorsque quelque chose tombe en panne à ce stade.
category
La catégorie dans les préférences utilisateur sous laquelle notre module complémentaire est regroupée. Il exploite des moteurs compatibles POV qui sont des moteurs de rendu, il était donc logique de l'ajouter historiquement à la catégorie Render. Malheureusement, cela ne reflète pas la nature polyvalente qu'il a fini par développer (comme la livraison dans un importateur, la coloration syntaxique de l'éditeur de texte, etc...). Si plusieurs catégories sont possibles, nous devrions peut-être les ajouter.
location
Où trouver le module complémentaire une fois qu'il est activé. Cela peut être une référence à un panneau spécifique ou à un cas particulier, une description de son emplacement dans un menu.
description
Une description concise de ce que fait le module complémentaire.
warning
S'il ne s'agit pas d'une chaîne vide, le module complémentaire apparaîtra avec un signe d'avertissement dans les préférences utilisateur. Vous pouvez l'utiliser pour marquer un module complémentaire comme expérimental par exemple.
doc_url
La documentation publique en ligne, elle est un dérivé du travail effectué ici. Ce sera un élément cliquable dans les préférences de l'utilisateur.
tracker_url
Le module complémentaire POV étant une partie intégrée de Blender, il est associé à sa propre entrée de suivi des bogues et cette clé fournira un pointeur vers celui-ci.
Commentaires
Les commentaires sur une seule ligne commencent par un caractère dièse (#) immédiatement suivi d'un espace vide et le commentaire commence par une majuscule mais ne se termine pas par un point. Ils peuvent être ajoutés après une ligne de code suffisamment courte (laisser un espace vide avant le #).
L'une des directives de formatage/style de codage à utiliser est PEP8 donc, par exemple, vous devez veiller à faire des lignes suffisamment courtes tout au long du texte. les fichiers de script de moins de 79 caractères et moins de 72 pour les docstrings (voir ci-dessous) ou les commentaires.
Périodiquement, la Blender Foundation vérifie cette conformité pep8 sur les scripts Blender. La longueur de ligne tolérée a fini par culminer à 100 caractères.
Si vous devez vraiment dépasser cette limite, par exemple pour des memos temporaires de travaux en cours, veuillez ajouter
# nopep8
à la fin de la ligne concernée
D'autres règles incluent:
- des camel caps pour les noms de classes: MyClass
- tous les noms de modules séparés par des underscores minuscules : mon_module
- indentations de 4 espaces (pas de tabulations)
- des espaces autour des operateurs mathématiques: 1 + 1, pas 1+1
- N'utiliser que des imports explicites, (ne jamais faire import *)
- N'utilisez pas une seule ligne pour un test et l'instruction qui en dépend: if val : body, séparez plutôt les blocs d'instruction des conditions dont ils dépendent sur au moins 2 lignes.
Alors que les commentaires sur plusieurs lignes peuvent commencer par des guillemets triples. Il est de convention que trois guillemets doubles sont utilisés pour les docstrings, un type spécifique de commentaire qui ne fait rien dans le programme mais qui apparaît toujours lorsque la console est utilisée pour obtenir de l'aide sur une fonction.
"""Ceci est une explication d'ordre général"""
En plus d'être une bonne pratique de mettre une chaîne doc au début de chaque classe pour la décrire et également au début de chaque fichier pour expliquer son utilisation relative, cela permet également aux appels en ligne de commande d'afficher cette doc sur demande. C'est l'une des principales différences entre les docstrings et les # commentaires inline (sur seulement une partie d'une ligne).
Fonctions
Les fonctions sont définies avec le mot-clé def, suivi du nom de la fonction et d'une paire de parenthèses. Lorsque la fonction est référencée pour la déplacer et la transmettre entre fichiers, elle n'a plus besoin de parenthèses, mais presque une fois sur deux, elles suivent le nom de la fonction et entre elles, certains attributs peuvent éventuellement être spécifiés.
Classes
Non seulement les Classes vous permettent de créer des définitions réutilisables. Mais c’est le seul moyen de configurer les éléments de l’interface utilisateur dans Blender. La bonne nouvelle est que cela signifie que les développeurs d’addons ont en réalité moins de travail à faire. L'implémentation codée en dur se charge de déterminer quand et quelles parties mettre à jour automatiquement pour tous les éléments de l'interface utilisateur, et l'héritage des classes parmi lesquelles choisir (l'API) garantit que l'on peut gérer les situations de manière optimisée. Certaines fonctions héritées comme draw() sont à surcharger par le développeur du module complémentaire qui réécrit chacune selon ses besoins.
Pour créer de nouvelles classes, utilisez le schéma de définition de classe habituel comme indiqué dans les modèles Python de l'éditeur de texte interne à Blender... Ou copiez et collez en un ailleurs dans le script.
Nomenclature de Classes
Respectez autant que possible le même modèle de nommage que celui demandé par la Blender Foundation : OT signifie que c'est un opérateur, MT un menu, PT un panneau : Le nom de la classe doit être composé comme suit : NOMDADDON_PT_panneau_principal NOMDADDON - En majuscules, nous tapons le nom du module complémentaire. Un trait de soulignement peut être utilisé si le nom comporte plusieurs mots.
_PT_ - il est ensuite séparé par deux lettres qui désignent le type de classe (dont le type est hérité).
main_panel - en utilisant des minuscules, nous pouvons taper le nom de l'opérateur/panneau/header(en-tête) etc.
Autres Class Types:
HT – Header MT – Menu OT – Operator PT – Panel UL – UI list
Ainsi, le nom de l'addon étant POV dans notre cas, voici quelques exemples de noms de classes valides et de leurs identifiants :
class POV_MATERIAL_PT_replacement_field(bpy.types.Panel):
bl_idname = 'pov_PT_replacement_field_panel'
class POV_OT_add_basic(bpy.types.Operator):
bl_idname = 'pov.addbasicitem'
class POV_VIEW_MT_Basic_Items(bpy.types.Menu):
bl_idname = 'pov_MT_basic_items_tools'
Remarquez comme l'identifiant de l'opérateur est différent ? Nous n’avons pas besoin d’ajouter "OT" mais plutôt un point. Nous n'avons pas non plus besoin d'ajouter "_operator" à la fin.
L'API Blender Python permet l'intégration de :
bpy.types.Panel
bpy.types.Menu
bpy.types.Operator
bpy.types.PropertyGroup
bpy.types.KeyingSet
bpy.types.RenderEngine
Ceci est intentionnellement limité. Actuellement, pour des fonctionnalités plus avancées telles que les modificateurs de maillage, les types d'objets ou les nœuds de shader, C/C++ doit être utilisé.
Pour l'intégration Python, Blender définit des méthodes communes à tous les types. Cela fonctionne en créant une sous-classe Python d'une classe Blender qui contient des variables et des fonctions spécifiées par la classe parent qui sont prédéfinies pour s'interfacer avec Blender.
Notez que nous sous-classons un membre de bpy.types, ceci est commun à toutes les classes qui peuvent être intégrées à Blender et utilisées afin que nous sachions s'il s'agit d'un opérateur et non d'un panneau lors de l'inscription au registre.
Les deux propriétés de classe commencent par un préfixe bl_. Il s'agit d'une convention utilisée pour distinguer les propriétés de Blender de celles que vous ajoutez vous-même.
Opérateurs
La plupart des modules complémentaires définissent de nouveaux opérateurs, ce sont des classes qui implémentent des fonctionnalités spécifiques. La définition réelle de l'opérateur prend la forme d'une classe dérivée de bpy.types.Operator
import bpy
class MyTool(bpy.types.Operator):
"""Do things to an object"""
bl_idname = "object.do_things_to_object"
bl_label = "Do things to an object"
bl_options = {'REGISTER', 'UNDO'}
La docstring au début de la définition de classe sera utilisée comme info-bulle partout où cet opérateur sera disponible, par exemple dans un menu, tandis que bl_label définit l'étiquette réelle utilisée dans l'entrée de menu elle-même. Ici, nous avons gardé les deux pareils. Les opérateurs feront partie des données de Blender et sont stockés dans le module bpy.ops. Ce bl_idname garantira que l'entrée de cet opérateur sera appelée bpy.ops.object.move_object. Les opérateurs sont normalement enregistrés afin de les rendre utilisables et c'est effectivement le défaut de bl_options. Cependant, si nous voulons également que le module complémentaire apparaisse dans l'historique afin qu'il puisse être annulé ou répété, nous devons ajouter UNDO à l'ensemble d'indicateurs attribué à bl_options, comme ici.
Les opérateurs ont des limitations qui peuvent rendre leur écriture fastidieuse.
Les principales limitations sont…
Impossible de transmettre des données telles que des objets, des maillages ou des matériaux sur lesquels opérer (les opérateurs utilisent le contexte à la place)
La valeur de retour de l'appel d'un opérateur donne le succès (s'il s'est terminé ou s'il a été annulé), dans certains cas, il serait plus logique du point de vue de l'API de renvoyer le résultat de l'opération.
La fonction poll() d'interrogation des opérateurs peut échouer lorsqu'une fonction API déclenche une exception donnant des détails sur la raison exacte.
La fonction execute()
Une classe d'opérateur peut avoir n'importe quel nombre de fonctions membres, mais pour être utile, elle doit normalement en avoir une qui remplace la fonction execute() :
def execute(self, context):
context.active_object.rotation.z += 33
return {'FINISHED'}
La fonction execute() reçoit une référence à un objet context. Ce contexte contient entre autres un attribut active_object qui pointe vers l'objet actif de Blender. Chaque objet dans Blender possède un attribut rotation qui est un vecteur avec des composantes x, y et z. Changer la rotation d'un objet est aussi simple que de changer l'un de ces composants, ce qui est exactement ce que nous faisons à la ligne 2.
La fonction execute() signale une exécution réussie en renvoyant un ensemble d'indicateurs, dans ce cas un ensemble composé uniquement de la chaine de caractères "FINISHED".
Lorsque nous activons la case à cocher Enable add-on dans les preferences, Blender recherchera une fonction register() et l'exécutera. De même, lors de la désactivation d'un module complémentaire, la fonction unregister() est appelée. Utilisons-les donc à la fois pour enregistrer notre opérateur auprès de Blender et pour insérer une entrée de menu qui fait référence à notre opérateur. la désinscrire et désafficher est également important pour que notre add-on ne soit pas perçu comme invasif ou destructeur d'une configuration précédente sans retour possible.
Pour créer une entrée de menu, nous devons faire deux choses :
- Créer une fonction qui produira une entrée de menu
- Et ajouter cette fonction à un menu approprié.
De nos jours, presque tout dans Blender est disponible sous forme d'objet Python et les menus ne font pas exception. Nous voulons ajouter notre entrée au menu Objet dans la vue 3D, nous appelons donc
bpy.types.VIEW3D_MT_object.append()
et lui passer une référence à la fonction que nous définissons un peu plus bas. Comment savons-nous le nom de cet objet de menu? Si vous avez cochéFile⇒ User preferences ⇒ Interface ⇒ Python Tooltips le nom du menu sera affiché dans une info-bulle lorsque vous survolez un menu. À partir de l'image ci-dessus, nous pouvons voir que nous pouvons utiliser bpy.types.VIEW3D_MT_object.append() pour ajouter quelque chose au menu Objet car VIEW3D_MT_object s'affiche alors dans le texte de la bulle apparue au survol de ce menu.
La fonction menu_func() n'implémente pas d'action elle-même (c'est le rôle d'un opérateur) | |
Mais lorsqu'elle est appelée, menu_func() ajoutera un élément d'interface utilisateur pointant vers l'objet qui lui est passé dans le paramètre self. Cet élément d'interface peut être modifié par l'utilisateur et, à son tour, interagira avec l'opérateur. |
Ici, nous allons simplement ajouter une entrée d'opérateur (c'est-à-dire un élément qui exécutera notre opérateur lorsqu'il sera cliqué).
L'argument self passé à menu_func() fait référence au menu.
Ce menu possède un attribut layout avec une fonction operator() à laquelle nous passons le nom de notre opérateur. Cela garantira qu'à chaque fois qu'un utilisateur survolera le menu Object, notre opérateur sera affiché dans la liste des options.
Le nom de notre nouvel opérateur MyTool se trouve dans son attribut bl_idname, c'est pourquoi nous passons MyTool.bl_idname.
Le nom de l'entrée et son info-bulle sont déterminés en regardant le bl_label et la docstring définis dans notre classe MyTool et l'icône utilisée dans le menu est déterminée en passant un paramètre facultatif icon à la fonction operator().
Définir un menu ne suffit pas en soi à rendre ce menu utilisable. Pour que l'utilisateur puisse le trouver et l'utiliser, nous devons ajouter le menu à son parent depuis la fonction register() à la fin de son fichier python.
def menu_func(self, context):
self.layout.operator(MyTool.bl_idname, icon='MESH_CUBE')
def register():
bpy.types.VIEW3D_MT_object.append(menu_func)
def unregister():
bpy.types.VIEW3D_MT_object.remove(menu_func)
Les opérateurs doivent être enregistrés de la même manière, donc ajouter un opérateur enregistré à un menu nécessite deux à trois actions distinctes :
- Enregistrer l'opérateur ;
- puis l'ajouter à un menu existant ;
- Ou enregistrer d'abord le menu nouvellement créé auquel il doit appartenir et ensuite seulement y ajouter l'opérateur.
Ce n'est qu'après tout cela que le nouvel opérateur apparaîtra sur l'interface utilisateur, du moins, si rien d'autre, en appuyant sur F3 dans la fenêtre de vue 3D et en saisissant le libellé de l'opérateur
Tout cela pourrait paraître une peu plus compliqué que nécessaire mais cela permet par exemple d'afficher bien d'autres choses qu'uniquement de simples entrées cliquables dans un menu, par exemple de regrouper plusieurs opérateurs dans une même case, par exemple s'ils font partie de modalités disponibles dans une même sous-thématique, etc.
Les commandes unregister() de désinscription au registre doivent apparaître dans l'ordre inverse de leurs lignes register() correspondantes | |
Cela garantit que vous n'essayez pas de désinscrire, par exemple, un menu qui a été ajouté en tant qu'enfant d'une classe d'élément d'interface utilisateur que vous pourriez essayer de désinscrire avant cet enfant, désinscrivant ainsi quelque chose qui n'est déjà plus là... Ce qui déclencherait une erreur et l'échec de l'activation/désactivation du module complémentaire. |
Optimisation
Gardez vos notations "pointées" minimale
Comme dans de nombreux langages orientés objet, Python permet la visualisation d'une structure hiérarchique avec ses niveaux séparés par des points (imaginez chaque point comme l'ouverture de la plus grande poupée russe à gauche qui enjambe les plus petites à sa droite).
Remplacez ses parcours inutilement répétés par des alias | |
L'opérateur point est un moyen simple de récupérer une classe, une donnée, une méthode, n'importe quel objet Python inclus dans une hiérarchie plus large... Mais sachez qu'il est coûteux, surtout s'il est utilisé dans une boucle. |
Lorsque l'on invoque
bpy.data.objects["my_obj"]
Chaque point plonge plus profondément d'un niveau et toute la hiérarchie est parcourue jusqu'à lui. Vous devez toujours le faire au moins une fois pour atteindre l'élément souhaité, il est donc acceptable d'utiliser cette syntaxe lorsque cette référence n'est faite qu'une seule fois, mais au-delà, préférez toujours éviter de refaire entièrement tout ce parcours, et donnez lui plutôt une variable d'alias comme
obj = bpy.data.objects["my_obj"]
Ainsi vous pouvez ensuite toujours l'utiliser, en ne gérant plus que les ramifications du parcours vers des attributs occasionnels à usages uniques comme l'exemple ci-dessous:
obj.location.z += 1
pour tous les autres attributs disponibles à vos opérations de lecture ou d'écriture. Définissez autant de ces alias requis qu'il le faut avant d'entrer dans une boucle plutôt qu'à l'intérieur de celle-ci. Imaginez si votre GPS vous indiquait toujours les directions précédentes prises depuis le point de départ en passant par les suivantes à chaque virage !
Utilisez try/except avec parcimonie
L'instruction try est utile pour gagner du temps lors de l'écriture d'un code de vérification d'erreurs.
Cependant, try est nettement plus lent qu'un if car une exception doit aussi être définie à chaque fois, évitez donc d'utiliser try dans les zones de votre code qui s'exécutent en boucle et s'exécutent plusieurs fois.
Il existe des cas où l'utilisation de try est plus rapide que d'explicitement détecter par un test (if ou autre) la condition complexe qui génèrerait potentiellement une erreur, il vaut donc la peine d'expérimenter.
Une bonne façon de supprimer certains de vos try ... except lorsque l'erreur constatée s'avère une AttributeError est de les remplacer par un test en amont avec la fonction hasattr() :
for member in dir(properties_freestyle):
subclass = getattr(properties_freestyle, member)
try:
subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
except:
pass
pourrait alors devenir
for member in dir(properties_freestyle):
subclass = getattr(properties_freestyle, member)
if hasattr(subclass, "COMPAT_ENGINES"):
subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
Si vous devez utiliser try:... except:... ne laissez pas le type d'exception non spécifié, cela ne donne absolument aucun retour d'information ni aucune lisibilité du code. Au lieu de cela, si vous n'avez aucune idée de ce que cela pourrait être, remplacez le par quelque chose qui donnerait plus d'informations pendant votre phase de débogage :
(BaseException est au sommet de la hiérarchie des exceptions)
def your_function(b, c):
try:
a = b + c
return True
except BaseException as e:
print(e.__doc__)
print('An exception occurred: {}'.format(e))
return False
Séparez vos fichiers Python (*.py)
Historiquement, le script a ensuite été agencé en unpackage (plusieurs fichiers dans un dossier) en utilisant le moins de fichiers possible pour que le flux de données soit plus facile à comprendre. Il y avait initialement 3 fichiers principaux:
- __init__.py
- ui.py
- render.py
Cependant, certains fichiers dépassaient les 10 000 lignes, ce qui, pour un tel add-on était encore trop volumineux. Il est donc actuellement toléré que le plus gros fichier reste en dessous de 2000 lignes. Il est très probable que les fichiers pourraient perdre un peu de poids, en supprimant le support 32 bits obsolète et en modularisant une partie du code en quelques fonctions plus réutilisables. Voici les fichiers actuels composant l'add-on et leur utilisation respective :
Initialisation
__init__.py
Création des préférences utilisateur du module complémentaire POV, autorisation de l'utilisateur à mettre à jour le module complémentaire vers la dernière version et chargement de tous les autres modules. (Notez les doubles traits de soulignement autour du nom du fichier. Cela en fait le premier fichier lancé) définition de l'unité « principale » pour un package ; cela oblige également Python à traiter le répertoire spécifique comme un package. C'est l'unité qui sera utilisée lorsque l'on appele import render_povray (et render_povray est un répertoire).
scenography_properties.py
Initialisation des propriétés pour traduire les paramètres de caméra/lumière/environnement de Blender en langage POV.
object_properties.py
Initialisation des propriétés pour traduire les paramètres d'objets de Blender en langage POV.
shading_properties.py
Initialisation des propriétés pour traduire les paramètres de matériaux de Blender en langage POV.
texturing_properties.py
Initialisation des propriétés pour traduire les paramètres d'influences de textures des matériaux, d'environnement (world)... de Blender en langage POV.
render_properties.py
Initialisation des propriétés pour traduire les paramètres de rendu (de Blender mais surtout les plus spécifiquement natifs de POV).
scripting_properties.py
Initialisation des propriétés et champs pour renseigner des instructions directement dans le langage de description de scène POV.
Interface
Quelques notes à garder à l’esprit lors de la création d'éléments d’interface utilisateur :
Les commandes fournies par Blender pour l'interface utilisateur sont relativement simples. Les déclarations d'interface sont là pour créer facilement une disposition acceptable. Règle générale ici : si vous avez besoin de plus de code pour la disposition de leur interface que pour les propriétés elles-mêmes, alors vous vous y prenez mal.
Exemples d'interface:
layout()
La disposition de base est une disposition simple de Haut -> en Bas.
layout.prop() layout.prop()
layout.row()
Utilisez row() lorsque vous souhaitez plus d’une propriété sur une seule ligne.
row = layout.row() row.prop() row.prop()
layout.column()
Utilisez column(), lorsque vous souhaitez que vos propriétés s'alignent sur une colonne.
col = layout.column() col.prop() col.prop()
layout.split()
Cela peut être utilisé pour créer des mises en page plus complexes. Par exemple, vous pouvez diviser la mise en page et créer deux dispositions column() l'une à côté de l'autre. N'utilisez pas split() lorsque vous souhaitez simplement deux propriétés consécutives. Utilisez row() pour cela.
split = layout.split()
col = split.column() col.prop() col.prop()
col = split.column() col.prop() col.prop()
Noms à déclarer:
Essayez d'utiliser uniquement les noms de variables suivants pour vos déclarations d'interfaces:
row pour une disposition en row()
col pour une disposition en column()
split pour une disposition en split()
flow pour une disposition en column_flow()
sub pour une disposition enchâssée (une colonne à l'intérieur d'une colonne par exemple)
base_ui.py
affichage d'un espace de travail inspiré du logiciel Moray et définition certaines fonctions utilitaires. Chargement de tous les autres modules liés à l'interface graphique
scenography_gui.py)
Affichage des propriétés caméra/lumière/environnement de scenography_properties.py pour que l'utilisateur puisse les modifier
object_gui.py :
Affichage des propriétés objets depuis object_properties.py pour que l'utilisateur puisse les modifier
shading_gui.py
Affichage des propriétés de shading_properties.py pour que l'utilisateur puisse les modifier
shading_nodes.py
Traduction des arborescences de nodes vers le fichier POV
texturing_gui.py
Affichage des propriétés de texturing_properties.py pour que l'utilisateur puisse les modifier
render_gui.py
Affichage des propriétés de render_properties.py pour que l'utilisateur puisse les modifier
scripting_gui.py
Affichage des propriétés de scripting_properties.py pour que l'utilisateur puisse ajouter son code POV personnalisé
Render / import / edit
render.py
Traduction de la géométrie et des diverses propriétés d'Interface Utilisateur (celles de Blender et les natives POV) vers le fichier POV
scenography.py
Traduction des propriétés de la caméra, des lumières, et de l'environnement en fonctionnalités POV correspondantes
primitives.py
Affichage de primitives POV natives importées ou créées dans la vue 3D pour éventuellement les y modifier avant rendu
object_mesh_topology.py
Traduction vers POV des géométries de maillages
object_curve_topology.py
Traduction vers POV des géométries basées sur des courbes vectorielles
object_particles.py
Traduction vers POV des objets basés sur des simulations de particules
shading.py
Traduction des propriétés d'ombrage sous la forme de textures déclarées en haut du fichier POV
texturing.py
Traduction des influences (modulation bitmap) de textures en langage POV
df3_library.py
Rendu de fumée par l'intermédiaire de fichiers *.df3
scripting.py
Insertion d'éléments en language de description de scene natif POV injectables directement dans le fichier exporté sans avoir à ce qu'ils s'affichent dans Blender
update_files.py
Mise à jour de nouvelles variables depuis leurs valeurs dans des versions d'API Blender plus anciennes. Ce fichier à besoin d'être actualisé
Presets
Parallèlement à ces fichiers essentiels coexistent également quelques bibliothèques supplémentaires pour aider Blender à résister à d'autres interfaces compatibles avec Persistence of Vision telles que povwin QTPOV ou VSCode.
Matériaux (sss)
Irradiance
Environnement
Lumière
04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py
05_(4000K)_100W_Metal_Halide.py
06_(3200K)_100W_Quartz_Halogen.py
09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py
10_(4300K)_40W_Vintage_Fluorescent_T12.py
11_(5000K)_18W_Standard_Fluorescent_T8.py
12_(4200K)_18W_Cool_White_Fluorescent_T8.py
13_(3000K)_18W_Warm_Fluorescent_T8.py
14_(6500K)_54W_Grow_Light_Fluorescent_T5-HO.py
15_(3200K)_40W_Induction_Fluorescent.py
16_(2100K)_150W_High_Pressure_Sodium.py
17_(1700K)_135W_Low_Pressure_Sodium.py
18_(6800K)_175W_Mercury_Vapor.py
22_(30000K)_40W_Black_Light_Fluorescent.py
templates
C++
Certains domaines n'ont pu recevoir de fonctionnalités spécifiques à POV qu'en utilisant du C++ :
blender/editors/space_text/text_format_pov.c
blender/editors/space_text/text_format_ini.c
Mspace_text.c
Mtext_format.h
Mtext_format_ini.c
Mtext_format_pov.c
et le fichier MCMakelists.txt a du être modifié pour inclure ces fichiers ajoutés.
Mais dans Blender, toute l'interface utilisateur est écrite en Python, c'est pourquoi le fichier scripts/startup/bl_ui/space_text.py a également été mis à jour même s'il se trouve en dehors des dossiers habituels des addons :
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8-80 compliant>
import bpy
from bpy.types import Header, Menu, Panel
from bpy.app.translations import pgettext_iface as iface_
class TEXT_HT_header(Header):
bl_space_type = 'TEXT_EDITOR'
def draw(self, context):
layout = self.layout
st = context.space_data
text = st.text
row = layout.row(align=True)
row.template_header()
TEXT_MT_editor_menus.draw_collapsible(context, layout)
if text and text.is_modified:
sub = row.row(align=True)
sub.alert = True
sub.operator("text.resolve_conflict", text="", icon='HELP')
row = layout.row(align=True)
row.template_ID(st, "text", new="text.new", unlink="text.unlink", open="text.open")
row = layout.row(align=True)
row.prop(st, "show_line_numbers", text="")
row.prop(st, "show_word_wrap", text="")
row.prop(st, "show_syntax_highlight", text="")
if text:
osl = text.name.endswith(".osl") or text.name.endswith(".oso")
if osl:
row = layout.row()
row.operator("node.shader_script_update")
else:
row = layout.row()
row.operator("text.run_script")
row = layout.row()
row.active = text.name.endswith(".py")
row.prop(text, "use_module")
row = layout.row()
if text.filepath:
if text.is_dirty:
row.label(text=iface_("File: *%r (unsaved)") %
text.filepath, translate=False)
else:
row.label(text=iface_("File: %r") %
text.filepath, translate=False)
else:
row.label(text="Text: External"
if text.library
else "Text: Internal")
class TEXT_MT_editor_menus(Menu):
bl_idname = "TEXT_MT_editor_menus"
bl_label = ""
def draw(self, context):
self.draw_menus(self.layout, context)
@staticmethod
def draw_menus(layout, context):
st = context.space_data
text = st.text
layout.menu("TEXT_MT_view")
layout.menu("TEXT_MT_text")
if text:
layout.menu("TEXT_MT_edit")
layout.menu("TEXT_MT_format")
layout.menu("TEXT_MT_templates")
class TEXT_PT_properties(Panel):
bl_space_type = 'TEXT_EDITOR'
bl_region_type = 'UI'
bl_label = "Properties"
def draw(self, context):
layout = self.layout
st = context.space_data
flow = layout.column_flow()
flow.prop(st, "show_line_numbers")
flow.prop(st, "show_word_wrap")
flow.prop(st, "show_syntax_highlight")
flow.prop(st, "show_line_highlight")
flow.prop(st, "use_live_edit")
flow = layout.column_flow()
flow.prop(st, "font_size")
flow.prop(st, "tab_width")
text = st.text
if text:
flow.prop(text, "use_tabs_as_spaces")
flow.prop(st, "show_margin")
col = flow.column()
col.active = st.show_margin
col.prop(st, "margin_column")
class TEXT_PT_find(Panel):
bl_space_type = 'TEXT_EDITOR'
bl_region_type = 'UI'
bl_label = "Find"
def draw(self, context):
layout = self.layout
st = context.space_data
# find
col = layout.column(align=True)
row = col.row(align=True)
row.prop(st, "find_text", text="")
row.operator("text.find_set_selected", text="", icon='TEXT')
col.operator("text.find")
# replace
col = layout.column(align=True)
row = col.row(align=True)
row.prop(st, "replace_text", text="")
row.operator("text.replace_set_selected", text="", icon='TEXT')
col.operator("text.replace")
# settings
layout.prop(st, "use_match_case")
row = layout.row(align=True)
row.prop(st, "use_find_wrap", text="Wrap")
row.prop(st, "use_find_all", text="All")
class TEXT_MT_view(Menu):
bl_label = "View"
def draw(self, context):
layout = self.layout
layout.operator("text.properties", icon='MENU_PANEL')
layout.separator()
layout.operator("text.move",
text="Top of File",
).type = 'FILE_TOP'
layout.operator("text.move",
text="Bottom of File",
).type = 'FILE_BOTTOM'
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
layout.operator("screen.screen_full_area", text="Toggle Fullscreen Area").use_hide_panels = True
class TEXT_MT_text(Menu):
bl_label = "Text"
def draw(self, context):
layout = self.layout
st = context.space_data
text = st.text
layout.operator("text.new")
layout.operator("text.open")
if text:
layout.operator("text.reload")
layout.column()
layout.operator("text.save")
layout.operator("text.save_as")
if text.filepath:
layout.operator("text.make_internal")
layout.column()
layout.operator("text.run_script")
class TEXT_MT_templates_py(Menu):
bl_label = "Python"
def draw(self, context):
self.path_menu(
bpy.utils.script_paths("templates_py"),
"text.open",
props_default={"internal": True},
)
class TEXT_MT_templates_osl(Menu):
bl_label = "Open Shading Language"
def draw(self, context):
self.path_menu(
bpy.utils.script_paths("templates_osl"),
"text.open",
props_default={"internal": True},
)
class TEXT_MT_templates_pov(Menu):
bl_label = "POV-Ray Scene Description Language"
def draw(self, context):
self.path_menu(
bpy.utils.script_paths("templates_pov"),
"text.open",
props_default={"internal": True},
)
class TEXT_MT_templates(Menu):
bl_label = "Templates"
def draw(self, context):
layout = self.layout
layout.menu("TEXT_MT_templates_py")
layout.menu("TEXT_MT_templates_osl")
layout.menu("TEXT_MT_templates_pov")
class TEXT_MT_edit_select(Menu):
bl_label = "Select"
def draw(self, context):
layout = self.layout
layout.operator("text.select_all")
layout.operator("text.select_line")
class TEXT_MT_format(Menu):
bl_label = "Format"
def draw(self, context):
layout = self.layout
layout.operator("text.indent")
layout.operator("text.unindent")
layout.separator()
layout.operator("text.comment")
layout.operator("text.uncomment")
layout.separator()
layout.operator_menu_enum("text.convert_whitespace", "type")
class TEXT_MT_edit_to3d(Menu):
bl_label = "Text To 3D Object"
def draw(self, context):
layout = self.layout
layout.operator("text.to_3d_object",
text="One Object",
).split_lines = False
layout.operator("text.to_3d_object",
text="One Object Per Line",
).split_lines = True
class TEXT_MT_edit(Menu):
bl_label = "Edit"
@classmethod
def poll(cls, context):
return (context.space_data.text)
def draw(self, context):
layout = self.layout
layout.operator("ed.undo")
layout.operator("ed.redo")
layout.separator()
layout.operator("text.cut")
layout.operator("text.copy")
layout.operator("text.paste")
layout.operator("text.duplicate_line")
layout.separator()
layout.operator("text.move_lines",
text="Move line(s) up").direction = 'UP'
layout.operator("text.move_lines",
text="Move line(s) down").direction = 'DOWN'
layout.separator()
layout.menu("TEXT_MT_edit_select")
layout.separator()
layout.operator("text.jump")
layout.operator("text.start_find", text="Find...")
layout.operator("text.autocomplete")
layout.separator()
layout.menu("TEXT_MT_edit_to3d")
class TEXT_MT_toolbox(Menu):
bl_label = ""
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
layout.operator("text.cut")
layout.operator("text.copy")
layout.operator("text.paste")
layout.separator()
layout.operator("text.run_script")
classes = (
TEXT_HT_header,
TEXT_MT_edit,
TEXT_MT_editor_menus,
TEXT_PT_properties,
TEXT_PT_find,
TEXT_MT_view,
TEXT_MT_text,
TEXT_MT_templates,
TEXT_MT_templates_py,
TEXT_MT_templates_osl,
TEXT_MT_templates_pov,
TEXT_MT_edit_select,
TEXT_MT_format,
TEXT_MT_edit_to3d,
TEXT_MT_toolbox,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)
Il est à noter que la ligne de commande spéciale
# <pep8-80 compliant>
inscrirait pep-80 plutôt que pep-8 car se rapprocher de la programmation du coeur de Blender nécessite souvent des normes plus exigeantes : pep-80 demande des longueurs de ligne encore plus courtes et donc un fichier réussissant les tests automatisés pep-80 que la Blender Foundation exécute régulièrement peut échouer en réussissant PEP-8.