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