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