Blender – script para vídeo

Depois de muito tempo sem postagem…

Estou trabalhando o uso de Python scripts no Blender para uso em animações. É um trabalho bem diferente do uso na game engine, mas igualmente cheio de possibilidades.

Esses primeiros, que estou compartilhando são super simples, mas mostram o uso das APIs para tarefas do tipo: criam uma esfera, uma animação linear, edição de curvas para animação, e iniciam a renderização.

1- super simples

# Paulo Barbeiro :: paulobarbeiro.com.br :: 2014
# Creates an sphere
# start some settings for video
# builds an animation:
# Sine, cosine calculus building an spiral.
#
# Execute the script (in Terminal) as follows:
# ./blender -b /path/to/an/empty/blender/file.blend -P /path/to/this/script.py
#
import bpy, bgl, blf, sys
from bpy import data, ops, props, types, context
import math
#pre-settings for video
bpy.context.scene.file_format = 'AVI_JPEG'
bpy.context.scene.render.resolution_percentage = 20
bpy.context.scene.render.antialiasing_samples = '5'
bpy.context.scene.render.filepath = '//outputs/video_output.avi'
#creates an sphere
bpy.ops.mesh.primitive_uv_sphere_add(segments=16, ring_count=16, size=0.25, location=(1,0,0))
#Simple linear animation in two keyFrames
#obj = bpy.context.object
#obj.location[2] = 0.0
#obj.keyframe_insert(data_path="location", frame=10.0, index=2)
#obj.location[2] = 1.0
#obj.keyframe_insert(data_path="location", frame=20.0, index=2)
#More advanced animation, editing animation curves, building an spiral.
obj = bpy.data.objects['Sphere']
obj = bpy.context.object
obj.animation_data_create()
obj.animation_data.action = bpy.data.actions.new(name="Spiral")
fcu_x = obj.animation_data.action.fcurves.new(data_path="location", index=0)
fcu_y = obj.animation_data.action.fcurves.new(data_path="location", index=1)
fcu_z = obj.animation_data.action.fcurves.new(data_path="location", index=2)
fcu_x.keyframe_points.add(9)
fcu_y.keyframe_points.add(9)
fcu_z.keyframe_points.add(9)
deg = 0
it = 0
for point in fcu_x.keyframe_points:
#print("---")
rad_angle = math.radians(deg)
x = (math.cos(rad_angle)*2)
y = (math.sin(rad_angle)*2)
deg += 45
point.co = deg, x
fcu_y.keyframe_points[it].co = deg, y
#fcu_z.keyframe_points[it].co = deg, it*.25
it += 1
#fcu_z.keyframe_points[1].co = 20.0, 1.0
bpy.ops.graph.interpolation_type(type='BEZIER')
bpy.context.scene.frame_end = deg
bpy.ops.render.render( animation=True )

2- usando matriz de rotação

# Paulo Barbeiro :: paulobarbeiro.com.br :: 2014
import bpy
import mathutils
import bmesh #needed
#from bpy.app.handlers import persistent
from mathutils import Vector, Euler, Quaternion, Matrix
import math #python module
# Recalculate MAtrix rotation, to aling it along a vector
# In this example:
# We will align the 'object', pointing to the 'target'
target = bpy.data.objects["target"]
object = bpy.data.objects["Led"]
vec_target = target.location
vec_loc = object.location
#look_at_ob = tar
#look_at_ob_loc = look_at_ob.location
#decompose object world matrix
loc, rot, scale = object.matrix_world.decompose()
#preparing vectors to format the matrix
forward = vec_target - vec_loc
up = Vector((0,0,1))
right = forward.cross(up).normalized() # Vector.length closer to 1.0
up = right.cross(forward)
scale_mat = Matrix()
scale_mat[0][0] = scale[0]
scale_mat[1][1] = scale[1]
scale_mat[2][2] = scale[2]
print("CHECKING MATH ---- this can be removed, checking purpose only.")
print( "Right: ",right )
print( "Forward:",forward )
print( "Up: ",up )
rotMt = Matrix((right, forward, up))
print(rotMt)
rotMt.transpose()
print(rotMt)
rotMt.normalize();
print(rotMt)
rotMt.to_4x4()
print( rotMt )
print("end checking math ----------------")
#new rotation Matrix: it will align object +Y axis to the object
rot = Matrix((right, forward, up)).transposed().normalized().to_4x4()
# +X axis as looking direction: this matrixes will set +X axis to the target
axis_conversion = Matrix.Rotation(math.radians(90), 4, 'Z')
axis_conversion_2 = Matrix.Rotation(math.radians(0), 4, 'X')
mat = Matrix.Translation(loc) * rot * axis_conversion * axis_conversion_2 * scale_mat
object.matrix_world = mat

3- editando curvas de animação

# Paulo Barbeiro :: paulobarbeiro.com.br :: 2014
#
# Basic principle, in Actions is:
# Actions DO BELONG to Blender data, NOT to the object
# the actions are listed in the Actions proprety of Blender Data
# The curves, that form the action are listed in f-curves property
# The way to identify each croup of curves, are the
# Groups, or, data_path properties.
# Each curve has a keyframe item, listed in the Keyframes property.
import bpy
import math
# get an object which will be associated to the animation
obj = bpy.data.objects["Cube"]
# get the actions
actions = bpy.data.actions
#create a name for the new action.
newActionName = "Action#"+str(len(actions))
#instantiate new action
newaction = bpy.data.actions.new(name=newActionName)
#creating curves for rotation
fcu_Rx = newaction.fcurves.new("rotation_euler", 0, "Rotation")
fcu_Ry = newaction.fcurves.new("rotation_euler", 1, "Rotation")
fcu_Rz = newaction.fcurves.new("rotation_euler", 2, "Rotation")
#inserting keyframes
fcu_Rx.keyframe_points.insert(0, 0)
fcu_Rx.keyframe_points.insert(10, math.radians(180))# angle in radians
fcu_Ry.keyframe_points.insert(0, 0)
fcu_Ry.keyframe_points.insert(10,0)
fcu_Rz.keyframe_points.insert(0, 0)
fcu_Rz.keyframe_points.insert(10,0)
# Change curve style.
fcu_Ry.keyframe_points.insert(10, math.radians(90))
# editando interpolação (o primeiro keyframe dita o modo de interpolação para o segmento)
fcu_Ry.keyframe_points[0].interpolation = "BEZIER"
#"LINEAR"
#"CONSTANT"
#"BEZIER"
#You can edit the curve handlers - left handle / right hadle
fcu_Ry.keyframe_points[1].handle_left.x = 10
fcu_Ry.keyframe_points[1].handle_left.y = 1
fcu_Ry.keyframe_points[1].handle_right.x = 12
fcu_Ry.keyframe_points[1].handle_right.y = 1.8
#attach new action to the object
obj.animation_data.action = newaction

4- usando event handlers, animando um ‘enxame’

# Paulo Barbeiro :: paulobarbeiro.com.br :: 2014
# This code shows how can we use event handlers. No animation here is recorded.
# The objects are animated by the flocking algorithm, and repositioned every single frame.
#
import bpy
import math
from math import *
from mathutils import Vector, Euler, Quaternion, Matrix
from bpy.app.handlers import persistent
#pre set for video configs
bpy.data.scenes["Scene"].frame_start = 1
bpy.data.scenes["Scene"].frame_end = 1000
bpy.data.scenes["Scene"].render.resolution_percentage = 20
bpy.data.scenes["Scene"].render.use_antialiasing = False
bpy.data.scenes["Scene"].render.antialiasing_samples = '5'
bpy.data.scenes["Scene"].render.filepath = "//output/06cc-SumilationAutonomus_SwarmFollow-03-"
#variables
running = False
start_frame_check = True
end_frame_check = True
postRender_frame_check = True
##########################################################
# MVC
# Model = class boild
# View = bpy.data.objects
# Controller = class floc
# Based on Shiffman's code
##########################################################
class Flock():
boids = []
def __init__(self):
pass
def run(self):
for b in self.boids:
try:
b.run(self.boids)
except:
pass
def addBoild(self, boid):
self.boids.append(boid)
class Boild():
startloc = 0
loc = 0
vel = Vector((0,1,0))
acc = 0
#r = 3.0
wandertheta = 0.0
maxforce = 0.50 # 0.15 # Maximum steering force
maxspeed = 1.0 # 0.35 # Maximum speed
slowdown_distance = 5 # 5
name=''
mesh=''
#seeker config
seeker_dist = 4
def __init__(self, x, y, z, n):
self.mesh = bpy.data.objects[n]
self.loc = self.mesh.location#Vector( (x, y, z) )
self.startloc = self.mesh.location.copy()
self.vel = self.vel - self.loc
self.vel.normalize()
self.acc = Vector((0,0,0))
self.front = self.vel.copy()
self.vel *= 0.5
self.name = n
def run(self, boids):
#self.circleseek()
self.flock(boids)
self.update()
def flock(self, boids):
sep = self.separate(boids) # Separation
ali = self.align(boids) # Alignment
coh = self.cohesion(boids) # Cohesion
# Arbitrarily weight these forces
# When check for colision, greater the distance to check, less the weight to aplly
if self.colision(boids, 4.0):
sep*=(1.2)#1.5
else:
sep*=(1.0)#1.5
ali*=(0.0)#1.0
coh*=(1.0)#1.0
# Add the force vectors to acceleration
self.acc+=(sep)
self.acc+=(ali)
self.acc+=(coh)
def circleseek(self):
circleRadius = 1.0 #raio do circulo
circleDist = 2.0 #distancia do boild para o circulo
tar = bpy.data.objects['Cube']
circleloc = self.vel.copy()
circleloc.normalize()
circleloc *= circleDist
circleloc += self.loc
dir = tar.location - circleloc
self.acc = self.steer(dir, True)
def steer(self, target, slowdown):
steer = Vector((0,0,0))
desired = target - self.loc
dist = desired.length
if dist > 0:
desired.normalize()
if slowdown and (dist<self.slowdown_distance):
desired *= (self.maxspeed*dist/10.0)
else:
desired *= self.maxspeed
steer = desired-self.vel
if( steer.length > self.maxforce ):
steer.normalize()
steer *= self.maxforce
return steer
def limit(self, vector, limit):
if vector.length > limit:
vector.normalize()
return vector * limit
return vector
def separate(self, boids):
desiredseparation = 15.0 #25.0
steer = Vector((0,0,0))
count = 0
#For every boid in the system, check if it's too close
for other in boids:
d = self.loc-other.loc
#If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d.length > 0) and (d.length < desiredseparation)):
# Calculate vector pointing away from neighbor
diff = d.copy()
diff.normalize()
diff = diff/d.length # Weight by distance
steer+=diff
count+=1
# Average -- divide by how many
if (count > 0):
steer = steer/count
# As long as the vector is greater than 0
if (steer.length > 0):
#Implement Reynolds: Steering = Desired - Velocity
steer.normalize()
steer*=self.maxspeed
steer-=self.vel
steer = self.limit(steer, self.maxforce)
return steer
def align(self, boids):
neighbordist = 50.0 #50.0
steer = Vector((0,0,0))
count = 0
for other in boids:
d = self.loc-other.loc
if ((d.length > 0) and (d.length < neighbordist)):
steer+=other.vel
count+=1
if (count > 0):
steer/=count
# As long as the vector is greater than 0
if (steer.length > 0):
# Implement Reynolds: Steering = Desired - Velocity
steer.normalize()
steer*=self.maxspeed
steer-=self.vel
steer = self.limit(steer, self.maxforce)
#steer.limit(maxforce)
return steer
def cohesion(self, boids):
neighbordist = 50.0 #50.0
sum = Vector((0,0,0)) # Start with empty vector to accumulate all locations
count = 0
for other in boids:
d = self.loc-other.loc
if (d.length > 0) and (d.length < neighbordist):
sum += other.loc # Add location
count+=1
# Cohersion with target
target = bpy.data.objects['Cube']
dc = self.loc - target.location
if (dc.length > 0) and (d.length < neighbordist):
sum += target.location #other.loc # Add location
count+=1
if (count > 0):
sum/=count
result = self.steer(sum,False)# Steer towards the location
return result
return sum
def colision(self, boids, dist):
# When check for colision, greater the distance to check, less the weight to aplly
colisiondist = dist #50.0
#sum = Vector((0,0,0)) # Start with empty vector to accumulate all locations
#count = 0
for other in boids:
d = self.loc-other.loc
if (d.length > 0) and (d.length < colisiondist):
#sum += other.loc # Add location
#count+=1
return True
return False
def update(self):
#self.loc = self.mesh.location
#Movimentação
#print(targ.location)
#self.acc = targ.location-self.loc
#self.acc.normalize()
self.vel = self.vel+self.acc
if self.vel.length > self.maxspeed:
self.vel.normalize()
self.vel *= self.maxspeed
self.loc = self.loc+self.vel
tar = bpy.data.objects['Cube']
path_for_angle = tar.location-self.loc
#path = targ.location - self.loc
ang = self.vel.angle(path_for_angle)
ang_deg = math.degrees(ang)
#Manipulação da matrix
ob = self.mesh
loc, rot, scale = ob.matrix_world.decompose()
#forward = look_at_ob_loc - loc # look to the object
forward = self.vel # look to the way vel
#forward = self.acc # look to the object, same as acc
up = Vector((0,0,1))
right = forward.cross(up).normalized() # Vector.length closer to 1.0
up = right.cross(forward)
scale_mat = Matrix()
scale_mat[0][0] = scale[0]
scale_mat[1][1] = scale[1]
scale_mat[2][2] = scale[2]
rot = Matrix((right, forward, up)).transposed().normalized().to_4x4()
# +Z axis as looking direction
#axis_conversion = Matrix.Rotation(math.radians(180), 4, 'Z')
#axis_conversion_2 = Matrix.Rotation(math.radians(90), 4, 'X')
# +X axis as looking direction
#axis_conversion = Matrix.Rotation(math.radians(90), 4, 'Z')#math.radians(180), 4, 'Z'
#axis_conversion_2 = Matrix.Rotation(math.radians(0), 4, 'X')#math.radians(90), 4, 'X'
# +Y axis as looking direction
axis_conversion = Matrix.Rotation(math.radians(0), 4, 'Z')
axis_conversion_2 = Matrix.Rotation(math.radians(0), 4, 'X')
axis_conversion_3 = Matrix.Rotation(math.radians(ang_deg), 4, 'Y')
mat = Matrix.Translation(self.loc) * rot * axis_conversion * axis_conversion_2 * axis_conversion_3 * scale_mat
ob.matrix_world = mat
self.acc *= 0
Swarm = Flock()
# objects for the flocking
boilds_objs = ["Led", "Led.002", "Led.003", "Led.004", "Led.005"]
for bo in boilds_objs:
Swarm.addBoild( Boild(0,0,0, bo) )
#Main Loop*
@persistent
def my_handler(scene):
print("Frame Change", scene.frame_current)
#@persistent
def start_frame(scene):
global running, Swarm, boild, start_frame_check, end_frame_check
if not running:
print("Frame Change", scene.frame_current)
print( type(scene.frame_current) )
Swarm.run()
running = True
#@persistent
def end_frame(scene):
global running, start_frame_check, end_frame_check
#pass
running = False
def PostRender(scene):
global Vector, Euler, Swarm
print("Render is Over")
for ob in Swarm.boilds:
ob.mesh.location = ob.startloc
ob.mesh.rotation_euler = Euler((0,0,0))
bpy.app.handlers.frame_change_pre.append(start_frame)
bpy.app.handlers.frame_change_post.append(end_frame)
bpy.app.handlers.render_pre.append(start_frame)
bpy.app.handlers.render_post.append(end_frame)
bpy.app.handlers.render_complete.append(PostRender)
bpy.ops.render.render(animation=True)

No futuro eu posto outros exemplos.