跳转至内容

Blender 3D:新手到专业/高级教程/Python脚本/导出脚本

来自Wikibooks,开放世界中的开放书籍

此页面是最初为Blender 2.44构建的优秀教程的更新。

Blender不仅可用于创建完整的动画,它还是一个很棒的建模器。你可以在Blender中构建完整的3D场景,然后将其导出为有用的格式。事实上,你可以用它做更多的事情,例如我将其用作其他人制作的免费2D游戏的关卡编辑器。游戏需要在短时间内完成,但在截止日期前的两周,仍然没有它的关卡编辑器。它有一个自定义的ASCII关卡格式,由材料、顶点、三角形和对象的列表组成。因此,我记起了Blender Python导出器,自愿编写了一个Blender导出脚本,以便可以用作关卡编辑器。结果非常好,Blender现在可以完全用作该游戏的关卡编辑器。

在本教程中,我们将学习如何为Blender编写一个简单的Python导出脚本。无需任何Python基础知识,它将解释如何查询场景中的对象,以及如何将它们写入文件。它还将通过展示如何在导出时处理数据来演示导出脚本的实用性,从而实现使用任何其他现有格式都无法实现的功能。

因此,打开Blender,确保加载默认场景,然后开始吧…

了解场景中的事物

[编辑 | 编辑源代码]
Blender259 EditorSelector
Blender259编辑器选择器

在我们可以导出任何东西之前,我们必须知道要导出什么。获取此信息的一种方法是“大纲”窗口(SHIFT-F9)。它将列出Blender当前知道的所有内容。现在,我们希望从脚本中获取相同的信息。点击编辑器选择按钮,然后从弹出菜单中选择Python控制台

现在,你已准备好迎接激动人心的时刻,你即将执行第一个Blender脚本命令。键入以下内容并按回车键(或者你可以在脚本窗口的顶部输入导入Blender,然后在它下面的这些行中,在所有“dir(x)”行之前加上print,并选择文件->执行)

list(bpy.data.objects)

结果,你应该会看到如下内容

[bpy.data.objects["Camera"], bpy.data.objects["Cube"], bpy.data.objects["Lamp"]]

现在,刚刚发生了什么?在“list(bpy.data.objects)”中赋予“list”的变量由三个单词组成,用两个点隔开。点将不同的东西分隔开来。第一个,bpy,表示使用来自bpy模块的函数。data是Blender的子模块。最后,objects是bpy.data.的迭代器。list()函数用于循环遍历bpy.data.objects中的所有数据,并将其作为所有可用对象的列表返回。在我们的例子中,这是一个相机、一个立方体和一个灯。

要获取有关对象的更多信息,可以使用对象名称作为bpy.data.objects中的键,并将其分配给变量,如下所示

camera = bpy.data.objects["Camera"]
cube = bpy.data.objects["Cube"]
lamp = bpy.data.objects["Lamp"]

我们刚刚将三个对象分配给了三个变量:camera、cube和lamp。要查看变量的内容,只需键入其名称即可

cube 
bpy.data.objects['Cube']
camera 
bpy.data.objects['Camera']
lamp 
bpy.data.objects['Lamp']

有时使用Python的dir()函数获取有关对象的更多信息会很有用。例如

dir(cube)

将写入对象的所有函数和属性的名称。相当多。但不用担心,很快你就会知道如何使用它们。你可能还想找出某物的类型,你可以这样做

type(cube)

在这种情况下,只需键入“cube”即可显示类型,但在实际脚本中,你将使用type()。另一个可能有用的功能是查看Python对象的文档。为此,在变量或对象上使用help()函数。

help(bpy.data.objects)

这将打印我们使用的bpy.data.objects函数的文档。当然,查看文档的更简单方法是使用在线HTML帮助。点击帮助->Python API参考。希望现在你的浏览器打开并显示Blender Python API的在线文档。如果没有,你也可以在这里找到它

http://www.blender.org/documentation/blender_python_api_2_59_2/

在文档中,点击bpy,然后点击data,你可以看到更多示例。当你需要在脚本中执行教程中未涉及的操作时,使用文档将变得绝对至关重要。你将需要这样做,否则你根本不想学习脚本编写。

根据你在脚本编写方面走多远,你将需要另一个资源,即Python参考

https://docs.pythonlang.cn/

对于本教程,也许可以阅读python文档中的“教程”部分,但即使不阅读,你也能理解所有内容。

现在,让我们尝试更多地了解我们的立方体。键入

cube.type

它将告诉我们立方体实际上是Blender中的网格对象。在在线文档中查找“type”。由于变量cube保存了一个对象,而“type”是该对象的属性,因此点击对象。在那里,你会找到它的“type”。

现在我们知道立方体是网格,让我们进一步了解网格。

cubedata = bpy.data["Cube"]

'注意:在Blender 2.6.0中,脚本命令实际上是这样的,但上述命令仍然有效:(Chronus001)

'注意:在Blender 2.6.5中,上述命令不再有效

cubedata = bpy.data.meshes['Cube']

每个Blender对象都分配有数据,具体取决于类型。对于网格,数据类型为Mesh。在文档中,再次回到顶部,查找Mesh模块。它将包含Mesh类型的文档。你还可以尝试

dir(cubedata)

以了解可用的函数和属性。试试这些

list(cubedata.vertices)
list(cubedata.faces)

第一行将列出立方体网格的8个顶点。第二行将列出它的6个面。


注意:在Blender 2.77(以及可能接下来的版本中),cubedata.faces成员已被cubedata.polygons替换。


要获取面的数量(或多边形),以下命令可以完成任务 

print(len(cubedata.faces))

或者

print(len(cubedata.polygons))


要从列表中获取成员,你需要在方括号中指定索引,从0开始。所以

v = cubedata.vertices[0]

这将把立方体的第一个顶点分配给变量v。到目前为止,你已经知道如何使用dir()获取v中可能有趣的事物的列表,使用type()了解其类型,以及在哪里查找API文档。它在Blender/Mesh模块中,当你点击“类”下的一个“MVert”时。

v.co

这将显示第一个顶点的3D坐标。现在,如果我们想知道所有顶点的坐标怎么办?当然,我们可以将它们全部分配给一个变量,但真正的做法是使用循环结构。有很多方法可以做到这一点,但一种简单的方法如下所示

for v in cubedata.vertices: print(v.co)

for variable in list:结构将列表的每个元素依次分配给变量,然后执行冒号后面的命令,变量具有特定列表元素的值。在实际脚本中,冒号后面会有不止一条命令 - 因此你将在接下来的行中编写它们。

到目前为止,你应该已经了解了足够的内容,可以在下一节中尝试编写真正的脚本。

创建脚本

[编辑 | 编辑源代码]

你可以在外部文本编辑器或Blender内置的文本编辑器中编写脚本。移动到要更改为文本编辑器的面板,点击编辑器选择按钮并选择“文本编辑器”(快捷键)SHIFT+F11。点击底部的+新建按钮。如果需要,你可以使用底部的按钮启用行号和语法着色。使用文件新建创建一个新脚本,将下面的代码粘贴到其中,并保存。或者,将下面的代码粘贴到文件中,然后在Blender中使用文件打开打开该文件。作为名称,选择以.py为扩展名的名称,例如wikibooks.py。将其放入Blender的用户脚本路径。

对于不同的操作系统,路径如下:

  • Linux:~/.blender/scripts
  • Windows XP:C:\Program Files\Blender Foundation\Blender\.blender\scripts
  • Windows XP(备选):C:\Documents and Settings\USERNAME\Application Data\Blender Foundation\Blender\.blender\scripts


  • Windows Vista:C:\Users\USERNAME\AppData\Roaming\Blender Foundation\Blender\.blender\scripts
  • Mac OS X
    • 在Mac OSX下,路径实际上隐藏在blender.app中,因此要了解路径,你必须知道脚本文件夹实际上隐藏在blender.app本身中。假设Blender位于应用程序目录中,则路径为“/Applications/blender/blender.app/Contents/MacOS/.blender/scripts”如果你尝试从Finder中打开.app内容,你会注意到路径的.blender部分不可见,但Blender仍然可以导航到此文件夹。

    • 右键点击(或按住ctrl键并点击)文件“blender”,然后在弹出菜单中选择“显示包内容”。它将显示blender文件夹下所有隐藏的文件,并在其中选择“scripts”文件夹。
    • 要从OSX终端查看此文件夹,请在列出的路径的MacOS文件夹中使用ls -a命令(列出所有文件夹/文件,即使是隐藏的)。最好在“/Applications/blender-2.37a-OSX-10.3-powerpc”文件夹中为scripts文件夹创建一个别名,以便可以通过Finder轻松操作脚本。我知道Blender的脚本文件夹应该埋藏在应用程序内部这一点令人困惑,但这是为了保持应用程序的可移植性并且不需要安装。
    • 比上述方法更安全的方法是将您的脚本保存在您的主文件夹中的某个位置:使用此方案,当您升级blender应用程序时,不会存在删除脚本的风险,因为它们不包含在应用程序文件夹中。遵循此原则的一种方法如下:在您自己的主目录中创建一个将包含您的脚本(或其中一些脚本)的文件夹;然后,而不是将您的文件直接放在上面讨论的.../.blender/scripts/文件夹中,只需在.../.blender/scripts/文件夹中添加一个指向您的脚本目录的链接(例如使用“ln -s”Unix命令,或通过执行“open /Applications/blender-2.37a-OSX-10.3-powerpc/blender.app/Contents/MacOS/.blender/scripts/”[根据您的blender版本调整],然后通过Finder创建链接,使用文件->创建别名)。Blender现在将找到您放在主目录中的所有脚本:它将遵循您在其.../.blender/scripts/文件夹中创建的链接,转到您自己目录中的相应文件夹,并找到您放置在那里的所有python脚本。

注意:对于2.78+版本,此脚本头部已弃用。更多信息:[[1]] Blender 2.78 - 附加组件教程

#!BPY

"""
Name: 'Wikibooks'
Blender: 259
Group: 'Export'
Tooltip: 'Wikibooks sample exporter'
"""
import Blender
import bpy

def write(filename):
    out = open(filename, "w")
    sce= bpy.data.scenes.active
    for ob in sce.objects:
        out.write(ob.type + ": " + ob.name + "\n")
    out.close()

Blender.Window.FileSelector(write, "Export")

现在,返回脚本窗口,并在其菜单中点击脚本更新菜单。如果您将其保存到正确的路径,从现在开始,文件导出菜单中应该有一个“Wikibooks”条目。尝试使用它导出任何场景。它应该打开文件选择器对话框,在您选择一个文件并按下“导出”按钮后,将场景中所有对象的列表写入其中。每行一个对象,包含类型,后跟冒号和名称。

它是如何工作的?如果您查看脚本,您可能已经知道了。但以防万一,让我们逐行查看脚本。

#!BPY

它告诉Blender这是一个Blender脚本,因此它在扫描脚本时会考虑它。接下来简单地跟随一个字符串,用三个引号括起来,因此它可以跨越多行。

"""
Name: 'Wikibooks'
Blender: 259
Group: 'Export'
Tooltip: 'Wikibooks sample exporter'
"""

它包含四个项目,Blender使用它们将脚本放置到其菜单中。名称、组(子菜单名称)和工具提示,都用单引号括起来。以及此版本的Blender。请注意,组名称必须是Blender预定义的名称之一(检查其脚本菜单中的子菜单以查看有效名称);如果Blender无法识别组名称,则脚本将放在“Misc”子菜单中。

import Blender
import bpy

还记得我们说过bpy模块中的所有函数都以“Blender.”开头吗?在交互式shell中,我们可以简单地使用它们,但在python脚本中,所有使用的模块都必须使用import语句声明(如果您想在脚本中直接使用Blender模块中的函数,您可以简单地将上面的import语句替换为“from Blender import *”:不再需要“Blender.”前缀;但是,这会减慢脚本的加载速度)。因此,以上只是允许我们在脚本中使用Blender模块中的函数。

bpy模块是新的,将替换Blender用于数据访问。

def write(filename):

这在Python中定义了一个函数。语法是def name(parameters):。在我们的例子中,名称是“write”,我们有一个参数,称为“filename”。

    out = open(filename, "w")

在这里,我们打开一个用于写入的文件(“w”),其名称传递给函数(filename)。python函数“open”将打开文件,并返回对它的引用,我们将其存储在变量“out”中。

   sce= bpy.data.scenes.active
   for ob in sce.objects:
       out.write(ob.type + ": " + ob.name + "\n")

这三行是我们真正的导出脚本。您已经知道第一行做了什么——首先我们获取当前场景,然后获取该场景中所有对象的列表,for循环依次将每个对象分配给变量“ob”。第二行写入文件——首先是对象的类型,然后是字符串“: ”,然后是对象的名称,最后是一个换行符。

Blender.Window.FileSelector(write, "Export")

这是脚本开始执行的地方。它只是调用一个Blender函数(在API文档中查找),它打开文件选择器。它将显示一个“导出”按钮,当用户点击它时,我们上面定义的函数“write”将被调用并传递选定的文件名。

这个脚本目前还没有什么用,但它展示了基本知识。您现在应该能够例如列出场景中的所有材质。(提示:它们就像对象一样,尝试在API文档中找到它们。)

在下一节中,我们将学习如何将有关对象的更多信息导出到我们的文本文件。

导出网格

[编辑 | 编辑源代码]

我们的导出脚本列出了每个对象的类型和名称,但这还没有什么用。如果我们想在另一个应用程序中加载导出的数据,我们需要更多信息。让我们尝试以OBJ格式导出网格对象。

下面的示例是在OBJ文件格式中的立方体。

v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000001 1.000000 1.000000
v 0.999999 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
f 1 2 3 4
f 5 8 7 6
f 1 5 6 2
f 2 6 7 3
f 3 7 8 4
f 5 1 4 8

这是一个简单的obj导出脚本,它导出选定的网格对象,用于导出上面的OBJ文件。

import Blender
import bpy

def write_obj(filepath):
	out = file(filepath, 'w')
	sce = bpy.data.scenes.active
	ob = sce.objects.active
	mesh = ob.getData(mesh=1)
	for vert in mesh.verts:
		out.write( 'v %f %f %f\n' % (vert.co.x, vert.co.y, vert.co.z) )
	
	for face in mesh.faces:
		out.write('f')
		
		for vert in face.v:
			out.write( ' %i' % (vert.index + 1) )
		out.write('\n')
	out.close()
Blender.Window.FileSelector(write_obj, "Export")

此脚本将导出一个可以被许多应用程序读取的OBJ文件。让我们看看发生了什么。

	sce = bpy.data.scenes.active
	ob = sce.objects.active

在这里,我们获取您在当前场景中最后选择的物体。如果没有任何选定的物体,这将引发错误,但这是一种测试新导出器的简单方法。

	mesh = ob.getData(mesh=1)

这获取了对象的链接数据块。目前我们不知道它是一个网格,另一个需要添加错误检查的情况。

	for vert in mesh.verts:
		out.write( 'v %f %f %f\n' % (vert.co.x, vert.co.y, vert.co.z) )

在这里,我们为每个顶点写一行,使用字符串格式化将左边的“%f”替换为右边的3个值。

	for face in mesh.faces:
		out.write('f')
		
		for vert in face.v:
			out.write( ' %i' % (vert.index + 1) )
		out.write('\n')

在OBJ格式中,每个面都引用了许多顶点索引。对于每个面,我们有一行以“f”开头,然后遍历面中的顶点。就像mesh.verts是网格中所有顶点的列表一样,face.v是面中顶点的列表,最多4个顶点。(其中mesh和face是分配给Mesh和MFace对象的任意变量名)每个顶点都在同一行上写入其索引,并加1。这是因为在OBJ文件格式中,第一个顶点的索引为1,而在Python和Blender中,列表中的第一个项目的索引为0。

写入新的一行,以便下一个面从新的一行开始。——在python中,“\n”表示写入文件时换行。

华夏公益教科书