2

Blender Python API概述

本文档的目的是解释Python和Blender如何组合在一起,涵盖了一些在阅读API参考和示例脚本时可能不明显的功能。

Python in Blender

Blender有一个嵌入式Python解释器,它在Blender启动时加载,并在Blender运行时保持活动状态。该解释器运行脚本来绘制用户界面,并用于Blender的一些内部工具。
Blender的嵌入式解释器提供了典型的Python环境,因此关于如何编写Python脚本的教程中的代码也可以使用Blender的解释器运行。Blender为嵌入式解释器提供了Python模块,例如bpy和mathutils,因此可以将它们导入到脚本中,并可以访问Blender的数据,类和函数。处理Blender数据的脚本需要导入模块才能工作。
这是一个简单的示例,它移动附加到名为Cube的对象的顶点:

import bpy
bpy.data.objects["Cube"].data.vertices[0].co.x += 1.0

这会直接修改Blender的内部数据。在交互式控制台中运行此操作时,您将看到3D视口更新。

默认环境

在开发自己的脚本时,了解Blender如何设置其Python环境可能会有所帮助。许多Python脚本与Blender捆绑在一起,可以用作参考,因为它们使用脚本作者编写工具的相同API。脚本的典型用法包括:用户界面,导入/导出,场景操作,自动化,定义自己的工具集和定制。
在启动时,Blender会扫描scripts/startup/目录中的Python模块并导入它们。此目录的确切位置取决于您的安装。请参阅目录布局文档。

脚本加载

这看起来很明显,但重要的是要注意直接执行脚本和将脚本作为模块导入之间的区别。
通过直接执行脚本来扩展Blender意味着脚本完成执行后脚本定义的类在Blender中保持可用。与将脚本作为模块导入相比,以这种方式使用脚本使得将来访问其类(例如取消注册它们)变得更加困难。将脚本作为模块导入时,其类实例将保留在模块中,稍后可以通过再次导入该模块来访问。
因此,最好避免直接执行通过注册类来扩展Blender的脚本。
以下是在Blender中直接运行脚本的一些方法。

  • 在文本编辑器中加载并按Run Script。
  • 键入或粘贴到交互式控制台中。
  • 使用Blender从命令行执行Python文件,例如:
blender --python /home/me/my_script.py

要作为模块运行:

  • 显而易见的方法,来自文本窗口或交互式控制台的命令。import some_module
  • 打开文本块并勾选“注册”选项,这将加载混合文件。
  • 复制到其中一个目录中scripts/startup,它们将在启动时自动导入。
  • 定义为加载项,启用加载项将其加载为Python模块。

附加组件

一些Blenders功能最好保持可选,除了在启动时加载的脚本我们有附加组件,这些附加组件保存在它们自己的目录中scripts/addons,并且只有在从用户首选项中选择时才会在启动时加载。
附加组件和内置Python模块之间的唯一区别是附加组件必须包含bl_info Blender用于读取元数据的变量,例如名称,作者,类别和URL。
用户首选项加载项列表使用bl_info显示有关每个加载项的信息。
有关bl_info字典的 详细信息,请参阅加载项。

通过类集成

在文本编辑器中运行Python脚本对于测试很有用,但是您需要扩展Blender以使工具可以像其他内置功能一样访问。
Blender Python api允许集成:

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

这是故意限制的。目前,对于更高级的功能,例如网格修改器,对象类型或着色器节点,必须使用C / C ++。
对于Python集成,Blender定义了所有类型共有的方法。这可以通过创建Blender类的Python子类来实现,该类包含由父类指定的变量和函数,这些变量和函数是预定义为与Blender接口的。
例如:

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)

首先请注意,我们将其成员子类化bpy.types,这对于可以与Blender集成并使用的所有类都是通用的,因此我们知道这是一个运算符而不是注册时的Panel。
两个类属性都以bl_前缀开头。这是一个用于区分Blender属性和您自己添加的属性的约定。
接下来看到execute函数,它接受运算符的实例和当前上下文。公共前缀不用于函数。
最后调用寄存器函数,这将获取类并将其加载到Blender中。请参阅班级注册。
关于继承,Blender不对所使用的类继承施加限制,注册检查将使用父类中定义的属性和函数。
class mix-in示例:

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)

请注意,这些类没有定义__init__(self)函数。而__init__()和__del__()如果定义了将被调用,在类实例寿命仅跨越执行。因此,例如一个面板将为每次重绘都有一个新实例,因此很少有理由在面板实例中存储变量。相反,持久变量应存储在Blenders ata中,以便在重新启动Blender时可以恢复状态。
注意

模态运算符是一个例外,它们的实例变量保持为Blender运行,请参见模态运算符模板。
因此,一旦类在Blender中注册,实例化类并调用函数就由Blender完成。实际上,您无法像在大多数Python API中所期望的那样从脚本中实例化这些类。
要运行运算符,可以通过运算符api调用它们,例如:

import bpy
bpy.ops.object.simple_operator()

用户界面类给出了绘制,按钮窗口,文件头,工具栏等的上下文,然后在显示该区域时绘制它们,因此Python脚本不会直接调用它们。

注册

模块注册

启动时加载的Blender模块需要register()和unregister()功能。这些是Blender从您的代码调用的唯一函数,否则它是常规的Python模块。
一个简单的Blender / 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()

这些函数通常出现在包含类注册的脚本的底部,有时会添加菜单项。您也可以将它们用于内部目的,为您自己的工具设置数据,但要小心,因为加载新的混合文件时寄存器不会重新运行。
使用了注册/取消注册调用,因此可以在Blender运行时切换加载项和重新加载脚本。如果寄存器调用放在脚本的主体中,则会在导入时调用注册,这意味着导入模块或将其类加载到Blender之间没有区别。
当脚本从另一个模块导入类时,这会成为问题,因为很难管理正在加载哪些类以及何时加载。
最后两行仅用于测试:

if __name__ == "__main__":
    register()

这允许脚本直接在文本编辑器中运行以测试更改。register()将脚本作为模块导入时,此调用将不会运行,因为__main__保留用于直接执行。

类注册

使用Blender注册类会导致类定义被加载到Blender中,并在现有功能的同时可用。
加载此类后,您可以bpy.types使用bl_idname而不是类原始名称来访问它。
加载类时,Blender执行完整性检查,确保找到所有必需的属性和函数,属性具有正确的类型,并且函数具有正确数量的参数。
大多数情况下,你不需要担心这个问题,但是如果类定义有问题,它将在注册时引发:
使用函数参数,将引发异常:def execute(self, context, spam)
ValueError: expected Operator, SimpleOperator class "execute" function to have 2 args, found 3
使用将提高。bl_idname = 1
TypeError: validating class error: Operator.bl_idname expected a string type, not int

多类

上面描述了将类加载到Blender中,对于简单的情况,调用bpy.utils.register_class(SomeClass)就足够了,但是当有很多类或子模块子模块有自己的类时,将它们全部列入注册可能会很繁琐。
为了更方便的加载/卸载bpy.utils.register_module(模块)和bpy.utils.unregister_module(模块)功能存在。
一个脚本,它定义了许多自己的操作符,面板菜单等,你只需要编写:

def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

内部Blender在可注册类型上收集子类,通过定义它们的模块存储它们。通过将模块名称传递给bpy.utils.register_module Blender,可以注册该模块及其子模块创建的所有类。

类间依赖性

在自定义Blender时,您可能希望将自己的设置组合在一起,毕竟,它们可能必须与其他脚本共存。要对这些属性进行分组,需要定义类,对于组内的组或组内的集合,您可以发现自己必须处理注册/取消注册的顺序。
自定义属性组本身就是需要注册的类。
假设您要存储自定义引擎的材质设置。

# 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()

注意

该类必须在用于属性之前注册,否则将引发错误:
ValueError: bpy_struct "Material" registration error: my_custom_props could not register
# 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()

注意

最低级别需要首先注册,而unregister()是register()的镜像
操纵类

可以在Blender运行时添加和删除属性,通常在注册或取消注册时发生,但对于某些特殊情况,在脚本运行时修改类型可能很有用。
例如:

# add a new property to an existing type
bpy.types.Object.my_float = bpy.props.FloatProperty()
# remove
del bpy.types.Object.my_float

这适用于您自己定义的PropertyGroup子类。

class MyPropGroup(bpy.types.PropertyGroup):
    pass
MyPropGroup.my_float = bpy.props.FloatProperty()

......这相当于:

class MyPropGroup(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()
动态定义类(高级)

在某些情况下,数据的说明符可能不在Blender,renderman着色器定义中,例如,将它们定义为类型并在运行中删除它们可能很有用。
for i in range(10):

idname = "object.operator_%d" % i

def func(self, context):
    print("Hello World", self.bl_idname)
    return {'FINISHED'}

opclass = type("DynOp%d" % i,
               (bpy.types.Operator, ),
               {"bl_idname": idname, "bl_label": "Test", "execute": func},
               )
bpy.utils.register_class(opclass)

注意

type()被调用来定义类。这是Python中类创建的替代语法,更适合动态构造类。
要从上一个示例中调用运算符:

>>> bpy.ops.object.operator_1()
Hello World OBJECT_OT_operator_1
{'FINISHED'}
>>> bpy.ops.object.operator_2()
Hello World OBJECT_OT_operator_2
{'FINISHED'}

小佩琪
9 声望6 粉丝