跳转到内容

Blender 3D:融入 Python/菜谱

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

图像函数

[编辑 | 编辑源代码]

在外部程序中编辑图像

[编辑 | 编辑源代码]

这在 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)

文本函数

[编辑 | 编辑源代码]

在 Unix 中粘贴文本

[编辑 | 编辑源代码]

在 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])

三角化 NMesh

[编辑 | 编辑源代码]

这是一个供其他脚本使用的函数,如果您想通过只处理三角形来简化您的工作,它会很有用。
最短边方法用于将四边形划分为 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 来自动平滑流形网格,它需要 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] ])

复制 NMesh 面

[编辑 | 编辑源代码]

返回一个新面,它具有与原始面相同的属性,但没有顶点

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

[编辑 | 编辑源代码]

缩放所有选中网格对象的 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

[编辑 | 编辑源代码]

将顶点 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()

获取 3 个点之间的角度

[编辑 | 编辑源代码]

获取线段 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

点在三角形内 (2D)

[编辑 | 编辑源代码]

如果 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 的幂值(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

Blender 对象脚本

[编辑 | 编辑源代码]

链接复制对象

[编辑 | 编辑源代码]

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 轨道示例

[编辑 | 编辑源代码]

操作 NLA 轨道和动作轨道的示例代码。

# Nov 16 2006
#
# Mike Stramba
# [email protected]
# 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)

Blender 窗口和用户界面脚本

[编辑 | 编辑源代码]

鼠标位置 3D 空间

[编辑 | 编辑源代码]
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 代码,在 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

颜色脚本

[编辑 | 编辑源代码]

处理颜色的脚本的地方。

RGB 到 HSV

[编辑 | 编辑源代码]

将红色/绿色/蓝色转换为色调/饱和度/值

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


HSV 到 RGB

[编辑 | 编辑源代码]

将色调/饱和度/值转换为红色/绿色/蓝色

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()

外部实用程序

[编辑 | 编辑源代码]

压缩所有 Blend 文件 (仅限 Unix)

[编辑 | 编辑源代码]

自 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()
华夏公益教科书