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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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’
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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.