From 4fd9df25c6d466a2fe0c97c325e1662b9902df96 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 7 Dec 2012 05:27:09 +0000 Subject: Add 2 documents to the python api reference. - Blender/Python Addon Tutorial: a step by step guide on how to write an addon from scratch - Blender/Python API Reference Usage: examples of how to use the API reference docs Thanks to John Nyquist for editing these docs and giving feedback. --- doc/python_api/rst/info_api_reference.rst | 305 +++++++++++++ doc/python_api/rst/info_gotcha.rst | 2 + doc/python_api/rst/info_overview.rst | 2 + doc/python_api/rst/info_quickstart.rst | 2 + doc/python_api/rst/info_tips_and_tricks.rst | 6 +- doc/python_api/rst/info_tutorial_addon.rst | 645 ++++++++++++++++++++++++++++ doc/python_api/sphinx_doc_gen.py | 7 + 7 files changed, 967 insertions(+), 2 deletions(-) create mode 100644 doc/python_api/rst/info_api_reference.rst create mode 100644 doc/python_api/rst/info_tutorial_addon.rst (limited to 'doc') diff --git a/doc/python_api/rst/info_api_reference.rst b/doc/python_api/rst/info_api_reference.rst new file mode 100644 index 00000000000..ddee46dce11 --- /dev/null +++ b/doc/python_api/rst/info_api_reference.rst @@ -0,0 +1,305 @@ + +******************* +Reference API Usage +******************* + +Blender has many interlinking data types which have an auto-generated reference api which often has the information +you need to write a script, but can be difficult to use. + +This document is designed to help you understand how to use the reference api. + + +Reference API Scope +=================== + +The reference API covers :mod:`bpy.types`, which stores types accessed via :mod:`bpy.context` - *The user context* +or :mod:`bpy.data` - *Blend file data*. + +Other modules such as :mod:`bge`, :mod:`bmesh` and :mod:`aud` are not using Blenders data API +so this document doesn't apply to those modules. + + +Data Access +=========== + +The most common case for using the reference API is to find out how to access data in the blend file. + +Before going any further its best to be aware of ID Data-Blocks in Blender since you will often find properties +relative to them. + + +ID Data +------- + +ID Data-Blocks are used in Blender as top-level data containers. + +From the user interface this isn't so obvious, but when developing you need to know about ID Data-Blocks. + +ID data types include Scene, Group, Object, Mesh, Screen, World, Armature, Image and Texture. +for a full list see the sub-classes of :class:`bpy.types.ID` + +Here are some characteristics ID Data-Blocks share. + +- ID's are blend file data, so loading a new blend file reloads an entire new set of Data-Blocks. +- ID's can be accessed in Python from ``bpy.data.*`` +- Each data-block has a unique ``.name`` attribute, displayed in the interface. +- Animation data is stored in ID's ``.animation_data``. +- ID's are the only data types that can be linked between blend files. +- ID's can be added/copied and removed via Python. +- ID's have their own garbage-collection system which frees unused ID's when saving. +- When a data-block has a reference to some external data, this is typically an ID Data-Block. + + +Simple Data Access +------------------ + +Lets start with a simple case, say you wan't a python script to adjust the objects location. + +Start by finding this setting in the interface ``Properties Window -> Object -> Transform -> Location`` + +From the button you can right click and select **Online Python Reference**, this will link you to: +:class:`bpy.types.Object.location` + +Being an API reference, this link often gives little more information then the tool-tip, though some of the pages +include examples (normally at the top of the page). + +At this point you may say *Now what?* - you know that you have to use ``.location`` and that its an array of 3 floats +but you're still left wondering how to access this in a script. + +So the next step is to find out where to access objects, go down to the bottom of the page to the **References** +section, for objects there are many references, but one of the most common places to access objects is via the context. + +It's easy to be overwhelmed at this point since there ``Object`` get referenced in so many places - modifiers, +functions, textures and constraints. + +But if you want to access any data the user has selected +you typically only need to check the :mod:`bpy.context` references. + +Even then, in this case there are quite a few though if you read over these - most are mode specific. +If you happen to be writing a tool that only runs in weight paint mode, then using ``weight_paint_object`` +would be appropriate. +However to access an item the user last selected, look for the ``active`` members, +Having access to a single active member the user selects is a convention in Blender: eg. ``active_bone``, +``active_pose_bone``, ``active_node`` ... and in this case we can use - ``active_object``. + + +So now we have enough information to find the location of the active object. + +.. code-block:: python + + bpy.context.active_object.location + +You can type this into the python console to see the result. + +The other common place to access objects in the reference is :class:`bpy.types.BlendData.objects`. + +.. note:: + + This is **not** listed as :mod:`bpy.data.objects`, + this is because :mod:`bpy.data` is an instance of the :class:`bpy.types.BlendData` class, + so the documentation points there. + + +With :mod:`bpy.data.objects`, this is a collection of objects so you need to access one of its members. + +.. code-block:: python + + bpy.data.objects["Cube"].location + + +Nested Properties +----------------- + +The previous example is quite straightforward because ``location`` is a property of ``Object`` which can be accessed +from the context directly. + +Here are some more complex examples: + +.. code-block:: python + + # access a render layers samples + bpy.context.scene.render.layers["RenderLayer"].samples + + # access to the current weight paint brush size + bpy.context.tool_settings.weight_paint.brush.size + + # check if the window is fullscreen + bpy.context.window.screen.show_fullscreen + + +As you can see there are times when you want to access data which is nested +in a way that causes you to go through a few indirections. + +The properties are arranged to match how data is stored internally (in blenders C code) which is often logical but +not always quite what you would expect from using Blender. + +So this takes some time to learn, it helps you understand how data fits together in Blender which is important +to know when writing scripts. + + +When starting out scripting you will often run into the problem where you're not sure how to access the data you want. + +There are a few ways to do this. + +- Use the Python console's auto-complete to inspect properties. *This can be hit-and-miss but has the advantage + that you can easily see the values of properties and assign them to interactively see the results.* + +- Copy the Data-Path from the user interface. *Explained further in :ref:`Copy Data Path `* + +- Using the documentation to follow references. *Explained further in :ref:`Indirect Data Access `* + + +.. _info_data_path_copy + +Copy Data Path +-------------- + +Blender can compute the Python string to a property which is shown in the tool-tip, on the line below ``Python: ...``, +This saves having to use the API reference to click back up the references to find where data is accessed from. + +There is a user-interface feature to copy the data-path which gives the path from an :class:`bpy.types.ID` data-block, +to its property. + +To see how this works we'll get the path to the Subdivision-Surface modifiers subdivision setting. + +Start with the default scene and select the **Modifiers** tab, then add a **Subdivision-Surface** modifier to the cube. + +Now hover your mouse over the button labeled **View**, The tool-tip includes :class:`bpy.types.SubsurfModifier.levels` +but we want the path from the object to this property. + +Note that the text copied won't include the ``bpy.data.collection["name"].`` component since its assumed that +you won't be doing collection look-ups on every access and typically you'll want to use the context rather +then access each :class:`bpy.types.ID` instance by name. + + +Type in the ID path into a Python console :mod:`bpy.context.active_object`. Include the trailing dot and don't hit "enter", yet. + +Now right-click on the button and select **Copy Data Path**, then paste the result into the console. + +So now you should have the answer: + +.. code-block:: python + + bpy.context.active_object.modifiers["Subsurf"].levels + +Hit "enter" and you'll get the current value of 1. Now try changing the value to 2: + +.. code-block:: python + + bpy.context.active_object.modifiers["Subsurf"].levels = 2 + +You can see the value update in the Subdivision-Surface modifier's UI as well as the cube. + + +.. _info_data_path_indirect + +Indirect Data Access +-------------------- + +For this example we'll go over something more involved, showing the steps to access the active sculpt brushes texture. + +Lets say we want to access the texture of a brush via Python, to adjust its ``contrast`` for example. + +- Start in the default scene and enable 'Sculpt' mode from the 3D-View header. + +- From the toolbar expand the **Texture** panel and add a new texture. + + *Notice the texture button its self doesn't have very useful links (you can check the tool-tips).* + +- The contrast setting isn't exposed in the sculpt toolbar, so view the texture in the properties panel... + + - In the properties button select the Texture context. + + - Select the Brush icon to show the brush texture. + + - Expand the **Colors** panel to locate the **Contrast** button. + +- Right click on the contrast button and select **Online Python Reference** This takes you to ``bpy.types.Texture.contrast`` + +- Now we can see that ``contrast`` is a property of texture, so next we'll check on how to access the texture from the brush. + +- Check on the **References** at the bottom of the page, sometimes there are many references, and it may take + some guess work to find the right one, but in this case its obviously ``Brush.texture``. + + *Now we know that the texture can be accessed from* ``bpy.data.brushes["BrushName"].texture`` + *but normally you won't want to access the brush by name, so we'll see now to access the active brush instead.* + +- So the next step is to check on where brushes are accessed from via the **References**. + In this case there is simply ``bpy.context.brush`` which is all we need. + + +Now you can use the Python console to form the nested properties needed to access brush textures contrast, +logically we now know. + +*Context -> Brush -> Texture -> Contrast* + +Since the attribute for each is given along the way we can compose the data path in the python console: + +.. code-block:: python + + bpy.context.brush.texture.contrast + + +There can be multiple ways to access the same data, which you choose often depends on the task. + +An alternate path to access the same setting is... + +.. code-block:: python + + bpy.context.sculpt.brush.texture.contrast + +Or access the brush directly... + +.. code-block:: python + + bpy.data.brushes["BrushName"].texture.contrast + + +If you are writing a user tool normally you want to use the :mod:`bpy.context` since the user normally expects +the tool to operate on what they have selected. + +For automation you are more likely to use :mod:`bpy.data` since you want to be able to access specific data and manipulate +it, no matter what the user currently has the view set at. + + +Operators +========= + +Most key-strokes and buttons in Blender call an operator which is also exposed to python via :mod:`bpy.ops`, + +To see the Python equivalent hover your mouse over the button and see the tool-tip, +eg ``Python: bpy.ops.render.render()``, +If there is no tool-tip or the ``Python:`` line is missing then this button is not using an operator and +can't be accessed from Python. + + +If you want to use this in a script you can press :kbd:`Control-C` while your mouse is over the button to copy it to the +clipboard. + +You can also right click on the button and view the **Online Python Reference**, this mainly shows arguments and +their defaults however operators written in Python show their file and line number which may be useful if you +are interested to check on the source code. + +.. note:: + + Not all operators can be called usefully from Python, for more on this see :ref:`using operators `. + + +Info View +--------- + +Blender records operators you run and displays them in the **Info** space. +This is located above the file-menu which can be dragged down to display its contents. + +Select the **Script** screen that comes default with Blender to see its output. +You can perform some actions and see them show up - delete a vertex for example. + +Each entry can be selected (Right-Mouse-Button), then copied :kbd:`Control-C`, usually to paste in the text editor or python console. + +.. note:: + + Not all operators get registered for display, + zooming the view for example isn't so useful to repeat so its excluded from the output. + + To display *every* operator that runs see :ref:`Show All Operators ` + diff --git a/doc/python_api/rst/info_gotcha.rst b/doc/python_api/rst/info_gotcha.rst index a48cb8fc15e..34145c2ac49 100644 --- a/doc/python_api/rst/info_gotcha.rst +++ b/doc/python_api/rst/info_gotcha.rst @@ -5,6 +5,8 @@ Gotchas This document attempts to help you work with the Blender API in areas that can be troublesome and avoid practices that are known to give instability. +.. _using_operators: + Using Operators =============== diff --git a/doc/python_api/rst/info_overview.rst b/doc/python_api/rst/info_overview.rst index 818eb692be9..b2d524b74af 100644 --- a/doc/python_api/rst/info_overview.rst +++ b/doc/python_api/rst/info_overview.rst @@ -1,3 +1,5 @@ +.. _info_overview: + ******************* Python API Overview ******************* diff --git a/doc/python_api/rst/info_quickstart.rst b/doc/python_api/rst/info_quickstart.rst index 62ad4e9c4d8..e1264ae9d52 100644 --- a/doc/python_api/rst/info_quickstart.rst +++ b/doc/python_api/rst/info_quickstart.rst @@ -1,3 +1,5 @@ +.. _info_quickstart: + *********************** Quickstart Introduction *********************** diff --git a/doc/python_api/rst/info_tips_and_tricks.rst b/doc/python_api/rst/info_tips_and_tricks.rst index 4dcbf431724..75e8ef61f6f 100644 --- a/doc/python_api/rst/info_tips_and_tricks.rst +++ b/doc/python_api/rst/info_tips_and_tricks.rst @@ -44,15 +44,17 @@ if this can't be generated, only the property name is copied. .. note:: - This uses the same method for creating the animation path used by :class:`FCurve.data_path` and :class:`DriverTarget.data_path` drivers. + This uses the same method for creating the animation path used by :class:`bpy.types.FCurve.data_path` and :class:`bpy.types.DriverTarget.data_path` drivers. +.. _info_show_all_operators + Show All Operators ================== While blender logs operators in the Info space, this only reports operators with the ``REGISTER`` option enabeld so as not to flood the Info view with calls to ``bpy.ops.view3d.smoothview`` and ``bpy.ops.view3d.zoom``. -However, for testing it can be useful to see **every** operator called in a terminal, do this by enabling the debug option either by passing the ``--debug`` argument when starting blender or by setting :mod:`bpy.app.debug` to True while blender is running. +However, for testing it can be useful to see **every** operator called in a terminal, do this by enabling the debug option either by passing the ``--debug-wm`` argument when starting blender or by setting :mod:`bpy.app.debug_wm` to True while blender is running. Use an External Editor diff --git a/doc/python_api/rst/info_tutorial_addon.rst b/doc/python_api/rst/info_tutorial_addon.rst new file mode 100644 index 00000000000..2a101041227 --- /dev/null +++ b/doc/python_api/rst/info_tutorial_addon.rst @@ -0,0 +1,645 @@ + +Addon Tutorial +############## + +************ +Introduction +************ + + +Intended Audience +================= + +This tutorial is designed to help technical artists or developers learn to extend Blender. +An understanding of the basics of Python is expected for those working through this tutorial. + + +Prerequisites +------------- + +Before going through the tutorial you should... + +* Familiarity with the basics of working in Blender. + +* Know how to run a script in Blender's text editor (as documented in the quick-start) + +* Have an understanding of Python primitive types (int, boolean, string, list, tuple, dictionary, and set). + +* Be familiar with the concept of Python modules. + +* Basic understanding of classes (object orientation) in Python. + + +Suggested reading before starting this tutorial. + +* `Dive Into Python `_ sections (1, 2, 3, 4, and 7). +* :ref:`Blender API Quickstart ` + to help become familiar with Blender/Python basics. + + +To best troubleshoot any error message Python prints while writing scripts you run blender with from a terminal, +see :ref:`Use The Terminal `. + +Documentation Links +=================== + +While going through the tutorial you may want to look into our reference documentation. + +* :ref:`Blender API Overview `. - + *This document is rather detailed but helpful if you want to know more on a topic.* + +* :mod:`bpy.context` api reference. - + *Handy to have a list of available items your script may operate on.* + +* :class:`bpy.types.Operator`. - + *The following addons define operators, these docs give details and more examples of operators.* + + +****** +Addons +****** + + +What is an Addon? +================= + +An addon is simply a Python module with some additional requirements so Blender can display it in a list with useful +information. + +To give an example, here is the simplest possible addon. + + +.. code-block:: python + + bl_info = {"name": "My Test Addon", "category": "Object"} + def register(): + print("Hello World") + def unregister(): + print("Goodbye World") + + +* ``bl_info`` is a dictionary containing addon meta-data such as the title, version and author to be displayed in the + user preferences addon list. +* ``register`` is a function which only runs when enabling the addon, this means the module can be loaded without + activating the addon. +* ``unregister`` is a function to unload anything setup by ``register``, this is called when the addon is disabled. + + + +Notice this addon does not do anything related to Blender, (the :mod:`bpy` module is not imported for example). + +This is a contrived example of an addon that serves to illustrate the point +that the base requirements of an addon are simple. + +An addon will typically register operators, panels, menu items etc, but its worth noting that _any_ script can do this, +when executed from the text editor or even the interactive console - there is nothing inherently different about an +addon that allows it to integrate with Blender, such functionality is just provided by the :mod:`bpy` module for any +script to access. + +So an addon is just a way to encapsulate a Python module in a way a user can easily utilize. + +.. note:: + + Running this script within the text editor won't print anything, + to see the output it must be installed through the user preferences. + Messages will be printed when enabling and disabling. + + +Your First Addon +================ + +The simplest possible addon above was useful as an example but not much else. +This next addon is simple but shows how to integrate a script into Blender using an ``Operator`` +which is the typical way to define a tool accessed from menus, buttons and keyboard shortcuts. + +For the first example we'll make a script that simply moves all objects in a scene. + + +Write The Script +---------------- + +Add the following script to the text editor in Blender. + +.. code-block:: python + + import bpy + + scene = bpy.context.scene + for obj in scene.objects: + obj.location.x += 1.0 + + +.. image:: run_script.png + :width: 924px + :align: center + :height: 574px + :alt: Run Script button + +Click the Run Script button, all objects in the active scene are moved by 1.0 Blender unit. +Next we'll make this script into an addon. + + +Write the Addon (Simple) +------------------------ + +This addon takes the body of the script above, and adds them to an operator's ``execute()`` function. + + +.. code-block:: python + + bl_info = { + "name": "Move X Axis", + "category": "Object", + } + + import bpy + + + class ObjectMoveX(bpy.types.Operator): + """My Object Moving Script""" # blender will use this as a tooltip for menu items and buttons. + bl_idname = "object.move_x" # unique identifier for buttons and menu items to reference. + bl_label = "Move X by One" # display name in the interface. + bl_options = {'REGISTER', 'UNDO'} # enable undo for the operator. + + def execute(self, context): # execute() is called by blender when running the operator. + + # The original script + scene = context.scene + for obj in scene.objects: + obj.location.x += 1.0 + + return {'FINISHED'} # this lets blender know the operator finished successfully. + + def register(): + bpy.utils.register_class(ObjectMoveX) + + + def unregister(): + bpy.utils.unregister_class(ObjectMoveX) + + + # This allows you to run the script directly from blenders text editor + # to test the addon without having to install it. + if __name__ == "__main__": + register() + + +.. note:: ``bl_info`` is split across multiple lines, this is just a style convention used to more easily add items. + +.. note:: Rather than using ``bpy.context.scene``, we use the ``context.scene`` argument passed to ``execute()``. + In most cases these will be the same however in some cases operators will be passed a custom context + so script authors should prefer the ``context`` argument passed to operators. + + +To test the script you can copy and paste this into Blender text editor and run it, this will execute the script +directly and call register immediately. + +However running the script wont move any objects, for this you need to execute the newly registered operator. + +.. image:: spacebar.png + :width: 924px + :align: center + :height: 574px + :alt: Spacebar + +Do this by pressing ``SpaceBar`` to bring up the operator search dialog and type in "Move X by One" (the ``bl_label``), +then press ``Enter``. + + + +The objects should move as before. + +*Keep this addon open in Blender for the next step - Installing.* + +Install The Addon +----------------- + +Once you have your addon within in Blender's text editor, you will want to be able to install it so it can be enabled in +the user preferences to load on startup. + +Even though the addon above is a test, lets go through the steps anyway so you know how to do it for later. + +To install the Blender text as an addon you will first have to save it to disk, take care to obey the naming +restrictions that apply to Python modules and end with a ``.py`` extension. + +Once the file is on disk, you can install it as you would for an addon downloaded online. + +Open the user **File -> User Preferences**, Select the **Addon** section, press **Install Addon...** and select the file. + +Now the addon will be listed and you can enable it by pressing the check-box, if you want it to be enabled on restart, +press **Save as Default**. + +.. note:: + + The destination of the addon depends on your Blender configuration. + When installing an addon the source and destination path are printed in the console. + You can also find addon path locations by running this in the Python console. + + .. code-block:: python + + import addon_utils + print(addon_utils.paths()) + + More is written on this topic here: + `Directory Layout `_ + + +Your Second Addon +================= + +For our second addon, we will focus on object instancing - this is - to make linked copies of an object in a +similar way to what you may have seen with the array modifier. + + +Write The Script +---------------- + +As before, first we will start with a script, develop it, then convert into an addon. + +.. code-block:: python + + import bpy + from bpy import context + + # Get the current scene + scene = context.scene + + # Get the 3D cursor + cursor = scene.cursor_location + + # Get the active object (assume we have one) + obj = scene.objects.active + + # Now make a copy of the object + obj_new = obj.copy() + + # The object won't automatically get into a new scene + scene.objects.link(obj_new) + + # Now we can place the object + obj_new.location = cursor + + +Now try copy this script into Blender and run it on the default cube. +Make sure you click to move the 3D cursor before running as the duplicate will appear at the cursor's location. + + +... go off and test ... + + +After running, notice that when you go into edit-mode to change the cube - all of the copies change, +in Blender this is known as *Linked-Duplicates*. + + +Next, we're going to do this in a loop, to make an array of objects between the active object and the cursor. + + +.. code-block:: python + + import bpy + from bpy import context + + scene = context.scene + cursor = scene.cursor_location + obj = scene.objects.active + + # Use a fixed value for now, eventually make this user adjustable + total = 10 + + # Add 'total' objects into the scene + for i in range(total): + obj_new = obj.copy() + scene.objects.link(obj_new) + + # Now place the object in between the cursor + # and the active object based on 'i' + factor = i / total + obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor)) + + +Try run this script with with the active object and the cursor spaced apart to see the result. + +With this script you'll notice we're doing some math with the object location and cursor, this works because both are +3D :class:`mathutils.Vector` instances, a convenient class provided by the :mod:`mathutils` module and +allows vectors to be multiplied by numbers and matrices. + +If you are interested in this area, read into :class:`mathutils.Vector` - there are many handy utility functions +such as getting the angle between vectors, cross product, dot products +as well as more advanced functions in :mod:`mathutils.geometry` such as bezier spline interpolation and +ray-triangle intersection. + +For now we'll focus on making this script an addon, but its good to know that this 3D math module is available and +can help you with more advanced functionality later on. + + +Write the Addon +--------------- + +The first step is to convert the script as-is into an addon. + + +.. code-block:: python + + bl_info = { + "name": "Cursor Array", + "category": "Object", + } + + import bpy + + + class ObjectCursorArray(bpy.types.Operator): + """Object Cursor Array""" + bl_idname = "object.cursor_array" + bl_label = "Cursor Array" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + scene = context.scene + cursor = scene.cursor_location + obj = scene.objects.active + + total = 10 + + for i in range(total): + obj_new = obj.copy() + scene.objects.link(obj_new) + + factor = i / total + obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor)) + + return {'FINISHED'} + + def register(): + bpy.utils.register_class(ObjectCursorArray) + + + def unregister(): + bpy.utils.unregister_class(ObjectCursorArray) + + + if __name__ == "__main__": + register() + + +Everything here has been covered in the previous steps, you may want to try run the addon still +and consider what could be done to make it more useful. + + +... go off and test ... + + +The two of the most obvious missing things are - having the total fixed at 10, and having to access the operator from +space-bar is not very convenient. + +Both these additions are explained next, with the final script afterwards. + + +Operator Property +^^^^^^^^^^^^^^^^^ + +There are a variety of property types that are used for tool settings, common property types include: +int, float, vector, color, boolean and string. + +These properties are handled differently to typical Python class attributes +because Blender needs to be display them in the interface, +store their settings in key-maps and keep settings for re-use. + +While this is handled in a fairly Pythonic way, be mindful that you are in fact defining tool settings that +are loaded into Blender and accessed by other parts of Blender, outside of Python. + + +To get rid of the literal 10 for `total`, we'll us an operator property. +Operator properties are defined via bpy.props module, this is added to the class body. + +.. code-block:: python + + # moved assignment from execute() to the body of the class... + total = bpy.props.IntProperty(name="Steps", default=2, min=1, max=100) + + # and this is accessed on the class + # instance within the execute() function as... + self.total + + +These properties from :mod:`bpy.props` are handled specially by Blender when the class is registered +so they display as buttons in the user interface. +There are many arguments you can pass to properties to set limits, change the default and display a tooltip. + +.. seealso:: :mod:`bpy.props.IntProperty` + +This document doesn't go into details about using other property types, +however the link above includes examples of more advanced property usage. + + +Menu Item +^^^^^^^^^ + +Addons can add to the user interface of existing panels, headers and menus defined in Python. + +For this example we'll add to an existing menu. + +.. image:: menu_id.png + :width: 334px + :align: center + :height: 128px + :alt: Menu Identifier + +To find the identifier of a menu you can hover your mouse over the menu item and the identifier is displayed. + +The method used for adding a menu item is to append a draw function into an existing class. + + +.. code-block:: python + + def menu_func(self, context): + self.layout.operator(ObjectCursorArray.bl_idname) + + def register(): + bpy.types.VIEW3D_MT_object.append(menu_func) + + +For docs on extending menus see: :doc:`bpy.types.Menu`. + + +Keymap +^^^^^^ + +In Blender addons have their own key-maps so as not to interfere with Blenders built in key-maps. + +In the example below, a new object-mode :class:`bpy.types.KeyMap` is added, +then a :class:`bpy.types.KeyMapItem` is added to the key-map which references our newly added operator, +using :kbd:`Ctrl-Shift-Space` as the key shortcut to activate it. + + +.. code-block:: python + + # store keymaps here to access after registration + addon_keymaps = [] + + def register(): + + # handle the keymap + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') + + kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'SPACE', 'PRESS', ctrl=True, shift=True) + kmi.properties.total = 4 + + addon_keymaps.append(km) + + + def unregister(): + + # handle the keymap + wm = bpy.context.window_manager + for km in addon_keymaps: + wm.keyconfigs.addon.keymaps.remove(km) + # clear the list + addon_keymaps.clear() + + +Notice how the key-map item can have a different ``total`` setting then the default set by the operator, +this allows you to have multiple keys accessing the same operator with different settings. + + +.. note:: + + While :kbd:`Ctrl-Shift-Space` isn't a default Blender key shortcut, its hard to make sure addons won't + overwrite each others keymaps, At least take care when assigning keys that they don't + conflict with important functionality within Blender. + +For API documentation on the functions listed above, see: +:class:`bpy.types.KeyMaps.new`, +:class:`bpy.types.KeyMap`, +:class:`bpy.types.KeyMapItems.new`, +:class:`bpy.types.KeyMapItem`. + + +Bringing it all together +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + bl_info = { + "name": "Cursor Array", + "category": "Object", + } + + import bpy + + + class ObjectCursorArray(bpy.types.Operator): + """Object Cursor Array""" + bl_idname = "object.cursor_array" + bl_label = "Cursor Array" + bl_options = {'REGISTER', 'UNDO'} + + total = bpy.props.IntProperty(name="Steps", default=2, min=1, max=100) + + def execute(self, context): + scene = context.scene + cursor = scene.cursor_location + obj = scene.objects.active + + for i in range(self.total): + obj_new = obj.copy() + scene.objects.link(obj_new) + + factor = i / self.total + obj_new.location = (obj.location * factor) + (cursor * (1.0 - factor)) + + return {'FINISHED'} + + + def menu_func(self, context): + self.layout.operator(ObjectCursorArray.bl_idname) + + # store keymaps here to access after registration + addon_keymaps = [] + + + def register(): + bpy.utils.register_class(ObjectCursorArray) + bpy.types.VIEW3D_MT_object.append(menu_func) + + # handle the keymap + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') + kmi = km.keymap_items.new(ObjectCursorArray.bl_idname, 'SPACE', 'PRESS', ctrl=True, shift=True) + kmi.properties.total = 4 + addon_keymaps.append(km) + + def unregister(): + bpy.utils.unregister_class(ObjectCursorArray) + bpy.types.VIEW3D_MT_object.remove(menu_func) + + # handle the keymap + wm = bpy.context.window_manager + for km in addon_keymaps: + wm.keyconfigs.addon.keymaps.remove(km) + # clear the list + del addon_keymaps[:] + + + if __name__ == "__main__": + register() + +.. image:: in_menu.png + :width: 591px + :align: center + :height: 649px + :alt: In the menu + +Run the script (or save it and add it through the Preferences like before) and it will appear in the menu. + +.. image:: op_prop.png + :width: 669px + :align: center + :height: 644px + :alt: Operator Property + +After selecting it from the menu, you can choose how many instance of the cube you want created. + + +.. note:: + + Directly executing the script multiple times will add the menu each time too. + While not useful behavior, theres nothing to worry about since addons won't register them selves multiple + times when enabled through the user preferences. + + +Conclusions +=========== + +Addons can encapsulate certain functionality neatly for writing tools to improve your work-flow or for writing utilities +for others to use. + +While there are limits to what Python can do within Blender, there is certainly a lot that can be achieved without +having to dive into Blender's C/C++ code. + +The example given in the tutorial is limited, but shows the Blender API used for common tasks that you can expand on +to write your own tools. + + +Further Reading +--------------- + +Blender comes commented templates which are accessible from the text editor header, if you have specific areas +you want to see example code for, this is a good place to start. + + +Here are some sites you might like to check on after completing this tutorial. + +* :ref:`Blender/Python API Overview ` - + *For more background details on Blender/Python integration.* + +* `How to Think Like a Computer Scientist `_ - + *Great info for those who are still learning Python.* + +* `Blender Development (Wiki) `_ - + *Blender Development, general information and helpful links.* + +* `Blender Artists (Coding Section) `_ - + *forum where people ask Python development questions* + diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 1a1a4bf909b..441a6c04efe 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -316,6 +316,8 @@ RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst")) INFO_DOCS = ( ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"), ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"), + ("info_tutorial_addon.rst", "Blender/Python Addon Tutorial: a step by step guide on how to write an addon from scratch"), + ("info_api_reference.rst", "Blender/Python API Reference Usage: examples of how to use the API reference docs"), ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"), ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writing scripts for blender"), ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"), @@ -1724,6 +1726,11 @@ def copy_handwritten_rsts(basepath): # changelog shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath) + # copy images, could be smarter but just glob for now. + for f in os.listdir(RST_DIR): + if f.endswith(".png"): + shutil.copy2(os.path.join(RST_DIR, f), basepath) + def rna2sphinx(basepath): -- cgit v1.2.3