Blender 3D: Blending Into Python/Printable version
This is the print version of Blender 3D: Blending Into Python You won't see this message or any elements not part of the book's content when you print or preview this page. |
The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python
Optimize
Writing Optimal Exporters and Importers for Blender:Python
[edit | edit source]If you are starting out in writing a script, don't take these guidelines too seriously, its worth getting things working and then sight a need for optimization before you begin to look for areas to optimize your script.
If you're making a tool for others, keep in mind that they might use it with larger datasets than those you have been testing with. So before releasing it can be good time to try to optimize.
For more generic python performance tips see http://wiki.python.org/moin/PythonSpeed/PerformanceTips
Getting Objects (Exporting/General Blender)
[edit | edit source]Blender.Object.Get() is often used to get all objects for exporting. This is bad practice since Object.Get() will return objects from every scene in Blender, this is almost never what the user wants and could result in overlapping data, but also could take a long time if the user has 2 or more large scenes consuming a lot of memory.
Instead use Blender.Scene.GetCurrent().getChildren() This returns all objects from the current scene.
Another alternative is to use Blender.Object.GetSelected() which returns selected objects on visible layers in the current scene.
Getting Mesh data (Exporting Only)
[edit | edit source]Meshes are not just thin wrappers like most of the other Types in Blender so you want to avoid meshObject.getData() and meshObject.data as much as possible, its best practice only calling each mesh data once.
To get the name of the data the object references do not use ob.getData().name simply to get the name of the object. Instead use obj.getData(1) ... meaning obj.getData(1=name_only) this is nice because it works for all data types, its just that
Note, recently (as of 15/10/05) the addition of the Mesh module (a thin wrapper around Blenders mesh data) means that you can do a meshObject.getData(mesh=1) without the problem NMesh has, however this is new and doesn't support all the NMesh functions.
Use new python functionality mesh.transform() (Exporting Only)
[edit | edit source]As of Blender 2.37 nmesh.transform(ob.matrix) can be used to transform an NMesh by a 4x4 transformation matrix.
On my system it is over 100 times faster than transforming each vert's location and normal by a matrix within python. Though the overall speed gained by using this in my obj exporter was about %5.
make sure that if you are exporting vertex normals add a 1,
mesh.transform(matrix, recalc_normals=1) This avoids un-needed vertex normal transforming.
Beware of python hogging memory. (python generally)
[edit | edit source]You may ignore this point, for small applications, but I have found that for larger scenes this can become a problem if not allowed for early on.
A List in python (and therefore blender:python) is never deallocated, however python can re-use the memory whilst the script is running. Basically making large lists in python and a lot of recursive variable assignment can leak memory that can only be regained with a restart. - *If you can- avoid making large lists.This is Fixed in Python 2.4.1 which will compile with blender.
Lists (Python General)
[edit | edit source]List Lookup
[edit | edit source]In Python there are some tricky list functions that save you having to search through the list.
Even though you're not looping on the list data python is, so you need to be aware of functions that will slow down your script by searching the whole list.myList.count(listItem) myList.index(listItem) myList.remove(listItem) if listItem in myList: ...The above functions performs a full loop on the list, so if you can avoid using them on large lists, your script will run faster.
Modifying Lists
[edit | edit source]In python we can add and remove from a list, This is slower when lists are modified in length at the start of the list, since all the data after the index of modification needs to be moved up or down 1 place.
Of course the most simple way to add onto the end of the list is to use myList.append(listItem) or myList.extend(someList) and the fastest way to remove an item is myList.pop()
To use an index you can use myList.insert(index, listItem) and pop also takes indicies for list removal, but these are slower.
Sometimes its faster (but more memory hungry) to just rebuild the list.
Say we want to remove all triangle faces in a list.
Rather thenfIdx = len(mesh.faces) # Loop backwards while fIdx: # While the value is not 0 fIDx -=1 if len(mesh.faces[fIDx].v) == 3: mesh.pop(fIdx) # Remove the triIt is faster to build a new list with list comprehension.
mesh.faces = [f for f in mesh.faces if len(f.v) != 3]Adding list items
[edit | edit source]If you have a list that you want to add onto another list, rather then appending in a for loop, use
myList.extend([li1, li2...])instead of...
for l in someList: myList.append(l)Note that insert can be used when needed, but it is slower than append especially when inserting at the start of a long list.
This example shows a very suboptimal way of making a reversed list.for l in someList: myList.insert(0,l)
Removing List Items
[edit | edit source]Use myList.pop(i) rather then myList.remove(listItem)
This requires you to have the index of the list Item but is faster since remove needs to search the list.
Using pop for removing list items favors a while loops instead of a for loop.
Here is an example of how to remove items in 1 loop, removing the last items first, which is faster (as explained above)listIndex = len(myList) while listIndex: listIndex -=1 if myList[listIndex].someTestProperty == 1: myList.pop(listIndex)
A fast way of removing items, where you can mess up the list order, is to swap 2 list items, so the item you remove is always last.popIndex = 5 # Swap so the popIndex is last. myList[-1], myList[popIndex] = myList[popIndex], myList[-1] # Remove last item (popIndex) myList.pop()When removing many items in a large list this can provide a good speedup.
Avoid Copying Lists
[edit | edit source]When passing a list/dictionary to a function, it is better to have the function modify the list rather then returning a new list. This means python dosn't need to create a new list in memory.
Functions that modify a list in their place are more efficient then functions that create new lists.
normalize(vec) faster: no re-assignment ...is faster then.
vec = normalize(vec) slower, only use for functions that are used to make new, unlinked lists.
Also be note that passing a sliced list makes a copy of the list in python memory eg..
foobar(mylist[4:-1])
If mylist was a large list of floats, a copy could use a lot of extra memory.
Dictionaries (Python General)
[edit | edit source]Dict Lookup
[edit | edit source]When you access a dictionary item, someDict['foo'] A lookup is performed, pythons lookups are very fast, but if your are accessing this data in a loop its better to make a variable that can be referenced faster.
for key in someDict.keys(): ...wip
Strings
[edit | edit source]Pathnames (Exporting)
[edit | edit source]Blender pathnames are not compatible with path names from other generic python modules.
python native functions don't understand blenders // as being the current file dir in blender and # to be the current framenumber.
A common example is where your exporting a scene and want to export the image paths with it.
Blender.sys.expandpath(img.filename)
Will give you the absolute path, so you can pass the path to functions from other python modules.
Correct string parsing (Import/Exporting)
[edit | edit source]Since many file formats are ASCII, the way you parse/export strings can make a large difference in how fast your program runs.
When importing strings to make into blender there are a few ways to parse the string.Passing String
[edit | edit source]Use float(string) rather then eval(string) and if you know the value will be an int then int(string) float() will work for an int too but its faster if ints are converted as ints.
Checking String Start/End
[edit | edit source]If your checking the start of a string for a keyword, use...
if line.startswith('vert '):...rather then
if line[0:5] == 'vert ':
Using Startswith is slightly faster (about 5%).
myString.endswith('foobar') can be used for line endings too.
Also, if your unsure whether the text is upper or lower case use lower or upper string function. Eg. if line.upper().startswith('VERT ')
Writing Strings to a File (Python General)
[edit | edit source]Here are 3 ways of joining multiple strings into 1 string for writing
This really applies to any area of your code that involves a lot of string joining.
Pythons string addition. Dont use if you can help it, especially in the main data writing loop.file.write(str1 + ' ' + str2 + ' ' + str3 + '\n')
String formatting. Use this when you're writing string data from floats and int'sfile.write('%s %s %s\n' % (str1, str2, str3))
Pythons string joining function. To join a list of stringsfile.write(' '.join([str1, str2, str3, '\n']))
join is fastest on many strings, string formatting is quite fast too (better for converting data types). And string arithmetic is slowest.
Other
[edit | edit source]Profile your code (Python General)
[edit | edit source]Simply timing sections of your code will point you to the part that needs optimizing. Eg
time1 = Blender.sys.time() for i in range(10): x = i*2 print Blender.sys.time() - time1
Psyco JIT Compiler (Python General)
[edit | edit source]A python module called psyco exists that can dynamically compile some of python functions. Most of the speed gains affect algorithms written in python, so importers and exporters have less to gain from them then scripts that deal with 3d math.
For many scripts, a no brainier way of using psyco is to do a.
import psyco psyco.full()Psyco can also profile your code.
import psyco psyco.profile()
For scripts to be distributed it is polite to try loading psyco to avoid raising an error.try: import psyco psyco.full() except: print 'For optimal performance on an x86 CPU, install psyco'Note that psyco reports to use a lot of system memory, if you run out of ram it may be best not to use psyco.
Use Try/Except Sparingly
[edit | edit source]The try function is useful to save time writing code to check a condition,
However 'try' is about 10 times slower than 'if', so don't use 'try' in areas of your code that execute in a loop that runs many times (1000's or more).
There are cases where using 'try' is faster than checking whether the condition will raise an error, so it is worth experimenting.
Python Objects
[edit | edit source]Use "is" instead of "=="
[edit | edit source]In some cases "is" can be used instead of "==" for comparison, "is not" for "!=".
"==" Checks whether the 2 variables are the same value, where as "is" tests that the python object is the same, shares the same memory - are both an instance of the same object. (Python Object, not Blender a Object)
The advantage of using "is" is that its faster since it dosent need to compare as much data.
Here is a benchmark test that compares a python class and a blender vector- one the same and one different.from Blender import * print '\n\nStarting benchmark.' # Test class class a: pass class1 = a() class2 = a() vec1 = Mathutils.Vector(1,2,3) vec2 = Mathutils.Vector(1,2,4) f = 400000 t = sys.time() for i in range(f): class1 is class1 class1 is class2 vec1 is vec1 vec1 is vec2 print ' Is Banchmark %.6f' % (sys.time() - t) t = sys.time() for i in range(f): class1 == class1 class1 == class2 vec1 == vec1 vec1 == vec2 print ' == Benchmark %.6f' % (sys.time() - t) print 'Done\n'My Computer showed the following results. I should do more tests- but this is representative of other tests.
Starting benchmark. Is Banchmark 0.284363 == Benchmark 3.367173 Done
The same test with vec1, vec2, class1, class2 as ints and floats.
"is" still faster.Starting benchmark. Is Banchmark 0.272853 == Benchmark 0.322123 Done
Cookbook
Image Functions
[edit | edit source]Edit Image in External Program
[edit | edit source]This runs in linux (probably any unix), and launches The Gimp. It could probably be modified to launch Photoshop in windows.
In Gnome, KDE and Mac OS X, you can use a command to open documents using the default or a specified application.
- KDE: kfmclient openURL <URL, relative or absolute path>
- Gnome: gnome-open <any URL or path Gnome understands>
- Mac OS X: open [-a <application name>] <path>
Win32 has some open command too, maybe somebody could add this in.
#!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()
Find Images
[edit | edit source]This script recursively searches for images that have broken files references.
It works by giving the user a root path, then finds and re-links all images within that path.
Its very useful when migrating projects to different computers.
#!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('//'))
Remove Double Images
[edit | edit source]This script finds images that are referenced more than once, and looks through all meshes texface's, and assigns only one of the images.
If a face has no users the image is removed.
This is useful because when an image is loaded more than once, it's also loaded into system memory and graphics card memory more than once, wasting resources.
Support for image type textures still needs doing.
#!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()
Material Functions
[edit | edit source]Toon Material Batch Conversion Script
[edit | edit source]this script changes *all* materials in your currently open blend file to toon materials. after executing, check the blender console for script output.
as this script alters *all* of your material settings in your currently opened blend file, you should *not* run it on a project that has not been saved yet! changes made to materials while using this script cannot be undone!
note: changes are not permanently committed to your blend file unless you choose to save your file after executing this script.
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()
Curve Functions
[edit | edit source]Length of a curve
[edit | edit source]This function gets the combined length of all edges. Most useful to get the length of a curve. Be careful, because it will get the length of every curve in a curve object!
Note that this function doesn't take the object's transformation into account when getting the length.
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)
Text Functions
[edit | edit source]Paste Text in Unix
[edit | edit source]Paste text in X11, requires 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)
Save all Texts as files
[edit | edit source]Saves all text editor texts as files in the current working directory. WARNING: This will overwrite files with those names!
import Blender
texts=Blender.Text.Get()
for text in texts:
out=file(text.name, 'w')
for line in text.asLines():
out.write(line+'\n')
Mesh Functions
[edit | edit source]Examples and functions for Blenders NMesh, GMesh and new Mesh module.
Mesh tool template
[edit | edit source]Use this to base your editmode mesh tool on.
#!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()
Desaturate Meshes VertCol
[edit | edit source]Uses the new Mesh module. In Blender 2.4 only. Desaturates using the same weighting as Photoshop uses.
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)
Point inside a Mesh
[edit | edit source]This function returns 1/0 depending on whether the provided point is inside a mesh. It relies on the mesh having a continuous skin, no holes in it. (Otherwise the problem doesn't make sense.)
It uses the method of seeing how many face intersections there are along a line segment between that point, and a point somewhere outside the meshs bounds.
An even number of intersections means it's outside, an odd for inside. Hence we return len(intersections) % 2 where intersections generates a list of intersections.
This function uses a Z direction vector so we can save some CPU cycles by first doing an X/Y bounds test to see if the points could intersect, before doing a full ray intersection.
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
Scanfill for importers
[edit | edit source]This function takes a mesh and a list of vert indicies representing an ngon. It returns a list of tri indicies that make up the scanfilled face. This is much more useful for importers.
it also handles cases where scanfill does not work by returning a triangle fan.
It may be faster than using the mesh.fill() function on your original mesh because cycling editmode can be slow on a lot of data. Another advantage with this function over simply using fill() is you can be sure the faces will be flipped the right way, according to the order of the indicies.
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])
Triangulate NMesh
[edit | edit source]This is a function to be used by other scripts, its useful if you want to make your life simpler by only dealing with triangles.
The shortest edge method is used for dividing the quad into 2 tri's.
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
Fix vertex winding in "bowtie" quads
[edit | edit source]Sometimes you may encounter quad faces which, although correctly coplanar, aren't quite "full". This comes from the vertices being in the wrong order, and causes the face to overlap itself, often leaving undesired holes and black areas where the normals point the wrong way. To see what this all means, just make a plane and switch places for the vertices at any edge. The connecting edges will then cross. Since the normals of the face don't make sense in this situation, the script can't guarantee that the normal points outwards after finishing.
#!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()
Remove Verts Without removing entire faces
[edit | edit source]Script that uses the mesh template above. removes verts but surrounding quads will be converted to tri's.
Note This only works with 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 AutoSmooth Mesh
[edit | edit source]This function uses GMesh to autosmooth manifold meshes, it requires the GMesh module.
Be careful because it will smooth your mesh in place, so make a copy of your original object if you don't want it modified.
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
ScanFill
[edit | edit source]To simulate typing "Shift F" in blender to create scanfill selected edge loop (edit mode only)
This will only work if the 3D view is open.
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)
Expanded Scanfill function
[edit | edit source]Self contained scanfill function, based on the code above. Note, this function needs a 3D view to be available
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()
Example function usage.
scanFillPoints([[-1,-1,0], [1,-1,1], [1,1,0], [0,0,0.2], [0,1,-.1], [0.1,1,-0.3] ])
Copy NMesh Face
[edit | edit source]Returns a new face that has the same properties as the original face but with no verts
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
Returns the Faces centre as a Vector
[edit | edit source]Note, Blenders Mesh API now has face.cent access
Take 1 NMFace and return a vector as its centre, will use an existing vector object "cent" if provided.
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)
Flip Faces Up
[edit | edit source]I used this script to flip all faces in many terrain meshes to point up. It uses Mesh as opposed to 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()
Scale UVs
[edit | edit source]Scales all uv coords for selected mesh objects.
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()
Find a material in all Meshes in all Scenes
[edit | edit source]Sometimes You have a lot of mesh object and materials you don't want any of them to use. This script can help you find those objects.
#!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()
Faces share an edge
[edit | edit source]The 2 faces share an edge, its best to make sure your not comparing the same faces, and remove the first '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
Get Edge Angles
[edit | edit source]Returns a list of angles, the combine angle difference of all faces that use the edges. the angles returned are in sync with mesh.edges. Edges with 0 or 1 faces will have Zero angle.
This function uses Blender.Mesh not Blender.NMesh mesh data.
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
Mesh Ray Intersect
[edit | edit source]Intersect a ray with a mesh, Assume the mesh has no loc/size/rot.
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
Copy Vertex UV to Face UV
[edit | edit source]Copies the Vertex UV coordinates (Sticky) to face UV coordinates (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()
Math Functions
[edit | edit source]Here is the place to add math examples, they can be blender specific or generic python math functions.
Changing Rotation Axis Order
[edit | edit source]If you ever have trouble converting between different rotation systems its possible that the order of rotations is the problem.
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()
Get Angle between 3 points
[edit | edit source]Get the angle between line AB and BC where b is the elbow.
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
Point inside a tri (2D)
[edit | edit source]Returns True if pt is inside the triangle.
only gives a correct answer when pt lies on the triangles plane.
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
Apply Matrix
[edit | edit source]Applys a 4x4 transformation as returned by object.getMatrix(), to a vector (3d point in space)
This is useful for finding out the position of a vertex in worldspace.
Blender 2.43 supports this simply by doing "newvwec = vec*matrix" but its good to know how to do it manually
#==================#
# 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]
2D Line Intersection
[edit | edit source]Intersect 2 lines, if so where.
If there is no intersection the retured X value will be None and the y will be an error code.
The first line is (x1,y1, x2,y2), the second (_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
Round to power of 2
[edit | edit source]This function takes any number and rounds it to the nearest power of 2 value (2,4,8,16,32,64,128,256,512,1024,2048, 4096...) This is useful for rounding textures to sized that load into graphics card memory.
It returns 3 values. rounded down, round closest, rounded up.
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.
Closest Point (snapping)
[edit | edit source]Returns the vector that is closest to point. Good for snapping.
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 Object Scripts
[edit | edit source]Linked Duplicate Object
[edit | edit source]There is no function in B:Python to make a linked duplicate of an object (Alt+D) so here is a function that does it for you.
Note Since this was written Blender.Object.Duplicate() has been added as well as 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"
Select Double Objects
[edit | edit source]Finds double object- objects that have the same dataname, type and loc/size/rot
#!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 strips Examples
[edit | edit source]Example code for manipulating NLA strips and action strips
# Nov 16 2006
#
# Mike Stramba
# mstramba@sympatico.ca
# BlenderArtists Mike_S
#
import Blender
from Blender import *
from Blender.Armature import NLA
ctr = 1
numStrips = 1
vflags ={32:'LOCK_ACTION',1:'SELECT',2:'STRIDE_PATH',8:'HOLD',16:'ACTIVE'}
def tranflag(flag):
if flag:
print
for v in vflags:
t = flag & v
if t:
print '\t\t',v,vflags[t]
def showStrip(strip):
print ctr,'/',numStrips
print strip.action.name
print '\tstripStart',strip.stripStart
print '\tstripEnd',strip.stripEnd
print '\tactionStart',strip.actionStart
print '\tactionEnd',strip.actionEnd
print '\tblendin',strip.blendIn
print '\tblendout',strip.blendOut
print '\tflag',strip.flag,
tranflag(strip.flag)
print '\tmode',strip.mode
print '\tbrepeat',strip.repeat
print '\tstrideAxis',strip.strideAxis
print '\tstrideBone',strip.strideBone
print '\tstrideLength',strip.strideLength
armOb=Object.Get('Armature')
actions=Armature.NLA.GetActions()
#
# Actions named 'rot', 'move', 'Run' assumed to exist, or substitute
# your own action names
#
rotAct = actions['rot']
movAct = actions['move']
runAct = actions['Run']
#
# get all NLA strips for this object
#
Char1NLAstrips = armOb.actionStrips
#
# set the current frame to where you want NLA strips to initially appear
# in the NLA editor
frame = 1
Blender.Set('curframe',frame)
#
# remove all NLA strips
#
Char1NLAstrips[:] = []
#
# some different ways of adding action strips to the NLA editor
#
Blender.Object.Get('Armature').actionStrips.append(Blender.Armature.NLA.GetActions()['tester'])
Char1NLAstrips.append(Blender.Armature.NLA.GetActions()['UpDown'])
armOb.actionStrips.append(rotAct)
Char1NLAstrips.append(movAct)
Char1NLAstrips.append(actions['Run'])
#
# get a strip
#
strip0 = Char1NLAstrips[0]
print '\nstrip0.action.name ="'+strip0.action.name+'"'
#
# show it's properties
#
showStrip(strip0)
#
# change it's stripStart, stripEND (add 50 frames)
# (effectively moving the strip
strip0.stripEnd += 50
strip0.stripStart += 50
#
# show the changes
#
showStrip(strip0)
#
# select the strip in the NLA editor
#
strip0.flag += NLA.Flags['SELECT']
Blender.Window.RedrawAll()
showStrip(strip0)
#
# move all strips by FrameOffset
#
def moveallStrips(FrameOffset):
for strip in Char1NLAstrips:
strip.stripEnd += FrameOffset
strip.stripStart += FrameOffset
moveallStrips(30)
#
# show all strips Properties
#
print
print '============ ALL STRIPS ================'
numStrips = len(Char1NLAstrips)
print numStrips,' NLA strips for ',armOb
for strip in Char1NLAstrips:
showStrip(strip)
Blender Windowing and User Interface Scripts
[edit | edit source]Mouse Location 3D Space
[edit | edit source]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()
Auto Buttons
[edit | edit source]Auto Buttons is a really easy way to add a stack of buttons into any script.
Add the AutoButtons text to the bottom of any script and any function ending with _bgui will have a button that calls it.
# 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)
An example of a function that could use this
def Print_Object_Selection_bgui():
Blender.Draw.PupMenu('|'.join(ob.name for ob in Blender.Object.GetSelected()))
Popup Menu Wrapper
[edit | edit source]This script takes a string that you would normally give to Draw.PupMenu() splitting up the menus by the groupsize.
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)
General Python
[edit | edit source]Here add general python code, useful when Python scripting.
Benchmark Script
[edit | edit source]Script that times different functions.
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()
Iterate Multiple Lists
[edit | edit source]Sometimes you want to loop over more than 1 list at once. if the lists your dealing with are large then creating a new list for the purpose can be slow and use too much memory. This class takes multiple lists and treats them like 1 big list. without having to make a new list.
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()
Some examples
for i in listIter( (range(10), range(22), range(5)) ):
print i
Another example that takes verts from 3 meshes and adds them to 1 mesh using this iterator and list comprehension.
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 ] )
Binary Conversion (Without Struct)
[edit | edit source]Thanks to 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))
Randomize List
[edit | edit source]Returns a randomized list. Note if you can import random, use random.shuffle(ls) instead.
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
Remove Doubles in List
[edit | edit source]Removes doubles in a list, modifying the original list. (will use an objects cmp() function)
def RemDoubles(List):
lIdx = 0
while lIdx < len(List):
if List.count(List[lIdx]) > 1:
List.pop(lIdx)
continue
lIdx+=1
Remove Doubles in List (Hash)
[edit | edit source]Return a new list with no doubles.
def RemDoublesHash(myList):
return list(set(myList))
Get flag properties of a sum
[edit | edit source]A lot of properties in Blender are flags and stored in a sum of exponentials of 2. To find out that a specific flag is set and it is included in the sum try this function:
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
Call the function in that way:
lmp = Lamp.Get(thisObj.data.getName())
lmpMode = lmp.getMode()
lmpFlags = self.powList(lmpMode)
if 16 in lmpFlags:
...
Fraction Data Type
[edit | edit source]Allows creation of fraction data and all operations on them within the real number system.
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
Get a name with sane chars
[edit | edit source]This function can be used when you are making a filename from an object/mesh/scene... name. Blender supports many characters in a name that a filesystem may not.
saneFilechars replaces these characters with "_"
def saneFilechars(name):
for ch in ' /\\~!@#$%^&*()+=[];\':",./<>?\t\r\n':
name = name.replace(ch, '_')
return name
Colour Scripts
[edit | edit source]A place for scripts that deal with colours.
RGB to HSV
[edit | edit source]Convert Red/Green/Blue to Hue/Saturation/Value
r,g,b values are from 0.0 to 1.0
h = [0,360], s = [0,1], v = [0,1]
if s == 0, then h = -1 (undefined)
The Hue/Saturation/Value model was created by A. R. Smith in 1978. It is based on such intuitive color characteristics as tint, shade and tone (or family, purety and intensity). The coordinate system is cylindrical, and the colors are defined inside a hexcone. The hue value H runs from 0 to 360º. The saturation S is the degree of strength or purity and is from 0 to 1. Purity is how much white is added to the color, so S=1 makes the purest color (no white). Brightness V also ranges from 0 to 1, where 0 is the black.
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 to RGB
[edit | edit source]Convert Hue/Saturation/Value to Red/Green/Blue
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
Interactive Tools
[edit | edit source]Freehand Polyline Draw Tool
[edit | edit source]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()
Render Functions
[edit | edit source]Render to a dir
[edit | edit source]# 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()
Render from all cameras
[edit | edit source]This script renders an animation from all cameras in a scene, it makes a new name from the camera and leaves the scene as it was originally.
Be sure to use useful camera names.
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)
Scriptlinks
[edit | edit source]Monitor Image
[edit | edit source]This Script needs to be used as a redraw scriptlink. It checks the date of the current image and attempts to reload the image, and if successful it redraws the image.
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.
Dynamic Text
[edit | edit source]This script needs to be used as a FrameChanged script linked to a Text object. Change the Years.append line to choice the years of the timeline.
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()
External Utils
[edit | edit source]Compress All Blend files (Unix Only)
[edit | edit source]Sine blender 2.41, support for GZip compression has been integrated into blender. So you can gzip all blend files and they will still open as expected.
This utility searches your hard-disk for blend files and gzips them if they are not already compressed.
Note: python3 required.
#!/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()
OrphanScripts
Orphan Scripts
[edit | edit source]Hi, post unmaintained scripts here.
Be wary of malicious code, especially the OS module, check in the history every so often for edits to see nobody's doing bad stuff.
Currently there are two scripts here in need of nice mothers or fathers.
This script is a simple L system that allows you to use pre-defined meshes. It DOES work in 2.43 and could possibly be a building generator (with some work).
#
# =====================
# README
# =====================
#
# this is a simple L-system that uses pre-made mesh elemental blocks
#
# source blocks are in a different layer - press tilde (`) in 3d view
# to see them all
#
# a new mesh is created every time; edge creases are carried over from
# source meshes to allow subsurf work
#
# this system uses the turtle concept with pushable/poppable state,
# just like most other L-systems
# however, the "drawing" operation here is to place a pre-made mesh
# instead the usual plant-oriented stuff
# as a result, this should be pretty well suitable for Discombobulator
# style work such as generating lots of small detail
#
# turtle instructions and any other tokens are XML-like tags
# instead of single characters; they can also contain an arbitrary
# argument string
# rewriting rules are actually defined as functions to allow
# procedural flexibility
#
# things left to do really depend on what the actual use is
# some ideas include using lists instead of strings for arbitrary
# argument data (may help with speed); supplying environment
# info to the rule function (for queries, like in some advanced
# L-systems out there) as well as the current model info
# for true Discombobulator-style detail generation there has
# to be a mode to populate the axiom (initial state) with
# existing model faces
#
# of course, more code cleanup is in order
#
import sys
import re
import Blender
from Blender import Mathutils
from Blender import NMesh
substrate = '<LimbArray>'#'<twist:90><turn:90><LimbArray>'
# custom characters and their translations
custom = [ \
('<Stem>', '<add:Stem><offset:0.5,0,0>'), \
('<Joint2>', '<add:Joint2Start><offset:0.2,0,0><pitch:-30_0><add:Joint2Middle><turn:-20_20><add:Joint2End><offset:0.2,0,0>') \
]
print "growing..."
# force recompile of ruleset
if 'ruleset' in sys.modules: del sys.modules['ruleset']
import ruleset
rules = [ (re.compile('<' + name + '(?::([^>]+))?>'), getattr(ruleset, name)) for name in dir(ruleset) ]
# do the matching
for stage in range(20):
curPos = 0
while True:
newPos, newEnd = None, None
newStr = None
for regex, func in rules:
match = regex.search(substrate, curPos)
if match != None:
if newPos == None or newPos > match.start():
newPos = match.start()
newEnd = match.end()
newStr = func(match.group(1))
if newPos == None: break
substrate = substrate[:newPos] + newStr + substrate[newEnd:]
curPos += len(newStr)
# translate custom chars
for char, repl in custom:
substrate = re.sub(char, repl, substrate)
print "interpreting..."
# interpret the result
class StackFrame:
# prev is the parent frame
def __init__(self, prev=None):
self.prev = prev
if prev != None:
self.matrix = prev.matrix
else:
self.matrix = Mathutils.Matrix()
self.matrix.identity()
self.matrix.resize4x4()
# modifications
def rotate(self, axis, angle):
rot = Mathutils.RotationMatrix(angle, 4, axis)
self.matrix = rot * self.matrix
def offset(self, xyz):
tra = Mathutils.TranslationMatrix(Mathutils.Vector(xyz))
self.matrix = tra * self.matrix
def scale(self, ratio):
sca = Mathutils.Matrix( \
[ ratio, 0, 0, 0 ], \
[ 0, ratio, 0, 0 ], \
[ 0, 0, ratio, 0 ], \
[ 0, 0, 0, 1 ])
self.matrix = sca * self.matrix
# raises exception if no parent
def getPrev(self):
if self.prev == None:
raise Exception, "no parent frames"
return self.prev
# appends current mesh to given one
# does NOT change position
def addMesh(self, meshObj, dest):
mesh = meshObj.getData()
vertBase = len(dest.verts)
for v in mesh.verts:
vec = Mathutils.Vector(v.co[0], v.co[1], v.co[2], 1)
vec = vec*self.matrix
cv = NMesh.Vert(vec.x, vec.y, vec.z)
dest.verts.append(cv)
for f in mesh.faces:
cf = NMesh.Face([ dest.verts[v.index + vertBase] for v in f.v ])
dest.addFace(cf)
if mesh.edges != None:
for e in mesh.edges:
v1, v2 = e.v1, e.v2
nv1, nv2 = dest.verts[v1.index + vertBase], dest.verts[v2.index + vertBase]
ne = dest.addEdge(nv1, nv2)
ne.crease = e.crease
#dest.update()
# create result mesh
mesh = NMesh.New()
#mesh.addEdgesData() # for things like creases
# do the dew
frame = StackFrame()
# parses and interprets a string of type
# X:Y_Z (randomly choose either X or something between Y and Z)
def number(str):
import random
choices = str.split(':')
choice = random.choice(choices)
limits = choice.split('_', 2)
if len(limits) == 2:
return random.uniform(float(limits[0]), float(limits[1]))
return float(limits[0])
instrMatch = re.compile(r"""
<
(?P<instr1>
\w+
)
(?:
:
(?P<arg>
[^>]+
)
)?
>
|
(?P<instr2>
[\[\]]
)
""", re.VERBOSE)
curPos = 0
while True:
match = instrMatch.search(substrate, curPos)
if match == None: break
curPos = match.end()
# collect instruction
instr = match.group('instr1')
if instr == None: instr = match.group('instr2')
arg = match.group('arg')
# do the dew
if instr == "[":
frame = StackFrame(frame)
elif instr == "]":
frame = frame.getPrev()
elif instr == "offset":
frame.offset([ number(a) for a in arg.split(',', 3) ])
elif instr == "turn":
frame.rotate("z", number(arg))
elif instr == "pitch":
frame.rotate("y", number(arg))
elif instr == "twist":
frame.rotate("x", number(arg))
elif instr == "scale":
frame.scale(number(arg))
elif instr == "add":
frame.addMesh(Blender.Object.Get(arg), mesh)
NMesh.PutRaw(mesh, "Result", 1, 1)
This script apparently WONT work in blender 2.43, updates welcome.
# Jamesk's Walk-o-matic version 0.49.9 (MODIFIED)
# for Blender 2.25 and a fully installed Python 2.0 [required]
# CHANGES FOR BLENDER 2.36 GENERALLY MARKED '#MDR:' ...
# Badly coded changes for Blender 2.43 by TwinStripe :) ! Appears to perform more passes but produces the same results!?!
# SET/CHECK THE PARAMETERS BELOW BEFORE EXECUTING THE SCRIPT.
# Make sure to select your proxy object, then run the script with ALT+P.
# Please consult the documentation for a full description of the parameters.
# ...Aaaaand check the console window for any messages.
# GENERAL SETTINGS:
FF = FIRST_FRAME = 1 # begin evaluating at this frame
LF = LAST_FRAME = 850 # stop evaluating after this frame
HS = HEEL_SEPARATION = 3.0 # desired distance between heel targets (in Blender Units)
MT = MOVE_TIME = 8.0 # number of frames/cycle a foot is moving
MSD = MOVE_STOP_DELAY = 0 # any value above zero will prolong the time a foot stays in the air.
HEEL_TO_FLAT_DISTANCE = 1 # desired distance between a heel target and its associated foot look-at-target
FLAT_TO_TLAT_DISTANCE = 0.5 # desired distance between a foot look-at-target and its associated toe look-at-target
AL = ALWAYS_LIFT = 0 # set to zero to prevent feet moving up/down when proxy has speed 0
CTD = C_TARG_DISTANCE = 2.0 # how far above proxy to place center target
LA = LIFT_AXIS = 'local' # lift feet along global Z or local proxy Z?
CTDLA = CTD_LIFT_AXIS = 'global' # raise center target along global Z or local proxy Z?
# NAMES FOR THE EMPTIES:
HEEL_LEFT, HEEL_RIGHT = 'heel.ikt.left', 'heel.ikt.right'
FLAT_LEFT, FLAT_RIGHT = 'foot.lat.left', 'foot.lat.right'
TLAT_LEFT, TLAT_RIGHT = 'toe.lat.left', 'toe.lat.right'
TARGET_CENTRE = 'target.centre'
# LIFT ENVELOPE SETTINGS:
LP = LIFT_PEAK = 0.5 # how far to lift above proxy initially
FLATLP = FLAT_LIFT_PEAK = 0.2 # how far to lift foot look-at-target above proxy initially
TLATLP = TLAT_LIFT_PEAK = 0.2 # how far to lift toe look-at-target above proxy initially
LPT = LIFT_PEAK_TIME = 0.2 # time to reach lift-peak. (relative to movetime)
MP = MID_PEAK = 0.4 # how far from proxy after lift-peak
FLATMP = FLAT_MID_PEAK = 0.4 # how far to lift foot look-at-target
TLATMP = TLAT_MID_PEAK = 0.4 # how far to lift toe look-at-target
MPT = MID_PEAK_TIME = 0.5 # time to reach mid-peak (relative to movetime)
FP = FINAL_PEAK = 0.5 # how far from proxy before setting down again
FLATFP = FLAT_FINAL_PEAK = 0.7 # how far to lift foot look-at-target
TLATFP = TLAT_FINAL_PEAK = 0.9 # how far to lift toe look-at-target
FPT = FINAL_PEAK_TIME = 0.8 # time to reach final_peak (relative to - you guessed it - movetime)
#
# Concept and coding by James Kaufeldt a.k.a. Jamesk
# Made in Sweden, november 2002
# Contact: james.k@home.se
#
# Special thanx to
# - [d0pamine] and [theeth] for some hints regarding vector math.
# - Martin [Strubi] Strubel from whom I borrowed the "normalize" function,
# len3(x), dist3(x,y) and sub3(x,y) funcs found in his "vect.py" utility module.
# - [eeshlo] for pointing out how simple it was to give names to the empties.
#
# ---------------------------------------------------------------------------------------
# EDITABLE SECTION ENDS HERE!
#
# NO USER DEFINABLE VALUES BEYOND THIS POINT!
# ---------------------------------------------------------------------------------------
#
#
#
#
LT = MT
CT = MT + LT
from Blender import Object, Scene, Window, Ipo
from Blender.Scene import Render
from Blender.Window import Types
import sys, math
proxy = Object.GetSelected()
status = 'UNDEFINED ERROR'
Layer = 0
print
print '----------------------------------'
print 'W A L K - O - M A T I C V 0.49.9'
print '----------------------------------'
print
# make sure that there's an actual walker proxy to use:
if proxy == []:
print 'No proxy indicated, terminating...'
status = 'NO PROXY OBJECT SELECTED'
if proxy != []:
proxy = proxy[0]
print 'Proxy in use: \t',proxy
Layer = proxy.Layer
print ' in Layer',Layer
status = 'OK'
sys.stdout.flush()
scene = Scene.GetCurrent()
# make sure there's a scene to use (should always be one, but wth...)
if scene == []:
print 'No scene available, terminating...'
status = 'NO CURRENT SCENE AVAILABLE'
if scene != []:
print 'Target scene: \t',scene
sys.stdout.flush()
# some generally useful functions below:
def normalize(v):
r = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
return (v[0]/r, v[1]/r, v[2]/r)
def len3(x):
return math.sqrt(x[0]*x[0] + x[1]*x[1] + x[2]*x[2])
def sub3(x, y):
return ((x[0] - y[0]), (x[1] - y[1]), (x[2] - y[2]))
def dist3(x, y):
return len3(sub3(x, y))
def moveAlong(origpos, distance, vector):
newpos = [0,0,0]
newpos[0] = origpos[0]+distance*vector[0]
newpos[1] = origpos[1]+distance*vector[1]
newpos[2] = origpos[2]+distance*vector[2]
return newpos
def invertVector(v):
return (-v[0], -v[1], -v[2])
#MDR:
def selectFrame(f):
f = int(f)
if scene.getRenderingContext().currentFrame() != f:
scene.getRenderingContext().currentFrame(f)
Window.Redraw(Window.Types.VIEW3D)
# MDR:
# Conversion routine: try to get an object, return None if it's not there.
# Just like Blender used to do. The easiest way to accomplish this is to
# allow the exception to occur, and catch it.
def TryGetObject(v):
try:
return Object.Get(v)
except:
return None
def getOffset(origin, frame, xdir, xdist, forward):
# origin: the point to offset frame: framenumber
# xdir: 1 = positive offset along X, -1 = negative offset
# xdist: how much to offset
selectFrame(frame)
loc = origin.getMatrix()[3]
loc = moveAlong(loc, forward, normalize(origin.getMatrix()[1]))
direction = normalize(origin.getMatrix()[0])
if xdir == -1:
direction = invertVector(direction)
return moveAlong(loc, xdist, direction)
def getLiftAxisOffset(origin, frame, liftaxis, liftdist):
# origin: the point to offset frame: framenumber
# liftaxis: 'global' or 'local' lifting liftdist: the amount of lift
selectFrame(frame)
loc = origin.getMatrix()[3]
direction = normalize(origin.getMatrix()[2])
if liftaxis=='global':
direction = [0,0,1.0]
return moveAlong(loc, liftdist, direction)
def getElevation(origin, frame, axis, zdist, xdir, xdist, forward):
# origin: the point to offset frame: framenumber
# axis: 'local' or 'global' zdist: how much to elevate
# xdir: the X offset xdist: the distance to offset along X
loc = getOffset(origin, frame, xdir, xdist, forward)
if axis=='local':
direction = normalize(origin.getMatrix()[2])
return moveAlong(loc, zdist, direction)
if axis=='global':
direction = [0, 0, 1.0]
return moveAlong(loc, zdist, direction)
def writeCurvePoint(ipo, frame, point):
# ipo: the IPOblock to use frame: at what frame
# point: the 3D coordinate to write
xc = ipo.getCurve('LocX')
yc = ipo.getCurve('LocY')
zc = ipo.getCurve('LocZ')
for idx, c in enumerate([xc,yc,zc]):
c.addBezier((frame, point[idx]))
c.update()
def makeIPO(name, ipol, expol):
# name: desired name for this IPOblock ipol: type of interpolation
# expol: type of extrapolation
ipo = Ipo.New('Object', name)
xc = ipo.addCurve('LocX')
yc = ipo.addCurve('LocY')
zc = ipo.addCurve('LocZ')
for curve in [xc, yc, zc]:
curve.setInterpolation(ipol)
curve.setExtrapolation(expol)
return ipo
def move(ipo, origin, destination, startframe, framespan, proxob, xdir, xdist, forward):
# ipo - what ipo to write points to origin - the location (3Dpoint) to start at
# destination - the location to end up at startframe - frame to set the first curvepoint at
# framespan - total number of frames for the move proxob - the proxy/reference object
# xdir - pos or neg offset along proxy X-axis xdist - how much to offset along proxy X-axis
writeCurvePoint(ipo, startframe, origin)
if AL==1 or origin!=destination:
# Write curvepoints for LiftPeak and LiftPeakTime:
# Pretty hackish formulae for proxyTime here... But they do work, so wtf...
lpProxyTime = startframe + (LPT*framespan*2)-framespan*0.25
lpRealTime = startframe+(framespan+MSD)*LPT
lpLocation = getElevation(proxob, lpProxyTime, LA, LP, xdir, xdist, forward)
writeCurvePoint(ipo, lpRealTime, lpLocation)
# Write curvepoints for MidPeak and MidPeakTime:
mpProxyTime = startframe + (MPT*framespan*2)-framespan*0.25
mpRealTime = startframe+(framespan+MSD)*MPT
mpLocation = getElevation(proxob, mpProxyTime, LA, MP, xdir, xdist, forward)
writeCurvePoint(ipo, mpRealTime, mpLocation)
# Write curvepoints for FinalPeak and FinalPeakTime:
fpProxyTime = startframe + (FPT*framespan*2)-framespan*0.25
fpRealTime = startframe+(framespan+MSD)*FPT
fpLocation = getElevation(proxob, fpProxyTime, LA, FP, xdir, xdist, forward)
writeCurvePoint(ipo, fpRealTime, fpLocation)
writeCurvePoint(ipo, startframe+framespan+MSD, destination)
return (startframe+framespan, destination)
def hold(ipo, location, startframe, framespan):
# ipo - what ipo to write points to # location - the position (3Dpoint) to hold at
# startframe - the first frame in the hold sequence # framespan - total number of frames to hold
writeCurvePoint(ipo, startframe+MSD, location)
writeCurvePoint(ipo, startframe+framespan, location)
return (startframe+framespan, location)
def recalculator(assignedTargets, targ1, targ2, basetarg):
# rewrites some globals based on the current arrangement of the empties:
loc1 = targ1.getLocation()
loc2 = targ2.getLocation()
loc3 = basetarg.getLocation()
# HEEL_SEPARATION:
if assignedTargets=='heels':
print 'Default heel empties found. Recalculating:'
global HS
HS = dist3(loc1, loc2)
print 'HEEL_SEPARATION set to',HS
if assignedTargets=='flats':
print 'Default foot look-at targets found. Reusing.'
global HEEL_TO_FLAT_DISTANCE
HEEL_TO_FLAT_DISTANCE = dist3(loc2, loc3)
print 'HEEL_TO_FLAT_DISTANCE set to', HEEL_TO_FLAT_DISTANCE
if assignedTargets=='tlats':
print 'Default toe look-at targets found. Reusing.'
global FLAT_TO_TLAT_DISTANCE
FLAT_TO_TLAT_DISTANCE = dist3(loc2, loc3)
print 'FLAT_TO_TLAT_DISTANCE set to',FLAT_TO_TLAT_DISTANCE
def doIt(forwardOffset, addCenter, whatsUp, firstName, secondName):
print
print 'Currently processing:',whatsUp
# Start building the IPO for the right foot:
ffootipo = makeIPO('rfoot', 'Linear', 'Constant')
cpf = currentProxyFrame = FF
# make first step (only half as far as the others):
ffootloc = getOffset(proxy, cpf, 1, HS/2, forwardOffset)
ffootframe = cpf
targetloc = getOffset(proxy, cpf+MT, 1, HS/2, forwardOffset)
ffootframe, ffootloc = move(ffootipo, ffootloc, targetloc, ffootframe, MT/2,proxy, 1, HS/2, forwardOffset)
ffootframe, ffootloc = hold(ffootipo, ffootloc, ffootframe, LT)
# now make the rest of the steps (full length):
while True:
cpf += CT
targetloc = getOffset(proxy, cpf+MT, 1, HS/2, forwardOffset)
ffootframe, ffootloc = move(ffootipo, ffootloc, targetloc, ffootframe, MT,proxy, 1, HS/2, forwardOffset)
ffootframe, ffootloc = hold(ffootipo, ffootloc, ffootframe, LT)
if cpf>LF:
break
# Then we'll build the IPO for the left foot:
sfootipo = makeIPO('lfoot', 'Linear', 'Constant')
cpf = currentProxyFrame = FF
# this one starts in hold-mode (waits for right foot to finish)
sfootloc = getOffset(proxy, cpf, -1, HS/2, forwardOffset)
sfootframe = cpf
sfootframe, sfootloc = hold(sfootipo, sfootloc, cpf, MT/2)
while True:
cpf += CT
targetloc = getOffset(proxy, cpf, -1, HS/2, forwardOffset)
sfootframe, sfootloc = move(sfootipo, sfootloc, targetloc, sfootframe, MT,proxy, -1, HS/2, forwardOffset)
sfootframe, sfootloc = hold(sfootipo, sfootloc, sfootframe, LT)
if cpf>LF:
break
if addCenter:
# And to finish it off, let's put something in the middle of this:
# This will simply add a third target floating above the proxy.
# It will respect the specified lift axis, hence useful as parent for an armature
ctargetipo = makeIPO('center', 'Linear', 'Constant')
for cframe in range(FF, LF):
targetloc = getLiftAxisOffset(proxy, cframe, CTDLA, CTD)
writeCurvePoint(ctargetipo, cframe, targetloc)
# Finished. Add or reuse empties and link them to their respective IPOblocks.
leftikt = TryGetObject(firstName)
leftnew = False
if leftikt==None:
leftikt = Object.New('Empty')
leftnew = True
rightikt = TryGetObject(secondName)
rightnew = False
if rightikt==None:
rightikt = Object.New('Empty')
rightnew = True
leftikt.name = firstName
rightikt.name = secondName
print 'Targets',leftikt,rightikt
if addCenter:
centertarget = TryGetObject(TARGET_CENTRE)
centernew = False
if centertarget==None:
centertarget = Object.New('Empty')
centernew = True
centertarget.name = TARGET_CENTRE
print 'Centertarget',centertarget
centertarget.Layer = Layer
if centernew:
scene.link(centertarget)
#MDR: 'SetIPO' was 'link'...
centertarget.setIpo(ctargetipo)
leftikt.Layer = Layer
rightikt.Layer = Layer
if leftnew:
scene.link(leftikt)
if rightnew:
scene.link(rightikt)
#MDR: Ditto... 'setIpo' was 'link'...
leftikt.setIpo(sfootipo)
rightikt.setIpo(ffootipo)
print whatsUp,'IPO:s',sfootipo,ffootipo
print '---------------------------------------------------------'
sys.stdout.flush()
#########################################
# if everything's OK, let's get to work #
#########################################
if status=='OK':
currentUserFrame = scene.getRenderingContext().currentFrame()
# grab any walkomat empties left in the scene:
oldleftheel =TryGetObject(HEEL_LEFT)
oldrightheel =TryGetObject(HEEL_RIGHT)
oldleftflat =TryGetObject(FLAT_LEFT)
oldrightflat =TryGetObject(FLAT_RIGHT)
oldlefttlat =TryGetObject(TLAT_LEFT)
oldrighttlat=TryGetObject(TLAT_RIGHT)
emptyipo = makeIPO('emptydummy', 'Linear', 'Constant')
# recalculate if there were any such empties:
if oldleftheel!=None and oldrightheel!=None:
# assign an empty IPO first to clear any anim:
# why isn't there some 'unlink' function somewhere???
#
# MDR: These 'setIpo' calls were 'link' ....
#
oldleftheel.setIpo(emptyipo)
oldrightheel.setIpo(emptyipo)
recalculator('heels', oldleftheel, oldrightheel, oldrightheel)
if oldleftflat!=None and oldrightflat!=None:
oldleftflat.setIpo(emptyipo)
oldrightflat.setIpo(emptyipo)
recalculator('flats', oldleftflat, oldrightflat, oldrightheel)
if oldlefttlat!=None and oldrighttlat!=None:
oldlefttlat.setIpo(emptyipo)
oldrighttlat.setIpo(emptyipo)
recalculator('tlats', oldlefttlat, oldrighttlat, oldrightflat)
# first pass, heel targets:
doIt(0, 1, 'Heel targets', HEEL_LEFT, HEEL_RIGHT)
#second pass, foot look-at targets:
LP = FLATLP
MP = FLATMP
FP = FLATFP
doIt(HEEL_TO_FLAT_DISTANCE, 0, 'Foot look-at targets', FLAT_LEFT, FLAT_RIGHT)
#third pass, toe look-at targets:
LP = TLATLP
MP = TLATMP
FP = TLATFP
doIt(HEEL_TO_FLAT_DISTANCE+FLAT_TO_TLAT_DISTANCE, 0, 'Toe look-at targets', TLAT_LEFT, TLAT_RIGHT)
# At last, as a friendly gesture, restore the frame to whatever the user
# was looking at before running the script, and refresh the screens:
scene.getRenderingContext().currentFrame(currentUserFrame)
Window.RedrawAll()
print 'Processing completed.'
print 'Thank you for using Walk-O-Matic :D'
sys.stdout.flush()
###################################################
# if things are not right, print some dying words:#
###################################################
if status!='OK':
print ''
print 'Walk-o-matic is sadly forced to report that'
print 'it could not go to work properly.'
print 'Cause of termination: ',status
print 'Please consult the documentation regarding proper use.'
sys.stdout.flush()
Features
Here is the temp location for features Campbell and Vince from MetaVR wish to add for improved modelling in simulation.
- Vert Select to UV Selection.
- UV Window, Align weld- to be able to welt to the left, right,top,bottom.
- Relax vertex to surrounding.
- Relax vertex- make the vertex evenly spaced and fit the surrounding faces/verts.
- Rotate along edge.
- Projection UV mapping modifier. (use cameras?)
- Move uv island to another edge non connected edge
- BuildingGenerator: BuildingGenerator — building Generator
Blender Python C API
Mini Unfinished Guide to adding a C/API extension
[edit | edit source]Add blurb- what this is good for etc.
Heres a mini tutorial
- Find a variable that you can not access from BPython
- Look for a button that modifies the value in the user interface
- Write down the buttons tooltip
- Use a find tool to find the Tooltip for the button in the source code (There are some source code searching tools you can get)
- The line with the tooltip will also reference a variable. Now you know what the variable name is for the button.
- Go into Python code folder and open the relevant C file (Lamp.c, Material.c etc.)
- Find a similar exististing variable in the c file- (e.g., if your setting a flag, then find another BPy function that sets a flag)
- Look for every instance of this variable, under each instance, copy it and replace the name with your new one. make the obvious modifications.
make sure to add the setattr and getattr values for class variable names. You don't have to do this, but you probably should eventually.
Recompile and test!
Voila
Note
Sometimes not all variables are seen directly from the UI.
As fas as I know all blender datatypes are structs internally.
To find all the variables used by this DataType-
Look through the files in- Blender/source/blender/makesdna/...
They are named well and you can see all the variables.
2.5 quickstart
In Blender 2.5 bpy
replaces the old Blender
module, introducing with it a completely new Python API. This page is designed to give an overview of how to perform key scripting tasks with bpy
, and should be of interest both to newcomers and those transitioning from the 2.4x releases.
The console
[edit | edit source]The built-in Python console has had a major overhaul in Blender 2.5, and now supports autocompletion (press Ctrl+Space). This is great for exploring all the new functions, though inline documentation is sparse.
Latest version (14-7-2010) activate console: Shift+F4.
Context + Operation = Result
[edit | edit source]The 2.5 series introduces the bpy.ops
module. Once you register your script as an "operation" it will integrate directly into the UI and can appear as a standard button identical to the built-in tools (which also use ops
). Usefully for programmers, the function call of each operation appears in the tooltip of its button.
There is also a big conceptual change in ops
: instead of being explicitly passed a target, operations deduce one from Blender's current "context". The context includes an array of what is currently selected, the current active object or bone, the active editing mode, the state of the Blender window, user preferences, and more besides.
Operations take no arguments and return no values. For this reason calling them from within complex scripts is considered sloppy, since you must indirectly set the operation's target, then (unless you already have a pointer) hunt indirectly for its results. Calling objects' functions directly is still supported, of course; unfortunately, as of 2.5 alpha 0 there are still a lot of ops that can't be replicated with direct calls.
Performing tasks in 2.5
[edit | edit source]Remember that ops
functions do not return a value. After calling one to create an object, you will need to get a handle to the result yourself!
Creation and destruction
[edit | edit source]bpy.ops.object.add(type='EMPTY')
- Creates an object and empty datablock, and links it to the current scene. See also #Manual object creation.
bpy.ops.scene.new(type='EMPTY')
- Creates a new scene. Can be a clone or full duplicate of the current scene.
mesh_object.data.add_geometry(verts, edges, faces)
- Adds verts, edges and faces to a mesh. Object mode only.
object.modifiers.new(type="ARMATURE",name="Armature")
- Adds a modifier to an object.
bpy.ops.mesh.edge_face_add()
- If two verts are selected, creates an edge. If three or more verts are selected, creates face(s) as well.
bpy.ops.mesh.primitive_cube_add()
- Adds a cube to the current mesh object. Other
primitive_*_add()
functions exist. bpy.ops.armature.bone_primitive_add(name="Bone")
- Adds a bone to the active armature.
bpy.ops.object.delete()
bpy.ops.*.delete()
- Deletes the selected objects, mesh, etc. Many versions exist for different types.
Manual object creation
[edit | edit source]If you need finer control than ops.object.add()
provides, or just return values, follow this example:
me_da = bpy.data.add_mesh('mesh_data') # a mesh datablock without an associated object me_ob = bpy.data.add_object('MESH','mesh_ob') # a mesh object without an associated datablock # N.B. in future releases the above calls will become bpy.data.<type>.new() me_ob.data = me_da # Assign the datablock to the object bpy.context.scene.objects.link(me_ob) # Link the object to the active scene
There is also add_armature()
, add_image()
, add_lamp()
, add_material()
and add_texture()
.
Getting objects and data
[edit | edit source]bpy.data.objects
bpy.data.meshes
bpy.data.*
- A group of collections of Python objects. There are many - one for each type. As with normal dictionaries you can pick objects out by name or by index, to add new datablocks use
new()
orload()
for images, sounds and fonts. object.data
- An object's datablock.
mesh.data.vertices
- A mesh's vertex collection. Read-only.
armature.data.bones
armature.data.edit_bones
- An armature's bone dict, in object and edit mode respectively. If you access the wrong object for the current context, it will appear empty.
Context management
[edit | edit source]bpy.ops.object.mode_set(mode='EDIT')
- Set the current mode. Note that the mode may not end up exactly as you set it here: for instance,
EDIT
can becomeEDIT_ARMATURE
. bpy.context.active_object
bpy.context.active_bone
bpy.context.active_pchan
bpy.context.active_base
- These functions return the user's currently active item, which is generally the last one of its type selected.
active_base
returns a generic Python type. - To set the active item, assign a Python object to one of the following values:
scene.objects.active
armature.data.bones.active
orarmature.data.edit_bones.active
- The active object is highlighted white in the outliner.
bpy.context.selected_objects
bpy.context.selected_bones
bpy.context.selected_bases
- Returns an array of the currently-selected objects. Other functions exist for editbones, "editable objects" and pchans.
object.selected
- Boolean value defining whether the object is selected.
Registering an operation
[edit | edit source]See .blender\scripts\templates\operator.py
. With your script registered, you will be able to add it to the Tool shelf and launch it from the spacebar menu.
Reference
Blender Python API Reference
DataBlock Wrapper Submodules
- Reference: Camera — Modify the properties of camera
- Reference: Curve — ...
- Reference: Image — Deals with Image objects loaded into Blender.
- Reference: Ipo — Animated curves used for controlling a veriety of properties.
- Reference: Lamp — Animated curves used for controlling a veriety of properties.
- Reference: Lattice — ...
- Reference: Material — ...
- Reference: Texture — ...
Blender Interface SubModules
- Reference: Draw — Access user interface elements.
- Reference: BGL — OpenGL Access inside blender.
- Reference: Window — Global events, user interactions.
Blender General SubModules
- Reference: Mathutils — 3D Math Functions
- Reference: Noise — Random/Noise/Turblence functions
- Reference: Registry — Global events, user interactions.
- Reference: Sys — Used instead of python sys, adds some blender specific functions.