HowTo:Use POV-Ray with Blender/Developers page
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
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)
add-on’s metadata
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)",
}
Before Blender 4.2, The general information about the add-on used to be defined in a dictionary with the name
bl_info
Each key of this dictionary provided Blender with specific information about the add-on although not all were equally important. Most of the information was used in the user preferences dialog and helped 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.
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.
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.
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).
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)
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.