Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
:tocdepth: 2
Change Log
**********
Changes in Blender's Python API between releases.
.. note, this document is auto generated by sphinx_changelog_gen.py
2.83 to 2.90
============
bpy.types.CyclesPreferences
---------------------------
Added
^^^^^
* :class:`bpy.types.CyclesPreferences.peer_memory`
bpy.types.BakeSettings
----------------------
Added
^^^^^
* :class:`bpy.types.BakeSettings.max_ray_distance`
bpy.types.BlendDataLibraries
----------------------------
Added
^^^^^
* :class:`bpy.types.BlendDataLibraries.remove`
bpy.types.BrushCapabilitiesSculpt
---------------------------------
Added
^^^^^
* :class:`bpy.types.BrushCapabilitiesSculpt.has_color`
bpy.types.BrushGpencilSettings
------------------------------
Added
^^^^^
* :class:`bpy.types.BrushGpencilSettings.curve_random_hue`
* :class:`bpy.types.BrushGpencilSettings.curve_random_pressure`
* :class:`bpy.types.BrushGpencilSettings.curve_random_saturation`
* :class:`bpy.types.BrushGpencilSettings.curve_random_strength`
* :class:`bpy.types.BrushGpencilSettings.curve_random_uv`
* :class:`bpy.types.BrushGpencilSettings.curve_random_value`
* :class:`bpy.types.BrushGpencilSettings.random_hue_factor`
* :class:`bpy.types.BrushGpencilSettings.random_saturation_factor`
* :class:`bpy.types.BrushGpencilSettings.random_value_factor`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_hue`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_radius`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_sat`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_strength`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_uv`
* :class:`bpy.types.BrushGpencilSettings.use_random_press_val`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_hue`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_radius`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_sat`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_strength`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_uv`
* :class:`bpy.types.BrushGpencilSettings.use_stroke_random_val`
bpy.types.ClothSettings
-----------------------
Added
^^^^^
* :class:`bpy.types.ClothSettings.fluid_density`
bpy.types.DopeSheet
-------------------
Added
^^^^^
* :class:`bpy.types.DopeSheet.show_hairs`
* :class:`bpy.types.DopeSheet.show_pointclouds`
bpy.types.FieldSettings
-----------------------
Added
^^^^^
* :class:`bpy.types.FieldSettings.wind_factor`
bpy.types.FileSelectIDFilter
----------------------------
Added
^^^^^
* :class:`bpy.types.FileSelectIDFilter.filter_hair`
* :class:`bpy.types.FileSelectIDFilter.filter_pointcloud`
* :class:`bpy.types.FileSelectIDFilter.filter_simulation`
bpy.types.FluidDomainSettings
-----------------------------
Added
^^^^^
* :class:`bpy.types.FluidDomainSettings.cache_frame_offset`
* :class:`bpy.types.FluidDomainSettings.cache_resumable`
* :class:`bpy.types.FluidDomainSettings.sys_particle_maximum`
Renamed
^^^^^^^
* **data_depth** -> :class:`bpy.types.FluidDomainSettings.openvdb_data_depth`
bpy.types.GPencilFrame
----------------------
Added
^^^^^
* :class:`bpy.types.GPencilFrame.keyframe_type`
bpy.types.GPencilStrokePoint
----------------------------
Added
^^^^^
* :class:`bpy.types.GPencilStrokePoint.uv_fill`
bpy.types.Gizmo
---------------
Added
^^^^^
* :class:`bpy.types.Gizmo.hide_keymap`
* :class:`bpy.types.Gizmo.use_tooltip`
bpy.types.BuildGpencilModifier
------------------------------
Added
^^^^^
* :class:`bpy.types.BuildGpencilModifier.percentage_factor`
* :class:`bpy.types.BuildGpencilModifier.use_percentage`
bpy.types.Brush
---------------
Added
^^^^^
* :class:`bpy.types.Brush.density`
* :class:`bpy.types.Brush.disconnected_distance_max`
* :class:`bpy.types.Brush.flow`
* :class:`bpy.types.Brush.invert_density_pressure`
* :class:`bpy.types.Brush.invert_flow_pressure`
* :class:`bpy.types.Brush.invert_hardness_pressure`
* :class:`bpy.types.Brush.invert_wet_mix_pressure`
* :class:`bpy.types.Brush.invert_wet_persistence_pressure`
* :class:`bpy.types.Brush.pose_deform_type`
* :class:`bpy.types.Brush.slide_deform_type`
* :class:`bpy.types.Brush.smear_deform_type`
* :class:`bpy.types.Brush.tip_scale_x`
* :class:`bpy.types.Brush.use_connected_only`
* :class:`bpy.types.Brush.use_density_pressure`
* :class:`bpy.types.Brush.use_flow_pressure`
* :class:`bpy.types.Brush.use_hardness_pressure`
* :class:`bpy.types.Brush.use_wet_mix_pressure`
* :class:`bpy.types.Brush.use_wet_persistence_pressure`
* :class:`bpy.types.Brush.wet_mix`
* :class:`bpy.types.Brush.wet_persistence`
bpy.types.Mesh
--------------
Added
^^^^^
* :class:`bpy.types.Mesh.sculpt_vertex_colors`
* :class:`bpy.types.Mesh.use_remesh_preserve_vertex_colors`
bpy.types.Scene
---------------
Function Arguments
^^^^^^^^^^^^^^^^^^
* :class:`bpy.types.Scene.alembic_export` (filepath, frame_start, frame_end, xform_samples, geom_samples, shutter_open, shutter_close, selected_only, uvs, normals, vcolors, apply_subdiv, flatten, visible_objects_only, renderable_only, face_sets, subdiv_schema, export_hair, export_particles, packuv, scale, triangulate, quad_method, ngon_method), *was (filepath, frame_start, frame_end, xform_samples, geom_samples, shutter_open, shutter_close, selected_only, uvs, normals, vcolors, apply_subdiv, flatten, visible_objects_only, renderable_only, face_sets, subdiv_schema, export_hair, export_particles, compression_type, packuv, scale, triangulate, quad_method, ngon_method)*
bpy.types.Screen
----------------
Added
^^^^^
* :class:`bpy.types.Screen.is_scrubbing`
* :class:`bpy.types.Screen.statusbar_info`
bpy.types.IDOverrideLibrary
---------------------------
Removed
^^^^^^^
* **auto_generate**
bpy.types.BevelModifier
-----------------------
Added
^^^^^
* :class:`bpy.types.BevelModifier.affect`
* :class:`bpy.types.BevelModifier.profile_type`
Removed
^^^^^^^
* **use_custom_profile**
* **use_only_vertices**
bpy.types.MultiresModifier
--------------------------
Added
^^^^^
* :class:`bpy.types.MultiresModifier.use_custom_normals`
bpy.types.OceanModifier
-----------------------
Added
^^^^^
* :class:`bpy.types.OceanModifier.invert_spray`
* :class:`bpy.types.OceanModifier.spray_layer_name`
* :class:`bpy.types.OceanModifier.use_spray`
bpy.types.SubsurfModifier
-------------------------
Added
^^^^^
* :class:`bpy.types.SubsurfModifier.use_custom_normals`
bpy.types.VertexWeightEditModifier
----------------------------------
Added
^^^^^
* :class:`bpy.types.VertexWeightEditModifier.normalize`
bpy.types.VertexWeightMixModifier
---------------------------------
Added
^^^^^
* :class:`bpy.types.VertexWeightMixModifier.invert_vertex_group_a`
* :class:`bpy.types.VertexWeightMixModifier.invert_vertex_group_b`
* :class:`bpy.types.VertexWeightMixModifier.normalize`
bpy.types.VertexWeightProximityModifier
---------------------------------------
Added
^^^^^
* :class:`bpy.types.VertexWeightProximityModifier.normalize`
bpy.types.MovieTrackingCamera
-----------------------------
Added
^^^^^
* :class:`bpy.types.MovieTrackingCamera.nuke_k1`
* :class:`bpy.types.MovieTrackingCamera.nuke_k2`
bpy.types.ShaderNodeTexSky
--------------------------
Added
^^^^^
* :class:`bpy.types.ShaderNodeTexSky.air_density`
* :class:`bpy.types.ShaderNodeTexSky.altitude`
* :class:`bpy.types.ShaderNodeTexSky.dust_density`
* :class:`bpy.types.ShaderNodeTexSky.ozone_density`
* :class:`bpy.types.ShaderNodeTexSky.sun_disc`
* :class:`bpy.types.ShaderNodeTexSky.sun_elevation`
* :class:`bpy.types.ShaderNodeTexSky.sun_intensity`
* :class:`bpy.types.ShaderNodeTexSky.sun_rotation`
* :class:`bpy.types.ShaderNodeTexSky.sun_size`
bpy.types.NodeSocketInterface
-----------------------------
Added
^^^^^
* :class:`bpy.types.NodeSocketInterface.NWViewerSocket`
* :class:`bpy.types.NodeSocketInterface.hide_value`
bpy.types.ObjectConstraints
---------------------------
Added
^^^^^
* :class:`bpy.types.ObjectConstraints.copy`
bpy.types.Sculpt
----------------
Removed
^^^^^^^
* **use_threaded**
bpy.types.Panel
---------------
Added
^^^^^
* :class:`bpy.types.Panel.list_panel_index`
bpy.types.PoseBoneConstraints
-----------------------------
Added
^^^^^
* :class:`bpy.types.PoseBoneConstraints.copy`
bpy.types.PreferencesEdit
-------------------------
Added
^^^^^
* :class:`bpy.types.PreferencesEdit.collection_instance_empty_size`
* :class:`bpy.types.PreferencesEdit.use_duplicate_hair`
* :class:`bpy.types.PreferencesEdit.use_duplicate_pointcloud`
bpy.types.PreferencesExperimental
---------------------------------
Added
^^^^^
* :class:`bpy.types.PreferencesExperimental.use_cycles_debug`
* :class:`bpy.types.PreferencesExperimental.use_new_hair_type`
* :class:`bpy.types.PreferencesExperimental.use_new_particle_system`
* :class:`bpy.types.PreferencesExperimental.use_sculpt_vertex_colors`
Removed
^^^^^^^
* **use_menu_search**
bpy.types.PreferencesView
-------------------------
Added
^^^^^
* :class:`bpy.types.PreferencesView.show_statusbar_memory`
* :class:`bpy.types.PreferencesView.show_statusbar_stats`
* :class:`bpy.types.PreferencesView.show_statusbar_version`
* :class:`bpy.types.PreferencesView.show_statusbar_vram`
bpy.types.CyclesCurveRenderSettings
-----------------------------------
Removed
^^^^^^^
* **cull_backfacing**
* **primitive**
* **resolution**
* **use_curves**
bpy.types.CyclesObjectSettings
------------------------------
Added
^^^^^
* :class:`bpy.types.CyclesObjectSettings.shadow_terminator_offset`
bpy.types.CyclesRenderLayerSettings
-----------------------------------
Added
^^^^^
* :class:`bpy.types.CyclesRenderLayerSettings.denoising_openimagedenoise_input_passes`
Removed
^^^^^^^
* **use_optix_denoising**
bpy.types.CyclesRenderSettings
------------------------------
Added
^^^^^
* :class:`bpy.types.CyclesRenderSettings.debug_optix_curves_api`
* :class:`bpy.types.CyclesRenderSettings.denoiser`
* :class:`bpy.types.CyclesRenderSettings.preview_denoiser`
* :class:`bpy.types.CyclesRenderSettings.use_denoising`
* :class:`bpy.types.CyclesRenderSettings.use_preview_denoising`
Removed
^^^^^^^
* **preview_denoising**
* **use_bvh_embree**
bpy.types.RenderEngine
----------------------
Added
^^^^^
* :class:`bpy.types.RenderEngine.bl_use_gpu_context`
Function Arguments
^^^^^^^^^^^^^^^^^^
* :class:`bpy.types.RenderEngine.bake` (depsgraph, object, pass_type, pass_filter, width, height), *was (depsgraph, object, pass_type, pass_filter, object_id, pixel_array, num_pixels, depth, result)*
bpy.types.CYCLES
----------------
Function Arguments
^^^^^^^^^^^^^^^^^^
* :class:`bpy.types.CYCLES.bake` (self, depsgraph, obj, pass_type, pass_filter, width, height), *was (self, depsgraph, obj, pass_type, pass_filter, object_id, pixel_array, num_pixels, depth, result)*
bpy.types.RenderSettings
------------------------
Added
^^^^^
* :class:`bpy.types.RenderSettings.metadata_input`
Removed
^^^^^^^
* **use_stamp_strip_meta**
bpy.types.SceneEEVEE
--------------------
Added
^^^^^
* :class:`bpy.types.SceneEEVEE.motion_blur_depth_scale`
* :class:`bpy.types.SceneEEVEE.motion_blur_max`
* :class:`bpy.types.SceneEEVEE.motion_blur_steps`
Removed
^^^^^^^
* **motion_blur_samples**
bpy.types.Sequence
------------------
Added
^^^^^
* :class:`bpy.types.Sequence.invalidate_cache`
bpy.types.SpeedControlSequence
------------------------------
Added
^^^^^
* :class:`bpy.types.SpeedControlSequence.frame_interpolation_mode`
bpy.types.MovieSequence
-----------------------
Added
^^^^^
* :class:`bpy.types.MovieSequence.reload_if_needed`
bpy.types.ShaderFxPixel
-----------------------
Added
^^^^^
* :class:`bpy.types.ShaderFxPixel.use_antialiasing`
Removed
^^^^^^^
* **color**
bpy.types.SpaceView3D
---------------------
Added
^^^^^
* :class:`bpy.types.SpaceView3D.show_object_select_hair`
* :class:`bpy.types.SpaceView3D.show_object_select_pointcloud`
* :class:`bpy.types.SpaceView3D.show_object_viewport_hair`
* :class:`bpy.types.SpaceView3D.show_object_viewport_pointcloud`
bpy.types.SpaceUVEditor
-----------------------
Added
^^^^^
* :class:`bpy.types.SpaceUVEditor.uv_opacity`
bpy.types.ThemeInfo
-------------------
Removed
^^^^^^^
* **info_report_error**
* **info_report_info**
* **info_report_warning**
bpy.types.ToolSettings
----------------------
Added
^^^^^
* :class:`bpy.types.ToolSettings.use_transform_correct_face_attributes`
* :class:`bpy.types.ToolSettings.use_transform_correct_keep_connected`
bpy.types.UILayout
------------------
Added
^^^^^
* :class:`bpy.types.UILayout.prop_decorator`
* :class:`bpy.types.UILayout.template_constraint_header`
* :class:`bpy.types.UILayout.template_constraints`
* :class:`bpy.types.UILayout.template_grease_pencil_modifiers`
* :class:`bpy.types.UILayout.template_modifiers`
Removed
^^^^^^^
* **template_constraint**
* **template_greasepencil_modifier**
* **template_modifier**
Function Arguments
^^^^^^^^^^^^^^^^^^
* :class:`bpy.types.UILayout.column` (align, heading, heading_ctxt, translate), *was (align)*
* :class:`bpy.types.UILayout.row` (align, heading, heading_ctxt, translate), *was (align)*
* :class:`bpy.types.UILayout.template_shaderfx` (), *was (data)*
bpy.types.View3DOverlay
-----------------------
Added
^^^^^
* :class:`bpy.types.View3DOverlay.display_handle`
* :class:`bpy.types.View3DOverlay.show_stats`
* :class:`bpy.types.View3DOverlay.use_gpencil_canvas_xray`
Removed
^^^^^^^
* **show_curve_handles**
bpy.types.XrSessionState
------------------------
Added
^^^^^
* :class:`bpy.types.XrSessionState.reset_to_base_pose`

View File

@@ -0,0 +1,151 @@
..
This document is appended to the auto generated BMesh API doc to avoid clogging up the C files with details.
to test this run:
./blender.bin -b -P doc/python_api/sphinx_doc_gen.py -- \
--partial bmesh* ; cd doc/python_api ; sphinx-build sphinx-in sphinx-out ; cd ../../
Introduction
------------
This API gives access to Blender's internal mesh editing API, featuring geometry connectivity data and
access to editing operations such as split, separate, collapse and dissolve.
The features exposed closely follow the C API,
giving Python access to the functions used by Blender's own mesh editing tools.
For an overview of BMesh data types and how they reference each other see:
`BMesh Design Document <https://developer.blender.org/docs/features/objects/mesh/bmesh/>`__.
.. note::
**Disk** and **Radial** data is not exposed by the Python API since this is for internal use only.
.. warning:: TODO items are...
- add access to BMesh **walkers**.
- add custom-data manipulation functions add, remove or rename.
Example Script
--------------
.. literalinclude:: __/__/__/scripts/templates_py/bmesh_simple.py
Standalone Module
^^^^^^^^^^^^^^^^^
The BMesh module is written to be standalone except for :mod:`mathutils`
which is used for vertex locations and normals.
The only other exception to this are when converting mesh data to and from :class:`bpy.types.Mesh`.
Mesh Access
-----------
There are two ways to access BMesh data, you can create a new BMesh by converting a mesh from
:attr:`bpy.types.BlendData.meshes` or by accessing the current Edit-Mode mesh.
See: :meth:`bmesh.types.BMesh.from_mesh` and :func:`bmesh.from_edit_mesh` respectively.
When explicitly converting from mesh data Python **owns** the data, that means that
the mesh only exists while Python holds a reference to it.
The script is responsible for putting it back into a mesh data-block when the edits are done.
Note that unlike :mod:`bpy`, a BMesh does not necessarily correspond to data in the currently open blend-file,
a BMesh can be created, edited and freed without the user ever seeing or having access to it.
Unlike Edit-Mode, the BMesh module can use multiple BMesh instances at once.
Take care when dealing with multiple BMesh instances since the mesh data can use a lot of memory.
While a mesh that the Python script owns will be freed when the script holds no references to it,
it's good practice to call :meth:`bmesh.types.BMesh.free` which will remove all the mesh data immediately
and disable further access.
Edit-Mode Tessellation
^^^^^^^^^^^^^^^^^^^^^^
When writing scripts that operate on Edit-Mode data you will normally want to re-calculate the tessellation after
running the script, this needs to be called explicitly.
The BMesh itself does not store the triangulated faces, instead they are stored in the :class:`bpy.types.Mesh`,
to refresh tessellation triangles call :meth:`bpy.types.Mesh.calc_loop_triangles`.
CustomData Access
-----------------
BMesh has a unified way to access mesh attributes such as UVs, vertex colors, shape keys, edge crease, etc.
This works by having a **layers** property on BMesh data sequences to access the custom data layers
which can then be used to access the actual data on each vert, edge, face or loop.
Here are some examples:
.. code-block:: python
uv_lay = bm.loops.layers.uv.active
for face in bm.faces:
for loop in face.loops:
uv = loop[uv_lay].uv
print("Loop UV: %f, %f" % uv[:])
vert = loop.vert
print("Loop Vert: (%f,%f,%f)" % vert.co[:])
.. code-block:: python
shape_lay = bm.verts.layers.shape["Key.001"]
for vert in bm.verts:
shape = vert[shape_lay]
print("Vert Shape: %f, %f, %f" % (shape.x, shape.y, shape.z))
.. code-block:: python
# In this example the active vertex group index is used,
# this is stored in the object, not the `BMesh`.
group_index = obj.vertex_groups.active_index
# Only ever one deform weight layer.
dvert_lay = bm.verts.layers.deform.active
for vert in bm.verts:
dvert = vert[dvert_lay]
if group_index in dvert:
print("Weight %f" % dvert[group_index])
else:
print("Setting Weight")
dvert[group_index] = 0.5
Keeping a Correct State
-----------------------
When modeling in Blender there are certain assumptions made about the state of the mesh:
- Hidden geometry isn't selected.
- When an edge is selected, its vertices are selected too.
- When a face is selected, its edges and vertices are selected.
- Duplicate edges / faces don't exist.
- Faces have at least three vertices.
To give developers flexibility these conventions are not enforced,
yet tools must leave the mesh in a valid state or else other tools may behave incorrectly.
Any errors that arise from not following these conventions is considered a bug in the script,
not a bug in Blender.
Selection / Flushing
^^^^^^^^^^^^^^^^^^^^
As mentioned above, it is possible to create an invalid selection state
(by selecting a face and then deselecting one of its vertices for example),
mostly the best way to solve this is to flush the selection
after performing a series of edits. This validates the selection state.
Module Functions
----------------

View File

@@ -0,0 +1,15 @@
.. _info_advanced-index:
********
Advanced
********
This chapter covers advanced use (topics which may not be required for typical usage).
.. NOTE(@campbellbarton): Blender-as-a-Python-module is too obscure a topic to list directly on the main-page,
so opt for an "Advanced" page which can be expanded on as needed.
.. toctree::
:maxdepth: 1
info_advanced_blender_as_bpy.rst

View File

@@ -0,0 +1,129 @@
**************************
Blender as a Python Module
**************************
Blender supports being built as a Python module,
allowing ``import bpy`` to be added to any Python script, providing access to Blender's features.
.. note::
Blender as a Python Module isn't provided on Blender's official download page.
- A pre-compiled ``bpy`` module is
`available via PIP <https://pypi.org/project/bpy/>`__.
- Or you may compile this yourself using the
`build instructions <https://developer.blender.org/docs/handbook/building_blender/python_module/>`__.
Use Cases
=========
Python developers may wish to integrate Blender scripts which don't center around Blender.
Possible uses include:
- Visualizing data by rendering images and animations.
- Image processing using Blender's compositor.
- Video editing (using Blender's sequencer).
- 3D file conversion.
- Development, accessing ``bpy`` from Python IDEs and debugging tools for example.
- Automation.
Usage
=====
For the most part using Blender as a Python module is equivalent to running a script in background-mode
(passing the command-line arguments ``--background`` or ``-b``),
however there are some differences to be aware of.
.. Sorted alphabetically as there isn't especially a logical order to show them.
Blender's Executable Access
The attribute :attr:`bpy.app.binary_path` defaults to an empty string.
If you wish to point this to the location of a known executable you may set the value.
This example searches for the binary, setting it when found:
.. code-block:: python
import bpy
import shutil
blender_bin = shutil.which("blender")
if blender_bin:
print("Found:", blender_bin)
bpy.app.binary_path = blender_bin
else:
print("Unable to find blender!")
Blender's Internal Modules
There are many modules included with Blender such as :mod:`gpu` and :mod:`mathutils`.
It's important that these are imported after ``bpy`` or they will not be found.
Command Line Arguments Unsupported
Functionality controlled by command line arguments (shown by calling ``blender --help``) isn't accessible.
Typically this isn't such a limitation although there are some command line arguments that don't have
equivalents in Blender's Python API (``--threads`` and ``--log`` for example).
.. note::
Access to these settings may be added in the future as needed.
Resource Sharing (GPU)
It's possible other Python modules make use of the GPU in a way that prevents Blender/Cycles from accessing the GPU.
Signal Handlers
Blender's typical signal handlers are not initialized, so there is no special handling for ``Control-C``
to cancel a render and a crash log is not written in the event of a crash.
Startup and Preferences
When the ``bpy`` module loads it contains the default startup scene
(instead of an "empty" blend-file as you might expect), so there is a default cube, camera and light.
If you wish to start from an empty file use: ``bpy.ops.wm.read_factory_settings(use_empty=True)``.
The user's startup and preferences are ignored to prevent your local configuration from impacting script behavior.
The Python module behaves as if ``--factory-startup`` was passed as a command line argument.
The user's preferences and startup can be loaded using operators:
.. code-block:: python
import bpy
bpy.ops.wm.read_userpref()
bpy.ops.wm.read_homefile()
Limitations
===========
Most constraints of Blender as an application still apply:
Reloading Unsupported
Reloading the ``bpy`` module via ``importlib.reload`` will raise an exception
instead of reloading and resetting the module.
Instead, the operator ``bpy.ops.wm.read_factory_settings()`` can be used to reset the internal state.
Single Blend File Restriction
Only a single ``.blend`` file can be edited at a time.
.. hint::
As with the application it's possible to start multiple instances,
each with their own ``bpy`` and therefore Blender state.
Python provides the ``multiprocessing`` module to make communicating with sub-processes more convenient.
In some cases the library API may be an alternative to starting separate processes,
although this API operates on reading and writing ID data-blocks and isn't
a complete substitute for loading ``.blend`` files, see:
- :meth:`bpy.types.BlendDataLibraries.load`
- :meth:`bpy.types.BlendDataLibraries.write`
- :meth:`bpy.types.BlendData.temp_data`
supports a temporary data-context to avoid manipulating the current ``.blend`` file.

View File

@@ -0,0 +1,257 @@
*******************
API Reference 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:`bmesh` and :mod:`aud` are not using Blender's 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 it's 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, Collection, Object, Mesh, Workspace, World, Armature, Image and Texture.
For a full list see the subclasses of :class:`bpy.types.ID`.
Here are some characteristics ID data-blocks share:
- IDs are blend-file data, so loading a new blend-file reloads an entire new set of data-blocks.
- IDs 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 IDs ``.animation_data``.
- IDs are the only data types that can be linked between blend-files.
- IDs can be added/copied and removed via Python.
- IDs have their own garbage-collection system which frees unused IDs when saving.
- When a data-block has a reference to some external data, this is typically an ID data-block.
Simple Data Access
------------------
In this simple case a Python script is used to adjust the object's location.
Start by collecting the information where the data is located.
First find this setting in the interface ``Properties editor -> Object -> Transform -> Location``.
From the button context menu 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 than the tooltip, though some of the pages
include examples (normally at the top of the page).
But you now know that you have to use ``.location`` and that it's an array of three floats.
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 ``Object`` gets 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 you'll notice that 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:
e.g. ``active_bone``, ``active_pose_bone``, ``active_node``, etc. and in this case you can use ``active_object``.
So now you 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 the number of samples for the Cycles render engine.
bpy.context.scene.cycles.samples
# Access to the current weight paint brush size.
bpy.context.tool_settings.weight_paint.brush.size
# Check if the window is full-screen.
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 Blender's 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 <info_data_path_copy>`.
- Using the documentation to follow references.
*Explained further in* :ref:`Indirect Data Access <info_data_path_indirect>`.
.. _info_data_path_copy:
Copy Data Path
--------------
Blender can compute the Python string to a property which is shown in the tooltip,
on the line below ``Python: ...``. This saves having to open the API references to find where data is accessed from.
In the context menu is a copy data-path tool which gives the path from an :class:`bpy.types.ID` data-block,
to its property.
To see how this works you'll get the path to the Subdivision Surface modifiers *Levels* 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 *Levels Viewport*,
The tooltip includes :class:`bpy.types.SubsurfModifier.levels` but you want the path from the object to this property.
Note that the text copied won't include the ``bpy.data.collections["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
than 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 execute the code, yet.
Now in the button's context menu select *Copy Data Path*, then paste the result into the console:
.. code-block:: python
bpy.context.active_object.modifiers["Subdivision"].levels
Press :kbd:`Return` and you'll get the current value of 1. Now try changing the value to 2:
.. code-block:: python
bpy.context.active_object.modifiers["Subdivision"].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
--------------------
This more advanced example shows the steps to access the active sculpt brushes texture.
For example, if you want to access the texture of a brush via Python to adjust its ``contrast``.
#. Start in the default scene and enable Sculpt Mode from the 3D Viewport header.
#. From the Sidebar expand the Brush Settings panel's *Texture* subpanel and add a new texture.
*Notice the texture data-block menu itself doesn't have very useful links (you can check the tooltips).*
#. The contrast setting isn't exposed in the Sidebar, so view the texture in the
:ref:`Properties Editor <blender_manual:bpy.types.Texture.contrast>`.
#. Open the context menu of the contrast field and select *Online Python Reference*.
This takes you to ``bpy.types.Texture.contrast``. Now you can see that ``contrast`` is a property of texture.
#. To find out 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 guesswork to find the right one,
but in this case it's ``tool_settings.sculpt.brush.texture``.
#. Now you 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, instead you want to access the active brush.
So the next step is to check on where brushes are accessed from via the references.
Now you can use the Python console to form the nested properties needed to access brush textures contrast:
:menuselection:`Context --> Tool Settings --> Sculpt --> Brush --> Texture --> Contrast`.
Since the attribute for each is given along the way you can compose the data path in the Python console:
.. code-block:: python
bpy.context.tool_settings.sculpt.brush.texture.contrast
Or access the brush directly:
.. code-block:: python
bpy.data.textures["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 hotkeys 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 tooltip,
e.g ``Python: bpy.ops.render.render()``,
If there is no tooltip 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:`Ctrl-C` while your mouse is over the button
to copy it to the clipboard.
You can also use button's context menu 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 <using_operators>`.
Info Editor
-----------
Blender records operators you run and displays them in the Info editor.
Select the Scripting workspace 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, then copied :kbd:`Ctrl-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 it's excluded from the output.
To display *every* operator that runs see :ref:`Show All Operators <info_show_all_operators>`.

View File

@@ -0,0 +1,362 @@
*************
Best Practice
*************
When writing your own scripts Python is great for new developers to pick up and become productive,
but you can also pick up bad practices or at least write scripts that are not easy for others to understand.
For your own work this is of course fine,
but if you want to collaborate with others or have your work included with Blender there are practices we encourage.
Style Conventions
=================
For Blender Python development we have chosen to follow Python suggested style guide to avoid mixing styles
among our own scripts and make it easier to use Python scripts from other projects.
Using our style guide for your own scripts makes it easier if you eventually want to contribute them to Blender.
This style guide is known as `pep8 <https://www.python.org/dev/peps/pep-0008/>`__
and here is a brief listing of pep8 criteria:
- 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 wildcard importing ``*``)
- Don't use multiple statements on a single line: ``if val: body``, separate onto two lines instead.
As well as pep8 we have additional conventions used for Blender Python scripts:
- Use single quotes for enums, and double quotes for strings.
Both are of course strings, but in our internal API enums are unique items from a limited set, e.g:
.. code-block:: python
bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.context.scene.render.filepath = "//render_out"
- pep8 also defines that lines should not exceed 79 characters;
we have decided that this is too restrictive so it is optional per script.
User Interface Layout
=====================
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.
The general rule here is: If you need more code for the layout declaration,
than for the actual properties, then the approach should be reconsidered.
.. rubric:: Example layouts:
``layout()``
The basic layout is a simple top-to-bottom layout.
.. code-block:: python
layout.prop()
layout.prop()
``layout.row()``
Use ``row()``, when you want more than one property in a single line.
.. code-block:: python
row = layout.row()
row.prop()
row.prop()
``layout.column()``
Use ``column()``, when you want your properties in a column.
.. code-block:: python
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.
Do not use split, when you simply want two properties in a row. Use ``row()`` instead.
.. code-block:: python
split = layout.split()
col = split.column()
col.prop()
col.prop()
col = split.column()
col.prop()
col.prop()
.. rubric:: 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)
Script Efficiency
=================
List Manipulation (General Python Tips)
---------------------------------------
Searching for List Items
^^^^^^^^^^^^^^^^^^^^^^^^
In Python there are some handy list functions that save you having to search through the list.
Even though you are not looping on the list data **Python is**,
so you need to be aware of functions that will slow down your script by searching the whole list.
.. code-block:: python
my_list.count(list_item)
my_list.index(list_item)
my_list.remove(list_item)
if list_item in my_list: ...
Modifying Lists
^^^^^^^^^^^^^^^
In Python you can add and remove from a list, this is slower when the list length is modified,
especially at the start of the list, since all the data after the index of
modification needs to be moved up or down one place.
The fastest way to add onto the end of the list is to use
``my_list.append(list_item)`` or ``my_list.extend(some_list)`` and
to remove an item is ``my_list.pop()`` or ``del my_list[-1]``.
To use an index you can use ``my_list.insert(index, list_item)`` or ``list.pop(index)``
for list removal, but these are slower.
Sometimes it's faster (but less memory efficient) to just rebuild the list.
For example if you want to remove all triangular polygons in a list.
Rather than:
.. code-block:: python
polygons = mesh.polygons[:] # Make a list copy of the meshes polygons.
p_idx = len(polygons) # Loop backwards
while p_idx: # While the value is not 0.
p_idx -= 1
if len(polygons[p_idx].vertices) == 3:
polygons.pop(p_idx) # Remove the triangle.
It's faster to build a new list with list comprehension:
.. code-block:: python
polygons = [p for p in mesh.polygons if len(p.vertices) != 3]
Adding List Items
^^^^^^^^^^^^^^^^^
If you have a list that you want to add onto another list, rather than:
.. code-block:: python
for l in some_list:
my_list.append(l)
Use:
.. code-block:: python
my_list.extend([a, b, c...])
Note that insert can be used when needed,
but it is slower than append especially when inserting at the start of a long list.
This example shows a very suboptimal way of making a reversed list:
.. code-block:: python
reverse_list = []
for list_item in some_list:
reverse_list.insert(0, list_item)
Python provides more convenient ways to reverse a list using the slice method,
but you may want to time this before relying on it too much:
.. code-block:: python
some_reversed_list = some_list[::-1]
Removing List Items
^^^^^^^^^^^^^^^^^^^
Use ``my_list.pop(index)`` rather than ``my_list.remove(list_item)``.
This requires you to have the index of the list item but is faster since ``remove()`` will search the list.
Here is an example of how to remove items in one loop,
removing the last items first, which is faster (as explained above):
.. code-block:: python
list_index = len(my_list)
while list_index:
list_index -= 1
if my_list[list_index].some_test_attribute == 1:
my_list.pop(list_index)
This example shows a fast way of removing items,
for use in cases where you can alter the list order without breaking the script's functionality.
This works by swapping two list items, so the item you remove is always last:
.. code-block:: python
pop_index = 5
# Swap so the pop_index is last.
my_list[-1], my_list[pop_index] = my_list[pop_index], my_list[-1]
# Remove last item (pop_index).
my_list.pop()
When removing many items in a large list this can provide a good speed-up.
Avoid Copying Lists
^^^^^^^^^^^^^^^^^^^
When passing a list or dictionary to a function,
it is faster to have the function modify the list rather than returning
a new list so Python doesn't have to duplicate the list in memory.
Functions that modify a list in-place are more efficient than functions that create new lists.
This is generally slower so only use for functions when it makes sense not to modify the list in place:
>>> my_list = some_list_func(my_list)
This is generally faster since there is no re-assignment and no list duplication:
>>> some_list_func(vec)
Also note that, passing a sliced list makes a copy of the list in Python memory:
>>> foobar(my_list[:])
If my_list was a large array containing 10,000's of items, a copy could use a lot of extra memory.
Writing Strings to a File (Python General)
------------------------------------------
Here are three ways of joining multiple strings into one string for writing.
This also applies to any area of your code that involves a lot of string joining:
String concatenation
This is the slowest option, do **not** use this if you can avoid it, especially when writing data in a loop.
>>> file.write(str1 + " " + str2 + " " + str3 + "\n")
String formatting
Use this when you are writing string data from floats and ints.
>>> file.write("%s %s %s\n" % (str1, str2, str3))
String joining
Use this to join a list of strings (the list may be temporary). In the following example, the strings are joined with
a space " " in between, other examples are "" or ", ".
>>> file.write(" ".join((str1, str2, str3, "\n")))
Join is fastest on many strings, string formatting is quite fast too (better for converting data types).
String concatenation is the slowest.
Parsing Strings (Import/Exporting)
----------------------------------
Since many file formats are ASCII,
the way you parse/export strings can make a large difference in how fast your script runs.
There are a few ways to parse strings when importing them into Blender.
Parsing Numbers
^^^^^^^^^^^^^^^
Use ``float(string)`` rather than ``eval(string)``, if you know the value will be an int then ``int(string)``,
``float()`` will work for an int too but it is faster to read ints with ``int()``.
Checking String Start/End
^^^^^^^^^^^^^^^^^^^^^^^^^
If you are checking the start of a string for a keyword, rather than:
>>> if line[0:5] == "vert ": ...
Use:
>>> if line.startswith("vert "):
Using ``startswith()`` is slightly faster (around 5%) and also avoids a possible error
with the slice length not matching the string length.
``my_string.endswith("foo_bar")`` can be used for line endings too.
If you are unsure whether the text is upper or lower case, use the ``lower()`` or ``upper()`` string function:
>>> if line.lower().startswith("vert ")
Error Handling
--------------
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.
Value Comparison
----------------
Python has two ways to compare values ``a == b`` and ``a is b``,
the difference is that ``==`` may run the object's comparison function ``__eq__()`` whereas ``is`` compares identity,
that is, that both variables reference the same item in memory.
In cases where you know you are checking for the same value which is referenced from multiple places, ``is`` is faster.
Time Your Code
--------------
While developing a script it is good to time it to be aware of any changes in performance, this can be done simply:
.. code-block:: python
import time
time_start = time.time()
# Do something...
print("My Script Finished: %.4f sec" % (time.time() - time_start))

View File

@@ -0,0 +1,177 @@
.. _info_contributing:
************************
Contribute Documentation
************************
This guide covers how to contribute to Blender's Python API documentation,
including writing examples, formatting documentation, and building the docs locally.
Setting Up Your Environment
===========================
Prerequisites
-------------
Before you can build the documentation, you need:
Blender Source Code
Clone the Blender repository following the
`official build instructions <https://developer.blender.org/docs/handbook/building_blender/>`__.
Python Environment *(optional)*
Set up a Python `virtual environment <https://docs.python.org/3/library/venv.html>`__.
Installing Documentation Requirements
-------------------------------------
Typically, you would set up a virtual environment and install the packages listed in
``doc/python_api/requirements.txt``. However, the only hard requirement is Sphinx,
which you can install directly:
.. code-block:: bash
pip install -r doc/python_api/requirements.txt
Building the Documentation
--------------------------
Once you have the requirements installed, you can build the documentation:
.. code-block:: bash
# From the Blender source root
make doc_py
You can then open ``doc/python_api/sphinx-out/index.html`` in your browser.
Modifying API Documentation
===========================
API documentation is automatically generated from Blender's source code, meaning that
class descriptions, method signatures, etc., are defined either within C/C++ files
(via ``PyDoc_STRVAR``) or as standard doc-strings in Python files.
**To modify API class or method descriptions:**
#. Locate the relevant source file in the Blender repository.
#. Find the relevant doc-string either inside ``PyDoc_STRVAR(...)`` for the Python C/API
or as a standard doc-string in a Python file.
#. Edit using **reStructuredText** formatting.
#. Rebuild the Python API docs with ``make doc_py`` to regenerate the pages.
Adding Example Code Snippets
============================
Code examples are a crucial part of the API documentation. They help users
understand how to use various classes, functions, and modules, appearing
above the API reference for each one.
Example File Naming Convention
------------------------------
Examples can be included as standalone script files instead of inlining
code-blocks in the doc-string. Create a file matching the naming conventions
below, and it will be included automatically.
Example files are located in ``doc/python_api/examples/`` and are matched by filename:
- ``module.N.py`` matches a module (e.g., ``gpu.0.py``).
- ``module.ClassName.N.py`` matches a class (e.g., ``bpy.types.Operator.0.py``).
- ``module.ClassName.method.N.py`` matches a method (e.g., ``bpy.types.Operator.invoke.0.py``).
- ``module.ClassName.attribute.N.py`` matches an attribute (e.g., ``bpy.types.Scene.frame_start.0.py``).
- ``module.member.N.py`` matches a module member (e.g., ``bpy.context.object.0.py``).
Multiple examples are supported, where ``N`` allows them to be ordered sequentially.
Example File Structure
----------------------
For each example, it is often useful to include a description explaining what the code demonstrates.
To support this, the doc-string at the start of the file is extracted and displayed above the code example.
The doc-string content will be formatted as reStructuredText.
Each example file should follow this structure:
.. code-block:: python
"""
Example Title
+++++++++++++
A description of what this example demonstrates.
This doc-string appears above the code in the documentation.
You can use **reStructuredText** formatting here, for example:
- *Italic* text with single asterisks
- **Bold** text with double asterisks
- ``inline code`` with double backticks
- :class:`bpy.types.Operator` to link to API classes
- Links to `external resources <https://www.blender.org/>`__
.. note::
You can use this to highlight important information.
Everything after this doc-string is included as code.
"""
import bpy
# Example code goes here
print("This is an example")
Important Notes
~~~~~~~~~~~~~~~
- The file must start with triple double-quotes ``"""`` (single-quoted doc-strings aren't recognized).
- Use section header underlines with ``+`` characters for the title.
- Everything after the doc-string is included as code in the documentation.
- To add additional code-blocks with text in between, add new files.
Best Practices for Documentation
================================
Writing Good Examples
---------------------
- **Keep it simple**: Focus on demonstrating one concept at a time.
- **Make it runnable**: Examples should work when pasted into Blender's Python console or text editor.
- **Use comments**: Comment thoroughly, assuming readers are new to the APIs and concepts being demonstrated.
Style Guidelines
----------------
For documentation, we aim for high-quality technical writing. Refer to these
style guides from the User Manual for markup and conventions:
- `Markup Guide <https://docs.blender.org/manual/en/latest/contribute/manual/guides/markup_guide.html>`__
- `Writing Guide <https://docs.blender.org/manual/en/latest/contribute/manual/guides/writing_guide.html>`__
- `reStructuredText Primer <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`__
Testing Your Changes
====================
After adding or modifying documentation, rebuild the docs (see
`Building the Documentation`_) and check for any warnings about broken links,
missing references, or formatting issues. Preview the generated HTML files
in your browser to verify they look correct.
Contributing Your Changes
=========================
Once you've added or improved documentation,
follow Blender's `contribution guidelines <https://developer.blender.org/docs/handbook/contributing/>`__
to create a pull request.

View File

@@ -0,0 +1,19 @@
*******
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 cause instability.
.. toctree::
:maxdepth: 1
info_gotchas_crashes.rst
info_gotchas_threading.rst
info_gotchas_internal_data_and_python_objects.rst
info_gotchas_operators.rst
info_gotchas_meshes.rst
info_gotchas_armatures_and_bones.rst
info_gotchas_file_paths_and_encoding.rst

View File

@@ -0,0 +1,99 @@
*****************
Bones & Armatures
*****************
Edit Bones, Pose Bones, Bone... Bones
=====================================
Armature Bones in Blender have three distinct data structures that contain them.
If you are accessing the bones through one of them, you may not have access to the properties you really need.
.. note::
In the following examples ``bpy.context.object`` is assumed to be an armature object.
Edit Bones
----------
``bpy.context.object.data.edit_bones`` contains edit bones;
to access them you must set the armature mode to Edit-Mode first (edit bones do not exist in Object or Pose-Mode).
Use these to create new bones, set their head/tail or roll, change their parenting relationships to other bones, etc.
Example using :class:`bpy.types.EditBone` in armature Edit-Mode
which is only possible in Edit-Mode:
>>> bpy.context.object.data.edit_bones["Bone"].head = Vector((1.0, 2.0, 3.0))
This will be empty outside of Edit-Mode:
>>> mybones = bpy.context.selected_editable_bones
Returns an edit bone only in Edit-Mode:
>>> bpy.context.active_bone
Bones (Object-Mode)
-------------------
``bpy.context.object.data.bones`` contains bones.
These *live* in Object-Mode, and have various properties you can change,
note that the head and tail properties are read-only.
Example using :class:`bpy.types.Bone` in Object or Pose-Mode
returning a bone (not an edit bone) outside of Edit-Mode:
>>> bpy.context.active_bone
This works, as with Blender the setting can be edited in any mode:
>>> bpy.context.object.data.bones["Bone"].use_deform = True
Accessible but read-only:
>>> tail = myobj.data.bones["Bone"].tail
Pose Bones
----------
``bpy.context.object.pose.bones`` contains pose bones.
This is where animation data resides, i.e. animatable transformations
are applied to pose bones, as are constraints and IK-settings.
Examples using :class:`bpy.types.PoseBone` in Object or Pose-Mode:
.. code-block:: python
# Gets the name of the first constraint (if it exists).
bpy.context.object.pose.bones["Bone"].constraints[0].name
# Gets the last selected pose bone (Pose-Mode only).
bpy.context.active_pose_bone
.. note::
Notice the pose is accessed from the object rather than the object data,
this is why Blender can have two or more objects sharing the same armature in different poses.
.. note::
Strictly speaking pose bones are not bones, they are just the state of the armature,
stored in the :class:`bpy.types.Object` rather than the :class:`bpy.types.Armature`,
yet the real bones are accessible from the pose bones via :class:`bpy.types.PoseBone.bone`.
Armature Mode Switching
=======================
While writing scripts that deal with armatures you may find you have to switch between modes,
when doing so take care when switching out of Edit-Mode not to keep references
to the edit bones or their head/tail vectors.
Further access to these will crash Blender so it's important that the script
clearly separates sections of the code which operate in different modes.
This is mainly an issue with Edit-Mode since pose data can be manipulated without having to be in Pose-Mode,
yet for operator access you may still need to enter Pose-Mode.

View File

@@ -0,0 +1,314 @@
********************************
Troubleshooting Errors & Crashes
********************************
.. _troubleshooting_crashes:
Help! My script crashes Blender
===============================
:abbr:`TL;DR (Too long; didn't read.)` Do not keep direct references to Blender data (of any kind)
when modifying the container of that data, and/or when some undo/redo may happen
(e.g. during modal operators execution...).
Instead, use indices (or other data always stored by value in Python, like string keys...),
that allow you to get access to the desired data.
Ideally it would be impossible to crash Blender from Python,
however, there are some problems with the API where it can be made to crash.
Strictly speaking this is a bug in the API but fixing it would mean adding memory verification
on every access since most crashes are caused by the Python objects referencing Blender's memory directly,
whenever the memory is freed or re-allocated, further Python access to it can crash the script.
But fixing this would make the scripts run very slow,
or writing a very different kind of API which doesn't reference the memory directly.
Here are some general hints to avoid running into these problems:
- Be aware of memory limits,
especially when working with large lists since Blender can crash simply by running out of memory.
- Many hard to fix crashes end up being because of referencing freed data,
when removing data be sure not to hold any references to it.
- Re-allocation can lead to the same issues
(e.g. if you add a lot of items to some Collection,
this can lead to re-allocating the underlying container's memory,
invalidating all previous references to existing items).
- Modules or classes that remain active while Blender is used,
should not hold references to data the user may remove, instead,
fetch data from the context each time the script is activated.
- Crashes may not happen every time, they may happen more on some configurations or operating systems.
- Be careful with recursive patterns, those are very efficient at hiding the issues described here.
- See last subsection about `Unfortunate Corner Cases`_ for some known breaking exceptions.
.. note::
To find the line of your script that crashes you can use the ``faulthandler`` module.
See the `Faulthandler docs <https://docs.python.org/dev/library/faulthandler.html>`__.
While the crash may be in Blender's C/C++ code,
this can help a lot to track down the area of the script that causes the crash.
.. note::
Some container modifications are actually safe, because they will never re-allocate existing data
(e.g. linked lists containers will never re-allocate existing items when adding or removing others).
But knowing which cases are safe and which aren't implies a deep understanding of Blender's internals.
That's why, unless you are willing to dive into the RNA C implementation, it's simpler to
always assume that data references will become invalid when modifying their containers,
in any possible way.
.. rubric:: Do not:
.. code-block:: python
class TestItems(bpy.types.PropertyGroup):
name: bpy.props.StringProperty()
bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)
first_item = bpy.context.scene.test_items.add()
for i in range(100):
bpy.context.scene.test_items.add()
# This is likely to crash, as internal code may re-allocate
# the whole container (the collection) memory at some point.
first_item.name = "foobar"
.. rubric:: Do:
.. code-block:: python
class TestItems(bpy.types.PropertyGroup):
name: bpy.props.StringProperty()
bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)
first_item = bpy.context.scene.test_items.add()
for i in range(100):
bpy.context.scene.test_items.add()
# This is safe, we are getting again desired data *after*
# all modifications to its container are done.
first_item = bpy.context.scene.test_items[0]
first_item.name = "foobar"
Undo/Redo
---------
For safety, you should assume that undo and redo always invalidates all :class:`bpy.types.ID`
instances (Object, Scene, Mesh, Light, etc.), as well obviously as all of their sub-data.
This example shows how you can tell undo changes the memory locations:
>>> hash(bpy.context.object)
-9223372036849950810
>>> hash(bpy.context.object)
-9223372036849950810
Delete the active object, then undo:
>>> hash(bpy.context.object)
-9223372036849951740
As suggested above, simply not holding references to data when Blender is used
interactively by the user is the only way to make sure that the script doesn't become unstable.
.. note::
Modern undo/redo system does not systematically invalidate all pointers anymore.
Some data (in fact, most data, in typical cases), which were detected as unchanged for a
particular history step, may remain unchanged and hence their pointers may remain valid.
Be aware that if you want to take advantage of this behavior for some reason, there is no
guarantee of any kind that it will be safe and consistent. Use it at your own risk.
Modifying Blender Data & Undo
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In general, when Blender data is modified, there should always be an undo step created for it.
Otherwise, there will be issues, ranging from invalid/broken undo stack, to crashes on undo/redo.
This is especially true when modifying Blender data :ref:`in operators <operator_modifying_blender_data_undo>`.
Undo & Library Data
^^^^^^^^^^^^^^^^^^^
One of the advantages with Blender's library linking system is that undo
can skip checking changes in library data since it is assumed to be static.
Tools in Blender are not allowed to modify library data.
But Python does not enforce this restriction.
This can be useful in some cases, using a script to adjust material values for example.
But it's also possible to use a script to make library data point to newly created local data,
which is not supported since a call to undo will remove the local data
but leave the library referencing it and likely crash.
So it's best to consider modifying library data an advanced usage of the API
and only to use it when you know what you're doing.
Abusing RNA property callbacks
------------------------------
Python-defined RNA properties can have custom callbacks. Trying to perform complex operations
from there, like calling an operator, may work, but is not officially recommended nor supported.
Main reason is that those callbacks should be very fast, but additionally, it may for example
create issues with undo/redo system (most operators store a history step, and editing an RNA
property does so as well), trigger infinite update loops, and so on.
Edit-Mode / Memory Access
-------------------------
Switching mode ``bpy.ops.object.mode_set(mode='EDIT')`` or ``bpy.ops.object.mode_set(mode='OBJECT')``
will re-allocate objects data,
any references to a meshes vertices/polygons/UVs, armatures bones,
curves points, etc. cannot be accessed after switching mode.
Only the reference to the data itself can be re-accessed, the following example will crash.
.. code-block:: python
mesh = bpy.context.active_object.data
polygons = mesh.polygons
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
# This will crash!
print(polygons)
So after switching mode you need to re-access any object data variables,
the following example shows how to avoid the crash above.
.. code-block:: python
mesh = bpy.context.active_object.data
polygons = mesh.polygons
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
# Polygons have been re-allocated.
polygons = mesh.polygons
print(polygons)
These kinds of problems can happen for any functions which re-allocate
the object data but are most common when switching mode.
Array Re-Allocation
-------------------
When adding new points to a curve or vertices/edges/polygons to a mesh,
internally the array which stores this data is re-allocated.
.. code-block:: python
bpy.ops.curve.primitive_bezier_curve_add()
point = bpy.context.object.data.splines[0].bezier_points[0]
bpy.context.object.data.splines[0].bezier_points.add()
# This will crash!
point.co = 1.0, 2.0, 3.0
This can be avoided by re-assigning the point variables after adding the new one or by storing
indices to the points rather than the points themselves.
The best way is to sidestep the problem altogether by adding all the points to the curve at once.
This means you don't have to worry about array re-allocation and it's faster too
since re-allocating the entire array for every added point is inefficient.
Removing Data
-------------
**Any** data that you remove shouldn't be modified or accessed afterwards,
this includes: F-Curves, drivers, render layers, timeline markers, modifiers, constraints
along with objects, scenes, collections, bones, etc.
The ``remove()`` API calls will invalidate the data they free to prevent common mistakes.
The following example shows how this precaution works:
.. code-block:: python
mesh = bpy.data.meshes.new(name="MyMesh")
# Normally the script would use the mesh here.
bpy.data.meshes.remove(mesh)
print(mesh.name) # <- Give an exception rather than crashing:
# ReferenceError: StructRNA of type Mesh has been removed
But take care because this is limited to scripts accessing the variable which is removed,
the next example will still crash:
.. code-block:: python
mesh = bpy.data.meshes.new(name="MyMesh")
vertices = mesh.vertices
bpy.data.meshes.remove(mesh)
print(vertices) # <- This may crash.
Unfortunate Corner Cases
------------------------
Besides all expected cases listed above, there are a few others that should not be
an issue but, due to internal implementation details, currently are:
Collection Objects
^^^^^^^^^^^^^^^^^^
Changing: ``Object.hide_viewport``, ``Object.hide_select`` or ``Object.hide_render``
will trigger a rebuild of Collection caches, thus breaking any current iteration over ``Collection.all_objects``.
.. rubric:: Do not:
.. code-block:: python
# `all_objects` is an iterator. Using it directly while performing operations on its members that will update
# the memory accessed by the `all_objects` iterator will lead to invalid memory accesses and crashes.
for object in bpy.data.collections["Collection"].all_objects:
object.hide_viewport = True
.. rubric:: Do:
.. code-block:: python
# `all_objects[:]` is an independent list generated from the iterator. As long as no objects are deleted,
# its content will remain valid even if the data accessed by the `all_objects` iterator is modified.
for object in bpy.data.collections["Collection"].all_objects[:]:
object.hide_viewport = True
Data-Blocks Renaming During Iteration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Data-blocks accessed from ``bpy.data`` are sorted when their name is set.
Any loop that iterates over data such as ``bpy.data.objects`` for example,
and sets the objects ``name`` must get all items from the iterator first (typically by converting to a list or tuple)
to avoid missing some objects and iterating over others multiple times.
sys.exit
========
Some Python modules will call ``sys.exit()`` themselves when an error occurs,
while not common behavior this is something to watch out for because it may seem
as if Blender is crashing since ``sys.exit()`` will close Blender immediately.
For example, the ``argparse`` module will print an error and exit if the arguments are invalid.
A dirty way of troubleshooting this is to set ``sys.exit = None`` and see what line of Python code is quitting,
you could of course replace ``sys.exit`` with your own function but manipulating Python in this way is bad practice.

View File

@@ -0,0 +1,75 @@
****************************
File Paths & String Encoding
****************************
Relative File Paths
===================
Blender's relative file paths are not compatible with standard Python modules such as ``sys`` and ``os``.
Built-in Python functions don't understand Blender's ``//`` prefix which denotes the blend-file path.
A common case where you would run into this problem is when exporting a material with associated image paths:
>>> bpy.path.abspath(image.filepath)
When using Blender data from linked libraries there is an unfortunate complication
since the path will be relative to the library rather than the open blend-file.
When the data block may be from an external blend-file pass the library argument from the :class:`bpy.types.ID`.
>>> bpy.path.abspath(image.filepath, library=image.library)
This returns the absolute path which can be used with native Python modules.
Unicode Problems
================
Python supports many different encodings so there is nothing stopping you from
writing a script in ``latin1`` or ``iso-8859-15``.
See `PEP 263 <https://www.python.org/dev/peps/pep-0263/>`__.
However, this complicates matters for Blender's Python API because ``.blend`` files don't have an explicit encoding.
To avoid the problem for Python integration and script authors we have decided that all strings in blend-files
**must** be ``UTF-8``, ``ASCII`` compatible.
This means assigning strings with different encodings to an object name, for instance, will raise an error.
Paths are an exception to this rule since the existence of non-UTF-8 paths on the user's file system cannot be ignored.
This means seemingly harmless expressions can raise errors, e.g:
>>> print(bpy.data.filepath)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-21: ordinal not in range(128)
>>> bpy.context.object.name = bpy.data.filepath
Traceback (most recent call last):
File "<blender_console>", line 1, in <module>
TypeError: bpy_struct: item.attr= val: Object.name expected a string type, not str
Here are two ways around file-system encoding issues:
>>> print(repr(bpy.data.filepath))
>>> import os
>>> filepath_bytes = os.fsencode(bpy.data.filepath)
>>> filepath_utf8 = filepath_bytes.decode('utf-8', "replace")
>>> bpy.context.object.name = filepath_utf8
Unicode encoding/decoding is a big topic with comprehensive Python documentation,
to keep it short about encoding problems -- here are some suggestions:
- Always use UTF-8 encoding or convert to UTF-8 where the input is unknown.
- Avoid manipulating file paths as strings directly, use ``os.path`` functions instead.
- Use ``os.fsencode()`` or ``os.fsdecode()`` instead of built-in string decoding functions when operating on paths.
- To print paths or to include them in the user interface use ``repr(path)`` first
or ``"%r" % path`` with string formatting.
.. note::
Sometimes it's preferable to avoid string encoding issues by using bytes instead of Python strings,
when reading some input it's less trouble to read it as binary data
though you will still need to decide how to treat any strings you want to use with Blender,
some importers do this.

View File

@@ -0,0 +1,211 @@
************************************
Internal Data & Their Python Objects
************************************
The Python objects wrapping Blender internal data have some limitations and constraints,
compared to 'pure Python' data. The most common things to keep in mind are documented here.
.. _blender_py_objects_life_time:
Life-Time of Python Objects Wrapping Blender Data
=================================================
Typically, Python objects representing (wrapping) Blender data have a limited life-time.
They are created on-demand, and deleted as soon as they are not used in Python anymore.
This means that storing python-only data in these objects should not be done for anything that
requires some form of persistence.
There are some exceptions to this rule. For example, IDs do store their Python instance, once created,
and re-use it instead of re-creating a new Python object every time they are accessed from Python.
And modal operators will keep their instance as long as the operator is running.
However, this is done for performance purposes and is considered an internal implementation detail.
Relying on this behavior from Python code side for any purpose is not recommended.
Furthermore, Blender may free its internal data, in which case it will try to invalidate a known
Python object wrapping it. But this is not always possible, which can lead to invalid memory access and
is another good reason to never store these in Python code in any persistent way.
See also the :ref:`troubleshooting crashes <troubleshooting_crashes>` documentation.
Data Names
==========
Naming Limitations
------------------
A common mistake is to assume newly created data is given the requested name.
This can cause bugs when you add data (normally imported) then reference it later by name:
.. code-block:: python
bpy.data.meshes.new(name=meshid)
# Normally some code, function calls, etc.
bpy.data.meshes[meshid]
Or with name assignment:
.. code-block:: python
obj.name = objname
# Normally some code, function calls, etc.
obj = bpy.data.meshes[objname]
Data names may not match the assigned values if they exceed the maximum length, are already used or an empty string.
It's better practice not to reference objects by names at all,
once created you can store the data in a list, dictionary, on a class, etc;
there is rarely a reason to have to keep searching for the same data by name.
If you do need to use name references, it's best to use a dictionary to maintain
a mapping between the names of the imported assets and the newly created data,
this way you don't run this risk of referencing existing data from the blend-file, or worse modifying it.
.. code-block:: python
# Typically declared in the main body of the function.
mesh_name_mapping = {}
mesh = bpy.data.meshes.new(name=meshid)
mesh_name_mapping[meshid] = mesh
# Normally some code, or function calls, etc.
# Use own dictionary rather than `bpy.data`.
mesh = mesh_name_mapping[meshid]
Library Collisions
------------------
Blender keeps data names unique (:class:`bpy.types.ID.name`) so you can't name two objects,
meshes, scenes, etc., the same by accident.
However, when linking in library data from another blend-file naming collisions can occur,
so it's best to avoid referencing data by name at all.
This can be tricky at times and not even Blender handles this correctly in some cases
(when selecting the modifier object for example, you can't select between multiple objects with the same name),
but it's still good to try avoiding these problems in this area.
If you need to select between local and library data, there is a feature in ``bpy.data`` members to allow for this.
.. code-block:: python
# Typical name lookup, could be local or library.
obj = bpy.data.objects["my_obj"]
# Library object name lookup using a pair,
# where the second argument is the library path matching bpy.types.Library.filepath.
obj = bpy.data.objects["my_obj", "//my_lib.blend"]
# Local object name look up using a pair,
# where the second argument excludes library data from being returned.
obj = bpy.data.objects["my_obj", None]
# Both the examples above also work for `get`.
obj = bpy.data.objects.get(("my_obj", None))
Stale Data
==========
No updates after setting values
-------------------------------
Sometimes you want to modify values from Python and immediately access the updated values, e.g:
After changing the object's :class:`bpy.types.Object.location`
you may want to access its transformation right after from :class:`bpy.types.Object.matrix_world`,
but this doesn't work as you might expect. There are similar issues with changes to the UI, that
are covered in the next section.
Consider the calculations that might contribute to the object's final transformation, this includes:
- Animation function curves.
- Drivers and their Python expressions.
- Constraints
- Parent objects and all of their F-Curves, constraints, etc.
To avoid expensive recalculations every time a property is modified,
Blender defers the evaluation until the results are needed.
However, while the script runs you may want to access the updated values.
In this case you need to call :class:`bpy.types.ViewLayer.update` after modifying values, for example:
.. code-block:: python
bpy.context.object.location = 1, 2, 3
bpy.context.view_layer.update()
Now all dependent data (child objects, modifiers, drivers, etc.)
have been recalculated and are available to the script within the active view layer.
No updates after changing UI context
------------------------------------
Similar to the previous issue, some changes to the UI may also not have an immediate effect. For example, setting
:class:`bpy.types.Window.workspace` doesn't seem to cause an observable effect in the immediately following code
(:class:`bpy.types.Window.workspace` is still the same), but the UI will in fact reflect the change. Some of the
properties that behave that way are:
- :class:`bpy.types.Window.workspace`
- :class:`bpy.types.Window.screen`
- :class:`bpy.types.Window.scene`
- :class:`bpy.types.Area.type`
- :class:`bpy.types.Area.ui_type`
Such changes impact the UI, and with that the context (:class:`bpy.context`) quite drastically. This can break
Blender's context management. So Blender delays this change until after operators have run and just before the UI is
redrawn, making sure that context can be changed safely.
If you rely on executing code with an updated context this can be worked around by executing the code in a delayed
fashion as well. Possible options include:
- :ref:`Modal Operator <modal_operator>`.
- :mod:`bpy.app.handlers`.
- :mod:`bpy.app.timers`.
It's also possible to depend on drawing callbacks although these should generally be avoided as failure to draw a
hidden panel, region, cursor, etc. could cause your script to be unreliable.
Can I redraw during script execution?
=====================================
The official answer to this is no, or... *"You don't want to do that"*.
To give some background on the topic:
While a script executes, Blender waits for it to finish and is effectively locked until it's done;
while in this state Blender won't redraw or respond to user input.
Normally this is not such a problem because scripts distributed with Blender
tend not to run for an extended period of time,
nevertheless scripts *can* take a long time to complete and it would be nice to see progress in the viewport.
Tools that lock Blender in a loop redraw are highly discouraged
since they conflict with Blender's ability to run multiple operators
at once and update different parts of the interface as the tool runs.
So the solution here is to write a **modal** operator, which is an operator that defines a ``modal()`` function,
See the modal operator template in the text editor.
Modal operators execute on user input or setup their own timers to run frequently,
they can handle the events or pass through to be handled by the keymap or other modal operators.
Examples of modal operators are Transform, Painting, Fly Navigation and File Select.
Writing modal operators takes more effort than a simple ``for`` loop
that contains draw calls but is more flexible and integrates better with Blender's design.
.. rubric:: Ok, Ok! I still want to draw from Python
If you insist -- yes it's possible, but scripts that use this hack will not be considered
for inclusion in Blender and any issue with using it will not be considered a bug,
there is also no guaranteed compatibility in future releases.
.. code-block:: python
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

View File

@@ -0,0 +1,111 @@
*********************
Modes and Mesh Access
*********************
When working with mesh data you may run into the problem where a script fails to run as expected in Edit-Mode.
This is caused by Edit-Mode having its own data which is only written back to the mesh when exiting Edit-Mode.
A common example is that exporters may access a mesh through ``obj.data`` (a :class:`bpy.types.Mesh`)
when the user is in Edit-Mode, where the mesh data is available but out of sync with the edit mesh.
In this situation you can...
- Exit Edit-Mode before running the tool.
- Explicitly update the mesh by calling :meth:`bmesh.types.BMesh.to_mesh`.
- Modify the script to support working on the edit-mode data directly, see: :func:`bmesh.from_edit_mesh`.
- Report the context as incorrect and only allow the script to run outside Edit-Mode.
.. _info_gotcha_mesh_faces:
N-Gons and Tessellation
=======================
Since 2.63 n-gons are supported, this adds some complexity
since in some cases you need to access triangles still (some exporters for example).
There are now three ways to access faces:
- :class:`bpy.types.MeshPolygon` --
this is the data structure which now stores faces in Object-Mode
(access as ``mesh.polygons`` rather than ``mesh.faces``).
- :class:`bpy.types.MeshLoopTriangle` --
the result of tessellating polygons into triangles
(access as ``mesh.loop_triangles``).
- :class:`bmesh.types.BMFace` --
the polygons as used in Edit-Mode.
For the purpose of the following documentation,
these will be referred to as polygons, loop triangles and BMesh-faces respectively.
Faces with five or more sides will be referred to as ``ngons``.
Support Overview
----------------
.. list-table::
:header-rows: 1
:stub-columns: 1
* - Usage
- :class:`bpy.types.MeshPolygon`
- :class:`bpy.types.MeshLoopTriangle`
- :class:`bmesh.types.BMFace`
* - Import/Create
- Poor *(inflexible)*
- Unusable *(read-only)*.
- Best
* - Manipulate
- Poor *(inflexible)*
- Unusable *(read-only)*.
- Best
* - Export/Output
- Good *(n-gon support)*
- Good *(When n-gons cannot be used)*
- Good *(n-gons, extra memory overhead)*
.. note::
Using the :mod:`bmesh` API is completely separate API from :mod:`bpy`,
typically you would use one or the other based on the level of editing needed,
not simply for a different way to access faces.
Creating
--------
All three data types can be used for face creation:
- Polygons are the most efficient way to create faces but the data structure is *very* rigid and inflexible,
you must have all your vertices and faces ready and create them all at once.
This is further complicated by the fact that each polygon does not store its own vertices,
rather they reference an index and size in :class:`bpy.types.Mesh.loops` which are a fixed array too.
- BMesh-faces are most likely the easiest way to create faces in new scripts,
since faces can be added one by one and the API has features intended for mesh manipulation.
While :class:`bmesh.types.BMesh` uses more memory it can be managed by only operating on one mesh at a time.
Editing
-------
Editing is where the three data types vary most.
- Polygons are very limited for editing,
changing materials and options like smooth works, but for anything else
they are too inflexible and are only intended for storage.
- Loop-triangles should not be used for editing geometry because doing so will cause existing n-gons to be tessellated.
- BMesh-faces are by far the best way to manipulate geometry.
Exporting
---------
All three data types can be used for exporting,
the choice mostly depends on whether the target format supports n-gons or not.
- Polygons are the most direct and efficient way to export providing they convert into the output format easily enough.
- Loop-triangles work well for exporting to formats which don't support n-gons,
in fact this is the only place where their use is encouraged.
- BMesh-Faces can work for exporting too but may not be necessary if polygons can be used
since using BMesh gives some overhead because it's not the native storage format in Object-Mode.

View File

@@ -0,0 +1,72 @@
***************
Using Operators
***************
.. _using_operators:
Blender's operators are tools for users to access, that can be accessed with Python too which is very useful.
Still operators have limitations that can make them cumbersome to script.
The 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 is 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.
Why does an operator's poll fail?
=================================
When calling an operator it gives an error like this:
>>> bpy.ops.action.clean(threshold=0.001)
RuntimeError: Operator bpy.ops.action.clean.poll() failed, context is incorrect
Which raises the question as to what the correct context might be?
Typically operators check for the active area type, a selection or active object they can operate on,
but some operators are more strict when they run.
In most cases you can figure out what context an operator needs
by examining how it's used in Blender and thinking about what it does.
If you're still stuck, unfortunately, the only way to eventually know what is causing the error is
to read the source code for the poll function and see what it is checking.
For Python operators it's not so hard to find the source
since it's included with Blender and the source file and line is included in the operator reference docs.
Downloading and searching the C code isn't so simple,
especially if you're not familiar with the C language but by searching the operator name or description
you should be able to find the poll function with no knowledge of C.
.. note::
Blender does have the functionality for poll functions to describe why they fail,
but it's currently not used much, if you're interested to help improve the API
feel free to add calls to :class:`bpy.types.Operator.poll_message_set` (``CTX_wm_operator_poll_msg_set`` in C)
where it's not obvious why poll fails, e.g:
>>> bpy.ops.object.vertex_group_add()
RuntimeError: Operator bpy.ops.object.vertex_group_add.poll() No active editable object
In some cases using :class:`bpy.types.Context.temp_override` to enable temporary logging or using the
``context`` category when :ref:`logging <blender_manual:command-line-args-logging-options>` can help.
The operator still doesn't work!
================================
Certain operators in Blender are only intended for use in a specific context,
some operators for example are only called from the properties editor where they check the current material,
modifier or constraint.
Examples of this are:
- :func:`bpy.ops.texture.slot_move`
- :func:`bpy.ops.constraint.limitdistance_reset`
- :func:`bpy.ops.object.modifier_copy`
- :func:`bpy.ops.buttons.file_browse`
Another possibility is that you are the first person to attempt to use this operator
in a script and some modifications need to be made to the operator to run in a different context.
If the operator should logically be able to run but fails when accessed from a script
it should be reported to the bug tracker.

View File

@@ -0,0 +1,92 @@
********************************
Python Threads are Not Supported
********************************
In short: Python threads cause Blender to crash in hard to diagnose ways. For
example, a crash can occur while rendering with Cycles, with Python drivers,
while a background thread is used to download some file.
So far, no work has been done to make Blender's Python integration thread safe,
so until it's properly supported, it's best not make use of this.
Note that some modules in the Python standard library may use threads as well.
An example is the
`multiprocessing.Queue <https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue>`_ class.
Python threading with Blender only works properly when the threads finish up
before the script does, for example by using ``threading.Thread.join()``. In other
words, they can only be used while the main Blender thread is blocked from
running.
Alternative Approaches
======================
For running Python code independently of Blender, it is recommended to use the
`multiprocessing <https://docs.python.org/3/library/multiprocessing.html>`_ module.
Code Examples
=============
Here is an example of threading supported by Blender:
.. code-block:: python
import threading
import requests
urls = [
"http://localhost:8000/file-1.blend",
"http://localhost:8000/file-2.blend",
"http://localhost:8000/file-3.blend",
]
def download(url: str) -> None:
name = threading.current_thread().name
print("{}: Starting".format(name))
response = requests.get(url)
print("{}: Request status code {}".format(name, response.status_code))
threads = [
threading.Thread(
name="thread-{}".format(index),
target=download,
args=(url,),
)
for index, url in enumerate(urls)
]
print("Starting threads...")
for t in threads:
t.start()
# NOTE: While threads are running, no code (including the main thread)
# may use bpy or any Blender API - only standard Python or third-party modules.
print("Waiting for threads to finish...")
for t in threads:
t.join()
# It's now safe to use bpy again since all threads have finished.
print("Threads all done, now Blender can continue")
This is an example of an **unsupported** case, using a timer that runs many times
a second:
.. code-block:: python
from threading import Timer
def my_timer():
t = Timer(0.1, my_timer)
t.daemon = True
t.start()
print("Running...")
my_timer()
Use cases like the one above, which leave the thread running once the script
finishes, may seem to work for a while, but end up causing random crashes or
errors in Blender's own drawing code.

View File

@@ -0,0 +1,487 @@
.. _info_overview:
************
API Overview
************
The purpose of this document is to explain how Python and Blender fit together,
covering some of the functionality that may not be obvious from reading the API references
and example scripts.
Python in Blender
=================
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 :mod:`bpy` and :mod:`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.
Here is a simple example which moves a vertex attached to an object named "Cube":
.. code-block:: python
import bpy
bpy.data.objects["Cube"].data.vertices[0].co.x += 1.0
This modifies Blender's internal data directly.
When you run this in the interactive console you will see the 3D Viewport update.
The Default Environment
=======================
When developing your own scripts it may help to understand how Blender sets up its Python environment.
Many Python scripts come bundled with Blender and can be used as a reference
because they use the same API that script authors write tools in.
Typical usage for scripts include: user interface, import/export,
scene manipulation, automation, defining your own tool set and customization.
On startup Blender scans the ``scripts/startup/`` directory for Python modules and imports them.
The exact location of this directory depends on your installation.
See the :ref:`directory layout docs <blender_manual:blender-directory-layout>`.
Script Loading
==============
This may seem obvious, but it is important to note the difference between
executing a script directly and importing a script as a module.
Extending Blender by executing a script directly means the classes that the script defines
remain available inside Blender after the script finishes execution.
Using scripts this way makes future access to their classes
(to unregister them for example) more difficult compared to importing the scripts as modules.
When a script is imported as a module, its class instances will remain
inside the module and can be accessed later on by importing that module again.
For this reason it is preferable to avoid directly executing scripts that extend Blender by registering classes.
Here are some ways to run scripts directly in Blender:
- Loaded in the text editor and press *Run Script*.
- Typed or pasted into the interactive console.
- Execute a Python file from the command line with Blender, e.g:
.. code-block:: sh
blender --python /home/me/my_script.py
To run as modules:
- The obvious way, ``import some_module`` command from the text editor or interactive console.
- Open as a text data-block and check the *Register* option, this will load with the blend-file.
- Copy into one of the directories ``scripts/startup``, where they will be automatically imported on startup.
- Define as an add-on, enabling the add-on will load it as a Python module.
Add-ons
-------
Some of Blender's functionality is best kept optional,
alongside scripts loaded at startup there are add-ons which are only loaded on startup
if enabled from the user preferences. Add-ons are typically distributed as extensions.
The only difference between add-on extensions and built-in Python modules is that add-ons must include a
``blender_manifest.toml`` which Blender uses to read metadata such as name, author, tags and project link.
The User Preferences add-on listing uses this to display information about each add-on.
See :ref:`Creating Extensions <blender_manual:extensions-index>` for details.
Integration through Classes
===========================
Running Python scripts in the text editor is useful for testing but you'll
want to extend Blender to make tools accessible like other built-in functionality.
The Blender Python API allows integration for:
- :class:`bpy.types.Panel`
- :class:`bpy.types.Menu`
- :class:`bpy.types.Operator`
- :class:`bpy.types.PropertyGroup`
- :class:`bpy.types.KeyingSet`
- :class:`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 predefined to interface with Blender.
For example:
.. code-block:: python
import bpy
class SimpleOperator(bpy.types.Operator):
bl_idname = "object.simple_operator"
bl_label = "Tool Name"
def execute(self, context):
print("Hello World")
return {'FINISHED'}
bpy.utils.register_class(SimpleOperator)
First note that it defines a subclass as a member of :mod:`bpy.types`,
this is common for all classes which can be integrated with Blender and
is used to distinguish an Operator from 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.
Next see the execute function, which takes an instance of the operator and the current context.
A common prefix is not used for functions.
Lastly the register function is called, this takes the class and loads it into Blender. See `Class Registration`_.
Regarding inheritance, Blender doesn't impose restrictions on the kinds of class inheritance used,
the registration checks will use attributes and functions defined in parent classes.
Class mix-in example:
.. code-block:: python
import bpy
class BaseOperator:
def execute(self, context):
print("Hello World BaseClass")
return {'FINISHED'}
class SimpleOperator(bpy.types.Operator, BaseOperator):
bl_idname = "object.simple_operator"
bl_label = "Tool Name"
bpy.utils.register_class(SimpleOperator)
.. note::
Modal operators are an exception, keeping their instance variable as Blender runs, see modal operator template.
So once the class is registered with Blender, instancing the class and calling the functions is left up to Blender.
In fact you cannot instantiate these classes from the script as you would expect with most Python APIs.
To run operators you can call them through the operator API, e.g:
.. code-block:: python
import bpy
bpy.ops.object.simple_operator()
User interface classes are given a context in which to draw, buttons, window, file header, toolbar, etc.,
then they are drawn when that area is displayed so they are never called by Python scripts directly.
.. _info_overview_class_construction_destruction:
Construction & Destruction
--------------------------
In the examples above, the classes don't define an ``__init__(self)`` function.
In general, defining custom constructors or destructors should not be needed, and is not recommended.
The lifetime of class instances is usually very short (also see the
:ref:`dedicated section <blender_py_objects_life_time>`), a panel for example will
have a new instance for every redraw.
Some other types, like :class:`bpy.types.Operator`, have an even more complex internal handling,
which can lead to several instantiations for a single operator execution.
There are a few cases where defining ``__init__()`` does make sense, e.g., when sub-classing a
:class:`bpy.types.RenderEngine`. When doing so, the parent matching function must always be called,
otherwise Blender's internal initialization won't happen properly:
.. code-block:: python
import bpy
class AwesomeRaytracer(bpy.types.RenderEngine):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_var = 42
...
.. warning::
The Blender-defined parent constructor must be called before any data access to the object, including
from other potential parent types ``__init__()`` functions.
.. warning::
Calling the parent's ``__init__()`` function is a hard requirement since Blender 4.4.
The 'generic' signature is the recommended one here, as Blender internal BPY code is typically
the only caller of these functions. The actual arguments passed to the constructor are fully
internal data, and may change depending on the implementation.
Unfortunately, the error message, generated in case the expected constructor is not called, can
be fairly cryptic and unhelpful. Generally they should be about failure to create a (python)
object:
MemoryError: couldn't create bpy_struct object
With Operators, it might be something like this:
RuntimeError: could not create instance of <OPERATOR_OT_identifier> to call callback function execute
.. note::
In case you are using complex/multi-inheritance, ``super()`` may not work (as the Blender-defined parent
may not be the first type in the MRO). It is best then to first explicitly invoke the Blender-defined
parent class constructor, before any other. For example:
.. code-block:: python
import bpy
class FancyRaytracer(AwesomeRaytracer, bpy.types.RenderEngine):
def __init__(self, *args, **kwargs):
bpy.types.RenderEngine.__init__(self, *args, **kwargs)
AwesomeRaytracer.__init__(self, *args, **kwargs)
self.my_var = 42
...
.. note::
Defining a custom ``__new__()`` function is strongly discouraged, not tested, and not considered
as supported currently.
Doing so presents a very high risk of crashes or otherwise corruption of Blender internal data.
But if defined, it must take the same two generic positional and keyword arguments,
and call the parent's ``__new__()`` with them if actually creating a new object.
.. note::
Due to internal
`CPython implementation details <https://discuss.python.org/t/cpython-usage-of-tp-finalize/64100>`__,
C++-defined Blender types do not define or use a ``__del__()`` (aka ``tp_finalize()``) destructor
currently.
As this function
`does not exist if not explicitly defined <https://stackoverflow.com/questions/36722390/python-3-super-del>`__,
that means that calling ``super().__del__()`` in the ``__del__()`` function of a sub-class will
fail with the following error:
``AttributeError: 'super' object has no attribute '__del__'``.
If a call to the MRO 'parent' destructor is needed for some reason, the caller code must ensure
that the destructor does exist, e.g., using something like this:
``getattr(super(), "__del__", lambda self: None)(self)``
.. _info_overview_registration:
Registration
============
Module Registration
-------------------
Blender modules loaded at startup require ``register()`` and ``unregister()`` functions.
These are the *only* functions that Blender calls from your code, which is otherwise a regular Python module.
A simple Blender Python module can look like this:
.. code-block:: python
import bpy
class SimpleOperator(bpy.types.Operator):
""" See example above """
def register():
bpy.utils.register_class(SimpleOperator)
def unregister():
bpy.utils.unregister_class(SimpleOperator)
if __name__ == "__main__":
register()
These functions usually appear at the bottom of the script containing class registration, sometimes adding menu items.
You can also use them for internal purposes setting up data for your own tools but take care
since register won't re-run when a new blend-file is loaded.
The register/unregister calls are used so it's possible to toggle add-ons and reload scripts while Blender runs.
If the register calls were placed in the body of the script, registration would be called on import,
meaning there would be no distinction between importing a module or loading its classes into Blender.
This becomes problematic when a script imports classes from another module
making it difficult to manage which classes are being loaded and when.
The last two lines are only for testing:
.. code-block:: python
if __name__ == "__main__":
register()
This allows the script to be run directly in the text editor to test changes.
This ``register()`` call won't run when the script is imported as a module
since ``__main__`` is reserved for direct execution.
Class Registration
------------------
Registering a class with Blender results in the class definition being loaded into Blender,
where it becomes available alongside existing functionality.
Once this class is loaded you can access it from :mod:`bpy.types`,
using the ``bl_idname`` rather than the class's original name.
.. note::
There are some exceptions to this for class names which aren't guaranteed to be unique.
In this case use: :func:`bpy.types.Struct.bl_rna_get_subclass_py`.
When loading a class, Blender performs sanity checks making sure all required properties and functions are found,
that properties have the correct type, and that functions have the right number of arguments.
Mostly you will not need concern yourself with this but if there is a problem
with the class definition it will be raised on registering:
Using the function arguments ``def execute(self, context, spam)``, will raise an exception:
``ValueError: expected Operator, SimpleOperator class "execute" function to have 2 args, found 3``
Using ``bl_idname = 1`` will raise:
``TypeError: validating class error: Operator.bl_idname expected a string type, not int``
Inter-Class Dependencies
^^^^^^^^^^^^^^^^^^^^^^^^
When customizing Blender you may want to group your own settings together,
after all, they will likely have to co-exist with other scripts.
To group these properties classes need to be defined,
for groups within groups or collections within groups
you can't avoid having to deal with the order of registration/unregistration.
Custom properties groups are themselves classes which need to be registered.
For example, if you want to store material settings for a custom engine:
.. code-block:: python
# Create new property:
# bpy.data.materials[0].my_custom_props.my_float
import bpy
class MyMaterialProps(bpy.types.PropertyGroup):
my_float: bpy.props.FloatProperty()
def register():
bpy.utils.register_class(MyMaterialProps)
bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialProps)
def unregister():
del bpy.types.Material.my_custom_props
bpy.utils.unregister_class(MyMaterialProps)
if __name__ == "__main__":
register()
.. note::
The class **must be** registered before being used in a property, failing to do so will raise an error:
``ValueError: bpy_struct "Material" registration error: my_custom_props could not register``
.. code-block:: python
# Create new property group with a sub property:
# bpy.data.materials[0].my_custom_props.sub_group.my_float
import bpy
class MyMaterialSubProps(bpy.types.PropertyGroup):
my_float: bpy.props.FloatProperty()
class MyMaterialGroupProps(bpy.types.PropertyGroup):
sub_group: bpy.props.PointerProperty(type=MyMaterialSubProps)
def register():
bpy.utils.register_class(MyMaterialSubProps)
bpy.utils.register_class(MyMaterialGroupProps)
bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialGroupProps)
def unregister():
del bpy.types.Material.my_custom_props
bpy.utils.unregister_class(MyMaterialGroupProps)
bpy.utils.unregister_class(MyMaterialSubProps)
if __name__ == "__main__":
register()
.. important::
The lowest class needs to be registered first and that ``unregister()`` is a mirror of ``register()``.
Manipulating Classes
^^^^^^^^^^^^^^^^^^^^
Properties can be added and removed as Blender runs,
normally done on register or unregister but for some special cases
it may be useful to modify types as the script runs.
For example:
.. code-block:: python
# Add a new property to an existing type.
bpy.types.Object.my_float: bpy.props.FloatProperty()
# Remove it.
del bpy.types.Object.my_float
This works just as well for ``PropertyGroup`` subclasses you define yourself.
.. code-block:: python
class MyPropGroup(bpy.types.PropertyGroup):
pass
MyPropGroup.my_float: bpy.props.FloatProperty()
This is equivalent to:
.. code-block:: python
class MyPropGroup(bpy.types.PropertyGroup):
my_float: bpy.props.FloatProperty()
Dynamic Class Definition (Advanced)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In some cases the specifier for data may not be in Blender, for example an external render engine's shader definitions,
and it may be useful to define them as types and remove them on the fly.
.. code-block:: python
for i in range(10):
idname = "object.operator_{:d}".format(i)
def func(self, context):
print("Hello World", self.bl_idname)
return {'FINISHED'}
op_class = type(
"DynOp{:d}".format(i),
(bpy.types.Operator, ),
{"bl_idname": idname, "bl_label": "Test", "execute": func},
)
bpy.utils.register_class(op_class)
.. note::
``type()`` is called to define the class.
This is an alternative syntax for class creation in Python, better suited to constructing classes dynamically.
To call the operators from the previous example:
>>> bpy.ops.object.operator_1()
Hello World OBJECT_OT_operator_1
{'FINISHED'}
>>> bpy.ops.object.operator_2()
Hello World OBJECT_OT_operator_2
{'FINISHED'}

View File

@@ -0,0 +1,479 @@
.. _info_quickstart:
**********
Quickstart
**********
This :abbr:`API (Application Programming Interface)` is generally stable
but some areas are still being extended and improved.
.. rubric:: Blender Python API features:
- Edit any data the user interface can (Scenes, Meshes, Particles etc.).
- Modify user preferences, keymaps and themes.
- Run tools with own settings.
- Create user interface elements such as menus, headers and panels.
- Create new tools.
- Create interactive tools.
- Create new rendering engines that integrate with Blender.
- Subscribe to changes to data and its properties.
- Define new settings in existing Blender data.
- Draw in the 3D Viewport using Python.
.. rubric:: (Still) missing features:
- Create new space types.
- Assign custom properties to every type.
Before Starting
===============
This document is intended to familiarize you with Blender Python API
but not to fully cover each topic.
A quick list of helpful things to know before starting:
- Enable :ref:`Developer Extra <blender_manual:bpy.types.PreferencesView.show_developer_ui>`
and :ref:`Python Tooltips <blender_manual:bpy.types.PreferencesView.show_tooltips_python>`.
- The :ref:`Python Console <blender_manual:bpy.types.SpaceConsole>`
is great for testing one-liners; it has autocompletion so you can inspect the API quickly.
- Button tooltips show Python attributes and operator names (when enabled see above).
- The context menu of buttons directly links to this API documentation (when enabled see above).
- Many python examples can be found in the text editor's template menu.
- To examine further scripts distributed with Blender, see:
- ``scripts/startup/bl_ui`` for the user interface.
- ``scripts/startup/bl_operators`` for operators.
Exact location depends on platform, see:
:ref:`directory layout docs <blender_manual:blender-directory-layout>`.
Running Scripts
---------------
The two most common ways to execute Python scripts are using the built-in
text editor or entering commands in the Python console.
Both the *Text Editor* and *Python Console* are space types you can select from the header.
Rather than manually configuring your spaces for Python development,
you can use the *Scripting* workspace accessible from the Topbar tabs.
From the text editor you can open ``.py`` files or paste them from the clipboard, then test using *Run Script*.
The Python Console is typically used for typing in snippets and for testing to get immediate feedback,
but can also have entire scripts pasted into it.
Scripts can also run from the command line with Blender but to learn scripting in Blender this isn't essential.
Key Concepts
============
Data Access
-----------
Accessing Data-Blocks
^^^^^^^^^^^^^^^^^^^^^
You can access Blender's data with the Python API in the same way as the animation system or user interface;
this implies that any setting that can be changed via a button can also be changed with Python.
Accessing data from the currently loaded blend-file is done with the module :mod:`bpy.data`.
It gives access to library data, for example:
>>> bpy.data.objects
<bpy_collection[3], BlendDataObjects>
>>> bpy.data.scenes
<bpy_collection[1], BlendDataScenes>
>>> bpy.data.materials
<bpy_collection[1], BlendDataMaterials>
Accessing Collections
^^^^^^^^^^^^^^^^^^^^^
You will notice that an index as well as a string can be used to access members of the collection.
Unlike Python dictionaries, both methods are available;
however, the index of a member may change while running Blender.
>>> list(bpy.data.objects)
[bpy.data.objects["Cube"], bpy.data.objects["Plane"]]
>>> bpy.data.objects['Cube']
bpy.data.objects["Cube"]
>>> bpy.data.objects[0]
bpy.data.objects["Cube"]
Accessing Attributes
^^^^^^^^^^^^^^^^^^^^
Once you have a data-block, such as a material, object, collection, etc.,
its attributes can be accessed much like you would change a setting using the graphical interface.
In fact, the tooltip for each button also displays the Python attribute
which can help in finding what settings to change in a script.
>>> bpy.data.objects[0].name
'Camera'
>>> bpy.data.scenes["Scene"]
bpy.data.scenes['Scene']
>>> bpy.data.materials.new("MyMaterial")
bpy.data.materials['MyMaterial']
For testing what data to access it's useful to use the Python Console, which is its own space type.
This supports auto-complete, giving you a fast way to explore the data in your file.
Example of a data path that can be quickly found via the console:
>>> bpy.data.scenes[0].render.resolution_percentage
100
>>> bpy.data.scenes[0].objects["Torus"].data.vertices[0].co.x
1.0
Data Creation/Removal
^^^^^^^^^^^^^^^^^^^^^
When you are familiar with other Python APIs you may be surprised that
new data-blocks in the bpy API cannot be created by calling the class:
>>> bpy.types.Mesh()
Traceback (most recent call last):
File "<blender_console>", line 1, in <module>
TypeError: bpy_struct.__new__(type): expected a single argument
This is an intentional part of the API design.
The Blender Python API can't create Blender data that exists outside the main Blender database
(accessed through :mod:`bpy.data`), because this data is managed by Blender (save, load, undo, append, etc).
Data is added and removed via methods on the collections in :mod:`bpy.data`, e.g:
>>> mesh = bpy.data.meshes.new(name="MyMesh")
>>> print(mesh)
<bpy_struct, Mesh("MyMesh.001")>
>>> bpy.data.meshes.remove(mesh)
.. _info_quickstart-custom_properties:
Custom Properties
^^^^^^^^^^^^^^^^^
Python can access properties on any data-block that has an ID
(data that can be linked in and accessed from :mod:`bpy.data`).
When assigning a property, you can pick your own names,
these will be created when needed or overwritten if they already exist.
This data is saved with the blend-file and copied with objects, for example:
.. code-block:: python
bpy.context.object["MyOwnProperty"] = 42
if "SomeProp" in bpy.context.object:
print("Property found")
# Use the get function like a Python dictionary
# which can have a fallback value.
value = bpy.data.scenes["Scene"].get("test_prop", "fallback value")
# Dictionaries can be assigned as long as they only use basic types.
collection = bpy.data.collections.new("MyTestCollection")
collection["MySettings"] = {"foo": 10, "bar": "spam", "baz": {}}
del collection["MySettings"]
Note that these properties can only be assigned basic Python types:
- int, float, string
- array of ints or floats
- dictionary (only string keys are supported, values must be basic types too)
.. note::
Using dictionaries allow to nest data into other data.
There is a hard limit of 1024 levels to the supported nesting depth.
Deeper nesting will lead to error messages and loss of data.
These properties are valid outside of Python. They can be animated by curves or used in driver paths.
For a list of types that support custom properties see:
:ref:`types supporting custom properties <bpy_types-custom_properties>`.
Context
-------
While it's useful to be able to access data directly by name or as a list,
it's more common to operate on the user's selection.
The context is always available from ``bpy.context`` and can be used to get the active object, scene,
tool settings along with many other attributes.
Some common use cases are:
>>> bpy.context.object
>>> bpy.context.selected_objects
>>> bpy.context.visible_bones
Note that the context is read-only, which means that these values cannot be modified directly.
But they can be changed by running API functions or by using the data API.
So ``bpy.context.active_object = obj`` will raise an error.
But ``bpy.context.view_layer.objects.active = obj`` works as expected.
The context attributes change depending on where they are accessed.
The 3D Viewport has different context members than the Python Console,
so take care when accessing context attributes that the user state is known.
See :mod:`bpy.context` API reference.
Operators (Tools)
-----------------
Operators are tools generally accessed by the user from buttons, menu items or key shortcuts.
From the user perspective they are a tool but Python can run these with its own settings
through the :mod:`bpy.ops` module.
Examples:
>>> bpy.ops.mesh.flip_normals()
{'FINISHED'}
>>> bpy.ops.mesh.hide(unselected=False)
{'FINISHED'}
>>> bpy.ops.object.transform_apply()
{'FINISHED'}
.. tip::
The :ref:`Operator Cheat Sheet <blender_manual:bpy.ops.wm.operator_cheat_sheet>`
gives a list of all operators and their default values in Python syntax, along with the generated docs.
This is a good way to get an overview of all Blender's operators.
Operator Poll()
^^^^^^^^^^^^^^^
Many operators have a "poll" function which checks if the cursor
is in a valid area or if the object is in the correct mode (Edit Mode, Weight Paint Mode, etc).
When an operator's poll function fails within Python, an exception is raised.
For example, calling ``bpy.ops.view3d.render_border()`` from the console raises the following error:
.. code-block:: python
RuntimeError: Operator bpy.ops.view3d.render_border.poll() failed, context is incorrect
In this case the context must be the 3D Viewport with an active camera.
To avoid using try-except clauses wherever operators are called, you can call the operator's
own ``poll()`` function to check if it can run the operator in the current context.
.. code-block:: python
if bpy.ops.view3d.render_border.poll():
bpy.ops.view3d.render_border()
Integration
===========
Python scripts can integrate with Blender in the following ways:
- By defining a render engine.
- By defining operators.
- By defining menus, headers and panels.
- By inserting new buttons into existing menus, headers and panels.
In Python, this is done by defining a class, which is a subclass of an existing type.
Example Operator
----------------
.. literalinclude:: __/__/__/scripts/templates_py/Operator/simple.py
Once this script runs, ``SimpleOperator`` is registered with Blender
and can be called from Operator Search or added to the toolbar.
To run the script:
#. Start Blender and switch to the Scripting workspace.
#. Click the *New* button in the text editor to create a new text data-block.
#. Copy the code from above and paste it into the text editor.
#. Click on the *Run Script* button.
#. Move your cursor into the 3D Viewport,
open the :ref:`Operator Search menu <blender_manual:bpy.ops.wm.search_menu>`,
and type "Simple".
#. Click on the "Simple Operator" item found in search.
.. seealso::
The class members with the ``bl_`` prefix are documented in the API reference :class:`bpy.types.Operator`.
.. note::
The output from the ``main`` function is sent to the terminal;
in order to see this, be sure to :ref:`use the terminal <use_the_terminal>`.
Example Panel
-------------
Panels are registered as a class, like an operator.
Notice the extra ``bl_`` variables used to set the context they display in.
.. literalinclude:: __/__/__/scripts/templates_py/UI/panel_simple.py
To run the script:
#. Start Blender and switch to the Scripting workspace.
#. Click the *New* button in the text editor to create a new text data-block.
#. Copy the code from above and paste it into the text editor.
#. Click on the *Run Script* button.
To view the results:
#. Select the default cube.
#. Click on the Object properties icon in the buttons panel (far right; appears as a tiny cube).
#. Scroll down to see a panel named "Hello World Panel".
#. Changing the object name also updates *Hello World Panel's* name: field.
Note the row distribution and the label and properties that are defined through the code.
.. seealso:: :class:`bpy.types.Panel`
Types
=====
Blender defines a number of Python types but also uses Python native types.
Blender's Python API can be split up into three categories.
Native Types
------------
In simple cases returning a number or a string as a custom type would be cumbersome,
so these are accessed as normal Python types.
- Blender float, int, boolean -> float, int, boolean
- Blender enumerator -> string
>>> C.object.rotation_mode = 'AXIS_ANGLE'
- Blender enumerator (multiple) -> set of strings
.. code-block:: python
# Setting multiple snap targets.
bpy.context.scene.tool_settings.snap_elements_base = {'VERTEX', 'EDGE'}
# Passing as an operator argument for report types.
self.report({'WARNING', 'INFO'}, "Some message!")
Internal Types
--------------
:class:`bpy.types.bpy_struct` is used for Blender data-blocks and collections.
Also for data that contains its own attributes: collections, meshes, bones, scenes, etc.
There are two main types that wrap Blender's data, one for data-blocks
(known internally as ``bpy_struct``), another for properties.
>>> bpy.context.object
bpy.data.objects['Cube']
>>> C.scene.objects
bpy.data.scenes['Scene'].objects
Note that these types reference Blender's data so modifying them is visible immediately.
Mathutils Types
---------------
Accessible from :mod:`mathutils` are vectors, quaternions, Euler angles, matrix and color types.
Some attributes such as :class:`bpy.types.Object.location`,
:class:`bpy.types.PoseBone.rotation_euler` and :class:`bpy.types.View3DCursor.location`
can be accessed as special math types which can be used together and manipulated in various useful ways.
Example of a matrix, vector multiplication:
.. code-block:: python
bpy.context.object.matrix_world @ bpy.context.object.data.vertices[0].co
.. note::
mathutils types keep a reference to Blender's internal data so changes can
be applied back.
Example:
.. code-block:: python
# Modifies the Z axis in place.
bpy.context.object.location.z += 2.0
# Location variable holds a reference to the object too.
location = bpy.context.object.location
location *= 2.0
# Copying the value drops the reference so the value can be passed to
# functions and modified without unwanted side effects.
location = bpy.context.object.location.copy()
Animation
=========
There are two ways to add keyframes through Python.
The first is through key properties directly, which is like inserting a keyframe from the button as a user.
You can also manually create the curves and keyframe data, then set the path to the property.
Here are examples of both methods. Both insert a keyframe on the active object's Z axis.
Simple example:
.. code-block:: python
obj = bpy.context.object
obj.location[2] = 0.0
obj.keyframe_insert(data_path="location", frame=10.0, index=2)
obj.location[2] = 1.0
obj.keyframe_insert(data_path="location", frame=20.0, index=2)
Using low-level functions:
.. code-block:: python
obj = bpy.context.object
# Create the action, with a slot for the object, a layer, and a keyframe strip:
action = bpy.data.actions.new(name="MyAction")
slot = action.slots.new(obj.id_type, obj.name)
strip = action.layers.new("MyLayer").strips.new(type='KEYFRAME')
# Create a channelbag to hold the F-Curves for the slot:
channelbag = strip.channelbag(slot, ensure=True)
# Create the F-Curve with two keyframes:
fcu_z = channelbag.fcurves.new(data_path="location", index=2)
fcu_z.keyframe_points.add(2)
fcu_z.keyframe_points[0].co = 10.0, 0.0
fcu_z.keyframe_points[1].co = 20.0, 1.0
# Assign the action and the slot to the object:
adt = obj.animation_data_create()
adt.action = action
adt.action_slot = slot

View File

@@ -0,0 +1,309 @@
***************
Tips and Tricks
***************
Here are various suggestions that you might find useful when writing scripts.
Some of these are just Python features that you may not have thought to use with Blender,
others are Blender-specific.
.. _use_the_terminal:
Use the Terminal
================
When writing Python scripts, it's useful to have a terminal open,
this is not the built-in Python console but a terminal application which is used to start Blender.
The three main use cases for the terminal are:
- You can see the output of ``print()`` as your script runs, which is useful to view debug info.
- The error traceback is printed in full to the terminal which won't always generate a report message in
Blender's user interface (depending on how the script is executed).
- If the script runs for too long or you accidentally enter an infinite loop,
:kbd:`Ctrl-C` in the terminal (:kbd:`Ctrl-Break` on Windows) will quit the script early.
.. seealso::
:ref:`blender_manual:command_line-launch-index`.
Interface Tricks
================
Access Operator Commands
------------------------
You may have noticed that the tooltip for menu items and buttons includes the ``bpy.ops.[...]`` command
to run that button, a handy (hidden) feature is that you can press :kbd:`Ctrl-C` over
any menu item or button to copy this command into the clipboard.
Access Data Path
----------------
To find the path from an :class:`ID` data-block to its setting isn't always so simple since it may be nested away.
To get this quickly open the context menu of the setting and select *Copy Data Path*,
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:`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 editor,
this only reports operators with the ``REGISTER`` option enabled so as not to flood the *Info* view
with calls to ``bpy.ops.view3d.smoothview`` and ``bpy.ops.view3d.zoom``.
Yet 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
======================
Blender's text editor is fine for small changes and writing tests but it's not full featured,
for larger projects you'll probably want to use a standalone editor or Python IDE.
Editing a text file externally and having the same text open in Blender does work
but isn't that optimal so here are two ways you can use an external file from Blender.
Using the following examples you'll still need text data-block in Blender to execute,
but reference an external file rather than including it directly.
Executing External Scripts
--------------------------
This is the equivalent to running the script directly, referencing a script's path from a two line code block.
.. code-block:: python
filename = "/full/path/to/myscript.py"
exec(compile(open(filename).read(), filename, 'exec'))
You might want to reference a script relative to the blend-file.
.. code-block:: python
import bpy
import os
filename = os.path.join(os.path.dirname(bpy.data.filepath), "myscript.py")
exec(compile(open(filename).read(), filename, 'exec'))
Executing Modules
-----------------
This example shows loading a script in as a module and executing a module function.
.. code-block:: python
import myscript
import importlib
importlib.reload(myscript)
myscript.main()
Notice that the script is reloaded every time, this forces use of the modified version,
otherwise the cached one in ``sys.modules`` would be used until Blender was restarted.
The important difference between this and executing the script directly is it
has to call a function in the module, in this case ``main()`` but it can be any function,
an advantage with this is you can pass arguments to the function from this
small script which is often useful for testing different settings quickly.
The other issue with this is the script has to be in Python's module search path.
While this is not best practice -- for testing purposes you can extend the search path,
this following example adds the current blend-file's directory to the search path
and then loads the script as a module.
.. code-block:: python
import sys
import os
import bpy
blend_dir = os.path.dirname(bpy.data.filepath)
if blend_dir not in sys.path:
sys.path.append(blend_dir)
import myscript
import importlib
importlib.reload(myscript)
myscript.main()
Use Blender without its User Interface
======================================
While developing your own scripts Blender's interface can get in the way,
manually reloading, running the scripts, opening file import, etc. adds overhead.
For scripts that are not interactive it can end up being more efficient not to use
Blender's interface at all and instead execute the script on the command line.
.. code-block:: sh
blender --background --python myscript.py
You might want to run this with a blend-file so the script has some data to operate on.
.. code-block:: sh
blender myscene.blend --background --python myscript.py
.. note::
Depending on your setup you might have to enter the full path to the Blender executable.
Once the script is running properly in background mode, you'll want to check the output of the script,
this depends completely on the task at hand, however, here are some suggestions:
- Render the output to an image, use an image viewer and keep writing over the same image each time.
- Save a new blend-file, or export the file using one of Blender's exporters.
- If the results can be displayed as text then print them or write them to a file.
While this can take a little time to setup, it can be well worth the effort
to reduce the time it takes to test changes. You can even have
Blender running the script every few seconds with a viewer updating the results,
so no need to leave your text editor to see changes.
Use External Tools
==================
When there are no readily available Python modules to perform specific tasks it's
worth keeping in mind you may be able to have Python execute an external command
on your data and read the result back in.
Using external programs adds an extra dependency and may limit who can use the script
but to quickly setup your own custom pipeline or writing one-off scripts this can be handy.
Examples include:
- Run Gimp in batch mode to execute custom scripts for advanced image processing.
- Write out 3D models to use external mesh manipulation tools and read back in the results.
- Convert files into recognizable formats before reading.
Bundled Python & Extensions
===========================
The Blender releases distributed from blender.org include a complete Python installation on all platforms,
this has the disadvantage that any extensions you have installed on your system's Python environment
will not be found by Blender.
There are two ways to work around this:
- Remove Blender Python subdirectory, Blender will then fall back on the system's Python and use that instead.
Depending on your platform,
you may need to explicitly reference the location of your Python installation using
the ``PYTHONPATH`` environment variable, e.g:
.. code-block:: sh
PYTHONPATH=/usr/lib/python3.7 ./blender --python-use-system-env
.. warning::
The Python (major, minor) version must match the one that Blender comes with.
Therefore you can't use Python 3.6 with Blender built to use Python 3.7.
- Copy or link the extensions into Blender's Python subdirectory so Blender can access them,
you can also copy the entire Python installation into Blender's subdirectory,
replacing the one Blender comes with.
This works as long as the Python versions match and the paths are created in the same relative locations.
Doing this has the advantage that you can redistribute this bundle to others with Blender
including any extensions you rely on.
Insert a Python Interpreter into your Script
============================================
In the middle of a script you may want to inspect variables,
run functions and inspect the flow.
.. code-block:: python
import code
code.interact(local=locals())
If you want to access both global and local variables run this:
.. code-block:: python
import code
namespace = globals().copy()
namespace.update(locals())
code.interact(local=namespace)
The next example is an equivalent single line version of the script above which is easier to paste into your code:
.. code-block:: python
__import__('code').interact(local=dict(globals(), **locals()))
``code.interact`` can be added at any line in the script
and will pause the script to launch an interactive interpreter in the terminal,
when you're done you can quit the interpreter and the script will continue execution.
If you have **IPython** installed you can use its ``embed()`` function which uses the current namespace.
The IPython prompt has auto-complete and some useful features that the standard Python eval-loop doesn't have.
.. code-block:: python
import IPython
IPython.embed()
Admittedly this highlights the lack of any Python debugging support built into Blender,
but its still a handy thing to know.
Advanced
========
Blender as a Module
-------------------
From a Python perspective it's nicer to have everything as an extension
which lets the Python script combine many components.
Advantages include:
- You can use external editors or IDEs with Blender's Python API and execute scripts within the IDE
(step over code, inspect variables as the script runs).
- Editors or IDEs can auto-complete Blender modules and variables.
- Existing scripts can import Blender APIs without having to be run inside of Blender.
This is marked advanced because to run Blender as a Python module requires a special build option.
For instructions on building see
`Building Blender as a Python module <https://developer.blender.org/docs/handbook/building_blender/python_module/>`__.
Python Safety (Build Option)
----------------------------
Since it's possible to access data which has been removed (see :doc:`Gotchas <info_gotcha>`),
it can be hard to track down the cause of crashes.
To raise Python exceptions on accessing freed data (rather than crashing),
enable the CMake build option ``WITH_PYTHON_SAFETY``.
This enables data tracking which makes data access about two times slower
which is why the option isn't enabled in release builds.