Blender 3D:融入 Python/菜譜
這在 Linux (可能任何 Unix) 中執行,並啟動 The Gimp。它可能可以修改為在 Windows 中啟動 Photoshop。
在 Gnome、KDE 和 Mac OS X 中,您可以使用命令使用預設應用程式或指定應用程式開啟文件。
- KDE:kfmclient openURL <URL,相對路徑或絕對路徑>
- Gnome:gnome-open <Gnome 理解的任何 URL 或路徑>
- Mac OS X:open [-a <應用程式名稱>] <路徑>
Win32 也有一些 open 命令,也許有人可以新增它。
#!BPY
"""
Name: 'Edit Image in the Gimp'
Blender: 232
Group: 'UV'
Tooltip: 'Edit Image in the Gimp.'
"""
from Blender import *
import os
def main():
image = Image.GetCurrent()
if not image: # Image is None
print 'ERROR: You must select an active Image.'
return
imageFileName = sys.expandpath( image.filename )
#appstring = 'xnview "%f"'
#appstring = 'gqview "%f"'
appstring = 'gimp-remote "%f"'
# -------------------------------
appstring = appstring.replace('%f', imageFileName)
os.system(appstring)
if __name__ == '__main__':
main()
此指令碼遞迴地搜尋具有損壞檔案引用的影像。
它透過向用戶提供根路徑來工作,然後查詢並重新連結該路徑內的所有影像。
在將專案遷移到不同的計算機時,它非常有用。
#!BPY
"""
Name: 'Find all image files'
Blender: 232
Group: 'UV'
Tooltip: 'Finds all image files from this blend an relinks'
"""
__author__ = "Campbell Barton AKA Ideasman"
__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun"]
__bpydoc__ = """\
Blah
"""
from Blender import *
import os
#==============================================#
# Strips the slashes from the back of a string #
#==============================================#
def stripPath(path):
return path.split('/')[-1].split('\\')[-1]
# finds the file starting at the root.
def findImage(findRoot, imagePath):
newImageFile = None
imageFile = imagePath.split('/')[-1].split('\\')[-1]
# ROOT, DIRS, FILES
pathWalk = os.walk(findRoot)
pathList = [True]
matchList = [] # Store a list of (match, size), choose the biggest.
while True:
try:
pathList = pathWalk.next()
except:
break
for file in pathList[2]:
# FOUND A MATCH
if file.lower() == imageFile.lower():
name = pathList[0] + sys.sep + file
try:
size = os.path.getsize(name)
except:
size = 0
if size:
print ' found:', name
matchList.append( (name, size) )
if matchList == []:
print 'no match for:', imageFile
return None
else:
# Sort by file size
matchList.sort(key=lambda x: x[1], reverse=True )
print 'using:', matchList[0][0]
# First item is the largest
return matchList[0][0] # 0 - first, 0 - pathname
# Makes the pathe relative to the blend file path.
def makeRelative(path):
blendBasePath = sys.expandpath('//')
if path.startswith(blendBasePath):
path = path.replace(blendBasePath, '//')
path = path.replace('//\\', '//')
return path
def find_images(findRoot):
print findRoot
# findRoot = Draw.PupStrInput ('find in: ', '', 100)
if findRoot == '':
Draw.PupMenu('No Directory Selected')
return
# Account for //
findRoot = sys.expandpath(findRoot)
# Strip filename
while findRoot[-1] != '/' and findRoot[-1] != '\\':
findRoot = findRoot[:-1]
if not findRoot.endswith(sys.sep):
findRoot += sys.sep
if findRoot != '/' and not sys.exists(findRoot[:-1]):
Draw.PupMenu('Directory Dosent Exist')
Window.WaitCursor(1)
# ============ DIR DONE\
images = Image.Get()
len_images = float(len(images))
for idx, i in enumerate(images):
progress = idx / len_images
Window.DrawProgressBar(progress, 'searching for images')
# If files not there?
if not sys.exists(sys.expandpath(i.filename )):
newImageFile = findImage(findRoot, i.filename)
if newImageFile != None:
newImageFile = makeRelative(newImageFile)
print 'newpath:', newImageFile
i.filename = newImageFile
i.reload()
Window.RedrawAll()
Window.DrawProgressBar(1.0, '')
Window.WaitCursor(0)
if __name__ == '__main__':
Window.FileSelector(find_images, 'SEARCH ROOT DIR', sys.expandpath('//'))
此指令碼查詢被引用多次的影像,並檢查所有網格的紋理面,並只分配其中一個影像。
如果一個面沒有使用者,則該影像將被刪除。
這很有用,因為當一個影像被載入多次時,它也會被載入到系統記憶體和顯示卡記憶體中多次,從而浪費資源。
對影像型別紋理的支援還需要完成。
#!BPY
"""
Name: 'Remove Double Images'
Blender: 232
Group: 'UV'
Tooltip: 'Remove Double Images'
"""
from Blender import *
def main():
# Sync both lists
fNameList = []#
bImageList = [] # Sync with the one abovr.
bImageReplacePointer = dict() # The length of IMage.Get()
imgIdx = 0
# Sort by name lengths so image.001 will be replaced by image
Images = Image.Get()
Images.sort(key=lambda x: len(x.name), reverse=True )
for bimg in Images:
expendedFName = sys.expandpath(bimg.filename)
bImageReplacePointer[expendedFName] = bimg
print 'Remove Double Images, loading mesh data...',
uniqueMeshNames = []
# get all meshs
doubles = 0
for ob in Object.Get():
if ob.getType() == 'Mesh' and ob.getData(1) not in uniqueMeshNames:
m = ob.getData(mesh=1)
uniqueMeshNames.append(ob.getData(1))
# We Have a new mesh,
imageReplaced = 0
for f in m.faces:
image = None
try: image = f.image
except: pass
if image:
replaceImage = bImageReplacePointer[ sys.expandpath(f.image.filename) ]
if replaceImage.name != image.name:
f.image = replaceImage
imageReplaced = 1
if imageReplaced:
doubles += 1
m.update()
print '\tchanged', m.name
else:
print '\tunchanged', m.name
print 'Done, %i doubles removed.' % doubles
if __name__ == '__main__':
main()
此指令碼將您當前開啟的混合檔案中的 *所有* 材質更改為卡通材質。執行後,檢查 Blender 控制檯以獲取指令碼輸出。
由於此指令碼會更改您當前開啟的混合檔案中的 *所有* 材質設定,因此您 *不應* 在未儲存的專案上執行它!使用此指令碼時對材質所做的更改無法撤消!
注意:除非您在執行此指令碼後選擇儲存檔案,否則更改不會永久提交到混合檔案。
import Blender
from Blender import Material, Scene
from Blender.Scene import Render
print "\nTOON MATERIAL CONVERSION SCRIPT V1.0 STARTED...\n"
# Get list of active materials from Blender
materials = Blender.Material.Get()
# Get render information needed for edge setting
scn = Scene.GetCurrent()
context = scn.getRenderingContext()
print "PROGRESS: CONVERTING ALL MATERIALS TO TOON TYPE..."
# Change materials to Toon Diffuse/Specular
for m in materials:
# Diffuse Shader (2 = Toon)
m.setDiffuseShader(2)
# Specular Shader (3 = Toon)
m.setSpecShader(3)
# THE FOLLOWING SETTINGS CAN
# BE CHANGED TO DIFFERENT
# VALUES WITHIN THE SPECIFIED
# RANGE OF ACCEPTABLE NUMBERS:
# Diffuse Size (0 to 3.14)
m.setDiffuseSize(1.5)
# Diffuse Smooth (0 to 1.0)
m.setDiffuseSmooth(.5)
# Reflect Amount (0 to 1.0)
# - optionally here to help you
# with any necessary batch changes
# to all material reflection values
# Remove "#" from line below to use:
# m.setRef(.75)
# Specular (0 to 2.0)
m.setSpec(.3)
# Specular Smooth (0 to 1.0)
m.setSpecSmooth(.5)
# Specular Size (0 to 3.14)
m.setSpecSize(.4)
# Enable toon edge: 0 = off, 1 = on
context.enableToonShading(1)
# Edge Intension (0 to 255)
context.edgeIntensity(30)
print "PROGRESS: CONVERSION FINISHED!\nTWEAK MATERIALS AND LIGHTING AS NECESSARY."
Blender.Redraw()
此函式獲取所有邊的總長度。最有用的是獲取曲線的長度。小心,因為它會獲取曲線物件中每條曲線的長度!
請注意,此函式在獲取長度時不會考慮物件的變換。
from Blender import Mesh, Object
def curve_length(ob): # Can realy be any object
me= Mesh.New()
me.getFromObject(cu_ob.name)
totlength= 0.0
for ed in me.edges:
# Blender 2.42 can simply do
# totlength+= ed.length
totlength+= (ed.v1.co-ed.v2.co).length
return totlength
# TEST THE FUNCTION
cu_ob= Object.Get('mycurve')
print curve_length(cu_ob)
在 X11 中貼上文字,需要 uclip [[1]]
#!BPY
"""
Name: 'Text from Clipboard'
Blender: 234
Group: 'Add'
Tooltip: 'Text from Clipboard X11'
"""
from Blender import Text
import os
clip = os.popen('uclip -o')
clipTxt = clip.read()
text = Text.New(clipTxt[0:10])
text.write(clipTxt)
將所有文字編輯器文字儲存為當前工作目錄中的檔案。警告:這會覆蓋具有相同名稱的檔案!
import Blender
texts=Blender.Text.Get()
for text in texts:
out=file(text.name, 'w')
for line in text.asLines():
out.write(line+'\n')
Blender 的 NMesh、GMesh 和新 Mesh 模組的示例和函式。
將此用作編輯模式網格工具的基礎。
#!BPY
""" Registration info for Blender menus:
Name: 'Template Mesh Editmode tool...'
Blender: 237
Group: 'Mesh'
Tooltip: 'Change this template text tooltip'
"""
__author__ = "Your Name"
__url__ = ("blender", "elysiun")
__version__ = "1.0"
__bpydoc__ = """\
Multilin Script Help
Document your script here.
"""
from Blender import *
def main():
scn = Scene.GetCurrent()
ob = scn.getActiveObject() # Gets the current active object (If Any)
if ob == None or ob.getType() != 'Mesh': # Checks the active objects a mesh
Draw.PupMenu('ERROR%t|Select a mesh object.')
return
Window.WaitCursor(1) # So the user knowns the script is busy.
is_editmode = Window.EditMode() # Store edit mode state
if is_editmode: Window.EditMode(0) # Python must get a mesh in object mode.
me = ob.getData()
#================#
# EDIT MESH HERE #
#================#
for v in me.verts:
if v.sel: # Operating on selected verts is what the user expects.
v.co.x = v.co.x * 2
#================#
# FINISH EDITING #
#================#
me.update() # Writes the mesh back into Blender.
# Go back into editmode if we started in edit mode.
if is_editmode: Window.EditMode(1)
Window.WaitCursor(0)
if __name__ == '__main__': # Dont run the script if its imported by another script.
main()
使用新 Mesh 模組。僅限 Blender 2.4。使用與 Photoshop 相同的加權進行去飽和。
from Blender import Mesh, Object
for ob in Object.GetSelected():
if ob.getType() == 'Mesh':
me = ob.getData(mesh=1)
if me.faceUV:
for f in me.faces:
for c in f.col:
# Weighted colour conversion, as used by photoshop.
c.r = c.g = c.b = int(((c.r*30) + (c.g*59) + (c.b*11)) / 100.0)
此函式根據提供的點是否在網格內返回 1/0。它依賴於網格具有連續的皮膚,沒有孔洞。(否則問題就沒有意義。)
它使用的方法是檢視從該點到網格邊界外某個點之間的線段上有多少個面交叉點。
偶數個交叉點意味著它在外部,奇數個意味著它在內部。因此我們返回 len(intersections) % 2,其中intersections生成交叉點的列表。
此函式使用 Z 方向向量,因此我們可以透過首先進行 X/Y 邊界測試來節省一些 CPU 週期,以檢視點是否可能交叉,然後再進行完整的射線交叉測試。
from Blender import *
def pointInsideMesh(ob, pt):
Intersect = Mathutils.Intersect # 2 less dict lookups.
Vector = Mathutils.Vector
def ptInFaceXYBounds(f, pt):
co= f.v[0].co
xmax= xmin= co.x
ymax= ymin= co.y
co= f.v[1].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
co= f.v[2].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
if len(f.v)==4:
co= f.v[3].co
xmax= max(xmax, co.x)
xmin= min(xmin, co.x)
ymax= max(ymax, co.y)
ymin= min(ymin, co.y)
# Now we have the bounds, see if the point is in it.
return xmin <= pt.x <= xmax and \
ymin <= pt.y <= ymax
def faceIntersect(f):
isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
if not isect and len(f.v) == 4:
isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
return bool(isect and isect.z > obSpacePt.z) # This is so the ray only counts if its above the point.
obImvMat = Mathutils.Matrix(ob.matrixWorld)
obImvMat.invert()
pt.resize4D()
obSpacePt = pt* obImvMat
pt.resize3D()
obSpacePt.resize3D()
ray = Vector(0,0,-1)
me= ob.getData(mesh=1)
# Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
# Example, see if the cursor is inside the mesh.
if __name__ == '__main__':
scn= Scene.GetCurrent()
ob= scn.getActiveObject()
pt= Mathutils.Vector(Window.GetCursorPos())
print 'Testing if cursor is inside the mesh',
inside= pointInsideMesh(ob, pt)
print inside
此函式接收一個網格和一個表示 ngon 的頂點索引列表。它返回一個三角形索引列表,這些索引構成了掃描填充的面。這對匯入程式更有用。
它還處理掃描填充無法正常工作的情況,方法是返回一個三角形扇形。
它可能比對原始網格使用 mesh.fill() 函式更快,因為迴圈編輯模式在大量資料上可能很慢。此函式相對於簡單使用 fill() 的另一個優點是,您可以確保面將根據索引的順序以正確的方向翻轉。
from Blender import *
def ngon(from_mesh, indicies):
if len(indicies) < 4:
return [indicies]
is_editmode= Window.EditMode()
if is_editmode:
Window.EditMode(0)
temp_mesh = Mesh.New()
temp_mesh.verts.extend( [from_mesh.verts[i].co for i in indicies] )
temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
oldmode = Mesh.Mode()
Mesh.Mode(Mesh.SelectModes['VERTEX'])
for v in temp_mesh.verts:
v.sel= 1
# Must link to scene
scn= Scene.GetCurrent()
temp_ob= Object.New('Mesh')
temp_ob.link(temp_mesh)
scn.link(temp_ob)
temp_mesh.fill()
scn.unlink(temp_ob)
Mesh.Mode(oldmode)
new_indicies= [ [v.index for v in f.v] for f in temp_mesh.faces ]
if not new_indicies: # JUST DO A FAN, Cant Scanfill
print 'Warning Cannot scanfill!- Fallback on a triangle fan.'
new_indicies = [ [indicies[0], indicies[i-1], indicies[i]] for i in xrange(2, len(indicies)) ]
else:
# Use real scanfill.
# See if its flipped the wrong way.
flip= None
for fi in new_indicies:
if flip != None:
break
for i, vi in enumerate(fi):
if vi==0 and fi[i-1]==1:
flip= 0
break
elif vi==1 and fi[i-1]==0:
flip= 1
break
if flip:
for fi in new_indicies:
fi.reverse()
if is_editmode:
Window.EditMode(1)
return new_indicies
# === ===== EG
scn= Scene.GetCurrent()
me = scn.getActiveObject().getData(mesh=1)
ind= [v.index for v in me.verts if v.sel] # Get indicies
indicies = ngon(me, ind) # fill the ngon.
# Extand the faces to show what the scanfill looked like.
print len(indicies)
me.faces.extend([[me.verts[ii] for ii in i] for i in indicies])
這是一個供其他指令碼使用的函式,如果您想透過只處理三角形來簡化您的工作,它會很有用。
最短邊方法用於將四邊形劃分為 2 個三角形。
def triangulateNMesh(nm):
'''
Converts the meshes faces to tris, modifies the mesh in place.
'''
#============================================================================#
# Returns a new face that has the same properties as the origional face #
# but with no verts #
#============================================================================#
def copyFace(face):
newFace = NMesh.Face()
# Copy some generic properties
newFace.mode = face.mode
if face.image != None:
newFace.image = face.image
newFace.flag = face.flag
newFace.mat = face.mat
newFace.smooth = face.smooth
return newFace
# 2 List comprehensions are a lot faster then 1 for loop.
tris = [f for f in nm.faces if len(f) == 3]
quads = [f for f in nm.faces if len(f) == 4]
if quads: # Mesh may have no quads.
has_uv = quads[0].uv
has_vcol = quads[0].col
for quadFace in quads:
# Triangulate along the shortest edge
if (quadFace.v[0].co - quadFace.v[2].co).length < (quadFace.v[1].co - quadFace.v[3].co).length:
# Method 1
triA = 0,1,2
triB = 0,2,3
else:
# Method 2
triA = 0,1,3
triB = 1,2,3
for tri1, tri2, tri3 in (triA, triB):
newFace = copyFace(quadFace)
newFace.v = [quadFace.v[tri1], quadFace.v[tri2], quadFace.v[tri3]]
if has_uv: newFace.uv = [quadFace.uv[tri1], quadFace.uv[tri2], quadFace.uv[tri3]]
if has_vcol: newFace.col = [quadFace.col[tri1], quadFace.col[tri2], quadFace.col[tri3]]
nm.addEdge(quadFace.v[tri1], quadFace.v[tri3]) # Add an edge where the 2 tris are devided.
tris.append(newFace)
nm.faces = tris
有時您可能會遇到四邊形面,儘管它們正確地共面,但並不完全“完整”。這是由於頂點的順序錯誤,導致面自我重疊,通常在法線指向錯誤方向的地方留下不必要的孔洞和黑色區域。要檢視這到底意味著什麼,只需建立一個平面,然後交換任意邊上的頂點位置。連線的邊將交叉。由於面的法線在這種情況下沒有意義,因此指令碼無法保證在完成處理後法線指向外部。
#!BPY
"""
Name: 'Quadsorter'
Blender: 233
Group: 'Mesh'
Tip: 'Fix winding order for quad faces for all selected meshes'
Author: Yann Vernier (LoneTech)
"""
from Blender.Mathutils import Vector, CrossVecs, DotVecs
def sortface(f):
if len(f) != 4:
return f
v=[Vector(list(p)) for p in f]
v2m0=v[2]-v[0]
# The normal of the plane
n=CrossVecs(v[1]-v[0], v2m0)
#k=DotVecs(v[0],n)
#if DotVecs(v[3],n) != k:
# raise ValueError("Not Coplanar")
# Well, the above test would be a good hint to make triangles.
# Get a vector pointing along the plane perpendicular to v[0]-v[2]
n2=CrossVecs(n, v2m0)
# Get the respective distances along that line
k=[DotVecs(p,n2) for p in v[1:]]
# Check if the vertices are on the proper side
if cmp(k[1],k[0]) == cmp(k[1],k[2]):
#print "Bad",v
f.v=[f[0],f[2],f[3],f[1]]
from Blender.Object import GetSelected
for obj in GetSelected():
if obj.getType() == 'Mesh':
mesh=obj.data
for face in mesh.faces:
sortface(face)
mesh.update()
使用上面網格模板的指令碼。刪除頂點,但周圍的四邊形將轉換為三角形。
注意 這隻適用於 NMesh。
#================#
# EDIT MESH HERE #
#================#
for f in me.faces:
face_verts = f.v[:] # make a copy of the list.
for v in face_verts:
if v.sel:
f.v.remove(v)
# Remove all with less then 3 verts,
# When removing objects from a list its best to loop backwards
fIdx = len(me.faces)
while fIdx:
fIdx -=1
f = me.faces[fIdx]
if len(f.v) < 3:
del me.faces[fIdx]
# Remove all selected verts
# Loop backwards.
vIdx = len(me.verts)
while vIdx:
vIdx -=1
v = me.verts[vIdx]
if v.sel:
del me.verts[vIdx]
#================#
# FINISH EDITING #
#================#
此函式使用 GMesh 來自動平滑流形網格,它需要 GMesh 模組。
小心,因為它會就地平滑您的網格,因此如果您不想修改它,請複製您的原始物件。
from Blender import *
import GMesh
smooth = Draw.PupIntInput('smooth:', 20,1,89)
for ob in Object.GetSelected():
mesh = ob.getData()
gmesh = GMesh.NMesh2GMesh(mesh)
try:
gmesh.autoSmooth(smooth)
except:
print 'Error non manifold mesh'
continue # go onto the next item
mesh = GMesh.GMesh2NMesh(gmesh)
# Make the faces smooth
for f in mesh.faces:
f.smooth = 1
ob.link(mesh) # Link the new mesh with the original object
模擬在 Blender 中鍵入“Shift F”以建立掃描填充的選定邊迴圈(僅限編輯模式)
這隻有在 3D 檢視開啟的情況下才能工作。
import Blender
winid = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)[0]['id']
Blender.Window.SetKeyQualifiers(Blender.Window.Qual.SHIFT)
Blender.Window.QAdd(winid, Blender.Draw.FKEY,1)
Blender.Window.QHandle(winid)
Blender.Window.SetKeyQualifiers(0)
自包含的掃描填充函式,基於上面的程式碼。注意,此函式需要 3D 檢視可用
import Blender
# Take a list of points and return a scanfilled NMesh
def scanFillPoints(pointList):
Blender.Window.EditMode(0)
nme = Blender.NMesh.New()
# 2.37 compatability, not needed in 2.4
if not nme.edges:
nme.addEdgesData()
for p in pointList:
v = Blender.NMesh.Vert( p[0], p[1], p[2] )
nme.verts.append(v)
v.sel = 1
if len(nme.verts) >= 2:
nme.addEdge(nme.verts[-2], nme.verts[-1])
nme.addEdge(nme.verts[0], nme.verts[-1])
scn = Blender.Scene.GetCurrent()
actOb = scn.getActiveObject()
if actOb:
actSel = actOb.sel
else:
actSel = 0
ob = Blender.Object.New('Mesh')
ob.link(nme)
scn.link(ob)
scn.layers = range(1,20)
ob.sel = 1
Blender.Window.EditMode(1)
winid = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)[0]['id']
Blender.Window.SetKeyQualifiers(Blender.Window.Qual.SHIFT)
Blender.Window.QAdd(winid, Blender.Draw.FKEY,1)
Blender.Window.QHandle(winid)
Blender.Window.SetKeyQualifiers(0)
Blender.Window.EditMode(0)
# scn.unlink(ob)
# Select the old active object.
if actOb:
actOb.sel = actSel
# Reture the scanfilled faces.
return ob.getData()
示例函式用法。
scanFillPoints([[-1,-1,0], [1,-1,1], [1,1,0], [0,0,0.2], [0,1,-.1], [0.1,1,-0.3] ])
返回一個新面,它具有與原始面相同的屬性,但沒有頂點
def faceCopy(face):
newFace = NMesh.Face()
# Copy some generic properties
newFace.mode = face.mode
if face.image != None:
newFace.image = face.image
newFace.flag = face.flag
newFace.mat = face.mat
newFace.smooth = face.smooth
return newFace
注意,Blender 的 Mesh API 現在有 face.cent 訪問
接收 1 個 NMFace 並返回其中心點作為向量,如果提供,將使用現有的向量物件“cent”。
def faceCent(f, cent=None):
x = y = z = 0
for v in f.v:
x+=v.co[0]
y+=v.co[1]
z+=v.co[2]
if not cent:
return Mathutils.Vector([x/len(f.v), y/len(f.v), z/len(f.v)])
# Modify the provided vec
cent.x = x/len(f.v)
cent.y = y/len(f.v)
cent.z = z/len(f.v)
我使用此指令碼將許多地形網格中的所有面翻轉為向上。它使用 Mesh 而不是 NMesh。
from Blender import *
#==================#
# Apply Tpransform #
#==================# Used for skin
def apply_transform(vec, matrix):
x, y, z = vec
xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
vec.x = x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc
vec.y = x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc
vec.z = x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc
def apply_transform3x3(vec, matrix):
x, y, z = vec
vec.x = x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0]
vec.y = x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1]
vec.z = x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2]
# Point to z up.
noVec = Mathutils.Vector(0,0,-10000)
cent = Mathutils.Vector(0,0,0)
for ob in Object.GetSelected():
if ob.getType() != 'Mesh':
continue
mat = ob.matrixWorld
me = ob.getData(mesh=1)
# We know were mesh
# Select none
for f in me.faces: f.sel = 0
# Flip based on facing.
for f in me.faces:
no = f.no
apply_transform3x3(no, mat)
# Get the faces centre
cent.x, cent.y, cent.z = 0,0,0
for v in f.v:
cent += v.co
cent = cent * (1.0 / len(f.v))
apply_transform(cent, mat)
# Move the vec over the centre of the face.
noVec.x = cent.x
noVec.y = cent.y
# Are we not facing up?, if not then select and flip later.
if ((cent+no)-noVec).length <= (cent-noVec).length:
f.sel = 1
me.flipNormals()
縮放所有選中網格物件的 uv 座標。
from Blender import *
def main():
# Scale the UV down.
# This examples scales down by 1 pixel on a 512x512 image.
shrink = 1-(1/512.0)
for ob in Object.GetSelected():
if ob.getType() == 'Mesh':
me = ob.getData(mesh=1)
if me.faceUV:
for f in me.faces:
f.uv =\
tuple([ Mathutils.Vector(\
((uv[0]-0.5)*shrink)+0.5,\
((uv[1]-0.5)*shrink)+0.5,\
) for uv in f.uv])
if __name__ == '__main__':
main()
有時您有很多網格物件和材料,您不希望它們中的任何一個使用。此指令碼可以幫助您找到這些物件。
#!BPY
"""
Name: 'Find Mesh with Material'
Blender: 234
Group: 'Object'
Tooltip: 'Find Mesh with Material'
"""
from Blender import *
def main():
matToFind = Draw.PupStrInput('matName:', '', 21)
if matToFind == None:
return
Window.WaitCursor(1)
for scn in Scene.Get():
for ob in scn.getChildren():
if ob.getType() == 'Mesh':
for mat in ob.getData(mesh=1).materials:
matname = None
try:
matname = mat.name
except:
# Material must be None
continue
if matname == matToFind:
# Unselect all in the scene
for ob_ in scn.getChildren():
ob_.sel = 0
# Select the found object
ob.sel = 1
scn.makeCurrent()
Draw.PupMenu('Material "%s" found in object "%s".' % (matToFind, ob.name))
Window.WaitCursor(0)
return
Window.WaitCursor(0)
Draw.PupMenu('Material "%s" Not found.' % matToFind)
if __name__ == '__main__':
main()
這兩個面共享一條邊,最好確保您沒有比較相同的面,並刪除第一個“if”。
# Do the 2 faces share an edge?
# return true or false.
def faceShareEdge(face1, face2):
# Are we using the same verts. could be more comprehensive, since vert order may differ but still be the same.
if face1.v == face2.v:
return False
firstMatch = None
for v1 in face1:
if v1 in face2:
if firstMatch is None:
firstMatch = True
else:
return True
return False
返回一個角度列表,所有使用這些邊的面的組合角度差。返回的角度與 mesh.edges 同步。具有 0 或 1 個面的邊將具有零角度。
此函式使用 Blender.Mesh 而不是 Blender.NMesh 網格資料。
def getEdgeAngles(me):
Ang= Blender.Mathutils.AngleBetweenVecs
Vector= Blender.Mathutils.Vector
edges = dict( [ (ed.key, (i, [])) for i, ed in enumerate(me.edges) ] )
for f in me.faces:
#print f.index
for key in f.edge_keys:
edges[key][1].append(f.no)
edgeAngles=[0.0] * len(me.edges)
for eIdx, angles in edges.itervalues():
angles_len= len(angles)
if angles_len < 2:
pass
if angles_len==2:
edgeAngles[eIdx] = Ang(angles[0], angles[1])
else:
totAngDiff=0
for j in reversed(xrange(angles_len)):
for k in reversed(xrange(j)):
totAngDiff+= (Ang(angles[j], angles[k])/180) # /180 isnt needed, just to keeop the vert small.
edgeAngles[eIdx] = totAngDiff
return edgeAngles
將射線與網格相交,假設網格沒有位置/大小/旋轉。
import Blender
from Blender import Window, Mathutils, Object
Vector= Mathutils.Vector
Intersect= Mathutils.Intersect
Matrix= Mathutils.Matrix
def meshRayIntersect(me, Origin, Direction):
def faceIntersect(f):
isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, Direction, Origin, 1) # Clipped.
if isect:
return isect
elif len(f.v) == 4:
isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, Direction, Origin, 1) # Clipped.
return isect
''' Ray is a tuple of vectors (Origin, Direction) '''
isect= best_isect= None
dist_from_orig= 1<<30
for f in me.faces:
isect= faceIntersect(f)
if isect:
l= (isect-Origin).length
if l < dist_from_orig:
dist_from_orig= l
best_isect= isect
return best_isect, dist_from_orig
將頂點 UV 座標(粘性)複製到面 UV 座標(TexFace)。
#!BPY
#sticky2uv.py
""" Registration info for Blender menus:
Name: 'Vertex UV to face UV'
Blender: 241
Group: 'Mesh'
Tooltip: 'Copy vertex UV to face UV'
"""
__author__ = "Brandano"
__url__ = ("blender", "elysiun")
__version__ = "1.0"
__bpydoc__ = """\
Copies the Vertex UV coordinates (Sticky) to face UV coordinates (TexFace).
Warning: the original face UV's will be overwritten.
"""
import Blender
from Blender import Mesh
if (Blender.Object.GetSelected() != None):
for me in [ob.getData(mesh=1) for ob in Blender.Object.GetSelected() if ob.getType() == "Mesh"]:
if me.vertexUV:
me.faceUV = 1
for f in me.faces: f.uv = [v.uvco for v in f.verts]
me.update()
這裡是新增數學示例的地方,它們可以是 Blender 特定的或通用的 Python 數學函式。
如果您在不同旋轉系統之間轉換時遇到問題,則可能是旋轉順序出了問題。
import Blender
RotationMatrix= Blender.Mathutils.RotationMatrix
MATRIX_IDENTITY_3x3 = Blender.Mathutils.Matrix([1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0])
def eulerRotateOrder(x,y,z):
x,y,z = x%360,y%360,z%360 # Clamp all values between 0 and 360, values outside this raise an error.
xmat = RotationMatrix(x,3,'x')
ymat = RotationMatrix(y,3,'y')
zmat = RotationMatrix(z,3,'z')
# Standard BVH multiplication order, apply the rotation in the order Z,X,Y
# Change the order here
return (ymat*(xmat * (zmat * MATRIX_IDENTITY_3x3))).toEuler()
獲取線段 AB 和 BC 之間的角度,其中 b 是肘部。
import Blender
AngleBetweenVecs = Blender.Mathutils.AngleBetweenVecs
def getAng3pt3d(avec, bvec, cvec):
try:
ang = AngleBetweenVecs(avec - bvec, cvec - bvec)
if ang != ang:
raise "ERROR angle between Vecs"
else:
return ang
except:
print '\tAngleBetweenVecs failed, zero length?'
return 0
如果 pt 在三角形內,則返回 True。
僅當 pt 位於三角形的平面上時才會給出正確的結果。
from Blender import Mathutils
SMALL_NUM = 0.000001
def pointInTri2D(pt, tri1, tri2, tri3):
a = Mathutils.TriangleArea(tri1, tri2, tri3)
othera = Mathutils.TriangleArea(pt, tri1, tri2) + SMALL_NUM
if othera > a: return False
othera += Mathutils.TriangleArea(pt, tri2, tri3)
if othera > a: return False
othera += Mathutils.TriangleArea(pt, tri3, tri1)
if othera > a: return False
return True
將由 object.getMatrix() 返回的 4x4 變換應用於向量(空間中的 3D 點)
這對查詢頂點在世界空間中的位置很有用。
Blender 2.43 支援直接透過 “newvwec = vec*matrix” 來實現這一點,但瞭解如何手動執行這一點也很有幫助。
#==================#
# Apply Tpransform #
#==================#
def apply_transform(vec, matrix):
x, y, z = vec
xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
return x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,\
x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,\
x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc
def apply_transform3x3(vec, matrix):
x, y, z = vec
return x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0],\
x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1],\
x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2]
使兩條線段相交,如果相交,則返回交點位置。
如果沒有相交,則返回的 X 值將為 None,而 y 將是錯誤程式碼。
第一條線段為(x1,y1, x2,y2),第二條為(_x1,_y1, _x2,_y2)
SMALL_NUM = 0.000001
def lineIntersection2D(x1,y1, x2,y2, _x1,_y1, _x2,_y2):
# Bounding box intersection first.
if min(x1, x2) > max(_x1, _x2) or \
max(x1, x2) < min(_x1, _x2) or \
min(y1, y2) > max(_y1, _y2) or \
max(y1, y2) < min(_y1, _y2):
return None, 100 # Basic Bounds intersection TEST returns false.
# are either of the segments points? Check Seg1
if abs(x1 - x2) + abs(y1 - y2) <= SMALL_NUM:
return None, 101
# are either of the segments points? Check Seg2
if abs(_x1 - _x2) + abs(_y1 - _y2) <= SMALL_NUM:
return None, 102
# Make sure the HOZ/Vert Line Comes first.
if abs(_x1 - _x2) < SMALL_NUM or abs(_y1 - _y2) < SMALL_NUM:
x1, x2, y1, y2, _x1, _x2, _y1, _y2 = _x1, _x2, _y1, _y2, x1, x2, y1, y2
if abs(x2-x1) < SMALL_NUM: # VERTICLE LINE
if abs(_x2-_x1) < SMALL_NUM: # VERTICLE LINE SEG2
return None, 111 # 2 verticle lines dont intersect.
elif abs(_y2-_y1) < SMALL_NUM:
return x1, _y1 # X of vert, Y of hoz. no calculation.
yi = ((_y1 / abs(_x1 - _x2)) * abs(_x2 - x1)) + ((_y2 / abs(_x1 - _x2)) * abs(_x1 - x1))
if yi > max(y1, y2): # New point above seg1's vert line
return None, 112
elif yi < min(y1, y2): # New point below seg1's vert line
return None, 113
return x1, yi # Intersecting.
if abs(y2-y1) < SMALL_NUM: # HOZ LINE
if abs(_y2-_y1) < SMALL_NUM: # HOZ LINE SEG2
return None, 121 # 2 hoz lines dont intersect.
# Can skip vert line check for seg 2 since its covered above.
xi = ((_x1 / abs(_y1 - _y2)) * abs(_y2 - y1)) + ((_x2 / abs(_y1 - _y2)) * abs(_y1 - y1))
if xi > max(x1, x2): # New point right of seg1's hoz line
return None, 112
elif xi < min(x1, x2): # New point left of seg1's hoz line
return None, 113
return xi, y1 # Intersecting.
# Accounted for hoz/vert lines. Go on with both anglular.
b1 = (y2-y1)/(x2-x1)
b2 = (_y2-_y1)/(_x2-_x1)
a1 = y1-b1*x1
a2 = _y1-b2*_x1
if b1 - b2 == 0.0:
return None, None
xi = - (a1-a2)/(b1-b2)
yi = a1+b1*xi
if (x1-xi)*(xi-x2) >= 0 and (_x1-xi)*(xi-_x2) >= 0 and (y1-yi)*(yi-y2) >= 0 and (_y1-yi)*(yi-_y2)>=0:
return xi, yi
else:
return None, None
此函式取任何數字,並將其四捨五入到最接近的 2 的冪值(2,4,8,16,32,64,128,256,512,1024,2048, 4096...)。這對將紋理四捨五入到載入到顯示卡記憶體中的尺寸很有用。
它返回 3 個值:向下舍入、最接近的舍入、向上舍入。
def roundPow2(roundVal):
base2val = 1
while roundVal >= base2val:
base2val*=2
# dont round up if there the same, just give the same vars
if roundVal == base2val/2:
return base2val/2, base2val/2, base2val/2 # Round down and round up.
smallRound = base2val/2
largeRound = base2val
# closest to the base 2 value
diffLower = abs(roundVal - smallRound)
diffHigher = abs(roundVal - largeRound)
if diffLower < diffHigher:
mediumRound = smallRound
else:
mediumRound = largeRound
smallRound = base2val/2
largeRound = base2val
return smallRound, mediumRound, largeRound # round down, round mid and round up.
返回最接近點點的向量。適用於捕捉。
def getSnapVec(point, snap_points):
'''
Returns the closest vec to snap_points
'''
close_dist= 1<<30
close_vec= None
x= point[0]
y= point[1]
z= point[2]
for v in snap_points:
# quick length cmp before a full length comparison.
if abs(x-v[0]) < close_dist and\
abs(y-v[1]) < close_dist and\
abs(z-v[2]) < close_dist:
l= (v-point).length
if l<close_dist:
close_dist= l
close_vec= v
return close_vec
B:Python 中沒有函式可以建立物件的連結複製(Alt+D),因此這裡有一個為您完成此操作的函式。
注意自從編寫本文以來,Blender.Object.Duplicate() 以及 object.copy() 已被新增。
from Blender import *
# Like pressing Alt+D
def linkedCopy(ob, scn=None): # Just like Alt+D
if not scn:
scn = Scene.GetCurrent()
type = ob.getType()
newOb = Object.New(type)
if type != 'Empty':
newOb.shareFrom(ob)
scn.link(newOb)
newOb.setMatrix(ob.getMatrix())
# Copy other attributes.
newOb.setDrawMode(ob.getDrawMode())
newOb.setDrawType(ob.getDrawType())
newOb.Layer = ob.Layer
# Update the view
ob.select(0)
newOb.select(1)
return newOb
# You can call the function like this
try:
ob2duplicate = Object.GetSelected()[0]
linkedCopy(ob2duplicate)
Redraw()
except:
print "Nothing Selected"
查詢雙重物件 - 具有相同資料名稱、型別和位置/大小/旋轉的物件。
#!BPY
"""
Name: 'Select only double objects.'
Blender: 232
Group: 'Object'
Tooltip: 'Select double objects from the existing selection.'
"""
from Blender import *
def main():
# Collect the extra object data once only, so we dont need to request it again.
obinfo = [{'object':ob, 'dataname':ob.getData(1), 'type':ob.getType(), 'matrix':tuple(ob.matrixWorld)} for ob in Object.GetSelected() ]
print '\n\n\nStarting to select doubles for %i objects.' % len(obinfo)
doubleObs = [] # store doubles in this list
doubles = 0
# Comparison loop, compare items in the list only once.
obIdx1 = len(obinfo)
while obIdx1:
obIdx1 -=1
ob1 = obinfo[obIdx1]
# Deselect as we go, any doubles will be selected again. ob1['object'].sel = 0
ob1['object'].sel = 0
obIdx2 = obIdx1
while obIdx2:
obIdx2 -=1
ob2 = obinfo[obIdx2]
# Comparison loop done.
# Now we have both objects we can compare ob2 against ob1.
if \
ob1['dataname'] == ob2['dataname'] and\
ob1['type'] == ob2['type'] and\
ob1['matrix'] == ob2['matrix']:
# We have a double, print output and add to the double list.
doubles +=1
print '\t%i doubles found: "%s", "%s"' % (doubles, ob1['object'].name, ob2['object'].name)
doubleObs.append(ob2)
for ob in doubleObs:
ob['object'].sel = 1
if __name__ == '__main__':
t = sys.time()
main()
print 'Done in %.4f seconds.' % (sys.time()-t)
操作 NLA 軌道和動作軌道的示例程式碼。
# Nov 16 2006
#
# Mike Stramba
# mstramba@sympatico.ca
# BlenderArtists Mike_S
#
import Blender
from Blender import *
from Blender.Armature import NLA
ctr = 1
numStrips = 1
vflags ={32:'LOCK_ACTION',1:'SELECT',2:'STRIDE_PATH',8:'HOLD',16:'ACTIVE'}
def tranflag(flag):
if flag:
print
for v in vflags:
t = flag & v
if t:
print '\t\t',v,vflags[t]
def showStrip(strip):
print ctr,'/',numStrips
print strip.action.name
print '\tstripStart',strip.stripStart
print '\tstripEnd',strip.stripEnd
print '\tactionStart',strip.actionStart
print '\tactionEnd',strip.actionEnd
print '\tblendin',strip.blendIn
print '\tblendout',strip.blendOut
print '\tflag',strip.flag,
tranflag(strip.flag)
print '\tmode',strip.mode
print '\tbrepeat',strip.repeat
print '\tstrideAxis',strip.strideAxis
print '\tstrideBone',strip.strideBone
print '\tstrideLength',strip.strideLength
armOb=Object.Get('Armature')
actions=Armature.NLA.GetActions()
#
# Actions named 'rot', 'move', 'Run' assumed to exist, or substitute
# your own action names
#
rotAct = actions['rot']
movAct = actions['move']
runAct = actions['Run']
#
# get all NLA strips for this object
#
Char1NLAstrips = armOb.actionStrips
#
# set the current frame to where you want NLA strips to initially appear
# in the NLA editor
frame = 1
Blender.Set('curframe',frame)
#
# remove all NLA strips
#
Char1NLAstrips[:] = []
#
# some different ways of adding action strips to the NLA editor
#
Blender.Object.Get('Armature').actionStrips.append(Blender.Armature.NLA.GetActions()['tester'])
Char1NLAstrips.append(Blender.Armature.NLA.GetActions()['UpDown'])
armOb.actionStrips.append(rotAct)
Char1NLAstrips.append(movAct)
Char1NLAstrips.append(actions['Run'])
#
# get a strip
#
strip0 = Char1NLAstrips[0]
print '\nstrip0.action.name ="'+strip0.action.name+'"'
#
# show it's properties
#
showStrip(strip0)
#
# change it's stripStart, stripEND (add 50 frames)
# (effectively moving the strip
strip0.stripEnd += 50
strip0.stripStart += 50
#
# show the changes
#
showStrip(strip0)
#
# select the strip in the NLA editor
#
strip0.flag += NLA.Flags['SELECT']
Blender.Window.RedrawAll()
showStrip(strip0)
#
# move all strips by FrameOffset
#
def moveallStrips(FrameOffset):
for strip in Char1NLAstrips:
strip.stripEnd += FrameOffset
strip.stripStart += FrameOffset
moveallStrips(30)
#
# show all strips Properties
#
print
print '============ ALL STRIPS ================'
numStrips = len(Char1NLAstrips)
print numStrips,' NLA strips for ',armOb
for strip in Char1NLAstrips:
showStrip(strip)
import Blender
from Blender import Mathutils, Window, Scene, Draw, Mesh
from Blender.Mathutils import Matrix, Vector, Intersect
# DESCRIPTION:
# screen_x, screen_y the origin point of the pick ray
# it is either the mouse location
# localMatrix is used if you want to have the returned values in an objects localspace.
# this is usefull when dealing with an objects data such as verts.
# or if useMid is true, the midpoint of the current 3dview
# returns
# Origin - the origin point of the pick ray
# Direction - the direction vector of the pick ray
# in global coordinates
epsilon = 1e-3 # just a small value to account for floating point errors
def getPickRay(screen_x, screen_y, localMatrix=None, useMid = False):
# Constant function variables
p = getPickRay.p
d = getPickRay.d
for win3d in Window.GetScreenInfo(Window.Types.VIEW3D): # we search all 3dwins for the one containing the point (screen_x, screen_y) (could be the mousecoords for example)
win_min_x, win_min_y, win_max_x, win_max_y = win3d['vertices']
# calculate a few geometric extents for this window
win_mid_x = (win_max_x + win_min_x + 1.0) * 0.5
win_mid_y = (win_max_y + win_min_y + 1.0) * 0.5
win_size_x = (win_max_x - win_min_x + 1.0) * 0.5
win_size_y = (win_max_y - win_min_y + 1.0) * 0.5
#useMid is for projecting the coordinates when we subdivide the screen into bins
if useMid: # == True
screen_x = win_mid_x
screen_y = win_mid_y
# if the given screencoords (screen_x, screen_y) are within the 3dwin we fount the right one...
if (win_max_x > screen_x > win_min_x) and ( win_max_y > screen_y > win_min_y):
# first we handle all pending events for this window (otherwise the matrices might come out wrong)
Window.QHandle(win3d['id'])
# now we get a few matrices for our window...
# sorry - i cannot explain here what they all do
# - if you're not familiar with all those matrices take a look at an introduction to OpenGL...
pm = Window.GetPerspMatrix() # the prespective matrix
pmi = Matrix(pm); pmi.invert() # the inverted perspective matrix
if (1.0 - epsilon < pmi[3][3] < 1.0 + epsilon):
# pmi[3][3] is 1.0 if the 3dwin is in ortho-projection mode (toggled with numpad 5)
hms = getPickRay.hms
ortho_d = getPickRay.ortho_d
# ortho mode: is a bit strange - actually there's no definite location of the camera ...
# but the camera could be displaced anywhere along the viewing direction.
ortho_d.x, ortho_d.y, ortho_d.z = Window.GetViewVector()
ortho_d.w = 0
# all rays are parallel in ortho mode - so the direction vector is simply the viewing direction
#hms.x, hms.y, hms.z, hms.w = (screen_x-win_mid_x) /win_size_x, (screen_y-win_mid_y) / win_size_y, 0.0, 1.0
hms[:] = (screen_x-win_mid_x) /win_size_x, (screen_y-win_mid_y) / win_size_y, 0.0, 1.0
# these are the homogenious screencoords of the point (screen_x, screen_y) ranging from -1 to +1
p=(hms*pmi) + (1000*ortho_d)
p.resize3D()
d[:] = ortho_d[:3]
# Finally we shift the position infinitely far away in
# the viewing direction to make sure the camera if outside the scene
# (this is actually a hack because this function
# is used in sculpt_mesh to initialize backface culling...)
else:
# PERSPECTIVE MODE: here everything is well defined - all rays converge at the camera's location
vmi = Matrix(Window.GetViewMatrix()); vmi.invert() # the inverse viewing matrix
fp = getPickRay.fp
dx = pm[3][3] * (((screen_x-win_min_x)/win_size_x)-1.0) - pm[3][0]
dy = pm[3][3] * (((screen_y-win_min_y)/win_size_y)-1.0) - pm[3][1]
fp[:] = \
pmi[0][0]*dx+pmi[1][0]*dy,\
pmi[0][1]*dx+pmi[1][1]*dy,\
pmi[0][2]*dx+pmi[1][2]*dy
# fp is a global 3dpoint obtained from "unprojecting" the screenspace-point (screen_x, screen_y)
#- figuring out how to calculate this took me quite some time.
# The calculation of dxy and fp are simplified versions of my original code
#- so it's almost impossible to explain what's going on geometrically... sorry
p[:] = vmi[3][:3]
# the camera's location in global 3dcoords can be read directly from the inverted viewmatrix
d[:] = p.x-fp.x, p.y-fp.y, p.z-fp.z
# the direction vector is simply the difference vector from the virtual camera's position
#to the unprojected (screenspace) point fp
# Do we want to return a direction in object's localspace?
if localMatrix:
localInvMatrix = Matrix(localMatrix)
localInvMatrix.invert()
p = p*localInvMatrix
d = d*localInvMatrix # normalize_v3
p.x += localInvMatrix[3][0]
p.y += localInvMatrix[3][1]
p.z += localInvMatrix[3][2]
#else: # Worldspace, do nothing
d.normalize()
return True, p, d # Origin, Direction
# Mouse is not in any view, return None.
return False, None, None
# Constant function variables
getPickRay.d = Vector(0,0,0) # Perspective, 3d
getPickRay.p = Vector(0,0,0)
getPickRay.fp = Vector(0,0,0)
getPickRay.hms = Vector(0,0,0,0) # ortho only 4d
getPickRay.ortho_d = Vector(0,0,0,0) # ortho only 4d
# TEST FUNCTION
# MOVES & VERTS ON THE ACTIVE MESH.
def main():
ob = Scene.GetCurrent().getActiveObject()
me = ob.getData(mesh=1)
# Loop until the mouse is in the view.
mouseInView = False
while not mouseInView:
screen_x, screen_y = Window.GetMouseCoords()
mouseInView, Origin, Direction = getPickRay(screen_x, screen_y)
if Window.GetMouseButtons() == 1 and mouseInView:
i = 0
time = Blender.sys.time()
while Window.GetMouseButtons() == 1:
i+=1
screen_x, screen_y = Window.GetMouseCoords()
mouseInView, Origin, Direction = getPickRay(screen_x, screen_y, ob.matrix)
if mouseInView:
me.verts[0].co.x = Origin.x
me.verts[0].co.y = Origin.y
me.verts[0].co.z = Origin.z
me.verts[1].co.x = Origin.x - (Direction.x*1000)
me.verts[1].co.y = Origin.y - (Direction.y*1000)
me.verts[1].co.z = Origin.z - (Direction.z*1000)
Window.Redraw(Window.Types.VIEW3D)
print '100 draws in %.6f' % (((Blender.sys.time()-time) / float(i))*100)
if __name__ == '__main__':
main()
自動按鈕是在任何指令碼中新增一堆按鈕的非常簡單的方法。
將 AutoButtons 文字新增到任何指令碼的底部,任何以 _bgui 結尾的函式都將有一個呼叫它的按鈕。
# All functions to be displayed as buttons must use this suffix
GUI_SUFFIX= '_bgui'
BUTTON_LIST = [] # A list if dicts
EVENT = 1000
EVENTNUM = 1000
for func in dir():
if func.endswith(GUI_SUFFIX):
newButton = {}
newButton['name'] = func[:-5].replace('_', ' ')[2:]
newButton['func'] = func + '()'
newButton['event'] = EVENT
BUTTON_LIST.append( newButton )
EVENT+=1
def draw_gui():
# find the width of the widest button
button_height = 16; button_width = 100; ROW = 0
for button in BUTTON_LIST:
Draw.PushButton(button['name'], button['event'], 0, button_height*ROW, button_width, button_height, ''); ROW+=1
def handle_event(evt, val):
if evt in (Draw.ESCKEY, Draw.QKEY) and not val:
Draw.Exit()
def handle_button_event(evt):
if evt >= EVENTNUM and evt < EVENTNUM + len(BUTTON_LIST):
exec(BUTTON_LIST[evt - EVENTNUM]['func'])
else:
print 'invalid', evt
Draw.Register(draw_gui, handle_event, handle_button_event)
可以使用此功能的示例函式
def Print_Object_Selection_bgui():
Blender.Draw.PupMenu('|'.join(ob.name for ob in Blender.Object.GetSelected()))
此指令碼獲取您通常傳遞給 Draw.PupMenu() 的字串,並根據組大小將選單分開。
def PupMenuLess(menu, groupSize=30):
'''
Works like Draw.PupMenu but will add a more/less buttons if the number of
items is greater then the groupSize.
'''
more = [' more...']
less = [' less...']
menuList= menu.split('|')
# No Less Needed, just call.
if len(menuList) < groupSize:
return Draw.PupMenu(menu)
title = menuList[0].split('%t')[0]
# Split the list into groups
menuGroups = [[]]
for li in menuList[1:]:
if len(menuGroups[-1]) < groupSize:
menuGroups[-1].append(li)
else:
menuGroups.append([li])
# Stores the current menu group we are looking at
groupIdx = 0
while True:
# Give us a title with the menu number
numTitle = [ ' '.join([title, str(groupIdx + 1), 'of', str(len(menuGroups)), '%t'])]
if groupIdx == 0:
menuString = '|'.join(numTitle + menuGroups[groupIdx] + more)
elif groupIdx == len(menuGroups)-1:
menuString = '|'.join(numTitle + less + menuGroups[groupIdx])
else: # In the middle somewhere so Show a more and less
menuString = '|'.join(numTitle + less + menuGroups[groupIdx] + more)
result = Draw.PupMenu(menuString)
# User Exit
if result == -1:
return -1
if groupIdx == 0: # First menu
if result-1 < groupSize:
return result
else: # must be more
groupIdx +=1
elif groupIdx == len(menuGroups): # Last Menu
if result == 1: # Must be less
groupIdx -= 1
else: # Must be a choice
return result + (groupIdx*groupSize)
else:
if result == 1: # Must be less
groupIdx -= 1
elif result-2 == groupSize:
groupIdx +=1
else:
return result - 1 + (groupIdx*groupSize)
這裡新增通用 Python 程式碼,在 Python 指令碼編寫時很有用。
計時不同函式的指令碼。
def loopFor():
'''For loop test'''
for i in xrange(1000000):
a=i
def loopWhile():
'''While loop test'''
i=0
while i<1000000:
a=i
i+=1
def time_func(bench_func, iter=4):
''' Run the function 10 times '''
print '',bench_func.__doc__
t= Blender.sys.time()
for i in xrange(iter):
bench_func()
tme= (Blender.sys.time()-t) / 10
print '\tBenchmark %.4f average sec' % tme
return tme
def main():
print '\nRunning tests'
time_func(loopFor)
time_func(loopWhile)
if __name__ == '__main__':
main()
有時您想同時迴圈多個列表。如果您處理的列表很大,那麼為了這個目的建立一個新列表會很慢,並且會使用太多記憶體。此類會獲取多個列表,並將它們視為一個大型列表。而無需建立新列表。
type_list= type([])
type_tuple= type(())
class listIter:
def __init__(self, lists):
if type(lists) != type_list:
self.lists= list(lists)
else:
self.lists= lists
self.idx= self.lidx= 0
def next(self):
if self.lidx==len(self.lists):
raise StopIteration
idx=self.idx
lidx=self.lidx
self.idx+=1
if self.idx==len(self.lists[self.lidx]):
self.idx= 0
self.lidx+=1
return self.lists[lidx][idx]
def __iter__(self):
return self
def __getitem__(self, index):
i=0
for l in self.lists:
if i+len(l)>index:
return l[index-i]
i+=len(l)
raise IndexError
def __setitem__(self, index, value):
i=0
for l in self.lists:
if i+len(l)>index:
l[index-i]= value
return
i+=len(l)
raise IndexError
def __len__(self):
length=0
for l in self.lists:
length+=len(l)
return length
def index(self, value):
i=0
for l in self.lists:
for li in l:
if li == value:
return i
i+=1
raise ValueError
def remove(self, value):
for l in self.lists:
if value in li:
l.remove(i)
return
raise ValueError
def count(self, value):
return sum(l.count(value) for l in self.lists)
def extend(self, value):
for i in value: # See its an iterator
break
self.lists.append(value)
def pop(self, index):
i=0
for l in self.lists:
if i+len(l)>index:
return l.pop(index-i)
i+=len(l)
raise IndexError
def __str__(self):
return '['+ ''.join(str(l)[1:-1] for l in self.lists) +']'
def sort(self):
'''Cant to a full sort, just do a par'''
self.lists.sort()
for l in self.lists:
l.sort()
def append(self, value):
self.lists[-1].append(value)
def reverse(self):
for l in self.lists:
l.reverse()
self.lists.reverse()
一些示例
for i in listIter( (range(10), range(22), range(5)) ):
print i
另一個示例,使用此迭代器和列表推導從 3 個網格中獲取頂點,並將它們新增到一個網格中。
from Blender import Mesh
newme= Mesh.New()
# Using the iterator
newme.verts.extend( [v.co for v in listIter((me1.verts, me2.verts, me3.verts))] )
# Without the iterator
newme.verts.extend( [v.co for v in me1.verts ] )
newme.verts.extend( [v.co for v in me2.verts ] )
newme.verts.extend( [v.co for v in me3.verts ] )
感謝 SJH 07/29/2004 20:26:03
nybblechr_to_01_dqs={'-':'-','0':'0000', '1':'0001', '2':'0010', '3':'0011',
'4':'0100', '5':'0101', '6':'0110', '7':'0111',
'8':'1000', '9':'1001', 'A':'1010', 'B':'1011',
'C':'1100', 'D':'1101', 'E':'1110', 'F':'1111'}
# Int to binary
def i2b(j, wd=0):
return ''.join(nybblechr_to_01_dqs[x] for x in '%02X' % j))[-wd:].zfill(wd)
# Char to binary
def c2b(c, wd=0):
return i2b(ord(c))
# String to binary
def s2b(s, wd=0):
return ''.join(nybblechr_to_01_dqs[x] for x in ''.join('%02X' % ord(c) for c in s))[-wd:].zfill(wd)
# Binary to char
def b2c(b):
chr(int(b,2))
返回隨機化的列表。注意如果可以匯入 random,請使用 random.shuffle(ls) 替代。
def randList(ls):
lsCopy = ls[:]
randList = []
lenList = len(lsCopy)
while lenList != len(randList):
randIndex = int( Noise.random() * len(lsCopy) )
randList.append( lsCopy.pop( randIndex ) )
return randList
從列表中刪除重複項,修改原始列表。(將使用物件的 cmp() 函式)
def RemDoubles(List):
lIdx = 0
while lIdx < len(List):
if List.count(List[lIdx]) > 1:
List.pop(lIdx)
continue
lIdx+=1
返回一個沒有重複項的新列表。
def RemDoublesHash(myList):
return list(set(myList))
Blender 中的許多屬性都是標誌,並存儲在 2 的指數和中。要找出特定標誌是否已設定幷包含在和中,請嘗試使用此函式
def powList(self, x):
tmpx = x
exp = 0
expList = []
while tmpx != 0:
tmp = 2**exp
if tmp > tmpx:
elem = 2**(exp-1)
expList.append(elem)
tmpx -= elem
exp = 0
else:
exp += 1;
return expList
以這種方式呼叫函式
lmp = Lamp.Get(thisObj.data.getName())
lmpMode = lmp.getMode()
lmpFlags = self.powList(lmpMode)
if 16 in lmpFlags:
...
允許在實數系統中建立分數資料以及對分數資料的所有操作。
class fraction:
# Types without importing type - Does not retuire a python install.
type_float = type(0.1)
type_int = type(1)
def __init__(self, num, den=1):
if den == 0:
raise ValueError, 'Division by zero'
g = self.gcd(num, den)
self.num = num / g
self.den = den / g
def __str__(self):
return "%d/%d" % (self.num, self.den)
def __mul__(self, other):
if type(other) is fraction.type_int:
other = fraction(other)
elif type(other) is fraction.type_float:
return self.eval() * other
if not isinstance(other, fraction):
raise ValueError, 'Unsupported operand type for multiply operation ' + str(type(other))
return fraction(self.num * other.num, self.den * other.den)
__rmul__ = __mul__
def __add__(self, other):
if type(other) is fraction.type_int:
other = fraction(other)
elif type(other) is fraction.type_float:
return self.eval() + other
if not isinstance(other, fraction):
raise ValueError, 'Unsupported operand type for addition operation ' + str(type(other))
num = self.num * other.den + self.den * other.num
den = self.den * other.den
return fraction(num, den)
def __cmp__(self, other):
if type(other) is fraction.type_int or type(other) is fraction.type_float:
return self.eval() - other
if not isinstance(other, fraction):
raise ValueError, 'Comparative operation no supported for operand ' * type(other)
return (self.num * other.den - other.num * self.den)
def __neg__(self):
return fraction(self.num * -1, self.den)
def __invert__(self):
return fraction(self.den, self.num)
def __sub__(self, other):
return self + -other
def __rsub__(self, other):
return other + -self
def __div__(self, other):
return fraction(self.num, self.den) * fraction(other.den, other.num)
def __rdiv__(self, other):
return fraction(self.den, self.num) * fraction(other.num, other.den)
def __pow__(self, other):
if type(other) is fraction.type_int:
return fraction(self.num ** other, self.den ** other)
elif type(other) is fraction.type_float:
a = self.eval()
if a > 0:
return a ** other
else:
raise ValueError, 'Negative number raised to fractional power'
if not isinstance(other, fraction):
raise ValueError, 'Unsupported operand type for exponential operation ' + str(type(other))
return self.eval() ** other.eval()
def gcd(self, m, n):
if m % n:
return self.gcd(n, m % n)
return n
def eval(self):
return float(self.num) / self.den
##### Usage: #####
a = fraction(3, 4)
b = fraction(5, 6)
print a * b
print a - 3
print a ** b
#invalid - raising a negative number to a fractional power
print (-a)**b
當您從物件/網格/場景... 名稱建立檔名時,可以使用此函式。Blender 在名稱中支援許多檔案系統可能不支援的字元。
saneFilechars 將這些字元替換為“_”。
def saneFilechars(name):
for ch in ' /\\~!@#$%^&*()+=[];\':",./<>?\t\r\n':
name = name.replace(ch, '_')
return name
處理顏色的指令碼的地方。
將紅色/綠色/藍色轉換為色調/飽和度/值
r、g、b 值範圍為 0.0 到 1.0
h = [0,360],s = [0,1],v = [0,1]
如果 s == 0,則 h = -1 (未定義)
色調/飽和度/值模型由 A. R. Smith 於 1978 年建立。它基於諸如色調、陰影和色調(或色系、純度和強度)等直觀的顏色特徵。座標系是圓柱形的,顏色在六角錐體內部定義。色調值 H 的範圍為 0 到 360º。飽和度 S 是強度或純度的程度,範圍為 0 到 1。純度是指向顏色中添加了多少白色,因此 S=1 使顏色最純(沒有白色)。亮度 V 的範圍也在 0 到 1 之間,其中 0 是黑色。
def RGBtoHSV(R,G,B):
# min, max, delta;
min_rgb = min( R, G, B )
max_rgb = max( R, G, B )
V = max_rgb
delta = max_rgb - min_rgb
if not delta:
H = 0
S = 0
V = R # RGB are all the same.
return H,S,V
elif max_rgb: # != 0
S = delta / max_rgb
else:
R = G = B = 0 # s = 0, v is undefined
S = 0
H = 0 # -1
return H,S,V
if R == max_rgb:
H = ( G - B ) / delta # between yellow & magenta
elif G == max_rgb:
H = 2 + ( B - R ) / delta # between cyan & yellow
else:
H = 4 + ( R - G ) / delta # between magenta & cyan
H *= 60 # convert to deg
if H < 0:
H += 360
return H,S,V
將色調/飽和度/值轉換為紅色/綠色/藍色
def HSVtoRGB(H,S,V):
if not S: # S == 0
# achromatic (grey)
# R = G = B = V
return V,V,V # RGB == VVV
H /= 60; # sector 0 to 5
i = int( H ) # round down to int. in C its floor()
f = H - i # factorial part of H
p = V * ( 1 - S )
q = V * ( 1 - S * f )
t = V * ( 1 - S * ( 1 - f ) )
if i == 0:
R,G,B = V,t,p
elif i == 1:
R,G,B = q,V,p
elif i == 2:
R,G,B = p,V,t
elif i == 3:
R,G,B = p,q,V
elif i == 4:
R,G,B = t,p,V
else: # 5
R,G,B = V,p,q
return R,G,B
from Blender import *
def main():
# New Curve and add to Scene.
scn = Scene.GetCurrent()
cu = Curve.New()
# cu.setResolu(1)
x=y=z=w=t = 1
cu.appendNurb([x,y,z,w,t])
cu[0].type = 0 # Poly line
ob = Object.New('Curve')
ob.link(cu)
scn.link(ob)
ob.sel = 1 # Make active and selected
# Initialize progress bar for writing
Window.DrawProgressBar(0.0, '')
ticker = 0.0 # Used to cycle the progress bar
# Pause before drawing
while not Window.GetMouseButtons() & Window.MButs['L']:
sys.sleep(10)
Window.DrawProgressBar(ticker, 'Left Mouse to Draw')
ticker += 0.01
if ticker > 0.98: ticker = 0
oldx=oldy = -100000
# Mouse Clicked, lets draw
while Window.GetMouseButtons() & Window.MButs['L']:
x,y = Window.GetMouseCoords()
print abs(x-oldx)+abs(y-oldy)
if (oldx == x and oldy == y) or abs(x-oldx)+abs(y-oldy) < 10: # Mouse must have moved 10 before adding the next point
pass
else:
z = 0 # 2D Drawing for now
w = 100 #Weight is 1
cu.appendPoint(0, [x*0.001,y*0.001,z,w]) # Can add tilt here.
cu.update()
Window.Redraw(Window.Types.VIEW3D)
Window.DrawProgressBar(ticker, 'Drawing...')
ticker += 0.01
if ticker > 0.98: ticker = 0
oldx,oldy = x,y # Store the old mouse location to compare with new.
# Clear the progress bar
Window.DrawProgressBar(1.0, '')
main()
# recursive dir creation.
def _mkdir(newdir):
import os, sys
"""works the way a good mkdir should :)
- already exists, silently complete
- regular file in the way, raise an exception
- parent directory(ies) does not exist, make them as well
"""
if os.path.isdir(newdir):
pass
elif sys.exists(newdir):
raise OSError("a file with the same name as the desired " \
"dir, '%s', already exists." % newdir)
else:
head, tail = os.path.split(newdir)
if head and not os.path.isdir(head):
_mkdir(head)
#print "_mkdir %s" % repr(newdir)
if tail:
os.mkdir(newdir)
from Blender import *
from Blender.Scene import Render
if sys.sep == '\\':
path="c:\\tmp\\renderfarm\\render"
else:
path="/tmp/renderfarm/render"
# Should probably create the paths if not existing.
_mkdir(path)
scene= Scene.GetCurrent()
context = scene.getRenderingContext()
context.setRenderPath(path)
context.setImageType(Scene.Render.PNG)
context.enableExtensions(1)
context.renderAnim()
此指令碼從場景中的所有攝像機渲染動畫,它根據攝像機建立新的名稱,並將場景保留為原始狀態。
確保使用有用的攝像機名稱。
from Blender import Object, Scene
sce= Scene.GetCurrent()
context = sce.getRenderingContext()
output_path_orig= context.getRenderPath()
cams= [ob for ob in sce.getChildren() if ob.getType()=='Camera']
# backup the active cam.
orig_cam= sce.getCurrentCamera()
# loop over all the cameras in this scene, set active and render.
for i, c in enumerate(cams):
print '\tRendering %i of %i cameras.' % (i, len(cams))
context.setRenderPath('%s_%s_' % (output_path_orig, c.name)) # use a unique name
sce.setCurrentCamera(c) # set this camera to be active.
context.renderAnim()
print 'Done per camera render'
if orig_cam:
sce.setCurrentCamera(orig_cam) # restore the original cam
# restore the original path.
context.setRenderPath(output_path_orig)
此指令碼需要用作重新繪製指令碼連結。它檢查當前影像的日期,並嘗試重新載入影像,如果成功,則重新繪製圖像。
import Blender
try:
Blender.my_image_time
except:
Blender.my_image_time=0
import os
img= Blender.Image.GetCurrent() # currently displayed picture.
if img: # Image isnt None
path= Blender.sys.expandpath(img.filename)
if Blender.sys.exists(path):
t= os.path.getctime(path)
if t != Blender.my_image_time:
img.reload()
Blender.Window.Redraw(Blender.Window.Types.IMAGE)
Blender.my_image_time= t # global, persists between running the scripts.
此指令碼需要用作連結到文字物件的幀更改指令碼。更改 Years.append 行以選擇時間軸的年份。
import Blender as B
Years = []
# year = [year, initial frame, duration of frames]
Years.append([1800, 1, 10])
Years.append([1850, 100, 50])
Years.append([1994, 170, 100])
Years.append([2008, 300, 50])
Years.append([2050, 400, 50])
def when (frame, years):
iniY = 0
for y in range(len(years)):
if frame > years[y][1]:
iniY = y
iniYear = years[iniY][0]
iniFrame = years[iniY][1]
iniFrameDelay = years[iniY][2]
finYear = years[iniY+1][0]
finFrame = years[(iniY+1)][1]
frameRange = finFrame - (iniFrame + iniFrameDelay)
yearRange = finYear - iniYear
normFrame = float(frame - iniFrame - iniFrameDelay)
normFrame = normFrame/frameRange
if normFrame > 0:
newYear = str(int(iniYear + (yearRange * normFrame)))
else:
newYear = iniYear
return str(newYear)
if B.bylink:
actualFrame = B.Get("curframe")
year = B.link
dynYear = year.getData()
oldYear=dynYear.getText()
newYear=when (actualFrame,Years)
if newYear != oldYear:
dynYear.setText(newYear)
year.makeDisplayList()
B.Window.RedrawAll()
自 Blender 2.41 起,GZip 壓縮支援已整合到 Blender 中。因此,您可以壓縮所有 blend 檔案,它們仍然可以按預期開啟。
此實用程式在您的硬碟上搜索 blend 檔案,如果它們尚未壓縮,則對其進行 gzip 壓縮。
注意:需要 python3。
#!/usr/bin/python3
root_dir = '/mango/pro'
import os
import os.path
def blend_path_list(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
if filename.endswith(".blend"):
yield os.path.join(dirpath, filename)
def isblend_nogz(path):
try:
f = open(path, 'rb')
is_blend = (f.read(7) == b'BLENDER')
f.close()
return is_blend
except:
return False
def main():
print('Searching "%s"...' % root_dir)
files = list(blend_path_list(root_dir))
files.sort()
print('done.')
#print files
tot_files = len(files)
tot_compressed = tot_blends = tot_alredy_compressed = 0
tot_blend_size = 0
tot_blend_size_saved = 0
for f in files:
if len(f) >= 6: # .blend is 6 chars
f_lower = f.lower()
if (f_lower.endswith(".blend") or
f_lower[:-1].endswith(".blend") or
f_lower[:-2].endswith(".blend")): # .blend10 +
print(f, "...", end="")
tot_blends += 1
# allows for dirs with .blend, will just be false.
if isblend_nogz(f):
print("compressing ...", end="")
tot_compressed += 1
orig_size = os.path.getsize(f)
tot_blend_size += orig_size
os.system('gzip --best "%s"' % f)
os.system('mv "%s.gz" "%s"' % (f, f)) # rename the gz file to the original.
new_size = os.path.getsize(f)
tot_blend_size_saved += orig_size - new_size
print('saved %.2f%%' % (100 - (100 * (float(new_size) / orig_size))))
else:
print('alredy compressed.')
tot_alredy_compressed += 1
print('\nTotal files:', tot_files)
print('Total Blend:', tot_blends)
print('Total Blend Compressed:', tot_compressed)
print('Total Alredy Compressed:', tot_alredy_compressed)
print('\nTotal Size in Blends: %sMB' % (((tot_blend_size) / 1024) / 1024))
print('Total Saved in Blends: %sMB' % (((tot_blend_size_saved) / 1024) / 1024))
if __name__ == '__main__':
main()