Difference between revisions of "HowTo:Use POV-Ray with Blender/Developers page"

From POV-Wiki
Jump to navigation Jump to search
 
(25 intermediate revisions by the same user not shown)
Line 5: Line 5:
 
<tr><td width=33% bgcolor=#EEEEEF>
 
<tr><td width=33% bgcolor=#EEEEEF>
 
<font size="+2">[[HowTo:Use_POV-Ray_with_Blender|User's page]]</font size="+2"></td>
 
<font size="+2">[[HowTo:Use_POV-Ray_with_Blender|User's page]]</font size="+2"></td>
<td width=33% align=center>[[File:POVExporterLogo.png|center|200px]]</td>
+
<td width=33% align=center>[[File:POVExporterLogo.png|center|200px|link=https://povconverter.wordpress.com]]</td>
 
<td width=33% bgcolor=#EEEEEF align=right>
 
<td width=33% bgcolor=#EEEEEF align=right>
 
<font size="+2">[[HowTo:Use_POV-Ray_with_Blender/Developers_page|Developer's page]]</font size="+2"></td></tr>
 
<font size="+2">[[HowTo:Use_POV-Ray_with_Blender/Developers_page|Developer's page]]</font size="+2"></td></tr>
Line 16: Line 16:
 
*[http://povray.org/documentation/3.7.0/ POV-Ray Documentation]
 
*[http://povray.org/documentation/3.7.0/ POV-Ray Documentation]
 
*[[HowTo:Use_POV-Ray_with_Blender/Sample_Materials| Sample Materials]]
 
*[[HowTo:Use_POV-Ray_with_Blender/Sample_Materials| Sample Materials]]
*[[Media:POV_WORKSPACE_1.blend|POV-Ray centric default workspace]]
+
*[[Media:POV WORKSPACE 1.blend|POV-Ray centric default workspace]]
 
*---
 
*---
 
*---
 
*---
Line 26: Line 26:
 
*---
 
*---
 
*design ideas
 
*design ideas
*[[Media:regression_and_test_blendfiles.zip|regression files]]
+
*[[Media:regression and test blendfiles.zip|regression files]]
 
</div>
 
</div>
  
Line 39: Line 39:
  
  
The Blender to POV-Ray add-on and vice versa (aka "POV Converter") started out as a single Python file.  
+
The Blender to POV-Ray add-on and vice versa (aka "Bpover") started out as a single Python file.  
 
To be compliant with blender add-on packaging system this add-on had to:
 
To be compliant with blender add-on packaging system this add-on had to:
 
*provide some general information about the add-on: its name and version,
 
*provide some general information about the add-on: its name and version,
Line 48: Line 48:
 
===bpy===
 
===bpy===
 
Blender has an embedded Python interpreter which is loaded when Blender is started and stays active while Blender is running. This interpreter runs scripts to draw the user interface and is used for some of Blender’s internal tools as well. Blender’s embedded interpreter provides a typical Python environment, so code from tutorials on how to write Python scripts can also be run with Blender’s interpreter. Blender provides its Python [https://docs.python.org/3/tutorial/modules.html modules], such as ''bpy'' and ''mathutils'', to the embedded interpreter so they can be imported into a script and give access to Blender’s data, classes, and functions. Scripts that deal with Blender data will need to '''import''' the modules to work. So of all modules, if no other, bpy is generally always imported in blender scripts. (except in interactive console were this import is automatically done for convenience)
 
Blender has an embedded Python interpreter which is loaded when Blender is started and stays active while Blender is running. This interpreter runs scripts to draw the user interface and is used for some of Blender’s internal tools as well. Blender’s embedded interpreter provides a typical Python environment, so code from tutorials on how to write Python scripts can also be run with Blender’s interpreter. Blender provides its Python [https://docs.python.org/3/tutorial/modules.html modules], such as ''bpy'' and ''mathutils'', to the embedded interpreter so they can be imported into a script and give access to Blender’s data, classes, and functions. Scripts that deal with Blender data will need to '''import''' the modules to work. So of all modules, if no other, bpy is generally always imported in blender scripts. (except in interactive console were this import is automatically done for convenience)
 +
{| style="width:100%" border="0"
 +
|[[File:Blender-interactive-console.png|frame|left|Blender's interactive console]]
 +
|}
 +
<br/>
  
 
===bl_info===
 
===bl_info===
Line 121: Line 125:
 
"""This is an explanation of general relevance"""
 
"""This is an explanation of general relevance"""
 
</source>
 
</source>
It is good practice to put a doc string at the start of every class to describe it and also at the start of every file to explain its relative use.
+
Besides being good practice to put a doc string at the start of every class to describe it and also at the start of every file to explain its relative use, it also enables command line calls to display this doc on request. That's one of the main difference between docstrings and inlined # comments.
  
 
===Functions===
 
===Functions===
Line 127: Line 131:
  
 
===Classes===
 
===Classes===
[https://docs.python.org/3/tutorial/classes.html Classes] allow you to create reusable definitions. To create new classes, either use the regular class definition scheme as shown in the TextEditor Python Templates... Or copy and paste one from elsewhere in the script. Respect the same naming pattern as much as possible: OT means it's an operator, MT a menu, PT à panel, etc.
+
Not only do [https://docs.python.org/3/tutorial/classes.html Classes] allow you to create reusable definitions. But they are the only way to set up user interface elements in Blender. The good news is: that means addons developers actually have less work to do. The hardcoded implementation takes care of determining when and what parts to update for all ui elements automatically, and the inheritence of classes to choose from (the API) make sure one can handle situations in optimized ways. Some inherited functions such as draw() are to be overloaded by the add-on developer who rewrites each one to his need. 
 +
 
 +
To create new classes, either use the regular class definition scheme as shown in the TextEditor Python Templates... Or copy and paste one from elsewhere in the script.  
 +
==== Class Naming Convention ====
 +
Respect the same naming pattern as asked by the Blender Foundation as much as possible: OT means it's an operator, MT a menu, PT à panel:
 +
 
 +
The Class name should be as follows: ADDONNAME_PT_main_panel
 +
ADDONNAME - Using Uppercase, we type the Name of the Add-on. An underscore can be used if the name is more than one word.
 +
 
 +
_PT_ - it is then separated by two letters which denotes the Class Type (from which the type is inherited).
 +
 
 +
main_panel - using lowercase, we can type the name of the Operator/Panel/Header ect.
 +
 
 +
Other Class Types:
 +
 
 +
    HT – Header
 +
    MT – Menu
 +
    OT – Operator
 +
    PT – Panel
 +
    UL – UI list
 +
 
 +
So, addon name being POV in our case, here are some examples of Valid Class Names and their Identifiers:
 +
 
 +
<source lang="py">
 +
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'
 +
 
 +
 
 +
</source>
 +
 
 +
Notice how the Identifier for the Operator is different? We do not need to add the OT but instead we must add a full stop. We also do not need to add the '_operator' at the end.
 +
 
 +
 
  
 
The Blender Python api allows integration for:
 
The Blender Python api allows integration for:
Line 190: Line 234:
  
 
===Registering and adding a menu entry===
 
===Registering and adding a menu entry===
Defining an operator is not in itself enough to make this operator usable. In order for the user to find and use an operator, for example by pressing SPACE in the 3D view window and typing the label of the operator, we must register the operator. Adding a registered operator to a menu requires a separate action.
+
When we check the ''Enable add-on'' check-box in the ''user preferences'', Blender will look for a '''register()''' function and execute it. Likewise, when disabling an add-on the '''unregister()''' function is called. Let us use this to both register our operator with Blender and to insert a menu entry that refers to our operator.
 +
 
 +
In order to create a menu entry we have to do two things:
 +
*Create a function that will produce a menu entry
 +
*And append this function to a suitable menu.
 +
 
 +
Now almost everything in Blender is available as a Python object and menus are no exception. We want to add our entry to the Object menu in the 3D view so we call <source lang="py">bpy.types.VIEW3D_MT_object.append()</source> and pass it a reference to the function we define a little below. How do we know how this menu object is called? If you have checked ''File⇒ User preferences ⇒ Interface ⇒ Python Tooltips'' the name of the menu will be shown in a tooltip when you hover over a menu.From the image above we can see that we can use ''bpy.types.VIEW3D_MT_object.append()'' to add something to the Object menu because ''VIEW3D_MT_object'' is shown in the balloon text.
 +
 
 +
{{NiceTip|The ''menu_func()'' function does not implement an action itself (that's the role of an operator)| But when called, menu_func() will append a user interface element pointing to the object that is passed to it in the '''self''' parameter. This interface element can be modified by the user and in turn, will interact with the operator.}}<br/>
 +
 
 +
Here we will simply add an operator entry (that is, an item that will execute our operator when clicked).
 +
 
 +
The ''self'' argument that is passed to ''menu_func()'' refers to the menu.
 +
 
 +
This menu has a ''layout'' attribute with an ''operator()'' function to which we pass the name of our operator. This will ensure that every time a user hovers over the '''Object''' menu, our operator will be shown in the list of options.
 +
 
 +
The name of our new ''MyTool'' operator can be found in its ''bl_idname'' attribute so that is why we pass ''MyTool.bl_idname''.
 +
 
 +
The name of the entry and its tooltip is determined by looking at the ''bl_label'' and docstring defined in our ''MyTool'' class and the icon used in the menu is determined by passing an optional '''icon''' parameter to the ''operator()'' function.
 +
 
 +
Defining a menu is not in itself enough to make this menu usable. In order for the user to find and use it, we must append the menu to its parent from within the '''register()''' function at the end of its python file.  
 +
 
 
<source lang="py">
 
<source lang="py">
 +
def menu_func(self, context):
 +
    self.layout.operator(MyTool.bl_idname, icon='MESH_CUBE')
 
def register():     
 
def register():     
    bpy.utils.register_module(__name__)
 
 
     bpy.types.VIEW3D_MT_object.append(menu_func)
 
     bpy.types.VIEW3D_MT_object.append(menu_func)
 
def unregister():
 
def unregister():
    bpy.utils.unregister_module(__name__)
 
 
     bpy.types.VIEW3D_MT_object.remove(menu_func)
 
     bpy.types.VIEW3D_MT_object.remove(menu_func)
def menu_func(self, context):
 
    self.layout.operator(MyTool.bl_idname, icon='MESH_CUBE')
 
 
</source>
 
</source>
 +
Operators need to be registered the same way, so adding a registered operator to a menu requires two to three separate actions:
 +
*Register operator ;
 +
*then either append it to some existing menu ;
 +
*Or first register the newly created menu to which it should belong and only then append operator to it.
 +
Only after all that will the new operator appear to the user interface, at least, if nothing else, by pressing F3 in the 3D view window and typing the label of the operator
 +
 +
This may sound overly complicated but it makes it possible for example to show different things than just click-able entries in a menu for example to group several operators in a box.
 +
 +
{{NiceTip|The ''unregister()'' actions should appear in the opposite order to their corresponding register() lines| This makes sure that you do not try to unregister e.g. some menu that has been appended as a child of some UI element class that you might try to unregister before this child, thus unregistering something that is already no longer there... Which would trigger an error and failure to activate / deactivate the addon.}}<br/>
  
When we check the ''Enable an add-on'' check-box in the ''user preferences'', Blender will look for a '''register()''' function and execute it. Likewise, when disabling an add-on the '''unregister()''' function is called. Here we use this to both register our operator with Blender and to insert a menu entry that refers to our operator.
 
The '''bpy.utils.register_module()''' function will register any class in a module that has ''REGISTER'' defined in its ''bl_options'' set. In order to create a menu entry we have to do two things:
 
*Create a function that will produce a menu entry
 
*And append this function to a suitable menu.
 
Now almost everything in Blender is available as a Python object and menus are no exception. We want to add our entry to the Object menu in the 3D view so we call <source lang="py">bpy.types.VIEW3D_MT_object.append()</source> and pass it a reference to the function we define a little below. How do we know how this menu object is called? If you have checked ''File⇒ User preferences ⇒ Interface ⇒ Python Tooltips'' the name of the menu will be shown in a tooltip when you hover over a menu.From the image above we can see that we can use ''bpy.types.VIEW3D_MT_object.append()'' to add something to the Object menu because ''VIEW3D_MT_object'' is shown in the balloon text. Note that the ''menu_func()'' function does not implement an action itself but will, when called, append a user interface element to the object that is passed to it in the '''self''' parameter. This user interface element in turn will interact with the user. Here we will simply add an operator entry (that is, an item that will execute our operator when clicked). The ''self'' argument that is passed to ''menu_func()'' refers to the menu. This menu has a ''layout'' attribute with an ''operator()'' function to which we pass the name of our operator. This will ensure that every time a user hovers over the '''Object''' menu, our operator will be shown in the list of options. The name of our new ''MyTool'' operator can be found in its ''bl_idname'' attribute so that is why we pass ''MyTool.bl_idname''.
 
The name of the entry and its tooltip is determined by looking at the ''bl_label'' and docstring defined in our ''MyTool'' class and the icon used in the menu is determined by passing an optional '''icon''' parameter to the ''operator()'' function. This may sound overly complicated but it makes it possible for example to show different things than just click-able entries in a menu for example to group several operators in a box.
 
 
==Optimizing==
 
==Optimizing==
 
====Keep dot notation minimal====
 
====Keep dot notation minimal====
 +
As in many object oriented languages, Python allows the visualization of a hierarchical structure with its levels separated by dots (think of each dot as the opening of the bigger russian doll to the left stemming smaller ones to its right).
 
{{NiceTip|Replace its uselessly repeated lookups with aliases|
 
{{NiceTip|Replace its uselessly repeated lookups with aliases|
 
The dot operator is an easy way to grab some class, data, method, whatever python object included in a wider hierarchy... But be aware that it is expensive, especially if used in a loop.}}
 
The dot operator is an easy way to grab some class, data, method, whatever python object included in a wider hierarchy... But be aware that it is expensive, especially if used in a loop.}}
Line 232: Line 299:
  
 
There are cases where using ''try'' is faster than checking whether the condition will raise an error, so it is worth experimenting.
 
There are cases where using ''try'' is faster than checking whether the condition will raise an error, so it is worth experimenting.
 +
 +
 +
A good way to remove some of your try except is to replace them by a previous test with the hasattr() function when the error would be an AttributeError:
 +
<syntaxhighlight lang="python3">
 +
for member in dir(properties_freestyle):
 +
    subclass = getattr(properties_freestyle, member)
 +
    try:
 +
        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
 +
    except:
 +
        pass
 +
</syntaxhighlight>
 +
could become
 +
 +
<syntaxhighlight lang="python3">
 +
for member in dir(properties_freestyle):
 +
   subclass = getattr(properties_freestyle, member)
 +
   if hasattr(subclass, "COMPAT_ENGINES"):
 +
       subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
 +
</syntaxhighlight>
 +
If you do have to use try:... except:... don't leave the exception type unspecified, this gives absolutely no feedback nor code readability. Instead, if you have no clue what it could be, replace by something that would give more information during your debugging stage:
 +
 +
(BaseException is at the top of exception hierarchy)
 +
<syntaxhighlight lang="python3">
 +
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
 +
</syntaxhighlight>
  
 
==Separate python (*.py) files==
 
==Separate python (*.py) files==
Line 238: Line 337:
 
*''ui.py''
 
*''ui.py''
 
*''render.py''
 
*''render.py''
However, some of them reaching over 10 000 lines for such an add-on was still too much. So it is currently tolerated that the biggest file stays below 7000 lines. Most probably the files could lose some weight, by removing deprecated 32 bits support and modularizing some of the code into a few more reusable functions. Here are the current files composing the add-on and their respective use:
+
However, some of them reaching over 10 000 lines for such an add-on was still too much. So it is currently tolerated that the biggest file stays below 2000 lines. Most probably the files could lose some weight, by removing deprecated 32 bits support and modularizing some of the code into a few more reusable functions. Here are the current files composing the add-on and their respective use:
  
 +
===Initialize===
 
====__init__.py====
 
====__init__.py====
Initialize properties.
+
Create POV addon user preferences, aalow user to update addon to last version, and load all other modules.
 
(Note the double underscores around the file name. this makes it the first file launched)
 
(Note the double underscores around the file name. this makes it the first file launched)
 
defining the 'main' unit for a package; it also causes Python to treat the specific directory as a package. It is the unit that will be used when you call import render_povray (and render_povray is a directory).
 
defining the 'main' unit for a package; it also causes Python to treat the specific directory as a package. It is the unit that will be used when you call import render_povray (and render_povray is a directory).
  
====ui.py====
+
====scenography_properties.py====
Provide property buttons for the user to set up variables values.
+
Initialize properties for translating Blender cam/light/environment parameters to pov
 +
 
 +
====object_properties.py====
 +
Initialize properties for translating Blender objects parameters to pov
 +
 
 +
====shading_properties.py====
 +
Initialize properties for translating Blender materials parameters to pov
 +
 
 +
====texturing_properties.py====
 +
Initialize properties for translating Blender materials /world... texture influences to pov
 +
 
 +
====render_properties.py====
 +
Initialize properties for render parameters (Blender and POV native)
 +
 
 +
====scripting_properties.py====
 +
Initialize properties for scene description language parameters (POV native)
 +
 
 +
 
 +
===Interface===
 
Some notes to keep in mind when writing UI layouts:
 
Some notes to keep in mind when writing UI layouts:
  
Line 304: Line 422:
 
     sub for a sub layout (a column inside a column for example)
 
     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====
 
====render.py====
 
Translate geometry and UI properties (Blender and POV native) to the POV file
 
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====
 
====primitives.py====
 
Display some POV native primitives in 3D view for input and output
 
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====
 
====shading.py====
 
Translate shading properties to declared textures at the top of a pov file
 
Translate shading properties to declared textures at the top of a pov file
  
====nodes.py====
 
Translate node trees to the pov file
 
  
====df3.py====
+
====texturing.py====
 +
Translate blender texture influences into POV
 +
 
 +
 
 +
====df3_library.py====
 
Render smoke to *.df3 files
 
Render smoke to *.df3 files
 +
 +
 +
====scripting.py====
 +
Insert POV native scene description elements to exported POV file
 +
  
 
====update_files.py====
 
====update_files.py====
Line 482: Line 660:
  
 
and the MCMakelists.txt had to be modified to include added files.
 
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:
 +
 +
<source lang="py">
 +
# ##### 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)
 +
 +
</source>
 +
 +
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.

Latest revision as of 09:19, 30 December 2022


User's page
POVExporterLogo.png
Developer's page



The Blender to POV-Ray add-on and vice versa (aka "Bpover") started out as a single Python file. To be compliant with blender add-on packaging system this add-on had to:

  • provide some general information about the add-on: its name and version,
  • define some code to perform actions, mostly through 'operators' or 'functions'
  • and make sure these are registered so that they can be used.


bpy

Blender has an embedded Python interpreter which is loaded when Blender is started and stays active while Blender is running. This interpreter runs scripts to draw the user interface and is used for some of Blender’s internal tools as well. Blender’s embedded interpreter provides a typical Python environment, so code from tutorials on how to write Python scripts can also be run with Blender’s interpreter. Blender provides its Python modules, such as bpy and mathutils, to the embedded interpreter so they can be imported into a script and give access to Blender’s data, classes, and functions. Scripts that deal with Blender data will need to import the modules to work. So of all modules, if no other, bpy is generally always imported in blender scripts. (except in interactive console were this import is automatically done for convenience)

Blender's interactive console


bl_info

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)",
}


The general information about the add-on is defined in a dictionary with the name

bl_info

Each key of this dictionary provides Blender with specific information about the add-on although not all are equally important. Most of the information is used in the user preferences dialog and helps the user to find and select an add-on.

name

Kind of a code name for the addon, by which it is referenced in the Blender source... (UI name can show something else)

author

If you contribute you will be credited here !

version

The version of the add-on. Any numbering scheme is valid, as long as it is a tuple of three integers. Preferably only move the last position but we might choose for a more structured scheme later on.

blender

The minimal Blender version needed by this add-on. Again a tuple of three integers. Even if you expect things to work with older and nnewer versions it might be a good idea to list the earliest version that you actually tested your add-on with! Developing with something stable enough is important, and then test with latest version just before commit but don't generally update compatible version number then, only when something breaks at that stage.

category

The category in the user preferences our add-on is grouped under. It operates POV compatible engines which are renderers so it made sense to add it to the Render category historically. Unfortunately it doesnt reflect the multipurpose nature it ended up growing (such as shipped in importer, text editor syntax highlighting etc...) If several categories are possible maybe we should add them.

location

Where to find the add-on once it is enabled. This might a reference to a specific panel or inout case, a description of its location in a menu.

description

A concise description of what the add-on does.

warning

If this is not an empty string, the add-on will show up with a warning sign in the user preferences. You might use this to mark an add-on as experimental for example.

doc_url

the public on-line documentation, it is a derivative of the work happening here. It will be a click-able item in the user preferences.

tracker_url

The POV add-on being a bundled part of Blender it does have its own bug tracker entry associated with it and this key will provide a pointer to it.

Comments

Single line comments begin with a hash (#) character immediately followed by one blank space and the comment starting with a capital but not ending with a dot. They can be added after a short enough line of code (leave one blank space before the #).

For instance, at the beginning of the file, the comment

# <pep8 compliant>

means that one of the text formatting / coding style guideline to use is PEP8 so, for instance, you should take care to make short enough lines throughout all the script's files of less than 79 characters and less than 72 for docstrings (see below) or comments. Note that this very comment is a special syntax: Periodically the Blender Foundation runs checks for pep8 compliance on blender scripts, for scripts to be included in this check add this line as a comment at the top of the script.

Other rules include

  • camel caps for class names: MyClass
  • all lower case underscore separated module names: my_module
  • indentation of 4 spaces (no tabs)
  • spaces around operators. 1 + 1, not 1+1
  • only use explicit imports, (no importing *)
  • don’t use single line: if val: body, separate onto 2 lines instead.

While multi-line comments can begin with triple quotes. It is the convention that three double quotes are used for docstrings, a specific kind of comment not doing anything in the program but still appearing when console is used to get help about a function.

"""This is an explanation of general relevance"""

Besides being good practice to put a doc string at the start of every class to describe it and also at the start of every file to explain its relative use, it also enables command line calls to display this doc on request. That's one of the main difference between docstrings and inlined # comments.

Functions

Functions are defined with the def keyword, followed by the name of the function and a pair of parenthesis. When the function is referenced to move it around and pass it along between files, it no longer needs the parenthesis, but pretty much every other time, they do follow function's name and in between them, some attributes can eventually be specified.

Classes

Not only do Classes allow you to create reusable definitions. But they are the only way to set up user interface elements in Blender. The good news is: that means addons developers actually have less work to do. The hardcoded implementation takes care of determining when and what parts to update for all ui elements automatically, and the inheritence of classes to choose from (the API) make sure one can handle situations in optimized ways. Some inherited functions such as draw() are to be overloaded by the add-on developer who rewrites each one to his need.

To create new classes, either use the regular class definition scheme as shown in the TextEditor Python Templates... Or copy and paste one from elsewhere in the script.

Class Naming Convention

Respect the same naming pattern as asked by the Blender Foundation as much as possible: OT means it's an operator, MT a menu, PT à panel:

The Class name should be as follows: ADDONNAME_PT_main_panel ADDONNAME - Using Uppercase, we type the Name of the Add-on. An underscore can be used if the name is more than one word.

_PT_ - it is then separated by two letters which denotes the Class Type (from which the type is inherited).

main_panel - using lowercase, we can type the name of the Operator/Panel/Header ect.

Other Class Types:

   HT – Header
   MT – Menu
   OT – Operator
   PT – Panel
   UL – UI list

So, addon name being POV in our case, here are some examples of Valid Class Names and their Identifiers:

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'

Notice how the Identifier for the Operator is different? We do not need to add the OT but instead we must add a full stop. We also do not need to add the '_operator' at the end.


The Blender Python api allows integration for:

   bpy.types.Panel
   bpy.types.Menu
   bpy.types.Operator
   bpy.types.PropertyGroup
   bpy.types.KeyingSet
   bpy.types.RenderEngine

This is intentionally limited. Currently, for more advanced features such as mesh modifiers, object types, or shader nodes, C/C++ must be used.

For Python integration Blender defines methods which are common to all types. This works by creating a Python subclass of a Blender class which contains variables and functions specified by the parent class which are pre-defined to interface with Blender.

Note that we subclass a member of bpy.types, this is common for all classes which can be integrated with Blender and used so we know if this is an Operator and not a Panel when registering.

Both class properties start with a bl_ prefix. This is a convention used to distinguish Blender properties from those you add yourself.

Operators

Most add-ons define new operators, they are classes that implement specific functionality. The actual definition of the operator takes the form a class that is derived from 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'}

The docstring at the start of the class definition will be used as a tooltip anywhere this operator will be available, for example in a menu, while the bl_label defines the actual label used in the menu entry itself. Here we kept both the same. Operators will be part of Blender's data, and operators are stored in the module bpy.ops. This bl_idname will make sure this operator's entry will be called bpy.ops.object.move_object. Operators are normally registered in order to make them usable and that is indeed the default of bl_options. However, if we also want the add-on to show up in the history so it can be undone or repeated, we should add UNDO to the set of flags that is assigned to bl_options, as is done here.


Operators have limitations that can make them cumbersome to script.

Main limits are…

Can’t pass data such as objects, meshes or materials to operate on (operators use the context instead)

The return value from calling an operator gives the success (if it finished or was canceled), in some cases it would be more logical from an API perspective to return the result of the operation.

Operators poll function can fail where an API function would raise an exception giving details on exactly why.

The execute() function

An operator class can have any number of member functions but to be useful it normally overrides the execute() function:

def execute(self, context):
    context.active_object.rotation.z += 33
    return {'FINISHED'}

The execute() function is passed a reference to a context object. This context object contains among other things an active_object attribute which points to, Blenders active object. Each object in Blender has a rotation attribute which is a vector with x, y and z components. Changing the rotation of an object is as simple as changing one of these components, which is exactly what we do in line 2.

The execute() function signals successful completion by returning a set of flags, in this case a set consisting solely of a string FINISHED.

Registering and adding a menu entry

When we check the Enable add-on check-box in the user preferences, Blender will look for a register() function and execute it. Likewise, when disabling an add-on the unregister() function is called. Let us use this to both register our operator with Blender and to insert a menu entry that refers to our operator.

In order to create a menu entry we have to do two things:

  • Create a function that will produce a menu entry
  • And append this function to a suitable menu.

Now almost everything in Blender is available as a Python object and menus are no exception. We want to add our entry to the Object menu in the 3D view so we call

bpy.types.VIEW3D_MT_object.append()

and pass it a reference to the function we define a little below. How do we know how this menu object is called? If you have checked File⇒ User preferences ⇒ Interface ⇒ Python Tooltips the name of the menu will be shown in a tooltip when you hover over a menu.From the image above we can see that we can use bpy.types.VIEW3D_MT_object.append() to add something to the Object menu because VIEW3D_MT_object is shown in the balloon text.

BlightBulb.png
The menu_func() function does not implement an action itself (that's the role of an operator)
But when called, menu_func() will append a user interface element pointing to the object that is passed to it in the self parameter. This interface element can be modified by the user and in turn, will interact with the operator.


Here we will simply add an operator entry (that is, an item that will execute our operator when clicked).

The self argument that is passed to menu_func() refers to the menu.

This menu has a layout attribute with an operator() function to which we pass the name of our operator. This will ensure that every time a user hovers over the Object menu, our operator will be shown in the list of options.

The name of our new MyTool operator can be found in its bl_idname attribute so that is why we pass MyTool.bl_idname.

The name of the entry and its tooltip is determined by looking at the bl_label and docstring defined in our MyTool class and the icon used in the menu is determined by passing an optional icon parameter to the operator() function.

Defining a menu is not in itself enough to make this menu usable. In order for the user to find and use it, we must append the menu to its parent from within the register() function at the end of its python file.

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)

Operators need to be registered the same way, so adding a registered operator to a menu requires two to three separate actions:

  • Register operator ;
  • then either append it to some existing menu ;
  • Or first register the newly created menu to which it should belong and only then append operator to it.

Only after all that will the new operator appear to the user interface, at least, if nothing else, by pressing F3 in the 3D view window and typing the label of the operator

This may sound overly complicated but it makes it possible for example to show different things than just click-able entries in a menu for example to group several operators in a box.

BlightBulb.png
The unregister() actions should appear in the opposite order to their corresponding register() lines
This makes sure that you do not try to unregister e.g. some menu that has been appended as a child of some UI element class that you might try to unregister before this child, thus unregistering something that is already no longer there... Which would trigger an error and failure to activate / deactivate the addon.


Optimizing

Keep dot notation minimal

As in many object oriented languages, Python allows the visualization of a hierarchical structure with its levels separated by dots (think of each dot as the opening of the bigger russian doll to the left stemming smaller ones to its right).

BlightBulb.png
Replace its uselessly repeated lookups with aliases
The dot operator is an easy way to grab some class, data, method, whatever python object included in a wider hierarchy... But be aware that it is expensive, especially if used in a loop.

When calling

bpy.data.objects["my_obj"]

Every dot dives one level deeper and the whole hierarchy is walked through up to it. You always need to do it at least once to reach the desired item, so it's fine to use that syntax when the reference is only made once, but above that, always prefer to avoid the whole lookup and rather give it an alias like

obj = bpy.data.objects["my_obj"]

and then you can still use it , handling occasional attributes like below example:

obj.location.z += 1

and all other available attributes, for your reading or writing operations. Define as many of these required aliases before entering a loop rather than inside of it. Imagine if your GPS always told you previous directions taken from starting point up to the next one at every turn !

Use try/except sparingly

The try statement is useful to save time writing error checking code.

However try is significantly slower than an if since an exception has to be set each time, so avoid using try in areas of your code that execute in a loop and runs many times.

There are cases where using try is faster than checking whether the condition will raise an error, so it is worth experimenting.


A good way to remove some of your try except is to replace them by a previous test with the hasattr() function when the error would be an AttributeError:

for member in dir(properties_freestyle):
    subclass = getattr(properties_freestyle, member)
    try:
        subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
    except:
        pass

could become

for member in dir(properties_freestyle):
   subclass = getattr(properties_freestyle, member)
   if hasattr(subclass, "COMPAT_ENGINES"):
       subclass.COMPAT_ENGINES.add('POVRAY_RENDER')

If you do have to use try:... except:... don't leave the exception type unspecified, this gives absolutely no feedback nor code readability. Instead, if you have no clue what it could be, replace by something that would give more information during your debugging stage:

(BaseException is at the top of exception hierarchy)

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

Separate python (*.py) files

The script was later split into a package (several files into a folder) using as few files as possible to keep the flow of data easier to understand. There were initially 3 main files:

  • __init__.py
  • ui.py
  • render.py

However, some of them reaching over 10 000 lines for such an add-on was still too much. So it is currently tolerated that the biggest file stays below 2000 lines. Most probably the files could lose some weight, by removing deprecated 32 bits support and modularizing some of the code into a few more reusable functions. Here are the current files composing the add-on and their respective use:

Initialize

__init__.py

Create POV addon user preferences, aalow user to update addon to last version, and load all other modules. (Note the double underscores around the file name. this makes it the first file launched) defining the 'main' unit for a package; it also causes Python to treat the specific directory as a package. It is the unit that will be used when you call import render_povray (and render_povray is a directory).

scenography_properties.py

Initialize properties for translating Blender cam/light/environment parameters to pov

object_properties.py

Initialize properties for translating Blender objects parameters to pov

shading_properties.py

Initialize properties for translating Blender materials parameters to pov

texturing_properties.py

Initialize properties for translating Blender materials /world... texture influences to pov

render_properties.py

Initialize properties for render parameters (Blender and POV native)

scripting_properties.py

Initialize properties for scene description language parameters (POV native)


Interface

Some notes to keep in mind when writing UI layouts:

UI code is quite simple. Layout declarations are there to easily create a decent layout. General rule here: If you need more code for the layout declaration, then for the actual properties, you do it wrong.

Example layouts:

   layout()

The basic layout is a simple Top -> Bottom layout.

   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)

apple.py

chicken.py

cream.py

Ketchup.py

marble.py

potato.py

skim_milk.py

skin1.py

skin2.py

whole_milk.py

Radiosity

01_Debug.py

02_Fast.py

03_Normal.py

04_Two_Bounces.py

05_Final.py

06_Outdoor_Low_Quality.py

07_Outdoor_High_Quality.py

08_Outdoor(Sun)Light.py

09_Indoor_Low_Quality.py

10_Indoor_High_Quality.py

World

01_Clear_Blue_Sky.py

02_Partly_Hazy_Sky.py

03_Overcast_Sky.py

04_Cartoony_Sky.py

05_Under_Water.py

Light

01_(4800K)_Direct_Sun.py

02_(5400K)_High_Noon_Sun.py

03_(6000K)_Daylight_Window.py

04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py

05_(4000K)_100W_Metal_Halide.py

06_(3200K)_100W_Quartz_Halogen.py

07_(2850K)_100w_Tungsten.py

08_(2600K)_40w_Tungsten.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

19_(5200K)_700W_Carbon_Arc.py

20_(6500K)_15W_LED_Spot.py

21_(2700K)_7W_OLED_Panel.py

22_(30000K)_40W_Black_Light_Fluorescent.py

23_(30000K)_40W_Black_Light_Bulb.py

24_(1850K)_Candle.py

templates

abyss.pov

biscuit.pov

bsp_Tango.pov

chess2.pov

cornell.pov

diffract.pov

diffuse_back.pov

float5

gamma_showcase.pov

grenadine.pov

isocacti.pov

mediasky.pov

patio-radio.pov

subsurface.pov

wallstucco.pov

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.