Blender 3D: 融入 Python/优化
如果您刚开始编写脚本,不要过于重视这些指南,最好先让代码运行起来,然后在您开始寻找优化区域之前,发现对优化的需求。
如果您正在为其他人制作工具,请记住他们可能会使用比您测试的更大的数据集。因此,在发布之前,最好尝试优化一下。
有关更通用的 Python 性能提示,请参阅 http://wiki.python.org/moin/PythonSpeed/PerformanceTips
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 函数。
从 Blender 2.37 开始nmesh.transform(ob.matrix)可用于通过 4x4 变换矩阵变换 NMesh。
在我的系统上,它比在 python 中通过矩阵变换每个顶点的坐标和法线快 100 多倍。虽然在我的 obj 导出器中使用它所获得的整体速度约为 5%。
确保如果您要导出顶点法线,请添加 1,
mesh.transform(matrix, recalc_normals=1)这将避免不必要的顶点法线变换。
对于小型应用程序,您可以忽略这一点,但我发现对于大型场景,如果早期没有考虑到,这可能会成为问题。
Python 中的列表(以及因此 blender:python)永远不会被释放,但 python 可以在脚本运行时重新使用内存。基本上,在 python 中创建大型列表和大量递归变量赋值会泄漏内存,只有通过重新启动才能重新获得。 - *如果您能 - 避免创建大型列表。*这在 Python 2.4.1 中已修复,该版本将与 blender 编译在一起。
在 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 是一个包含大量浮点数的大型列表,则复制可能会占用大量额外的内存。
当您访问字典项时,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 ')
以下列出了将多个字符串连接成一个字符串以供写入的 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 在处理多个字符串时速度最快,字符串格式化速度也很快(更适合转换数据类型)。 字符串运算速度最慢。
只需对代码的部分进行计时,就可以找到需要优化的部分。 例如
time1 = Blender.sys.time() for i in range(10): x = i*2 print Blender.sys.time() - time1
存在一个名为 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 函数有助于节省编写代码以检查条件的时间,
但是 'try' 比 'if' 慢大约 10 倍,因此不要在代码中执行多次循环(1000 次或更多次)的区域使用 'try'。
在某些情况下,使用 'try' 比检查条件是否会引发错误更快,因此值得尝试一下。
在某些情况下,可以使用 "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