Featured post

new redirect for blender.org bpy docs.

http://www.blender.org/api/blender_python_api_current/ As of 10/11 november 2015 we can now link to the current api docs and not be worr...

June 27, 2011

randomizing geometry

this code randomizes all internal vertices on a surface of faces, it takes into account the shortest edge connected to each vertex. What it doesn't do is store the initial vertex position, so any consecutive randomize will random atop of the newly randomized coordinate

# must be in object mode
# with no verts selected on the object.

import bpy
import random
from mathutils import Vector

index_list = []
tracker_list = []

for i in bpy.context.active_object.data.edges:
index_list.append(i.vertices[0])
index_list.append(i.vertices[1])
tracker_list.append([i.vertices[0], i.vertices[1]])

# rudimentary numeric sorting, came in handy for debugging, but
# not essential for operational code.
sorted_list = sorted(index_list)

# use a set to eliminate multiples fast
sorted_set = set(sorted_list)

# track all verts with 4 or more other verts attached, store as list_internals
list_internals = []
for num in sorted_set:
if sorted_list.count(num) >= 4: # quad mesh only.
list_internals.append(num)

'''
# this selects all internal geometry.
for i in list_internals:
bpy.context.active_object.data.vertices[i].select = True
'''
print("="*20)

# store the length of the shortest connected edge, for each internal vertex
min_length_list = []
meshdata = bpy.context.active_object.data.vertices
for vert in list_internals:

# find each edge that uses this vert
# print(vert, "used in")
temp_storage = []
for pair in tracker_list:
if vert in pair:
vec1 = meshdata[pair[0]].co
vec2 = meshdata[pair[1]].co
dist = (vec1-vec2).length
temp_storage.append(dist)
# print(pair, "length is", dist)

shortest_attached_edge = min(temp_storage)
# print("shorted length is: ", shortest_attached_edge)

# store [vertnum, shortest_attached_edge]
min_length_list.append([vert, shortest_attached_edge])

for i in min_length_list:
print(i)

# randomize to .4 or shortest edge.
def randomize_vector(vector_in, amount):
amount = amount * 0.2
# random.uniform(0.7, 1.3)
vecx = random.uniform(vector_in.x-amount, vector_in.x+amount)
vecy = random.uniform(vector_in.y-amount, vector_in.y+amount)
vecz = random.uniform(vector_in.z-amount, vector_in.z+amount)

return Vector((vecx, vecy, vecz))

for i in min_length_list:
print(meshdata[i[0]].co, "randomize by ", 0.4*i[1] )
newvec = randomize_vector(meshdata[i[0]].co, 0.4*i[1])
print(newvec)
meshdata[i[0]].co = newvec


becomes

Filling a gap between two edges (interpolating between poly lines)

this script performs linear interpolation, with definable number of sections between two polylines. A polyline is a connected chain of edges. This script requires two polylines with the same number of edges to run.

import bpy

# constants
NUM_SECTIONS = 13 # num faces to span.
RATE = 1.0 / NUM_SECTIONS


def generate_main_temp_list(selected_objects):
# take two selected polylines (edge only mesh type)
iterator = 0
main_temp_list = []

# after this for loop we have a 2*n list of verts
for i in selected_objects:
sub_temp_list = []
for vert in i.data.vertices:
sub_temp_list.append(vert.co)
main_temp_list.append(sub_temp_list)

# in order to generalize the algorithm - note the length of the polyline
num_verts = len(main_temp_list[0])

# generate main_temp_list (multidimensional, num_sections*n)
iter = 1
for i in range(1,NUM_SECTIONS):
sub_temp_list = []

# vnum is used for going through each vert
for vnum in range(num_verts):
vertn = main_temp_list[0][vnum]
vertx = main_temp_list[len(main_temp_list)-1][vnum]
vert = vertn.lerp(vertx, iter*RATE)
sub_temp_list.append(vert)

# inserts new section after linear interpolation (lerp)
main_temp_list.insert(iter, sub_temp_list)
iter += 1

return main_temp_list


def generate_straight_list(main_temp_list):
# generate straight_vert_list (1 row of (n*n) elements)
straight_vert_list = []
small_iter = 0
for i in main_temp_list:
for m in i:
straight_vert_list.append(m)
small_iter += 1
return straight_vert_list


# wrap faces onto edges all the way down.
def make_edges_from_vertlist(Verts):

'''
takes a multi dimensional list, makes face indices

axis1: The amount of faces per edge as n-1, where n is num_verts
axis2: The amount of faces will be NUM_SECTIONS-1
faces created: is (axis1-1)*(axis2-1)

'''

def list_shift(mylist, n):
tmplist = []
for element in mylist:
tmplist.append(element+n)
return tmplist

Faces = []
axis1, axis2 = len(Verts), len(Verts[0])

print("="*79)
print('dimension 1', axis1, "so,", axis1-1, "faces")
print('dimension 2', axis2, "so,", axis2-1, "faces")

# example of format..if 11*11 verts
# Faces = [[0, 11, 12, 1],[9, 20, 21, 10],[11,22,23,12]]

for x in range(axis1-1):
tmpl = [x*axis2, x*axis2+axis2, x*axis2+axis2+1, x*axis2+1]
for z in range(axis2-1):
tlist = list_shift(tmpl, z)
Faces.append(tlist)

return Faces


# takes care of the verts to polygon issue.
def make_polygon(Verts, Faces, object_name):
object_mesh = object_name + "_mesh"
mesh = bpy.data.meshes.new(object_mesh)
mesh.from_pydata(Verts, [], Faces)
mesh.update()
new_object = bpy.data.objects.new(object_name, mesh)
new_object.data = mesh

scene = bpy.context.scene
scene.objects.link(new_object)
return


# main_temp_list: will contain multi dimensional list of verts per section
# straight_vert_list: will contain a single dimensional list of n vertices.
def init_functions(selected_objects):
main_temp_list = generate_main_temp_list(selected_objects)
Faces = make_edges_from_vertlist(main_temp_list)
straight_vert_list = generate_straight_list(main_temp_list)
make_polygon(straight_vert_list, Faces, "combo")
return


init_functions(bpy.context.selected_objects)


becomes, (this is a wireframe view, the script _does_ create faces too)

randomly placing vertices around a spherical surface

import bpy
import time
from math import radians
from random import randint
from mathutils import Vector, Euler

# constants
SPHERE_RADIUS = 0.7
NUM_VERTS = 116
MIN_DISTANCE = 0.19

# ttime in seconds beyond which iteration will be cancelled.
MAX_TIME = 20 

# consumable
Verts = []

# get start time
a_time = time.time()


def make_vertgon(Verts, object_name):
    object_mesh = object_name + "_mesh"
    mesh = bpy.data.meshes.new(object_mesh)
    mesh.from_pydata(Verts, [], [])
    mesh.update()
    new_object = bpy.data.objects.new(object_name, mesh)
    new_object.data = mesh
    
    scene = bpy.context.scene
    scene.objects.link(new_object)
    return


# populate the Verts list with verts randomly positioned around the radius
while(len(Verts)<=NUM_VERTS):
            
    ax_x = radians(randint(0, 360))
    ax_y = radians(randint(0, 360))
    ax_z = radians(randint(0, 360))
    myEul = Euler((ax_x, ax_y, ax_z), 'XYZ')
    
    outVec = Vector((SPHERE_RADIUS, 0.0, 0.0))
    outVec.rotate(myEul)

    # get current time
    b_time = time.time()
    
    # check time difference between current and start.
    elapsed_time = abs(a_time - b_time)    
    if elapsed_time > MAX_TIME:
        # breaking instead of running something that might be shy of infinite.
        break

    myToken = False    
    for B in Verts:       
        if (outVec-B).length < MIN_DISTANCE:
            myToken = True
            break
        
    if myToken == True:
        continue    
   
    Verts.append(outVec)
    
        
# draw verts randomly around the radius
make_vertgon(Verts, "stix")


'''
code notes:
    
this approach makes it obvious that SPHERE_RADIUS, NUM_VERTS and MIN_DISTANCE 
will reach equilibrium if their ratio approaches the optimal spread that 
NUM_VERTS has on the surface of the sphere.

The random nature of establishing vertex coordinates will often make it 
unlikely that any precise geometric distribution can be achieved.
'''

June 07, 2011

from_pydata a wave wrapping around a circle


import bpy
import math
from math import sin, radians, pi
from mathutils import Vector, Euler

# variables
z_float = 0.0
amp = 0.1
profile_radius = 1.0
n_petals = 14
n_verts = n_petals * 12
section_angle = 360.0 / n_verts
position = (2*(math.pi/(n_verts/n_petals)))

# consumables
Verts = []
Edges = []

# makes vertex coordinates
for i in range(n_verts):
# difference is a function of the position on the circumference
difference = amp * math.cos(i*position)
arm = profile_radius + difference
ampline = Vector((arm, 0.0, 0.0))

rad_angle = math.radians(section_angle*i)
myEuler = Euler((0.0, 0.0, rad_angle),'XYZ')

# changes the vector in place and because successive calls are accumulative
# we reset at the start of the loop.
ampline.rotate(myEuler)
x_float = ampline.x
y_float = ampline.y
Verts.append((x_float, y_float, z_float))

# makes edge keys
for i in range(n_verts):
if i == n_verts-1:
Edges.append([i, 0])
break
Edges.append([i, i+1])

# turns mesh into object and adds object to scene
profile_mesh = bpy.data.meshes.new("Base_Profile_Data")
profile_mesh.from_pydata(Verts, Edges, [])
profile_mesh.update()

profile_object = bpy.data.objects.new("Base_Profile", profile_mesh)
profile_object.data = profile_mesh

scene = bpy.context.scene
scene.objects.link(profile_object)
profile_object.select = True


if you add this:

difference = amp * math.cos(i*position)
if difference > 0:
difference = difference * .2

June 06, 2011

remap with input error checking

some thoughts on the remap function, this includes some invalid input checking.

# remap.py python 3.2 blender 2.57 r37243
# GPL2 license
# Author Dealga McArdle

import bpy
from mathutils import Vector

def remap(current, lower_old, upper_old, lower_new, upper_new):
'''
Remaps one range of values to another range of values, types must be float

arguments : Description
----------------------------------------------------------
current : Value to fit within the destination range
lower_old : Lowest value of the original range
upper_old : Highest value of the original range
lower_new : Lowest value of the destination range
upper_new : Highest value of the destination range

'''

# type checking, if any of the arguments are not float then return None.
lcheck = current, lower_old, upper_old, lower_new, upper_new
lstr = "current", "lower_old", "upper_old", "lower_new", "upper_new"

for i in range(len(lcheck)):
if lcheck[i].__class__ is not float:
print(lstr[i], "is not a float")
return None

# before calculations we can deal with some possible errors
if current <= lower_old: return lower_new
if current >= upper_old: return upper_new

# reusing a nicely coded Vector math utility :)
old_min = Vector((0.0, 0.0, lower_old))
old_max = Vector((0.0, 0.0, upper_old))
new_min = Vector((0.0, 0.0, lower_new))
new_max = Vector((0.0, 0.0, upper_new))

# calculate spread, fast and saves room!
spread_old = (old_max-old_min).length
spread_new = (new_max-new_min).length
factor_remap = spread_new / spread_old

current_vector = Vector((0.0, 0.0, current))
remap_temp = (current_vector-old_min).length
remapped = (remap_temp * factor_remap) + new_min[2]

# clamp output when rounding creates values beyond new range
if remapped < lower_new: return lower_new
if remapped > upper_new: return upper_new

# value seems alright!
return remapped

June 05, 2011

Vertex Colours (Z height to colour)

note to user: for blender 2.62, replace 'faces' with 'polygons'  - Most of this post contains information that is of little relevance to Blender 2.6x
Here is a link to updated version of this: update for 2.6x

below this point is outdated information

say you have just painted vertex colours in Vertex Paint mode.
>>> for i in bpy.context.active_object.data.vertex_colors[0].data:
      # for every face print, color4 only useful in a Quad mesh.
...   print(i.color1, i.color2, i.color3, i.color4)
will print layer 0 of Vertex Colors for that mesh object. This property is read/write
so you can manipulate the data according to (for example) their Z value.
# set everything to a midtone rgb Color((.5, .5, .5))
# modify vertex painted 'Vertex Colors' palette
import bpy
import random
from mathutils import Color

for i in bpy.context.active_object.data.vertex_colors[0].data:
    
    i.color1 = Color((0.5, 0.5, 0.5))
    i.color2 = Color((0.5, 0.5, 0.5))
    i.color3 = Color((0.5, 0.5, 0.5))
    i.color4 = Color((0.5, 0.5, 0.5))
this makes all colours utterly random ( super ugly! )
# modify vertex painted 'Vertex Colors' palette
import bpy
import random
from mathutils import Color

for i in bpy.context.active_object.data.vertex_colors[0].data:

    r = random.randint(0,1)
    g = random.randint(0,1)
    b = random.randint(0,1)
    VertCol = (r,g,b)
    
    i.color1 = Color((VertCol))
    i.color2 = Color((VertCol))
    i.color3 = Color((VertCol))
    i.color4 = Color((VertCol))


this checks their Z height and makes them lighter at heigher Z-values. First it checks the highest Z value and lowest Z value in the coordinate list for those Faces, then adjusts the Height-to-VertexColor function accordingly

This is much longer because it includes the remap function, which i'm sure could be done with one function call, like in actionscript or processing. EDIT: for an updated version of the remap function used below visit remap-with-input-error-checking
# modify vertex painted 'Vertex Colors' palette
import bpy
import random
from mathutils import Color, Vector

def remap(current, lower_old, upper_old, lower_new, upper_new):
    
    # error spreads must return difference between the values
    old_tup = lower_old, upper_old
    new_tup = lower_new, upper_new
    
    # reusing a nicely coded Vector math utility :)    
    old_min = Vector((0.0, 0.0, lower_old))
    old_max = Vector((0.0, 0.0, upper_old))
    new_min = Vector((0.0, 0.0, lower_new))
    new_max = Vector((0.0, 0.0, upper_new))
    
    # fast and saves room! 
    spread_old = (old_max-old_min).length
    spread_new = (new_max-new_min).length
    
    factor_remap = spread_new / spread_old
    
    current_vector = Vector((0.0, 0.0, current))
    remap_temp =(current_vector-old_min).length
        
    remapped = (remap_temp * factor_remap) + new_min[2]
    
    # i think color values are clamped by blender, but it is
    # good practice to not take this for granted in all cases.
    if remapped < lower_new: return lower_new
    if remapped > upper_new: return upper_new

    # value seems alright!  
    return remapped



# Find upper and lower z values.
# there are many ways to skin this cat, but i'm going to be lazy
z_list = []
for i in bpy.context.active_object.data.vertices:
    z_list.append(i.co.z)
    
z_list = sorted(z_list)
print('lower z',z_list[0])
print('upper z',z_list[len(z_list)-1])

lower_old = z_list[0]
upper_old = z_list[len(z_list)-1]
lower_new = 0.0
upper_new = 1.0

vertlist = bpy.context.active_object.data.vertices
facelist = bpy.context.active_object.data.faces
colorlist = bpy.context.active_object.data.vertex_colors[0].data

for i in range(len(colorlist)):

    remapped = []
    for element in range(4):
        vertnum1 = facelist[i].vertices[element]
        remap_1 = vertlist[vertnum1].co.z
        remap_1 = remap(remap_1, lower_old, upper_old, lower_new, upper_new)
        remapped.append(Color((remap_1,remap_1,remap_1))) 
            
    colorlist[i].color1 = remapped[0]
    colorlist[i].color2 = remapped[1]
    colorlist[i].color3 = remapped[2]
    colorlist[i].color4 = remapped[3]
    


June 04, 2011

using from_pydata

this makes a square, 4 verts, 4 edges.
# be in object mode with nothing selected.

import bpy

# create 4 verts, string them together to make 4 edges.
coord1 = (-1.0, 1.0, 0.0)
coord2 = (-1.0, -1.0, 0.0)
coord3 = (1.0, -1.0, 0.0)
coord4 = (1.0, 1.0, 0.0)

Verts = [coord1, coord2, coord3, coord4]
Edges = [[0,1],[1,2],[2,3],[3,0]]

profile_mesh = bpy.data.meshes.new("Base_Profile_Data")
profile_mesh.from_pydata(Verts, Edges, [])
profile_mesh.update()

profile_object = bpy.data.objects.new("Base_Profile", profile_mesh)
profile_object.data = profile_mesh  # this line is redundant .. it simply overwrites .data

scene = bpy.context.scene
scene.objects.link(profile_object)
profile_object.select = True



this makes a circle 12 verts, 12 edges. you can modify n_verts ( must be >= 3)
import bpy
import math
from math import sin, cos, radians

# variables
n_verts = 12
profile_radius = 1
section_angle = 360.0 / n_verts 
z_float = 0.0
Verts = []
Edges = []

for i in range(n_verts):
    x_float = sin(math.radians(section_angle*i))
    y_float = cos(math.radians(section_angle*i))
    Verts.append((x_float, y_float, z_float))

for i in range(n_verts):
    if i == n_verts-1:
        Edges.append([i, 0])
        break
    Edges.append([i, i+1])


profile_mesh = bpy.data.meshes.new("Base_Profile_Data")
profile_mesh.from_pydata(Verts, Edges, [])
profile_mesh.update()

profile_object = bpy.data.objects.new("Base_Profile", profile_mesh)
profile_object.data = profile_mesh

scene = bpy.context.scene
scene.objects.link(profile_object)
profile_object.select = True



here's a version using Euler, Vector, Vector.rotate, and math.radians
import bpy
import math
from math import radians, pi
from mathutils import Vector, Euler

# variables
n_verts = 20
profile_radius = 1
section_angle = 360.0 / n_verts 
z_float = 0.0
Verts = []
Edges = []

'''
>>> m = Vector((1.0, 0.0, 0.0))
>>> eul = Euler((0.0, 0.0, math.pi), 'XYZ')
>>> m.rotate(eul)
'''

ampline = Vector((1.0, 0.0, 0.0))
for i in range(n_verts):
    x_float = ampline.x
    y_float = ampline.y
    
    rad_angle = math.radians(section_angle)
    myEuler = Euler((0.0, 0.0, rad_angle),'XYZ')

    # changes the vector in place and is accumulative 
    ampline.rotate(myEuler)
    Verts.append((x_float, y_float, z_float))

for i in range(n_verts):
    if i == n_verts-1:
        Edges.append([i, 0])
        break
    Edges.append([i, i+1])


profile_mesh = bpy.data.meshes.new("Base_Profile_Data")
profile_mesh.from_pydata(Verts, Edges, [])
profile_mesh.update()

profile_object = bpy.data.objects.new("Base_Profile", profile_mesh)
profile_object.data = profile_mesh

scene = bpy.context.scene
scene.objects.link(profile_object)
profile_object.select = True



a slight variant of the above for loop in range, allows you to change the diameter as a function of the position on the circumference. This replaces lines 20-30 from the previous snippet
for i in range(n_verts):
    ampline = Vector((1.0, 0.0, 0.0))
    
    rad_angle = math.radians(section_angle*i)
    myEuler = Euler((0.0, 0.0, rad_angle),'XYZ')

    ampline.rotate(myEuler)
    x_float = ampline.x
    y_float = ampline.y
    
    Verts.append((x_float, y_float, z_float))