Come: Utilizzare POV-Ray con Blender (Pagina dei Sviluppatori)
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
Il componente aggiuntivo Blender per POV-Ray e viceversa (noto anche come "POV@Ble") è iniziato come un singolo file Python.
Per rispettare i criteri per le estensioni disponibili con Blender, questo componente aggiuntivo doveva:
- fornire alcune informazioni generali sul componente aggiuntivo: nome e versione,
- definire il codice per eseguire azioni, principalmente tramite "operatori" o "funzioni"
- e assicurarsi che siano iscritti nel registro affinché possano essere utilizzati.
bpy
Blender dispone di un interprete Python integrato che è incaricato di avviare Blender e resta attivo mentre Blender è in corso di esecuzione. L'interprete esegue script per progettare l'utente dell'interfaccia ed è anche utilizzato per alcuni strumenti interni di Blender. L'interprete integrato di Blender fornisce un ambiente tipico Python, quindi il codice didattico sulla forma di scrittura degli script Python può essere eseguito anche con l'interprete di Blender. Frullatore fornitomoduli Python, come bpy e mathutils, per la console integrata affinché possano essere importati in uno script e accedere ai dati, alle classi e alle funzioni di Blender. Gli script che trattano i dati di Blender devono essere l'oggetto di unimport di moduli per funzionare. Quindi, tra tutti i moduli, se non ce n'è un altro, bpy è generalmente sempre importato negli script di Blender. (eccetto nella console interattiva dove questa importazione viene eseguita automaticamente per maggiore comodità)
metadati del componente aggiuntivo
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)",
}
Prima di Blender 4.2, le informazioni generali sui componenti aggiuntivi erano definite in un dizionario denominato
bl_info
Ciascuna chiave in questo dizionario forniva a Blender informazioni specifiche sull'add-on, sebbene non tutte fossero ugualmente importanti. La maggior parte delle informazioni sono state utilizzate nella finestra di dialogo delle preferenze dell'utente e hanno aiutato l'utente a trovare e selezionare un componente aggiuntivo.
name
Una sorta di nome in codice per l'add-on, con cui viene indicato nei sorgenti di Blender... (il nome dell'interfaccia utente potrebbe mostrare qualcos'altro)
author
Se contribuisci, ti verrà accreditato qui!
version
Numero di versione del software. Qualsiasi schema di numerazione è valido, purché sia una tupla di tre numeri interi. È preferibile spostare solo l'ultima posizione, ma in seguito potremmo optare per uno schema più strutturato.
blender
La versione minima di Blender richiesta da questo componente aggiuntivo. Un'altra tupla di tre numeri interi. Anche se ti aspetti che le cose funzionino con le versioni più vecchie e quelle più recenti, potrebbe essere una buona idea elencare la versione più vecchia con cui hai effettivamente testato il tuo componente aggiuntivo! È importante sviluppare con qualcosa di abbastanza stabile e poi testare con l'ultima versione appena prima di impegnarsi, ma generalmente non aggiornare il numero di versione compatibile, solo quando qualcosa si rompe a quel punto.
category
La categoria nelle Preferenze utente in cui è raggruppato il nostro componente aggiuntivo. Sfrutta motori compatibili con POV che sono motori di rendering, quindi aveva senso aggiungerlo storicamente alla categoria Render. Sfortunatamente, questo non riflette la natura versatile che ha finito per sviluppare (come la spedizione a un importatore, l'evidenziazione della sintassi dell'editor di testo, ecc...). Se sono possibili più categorie, forse dovremmo aggiungerle.
location
Dove trovare il componente aggiuntivo una volta attivato. Può trattarsi di un riferimento a un pannello specifico o a un caso particolare, una descrizione della sua posizione in un menu.
description
Una descrizione concisa di ciò che fa il componente aggiuntivo.
warning
Se non è una stringa vuota, il componente aggiuntivo verrà visualizzato con un segno di avviso nelle preferenze dell'utente. Puoi usarlo per contrassegnare un componente aggiuntivo come sperimentale, ad esempio.
doc_url
La documentazione pubblica online è un derivato del lavoro svolto qui. Questo sarà un elemento cliccabile nelle preferenze dell'utente.
tracker_url
Dato che il componente aggiuntivo POV è una parte incorporata di Blender, viene fornito con una propria voce di tracciamento dei bug e questa chiave fornirà un puntatore ad esso.
Commenti
I commenti a riga singola iniziano con un carattere cancelletto (#) immediatamente seguito da uno spazio vuoto e il commento inizia con una lettera maiuscola ma non termina con un punto. Possono essere aggiunti dopo una riga di codice sufficientemente breve (lasciare uno spazio vuoto prima del #).
Ad esempio, all'inizio del file, il commento
# <pep8 compliant>
significa che una delle direttive di stile di formattazione/codifica del testo da utilizzare è PEP8 quindi ad esempio dovresti assicurarti di creare righe sufficientemente brevi in tutto lo script file di meno di 79 caratteri e meno di 72 per docstring (vedi sotto) o commenti. Nota che questo commento ha una sintassi speciale: Periodicamente, la Blender Foundation controlla gli script di Blender per la conformità pep8, affinché gli script siano inclusi in questo controllo, aggiungi questa riga come commento all'inizio dello script.
Altre regole includono:
- Camel caps per i nomi delle classi: MyClass
- tutti i nomi dei moduli separati da caratteri di sottolineatura minuscoli: mio_modulo
- 4 rientri di spazio (nessuna tabulazione)
- spazi attorno agli operatori matematici: 1 + 1, non
1+1
- Utilizzare solo importazioni esplicite (non eseguire mai import *)
- Non utilizzare una sola riga per un test e l'istruzione che dipende da esso:
if val : body, invece, separare i blocchi di istruzioni dalle condizioni da cui dipendono di almeno 2 righe.
Mentre i commenti su più righe possono iniziare con virgolette triple. È convenzione che vengano usate tre virgolette doppie per docstrings, un tipo specifico di commento che non fa nulla nel programma ma che appare comunque quando la console viene utilizzata per ottenere aiuto su una funzione.
"""Questa è una spiegazione generale"""
Oltre ad essere una buona pratica inserire una stringa di documento all'inizio di ogni classe per descriverla e anche all'inizio di ogni file per spiegarne l'utilizzo relativo, ciò consente anche alle chiamate da riga di comando di visualizzare questo documento su richiesta. Questa è una delle principali differenze tra i commenti docstrings e inline (solo su parte di una riga).
Funzioni
Le funzioni sono definite con def, seguita dal nome della funzione e da una coppia di parentesi. Quando si fa riferimento alla funzione per spostarla e passarla da un file all'altro, non ha più bisogno delle parentesi, ma quasi ogni altra volta queste seguono il nome della funzione e tra di esse si possono facoltativamente specificare alcuni attributi.
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.
Operators
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. |
Loorsque 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()
Use row(), when you want more than 1 property in one line.
row = layout.row() row.prop() row.prop()
layout.column()
Use column(), when you want your properties in a column.
col = layout.column() col.prop() col.prop()
layout.split()
This can be used to create more complex layouts. For example you can split the layout and create two column() layouts next to each other. Don’t use split, when you simply want two properties in a row. Use row() for that.
split = layout.split()
col = split.column() col.prop() col.prop()
col = split.column() col.prop() col.prop()
Declaration names:
Try to only use these variable names for layout declarations:
row for a row() layout
col for a column() layout
split for a split() layout
flow for a column_flow() layout
sub for a sub layout (a column inside a column for example)
base_ui.py
Provide a Moray like workspace and define some utility functions. Load all the other GUI related modules
scenography_gui.py)
Display cam/light/environment properties from situation_properties.py for user to change them
object_gui.py :
Display properties from object_properties.py for user to change them
shading_gui.py
Display properties from shading_properties.py for user to change them
shading_nodes.py
Translate node trees to the pov file
texturing_gui.py
Display properties from texturing_properties.py for user to change them
render_gui.py
Display properties from render_properties.py for user to change them
scripting_gui.py
Display properties from scripting_properties.py for user to add his custom POV code
Render / import / edit
render.py
Translate geometry and UI properties (Blender and POV native) to the POV file
scenography.py
Translate cam/light/environment properties to corresponding pov features
primitives.py
Display some POV native primitives in 3D view for input and output
object_mesh_topology.py
Translate to POV the meshes geometries
object_curve_topology.py
Translate to POV the curve based geometries
object_particles.py
Translate to POV the particle based geometries
shading.py
Translate shading properties to declared textures at the top of a pov file
texturing.py
Translate blender texture influences into POV
df3_library.py
Render smoke to *.df3 files
scripting.py
Insert POV native scene description elements to exported POV file
update_files.py
Update new variables to values from older API. This file needs an update
Presets
Along these essential files also coexist a few additional libraries to help make Blender stand up to other Persistence of Vision compatible frontends such as povwin or QTPOV
Material (sss)
Radiosity
World
Light
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++
Some areas could only be added POV specific functionality using 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
and the MCMakelists.txt had to be modified to include added files.
But in Blender, all of the user interface is written in Python, that's why the file scripts/startup/bl_ui/space_text.py also has been updated even though it lies outside the usual addons folder:
# ##### 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)
Notice that the special command line
# <pep8-80 compliant>
writes pep-80 rather than pep-8 as getting closer to the native blender program often requires more demanding standards : pep-80 asks for even shorter line lengths and so a file passing the pep-80 automated tests that the Blender Foundation regularly runs might fail passing PEP-80.