跳到内容

Blender 3D: 融入 Python/优化

来自维基教科书,开放世界中的开放书籍

为 Blender:Python 编写最佳导出器和导入器

[编辑 | 编辑源代码]

如果您刚开始编写脚本,不要过于重视这些指南,最好先让代码运行起来,然后在您开始寻找优化区域之前,发现对优化的需求。

如果您正在为其他人制作工具,请记住他们可能会使用比您测试的更大的数据集。因此,在发布之前,最好尝试优化一下。

有关更通用的 Python 性能提示,请参阅 http://wiki.python.org/moin/PythonSpeed/PerformanceTips

获取对象(导出/一般 Blender)

[编辑 | 编辑源代码]

Blender.Object.Get()通常用于获取所有要导出的对象。这是不好的做法,因为Object.Get()将返回 Blender 中每个场景中的对象,这几乎永远不是用户想要的,并且会导致数据重叠,但如果用户拥有 2 个或更多个大型场景,消耗大量内存,也会花费很长时间。

相反,使用Blender.Scene.GetCurrent().getChildren()这将返回当前场景中的所有对象。

另一种选择是使用Blender.Object.GetSelected()这将返回当前场景中可见层上的选定对象。

获取网格数据(仅导出)

[编辑 | 编辑源代码]

网格不仅仅是像 Blender 中大多数其他类型那样的薄包装器,因此您想要避免meshObject.getData()meshObject.data尽可能多地,最好只调用每个网格数据一次。

要获取对象引用的数据的名称,请不要使用 ob.getData().name 只是为了获取对象的名称。相反,使用obj.getData(1)…这意味着obj.getData(1=name_only)这很好,因为它适用于所有数据类型,只是

请注意,最近(截至 15/10/05)添加了 Mesh 模块(Blender 网格数据的薄包装器),这意味着您可以执行meshObject.getData(mesh=1)不会出现 NMesh 问题,但这是新的,不支持所有 NMesh 函数。

使用新的 python 功能 mesh.transform()(仅导出)

[编辑 | 编辑源代码]

从 Blender 2.37 开始nmesh.transform(ob.matrix)可用于通过 4x4 变换矩阵变换 NMesh。
在我的系统上,它比在 python 中通过矩阵变换每个顶点的坐标和法线快 100 多倍。虽然在我的 obj 导出器中使用它所获得的整体速度约为 5%。

确保如果您要导出顶点法线,请添加 1,
mesh.transform(matrix, recalc_normals=1)这将避免不必要的顶点法线变换。

注意 python 占用大量内存。(一般情况下)

[编辑 | 编辑源代码]

对于小型应用程序,您可以忽略这一点,但我发现对于大型场景,如果早期没有考虑到,这可能会成为问题。


Python 中的列表(以及因此 blender:python)永远不会被释放,但 python 可以在脚本运行时重新使用内存。基本上,在 python 中创建大型列表和大量递归变量赋值会泄漏内存,只有通过重新启动才能重新获得。 - *如果您能 - 避免创建大型列表。*

这在 Python 2.4.1 中已修复,该版本将与 blender 编译在一起。

列表(Python 通用)

[编辑 | 编辑源代码]

列表查找

[编辑 | 编辑源代码]

在 Python 中,有一些巧妙的列表函数可以帮助您避免搜索整个列表。
即使您没有循环遍历列表数据,python 也会,因此您需要注意会通过搜索整个列表来减慢脚本速度的函数。

myList.count(listItem)
myList.index(listItem)
myList.remove(listItem)
if listItem in myList: ...

上述函数对列表执行完整的循环,因此,如果您能够避免在大型列表上使用它们,脚本将运行得更快。

修改列表

[编辑 | 编辑源代码]

在 python 中,我们可以向列表添加和删除项目。当列表在列表开头修改长度时,这会比较慢,因为修改后的索引之后的所有数据都需要向上或向下移动 1 个位置。

当然,向列表末尾添加项目的简单方法是使用myList.append(listItem)myList.extend(someList)删除项目的最快方法是myList.pop()

要使用索引,您可以使用myList.insert(index, listItem)而 pop 也接受索引进行列表删除,但这些操作速度较慢。

有时,重新构建列表速度更快(但更占内存)。
假设我们要从列表中删除所有三角形面。

而不是

fIdx = len(mesh.faces) # Loop backwards
while fIdx: # While the value is not 0
	fIDx -=1

	if len(mesh.faces[fIDx].v) == 3:
		mesh.pop(fIdx) # Remove the tri

使用列表推导构建新列表速度更快。

mesh.faces = [f for f in mesh.faces if len(f.v) != 3]

添加列表项

[编辑 | 编辑源代码]

如果您有一个要添加到另一个列表的列表,而不是在 for 循环中追加,请使用

myList.extend([li1, li2...])

而不是...

for l in someList:
	myList.append(l)

请注意,insert 可以在需要时使用,但它比 append 速度慢,尤其是在向长列表开头插入时。
此示例显示了一种创建反转列表的非常次优方法。

for l in someList:
	myList.insert(0,l)


删除列表项

[编辑 | 编辑源代码]

使用myList.pop(i)而不是myList.remove(listItem)
这要求您拥有列表项的索引,但速度更快,因为 remove 需要搜索列表。
使用 pop 删除列表项时,循环应优先选择 while 循环而不是 for 循环。

以下是如何在一次循环中删除项的示例,先删除最后一个项,这样更快(如上所述)。

listIndex = len(myList)
while listIndex:
	listIndex -=1
	if myList[listIndex].someTestProperty == 1:
		myList.pop(listIndex)


一种快速删除项的方法是交换两个列表项,以便要删除的项始终在最后,这样可以打乱列表顺序。

popIndex = 5

# Swap so the popIndex is last.
myList[-1], myList[popIndex] = myList[popIndex], myList[-1]

# Remove last item (popIndex)
myList.pop()

在大型列表中删除多个项时,这可以提高速度。

避免复制列表

[编辑 | 编辑源代码]

将列表/字典传递给函数时,最好让函数修改列表,而不是返回一个新列表。 这意味着 Python 不需要在内存中创建新列表。
就地修改列表的函数比创建新列表的函数更有效。
normalize(vec)更快:没有重新赋值...比...快。
vec = normalize(vec)更慢,仅用于创建新的、未链接列表的函数。

还要注意,传递切片列表会在 Python 内存中创建列表的副本,例如...
foobar(mylist[4:-1])
如果 mylist 是一个包含大量浮点数的大型列表,则复制可能会占用大量额外的内存。

字典(Python 通用)

[编辑 | 编辑源代码]

字典查找

[编辑 | 编辑源代码]

当您访问字典项时,someDict['foo'] 会执行查找操作,Python 的查找速度非常快,但如果您在循环中访问这些数据,最好创建一个可以更快引用的变量。

for key in someDict.keys():
...wip

字符串

[编辑 | 编辑源代码]

路径名(导出)

[编辑 | 编辑源代码]

Blender 路径名与其他通用 Python 模块中的路径名不兼容。
Python 的原生函数不理解 Blender 的 //(代表 Blender 中的当前文件目录)和 #(代表当前帧号)。
一个常见的示例是导出场景时需要将图像路径一起导出。
Blender.sys.expandpath(img.filename)
将为您提供绝对路径,因此您可以将该路径传递给其他 Python 模块中的函数。

正确的字符串解析(导入/导出)

[编辑 | 编辑源代码]

由于许多文件格式是 ASCII 格式,因此您解析/导出字符串的方式会对程序运行速度产生很大影响。
将字符串导入 Blender 时,有几种解析字符串的方法。

传递字符串

[编辑 | 编辑源代码]

使用 float(string) 而不是 eval(string),如果您知道该值将是整数,则可以使用 int(string),float() 也适用于整数,但如果整数被转换为整数,则速度更快。

检查字符串开头/结尾

[编辑 | 编辑源代码]

如果您要检查字符串开头是否有关键字,请使用...

if line.startswith('vert '):

...而不是

if line[0:5] == 'vert ':


使用 Startswith 速度略快(大约 5%)。
myString.endswith('foobar') 也可以用于行尾。
此外,如果您不确定文本是使用大写还是小写,请使用 lower 或 upper 字符串函数。 例如:if line.upper().startswith('VERT ')

将字符串写入文件(Python 通用)

[编辑 | 编辑源代码]

以下列出了将多个字符串连接成一个字符串以供写入的 3 种方法
这实际上适用于代码中任何涉及大量字符串连接的部分。

Python 的字符串加法。 如果可以避免,请不要使用,尤其是在主要的 数据写入循环中。

file.write(str1 + ' ' + str2 + ' ' + str3 + '\n')


字符串格式化。 当您从浮点数和整数写入字符串数据时,请使用此方法。

file.write('%s %s %s\n' % (str1, str2, str3))


Python 的字符串连接函数。 用于连接字符串列表。

file.write(' '.join([str1, str2, str3, '\n']))


join 在处理多个字符串时速度最快,字符串格式化速度也很快(更适合转换数据类型)。 字符串运算速度最慢。

分析您的代码(Python 通用)

[编辑 | 编辑源代码]

只需对代码的部分进行计时,就可以找到需要优化的部分。 例如

time1 = Blender.sys.time()

for i in range(10):
	x = i*2

print Blender.sys.time() - time1

Psyco JIT 编译器(Python 通用)

[编辑 | 编辑源代码]

存在一个名为 Psyco 的 Python 模块,可以动态编译一些 Python 函数。 大多数速度提升都会影响用 Python 编写的算法,因此导入器和导出器比处理 3D 数学的脚本从这些算法中获得的收益要少。

对于许多脚本,使用 Psyco 的最简单方法是执行以下操作。

import psyco
psyco.full()

Psyco 还可以分析您的代码。

import psyco
psyco.profile()


为了方便脚本分发,最好尝试加载 Psyco,以避免引发错误。

try:
	import psyco
	psyco.full()
except:
	print 'For optimal performance on an x86 CPU, install psyco'

注意,Psyco 报告使用大量系统内存,如果内存不足,最好不要使用 Psyco。

谨慎使用 Try/Except

[编辑 | 编辑源代码]

try 函数有助于节省编写代码以检查条件的时间,
但是 'try' 比 'if' 慢大约 10 倍,因此不要在代码中执行多次循环(1000 次或更多次)的区域使用 'try'。

在某些情况下,使用 'try' 比检查条件是否会引发错误更快,因此值得尝试一下。

Python 对象

[编辑 | 编辑源代码]

使用 "is" 而不是 "=="

[编辑 | 编辑源代码]

在某些情况下,可以使用 "is" 而不是 "==" 进行比较,使用 "is not" 而不是 "!="。
"==" 检查两个变量的值是否相同,而 "is" 则测试 Python 对象是否相同,是否共享相同的内存——是否都是同一个对象的实例。(Python 对象,而不是 Blender 的对象)
使用 "is" 的优势在于速度更快,因为它不需要比较那么多数据。

以下是一个基准测试,比较 Python 类和 Blender 向量——一个是相同的,另一个是不同的。

from Blender import *

print '\n\nStarting benchmark.'

# Test class
class a:
        pass

class1 = a()
class2 = a()
vec1 = Mathutils.Vector(1,2,3)
vec2 = Mathutils.Vector(1,2,4)

f = 400000

t = sys.time()
for i in range(f):
        class1 is class1
        class1 is class2
        vec1 is vec1
        vec1 is vec2
print ' Is Banchmark %.6f' % (sys.time() - t)


t = sys.time()
for i in range(f):
        class1 == class1
        class1 == class2
        vec1 == vec1
        vec1 == vec2

print ' == Benchmark %.6f' % (sys.time() - t)
print 'Done\n'

我的电脑显示了以下结果。 我应该进行更多测试,但这足以代表其他测试。

Starting benchmark.
 Is Banchmark 0.284363
 == Benchmark 3.367173
Done


使用 vec1、vec2、class1、class2 作为整数和浮点数进行相同的测试。
"is" 仍然更快。

Starting benchmark.
 Is Banchmark 0.272853
 == Benchmark 0.322123
Done


华夏公益教科书