Blender创意编程入门

在这个教程中,我们将学习一些 Blender 脚本技术——比如如何使用代码处理、操作、复制和动画网格图元。要结合所有这些技术,我们将创建一个波浪形的锥形图案——一个看起来很酷的动画,你可以将其转换为循环 GIF。

我将使用bpy.data模块中的一系列属性和方法来回顾最重要的bpy库。我还将介绍如何从其他 Python 文件导入代码,以及如何使用其他代码编辑器来编写 Blender 代码。当然,使用 Blender 进行创意编码还有很多其他内容,但这就是我在这个简短的教程系列中所涵盖的全部内容。

在继续之前,启动 Blender(使用命令行)。如果已经打开它,请使用File > New > General创建一个新的 Blender 文件。可以看到一个新场景,其中一个立方体位于 xyz 坐标 (0, 0, 0)。

1、导入 bpy

bpy 库是所有魔术发生的原因。它包含九个主要模块,使你能够使用 Python 控制 Blender;它们是bpy.app, bpy.context, bpy.data, bpy.msgbus, bpy.ops, bpy.path, bpy.props, bpy.types, 和bpy.utils. 在 Python 控制台中,bpy库会自动导入并立即可用。但是,当你使用文本编辑器(或任何其他代码编辑器)编写 Python 脚本时,必须先添加必要的import行,然后才能使用bpy.

注意:除了bpy,Blender 还包括几个独立的模块,例如aud用于音频,以及mathutils用于操作矩阵、欧拉、四元数和向量。

切换到 Scripting 选项卡,然后在 Text Editor 中单击 New 以创建新的 Python 脚本。

切换到 Scripting 选项卡并启动一个新脚本

将以下代码添加到新脚本中以导入bpy并打印场景中的对象列表:

import bpy
print(bpy.data.objects)

运行脚本(使用 Alt-P 或 ▶ 按钮),终端应显示:

<bpy_collection[3], BlendDataObjects>

会议一下,bpy_collection[3]部分表示存在三个对象——相机、立方体和灯光。如果你在场景中添加或删除任何内容,[3] 会变化以反映对象的总数。

列出相机、立方体和灯光的大纲视图

不过bpy.data.objects 还有更多的作用. 例如,你可以使用它来处理 Outliner 列表中的特定项目。

2、选择对象

在前面内容中,我们使用 Python 控制台来影响在 3D 视口中选择的对象。不过,更多时候,我们会希望通过 Python 脚本来处理对象,而不依赖于 GUI 中选择的内容。可以使用bpy按名称、对象在对象序列中的位置或某些其他属性来选择对象。如果使用的是bpy.context,则必须在 3D 视口中选择立方体(因此它是橙色的)以使用 Python 代码对其进行操作。使用bpy.data.objects,可以处理对象,而不管 Blender 界面中的活动是什么。

使用 Pythonlist()函数 处理bpy.data.objects 以打印场景中的对象列表:

print(list(bpy.data.objects))

运行脚本时,它应该在终端中显示以下输出:

[bpy.data.objects['Camera'], bpy.data.objects['Cube'], bpy.data.objects['Light']]

可以使用其键(项目名称)或索引(序列中的顺序)来寻址任何对象。索引值从零开始——所以 Camera 是 item 0, Cube 是 item 1,等等。可以使用bpy.data.objects['Cube']bpy.data.objects[1]来寻址多维数据集。接下来,我们将使用不同的属性和方法来操作多维数据集。

3、属性和方法

如果你熟悉任何面向对象的编程语言,那么之前就应该接触过属性和方法(到目前为止,你可能已经在代码中发现了一些这样的示例)。我不会回顾 Blender 脚本 API 中的每个属性和方法——太多太多了!相反,我将使用一些示例来帮助你发现可用的内容;其余的可以使用API 文档

属性就像属于对象的变量。对象的数据类型决定了它的属性。例如,立方体(由顶点组成的三维网格)包括其尺寸、坐标等属性。立方体的数据类型是 bpy_types.Object ,可以通过在终端或控制台中运行  type(bpy.data.objects['Cube'])  来确认这一点。

location属性(众多bpy_types.Object属性之一)包含立方体的坐标,可以使用它来重新定位对象:

bpy.data.objects['Cube'].location = (3, 0, 0)

这会将立方体定位在 xyz 坐标 (3, 0, 0)。在这种情况下,只会影响 x 坐标,因此可以使用:

bpy.data.objects['Cube'].location[0] = 3

请注意,.location[0](3, 0, 0)中第一个 (x) 值的索引。或者,可以尝试该.location.x属性,这可以说是最直观的可读版本:

bpy.data.objects['Cube'].location.x = 3

运行脚本,立方体应移动到新位置,沿 x 轴距离场景中心三个单位:

这个立方体的 x 坐标现在是 3

由于我们已启用 python tooltips,因此如果将鼠标指针悬停在“对象属性”面板中的任何字段上,则会有一个工具提示指示如何在 Python 中为该特定对象处理该特定属性。该面板通常位于 Blender 界面的右下方区域,位于布局和脚本工作区中。在下图 中,工具提示显示了立方体 x 坐标的 Python 详细信息:

立方体的 x 坐标存储在bpy.data.objects["Cube"].location[0]

如果右键单击此字段,则会有一个名为Online Python Reference的菜单选项,它会在 Web 浏览器中打开该location属性的相关文档:

location在线参考中的条目

记下浏览器地址栏中的 URL:https://docs.blender.org/api/2.83/bpy.types.Object.html#bpy.types.Object.location。尤其是最后一个斜线之后的内容。加载/bpy.types.Object.html网页,然后是#bpy.types.Object.location跳转/滚动到location条目。还可以使用左栏中的搜索功能和链接导航参考。有时使用你的网络浏览器搜索功能(Ctrl+FCmd+F)在给定页面上快速查找内容会很方便。如果想尝试其他属性,不妨试试scale.

现在已经使用了一些不同的属性,让我们看看方法。

方法就像属于对象的函数。它们执行操作——例如,bpy.ops.mesh模块包括几种用于创建新网格的方法。可以很容易地区分属性和方法,因为方法的末尾有一对括号。在本节中,我们将使用不同的方法在场景中添加和删除对象。

primitive_cone_add() 方法将构造一个圆锥网格,换句话说,可以使用此方法将圆锥体添加到场景中。将此新行添加到脚本的末尾:

bpy.ops.mesh.primitive_cone_add()

当运行代码时,这应该会在场景中添加一个新的锥体。

使用primitive_cone_add() 添加一个圆锥

primitive_cone_add()方法还可以接受参数来指定圆锥半径、深度、位置、旋转、比例等。在脚本中添加第二条锥形,其中包含一个控制其位置的参数:

bpy.ops.mesh.primitive_cone_add(location=(-3, 0, 0))

运行脚本时,会出现一个新圆锥。但是,如果检查 Outliner 面板,你会注意到场景中现在有三个圆锥体。额外的锥体是舞台中央的锥体的副本(相同大小,相同位置)。每次运行脚本时,你都会添加更多重复项!

Outliner 面板显示有三个圆锥体(虽然看起来好像有两个)

为防止发生这种重复,可以添加一个循环来检查并删除场景中任何已有的网格。不再需要立方体或其代码,因此无需替换它即可。最终脚本如下所示:

import bpy

# clear meshes in the scene
for obj in bpy.data.objects:
    if obj.type == 'MESH':
        bpy.data.objects.remove(obj)

# add two cones
bpy.ops.mesh.primitive_cone_add()
bpy.ops.mesh.primitive_cone_add(location=(-3, 0, 0))

remove()方法从场景中移除对象。现在,每次运行脚本时,它都会在再次添加之前清除已有的网格。

可以在API 文档中找到更多bpy.ops.mesh方法。

3、动画

在 Blender 中进行动画编程非常令人满意。可以创建结合了 Python API、强大的渲染器和模拟功能的令人惊叹的动态图像。请记住,这不是“实时渲染”方法。我们将预先定义关键帧并将它们完全渲染出来以生成形成动画的帧序列。这不是交互式的,就像你可能在Processing或某些游戏引擎中创建的那样。下面是一个基本示例,可帮助你开始使用基于现有脚本的多帧编程。

注意:Blender 曾经包含一个游戏引擎,该功能现已停止使用。然而,UPBGE(旧 Blender 游戏引擎项目的一个分支)和Armory现在旨在在 Blender 环境中提供完整的游戏开发解决方案。

将以下代码添加到脚本的末尾:

...
# animation variables
total_frames = 100
keyframe_interval = 10

# define a one hundred frame timeline
bpy.context.scene.frame_end = total_frames
bpy.context.scene.frame_start = 0

# add keyframes
for frame in range(0, total_frames + 1, keyframe_interval):
    bpy.context.scene.frame_set(frame)
    cone = bpy.data.objects['Cone']
    cone.location.x = frame / 25
    cone.keyframe_insert(data_path='location')

有注释行(以#开头)来帮助解释每个步骤。该循环在从第 0 帧到第 100 帧 ( total_frames) 的时间线上每 10 帧 (keyframe_interval ) 插入一个新的关键帧。圆锥在关键帧之间沿 x 轴前进 0.04 个单位;Blender 将插入/补间此运动以使其平滑。

为了帮助可视化正在发生的事情,我为Dope Sheet切换了 Console 面板(在 3D 视口下方的区域中) 。可以看到每个关键帧都表示为一个黄点。按空格键开始和停止动画;蓝色播放头线的位置指示正在播放的帧:

圆锥动画的Dope Sheet
注意:当使用空格键开始动画时,它会在到达结尾时循环播放。在编码时让它循环很方便,这样当重新运行脚本时,动画会自动刷新预览。

你可以决定关键帧间隔的大小。默认情况下,关键帧之间的移动是线性的——所以,实际上,这个动画只需要第 0 帧和第 100 帧上的关键帧。但我想演示如何使用循环添加更多关键帧。如果想调整动画曲线,可以使用Graph Editor进行调整。

4、波浪锥(或圆锥波浪?)

下面脚本结合了本教程中的所有技术来生成由圆锥体制成的波浪形图案。下面图像展示了我们努力的结果:

这是动画的代码:

import bpy
from math import sin, tau

# clear meshes in the scene
for obj in bpy.data.objects:
    if obj.type == 'MESH':
        bpy.data.objects.remove(obj)

# animation variables
total_frames = 150
theta = 0.0

# define a one hundred frame timeline
bpy.context.scene.frame_end = total_frames
bpy.context.scene.frame_start = 0

for x in range(30):
    # generate a grid of cones
    for y in range(30):
        cone = bpy.ops.mesh.primitive_cone_add()
        cone = bpy.context.object
        cone.name = 'Cone-{}-{}'.format(x, y)
        cone.location[0] = x * 2
        cone.location[1] = y * 2
        # add keyframes to each cone
        for frame in range(0, total_frames):
            bpy.context.scene.frame_set(frame)
            cone.location.z = sin(theta + x) * 2 - 1
            cone.keyframe_insert(data_path='location')
            scale = sin(theta + y)
            cone.scale = (scale, scale, scale)
            cone.keyframe_insert(data_path='scale')
            theta += tau / total_frames

在这种情况下,我没有使用关键帧间隔。正如Dope Sheet所示,150 帧中的每一帧都有一个单独的关键帧。我使用了一个sin()函数来生成正弦波运动;必须从math库中导入这个函数,以及tau(等于 2π)。运行脚本可能需要一段时间,具体取决于计算机的能力。可以减少每个range()函数中的30参数以加快速度。

每一帧都有自己的关键帧

现在可以渲染动画了。我不在这里介绍如何执行此操作,但你可以参考Blender 手册以获得指导。我还添加了一些光照和材质效果,因此在上图中出现了白色背景上的黑色锥体。我使用GIMP将帧 (PNG) 编译成循环的 GIF 动画。

5、关于导入的更多信息

关于导入非 Blender 模块,我们应该了解一些问题,尤其是当你将脚本拆分为多个 Python 文件或在项目中使用第 3 方包(库和模块)时。

你必须使用importlib.reload() 重新加载你导入的任何 Python 代码。例如,这是一个名为bar.py的文件,其中包含一个greeting变量:

#bar.py
greeting = 'Hello, World!'

假设我将bar导入到名为foo.py的主脚本中,以便我可以使用greeting值。这是foo.py代码:

#foo.py
import bpy, os, sys, importlib
dir = os.path.dirname(bpy.data.filepath)
sys.path.append(dir)

import bar
importlib.reload(bar)
print(bar.greeting)

我的 .blend)文件、foo.pybar.py都保存在同一个文件夹中;dir变量指向该位置。dir路径附加到 sys.path以便我可以导入该文件夹中的任何 python 文件。如果我更改bar.py中 greeting的值,该行将打印更新后的值——仅仅是因为我包含了print()importlib.reload(bar) 这一行。

可以使用 pip(事实上的 Python 包安装程序)来管理第三方包,但应该使用 Blender 附带的版本。这是安装cowsay包的演示。

首先,打开终端并进入 Blender 安装目录,然后进入其中的2.83目录(或任何适用的版本号)。现在依次输入以下命令:

python/bin/python3.7m python/lib/python3.7/ensurepip
python/bin/pip3 install cowsay --target python/lib/python3.7/

在 Windows 上,需要输入:

python\bin\python.exe python\lib\ensurepip
python\bin\python.exe -m pip install cowsay --target python\lib

可以使用python/bin/pip3 list(Windows: python\bin\python.exe -m pip list )列出已安装的软件包。终端应显示如下内容:

Package    Version
---------- -------
cowsay     2.0.3
pip        19.0.3
setuptools 40.8.0

如果想知道 cowsay 是做什么的,可以使用以下代码运行一个新的 Blender 脚本:

import cowsay
cowsay.cow('Blender is rad!')

…这将在终端打印一头会说话的牛:

  _______________
< Blender is rad! >
  ===============
                   \
                    \
                      ^__^
                      (oo)\_______
                      (__)\       )\/\
                          ||----w |
                          ||     ||

6、使用其他代码编辑器

你可能更喜欢另一个不同的编辑器。这很简单。保存正在处理的 Blender 脚本(带有 .py 扩展名),然后在你首选的代码编辑器中打开它。我在这个例子中使用了Atom,如下图所示。我已经编辑了print()参数并保存了更改,这会提示 Blender 脚本编辑器显示一个解决冲突 按钮(封面上带有问号的红色书本图标):

解决冲突的图标以绿色突出显示

如果单击该按钮,则会提示从磁盘重新加载选项;这将更新 Blender 的脚本编辑器以反映我们在外部编辑器(Atom?)中所做的更改。还有一个Make text internal选项,它将脚本的 Blender 版本保存在 .blend 文件中(连同模型、材质和场景数据)。我更喜欢将 Python 脚本存储在单独的文件中,以便我可以选择使用外部工具处理代码并随意切换脚本。

也可以直接从命令行执行脚本,而无需完全启动 Blender。但是,必须添加一些代码来呈现输出/视觉结果:

# foo.py
import bpy
bpy.ops.mesh.primitive_cone_add(location=(-3, 0, 0))
output = '/home/nuc/Desktop/render.png'
bpy.context.scene.render.filepath = output
bpy.ops.render.render(write_still=True)

该脚本将一个圆锥体添加到标准场景中,然后将其渲染到一个名为render.png(在我的桌面上)的文件中。请注意,我还将脚本保存到我的桌面。要运行它,进入到我的桌面目录并执行以下命令:

<blender_dir>/blender --background --python foo.py

当然,需要用你的blender安装路径替换<blender_dir>。我用图像查看器打开输出文件 ( render.png )。如果图像文件更新,大多数查看器都会刷新,因此每次执行blender命令时,我都会看到新的渲染:

注意:可以按向上箭头键重复终端中的任何命令。
使用图像查看器 ( gThumb ) 和 Atom运行 Blender 'headless'

更好的是,可以另外配置代码编辑器以使用键盘快捷键(而不是终端)运行此命令。

如果想要一个包含一些可以操作的数据的场景,请将 .blend 文件添加到命令中:

<blender_dir>/blender bar.blend --background --python foo.py

Blender 网站为编写代码提供了更多提示和技巧。

7、结束语

在这个简短的教程中,我介绍了一些 Blender 脚本基础——用于单行的 Python 控制台、可以设置以启用 Python 工具提示和开发人员附加功能的选项、bpy模块及其一些属性和方法,以及如何编写动画. 我还介绍了一些从其他文件导入 Python 代码和使用外部代码编辑器的技术。

但是在 Blender 中进行创意编码有很多值得探索的地方。可以在 GUI 中执行的任何操作,几乎都可以使用 Python 代码进行复现,然后使用你设计的算法更进一步。Blender 的着色器节点系统非常适合使用可视化脚本语言创建着色器。

如果你正在寻找很酷的 Blender 脚本,可以尝试以下 GitHub 主题搜索:blender-pythonblender-scripts


原文链接:A Quick Intro to Blender Creative Coding

BimAnt翻译整理,转载请标明出处