import bpy,mathutils,random, math #autowalker #gpl block here #(c) Tube Project (Josh Wedlake) armature='Armature' #bone at ground level which aligns with y forward, z up, x to the animals right side in POSE MODE. root_bone='root' #child of root bone used to bounce the body. Y should point up in POSE MODE. body_bone='body' #the IK target prefix for each leg target_bone='leg_target' #the limit radius bone - the radius is taken from the head limit_radius_bone='leg_limit' limit_height_bone='leg_upper' limit_radius_multiplier=1.5 limit_height_multiplier=1 idle_speed_threshold=0.0001 #in blender units per frame leg_grouped_postfixes=[['.000','.001','.002'],['.005','.004','.003']] leg_grouped_start_phase=[[0,0.166,0.333],[0.5,0.66,0.833]] #phases must be between 0-1 leg_grouped_down_min=[1,1] leg_grouped_down_max=[3,3] use_stabilise=True use_leg_sequencing=True leg_grouped_idle_max=[15,15] #the number of frames the leg can be airborne before it switches to idling leg_grouped_idle_place=[3,3] #the number of frames it takes to place an idle leg use_armature_children=True use_key_minimal=True use_body_movement=True use_strict_limit=True body_movement_amount=1 boid_size=1 start_frame=1 end_frame=150 lift_profile=[0,0.7,1,0.7,0] def build_boids_list(): boid_list=[] for each_object in bpy.context.selected_objects: if each_object.type=='EMPTY': if each_object['tb_type']=='boid': boid_list.append(bpy.data.objects[each_object.name]) return boid_list def blend_rotations(rotation_a,rotation_b,amount): #amount is the amount of rot b to include in rot a rot_a_quat=rotation_a.to_quaternion() rot_b_quat=rotation_b.to_quaternion() rot_blend=rot_a_quat.slerp(rot_b_quat,amount) return rot_blend.to_euler() def get_transform_matrix_bones(this_armature,from_name,to_name,ignore_parent): #transform matrix from one bone space to another... data_bone = this_armature.data.bones[to_name] #all matrices are in armature space unless commented otherwise otherloc = this_armature.pose.bones[from_name].matrix # final 4x4 mat of target, location. bonemat_local = mathutils.Matrix(data_bone.matrix_local) # self rest matrix if data_bone.parent: parentposemat = mathutils.Matrix(this_armature.pose.bones[data_bone.parent.name].matrix) parentbonemat = mathutils.Matrix(data_bone.parent.matrix_local) else: parentposemat = bonemat_local.copy().identity() parentbonemat = bonemat_local.copy().identity() if parentbonemat == parentposemat or ignore_parent: newmat = bonemat_local.inverted() * otherloc else: bonemat = parentbonemat.inverted() * bonemat_local newmat = bonemat.inverted() * parentposemat.inverted() * otherloc return newmat def get_transform_matrix_bone_world(this_armature,from_name): #transform matrix from one bone to world data_bone=this_armature.data.bones[from_name] data_bone_mat_local=data_bone.matrix_local if data_bone.parent: #from parent pose bone space to armature space parentposemat = mathutils.Matrix(this_armature.pose.bones[data_bone.parent.name].matrix) #from parent data bone space to armature space parentbonemat = mathutils.Matrix(data_bone.parent.matrix_local) #from databone space to armature space to parent databone space bonemat = parentbonemat.inverted() * data_bone_mat_local #brackets contain (armature space to parent pose bone space <-> parent databone space to databone space).inverted() #brackets give databone space to armature space (via parent) #finally premultiply by world. newmat = this_armature.matrix_world*(bonemat.inverted()*parentposemat.inverted()).inverted() else: newmat=(this_armature.matrix_world*data_bone_mat_local) return newmat def get_world_position_bone(this_armature,from_name): return this_armature.matrix_world*this_armature.pose.bones[from_name].matrix def get_transform_matrix_world_bone(this_armature,to_name,ignore_parent): #transform matrix from world to bone space data_bone = this_armature.data.bones[to_name] #all matrices are in armature space unless commented otherwise bonemat_local = mathutils.Matrix(data_bone.matrix_local) # self rest matrix otherloc=this_armature.matrix_world.copy().inverted() if data_bone.parent: parentposemat = mathutils.Matrix(this_armature.pose.bones[data_bone.parent.name].matrix) parentbonemat = mathutils.Matrix(data_bone.parent.matrix_local) else: parentposemat = bonemat_local.copy().identity() parentbonemat = bonemat_local.copy().identity() if parentbonemat == parentposemat or ignore_parent: newmat = bonemat_local.inverted() * otherloc else: bonemat = parentbonemat.inverted() * bonemat_local newmat = bonemat.inverted() * parentposemat.inverted() * otherloc return newmat def create_armatures(boid_list,armature_object,root_bone,boid_size,leg_grouped_postfixes,target_bone,limit_height_bone,use_armature_children): armature_list=[] print('Boid List Count = ' + str(len(boid_list))) for each_boid in boid_list: if 'BRIG'+each_boid.name not in bpy.data.objects: bpy.ops.object.select_all(action='DESELECT') if use_armature_children: for each_child in armature_object.children: each_child.select=True armature_object.select=True bpy.context.scene.objects.active=armature_object #create a duplicate bpy.ops.object.duplicate(linked=False) boid_armature=bpy.context.active_object #rename it boid_armature.name='BRIG'+each_boid.name else: boid_armature=bpy.data.objects['BRIG'+each_boid.name] armature_list.append(boid_armature) #each_boid.update(refresh='OBJECT') #each_boid.update(refresh='DATA') #each_boid.update(refresh='TIME') bpy.context.scene.update() boid_armature.scale=mathutils.Vector((boid_size,boid_size,boid_size)) #boid_armature.location=each_boid.location.copy() #visual location boid_armature.location=each_boid.matrix_world.copy().to_translation() #boid_armature.update(refresh='OBJECT') #boid_armature.update(refresh='DATA') #boid_armature.update(refresh='TIME') bpy.context.scene.update() boid_armature.pose.bones[root_bone].location=\ each_boid.matrix_world.copy().to_translation()*boid_armature.matrix_world.copy().inverted()*boid_armature.data.bones[root_bone].matrix_local.copy().inverted() #copy boid rotation boid_armature.pose.bones[root_bone].rotation_quaternion=(each_boid.matrix_world.copy().to_3x3()*boid_armature.matrix_world.copy().to_3x3().inverted()).to_quaternion() #boid_armature.update(refresh='OBJECT') #boid_armature.update(refresh='DATA') #boid_armature.update(refresh='TIME') bpy.context.scene.update() boid_armature.pose.bones[root_bone].keyframe_insert('location') boid_armature.pose.bones[root_bone].keyframe_insert('rotation_quaternion') #reset feet and randomise start position... for leg_postfixes in leg_grouped_postfixes: for each_leg_postfix in leg_postfixes: boid_armature.pose.bones[target_bone+each_leg_postfix].location=boid_armature.pose.bones[limit_height_bone+each_leg_postfix].location*get_transform_matrix_bones(boid_armature,limit_height_bone+each_leg_postfix,target_bone+each_leg_postfix,False) boid_armature.pose.bones[target_bone+each_leg_postfix].keyframe_insert('location') return armature_list def build_leg_database(boid_list,armature_list,leg_grouped_postfixes,root_bone,target_bone,limit_radius_bone,limit_height_bone,leg_grouped_down_min,leg_grouped_start_phase,armature_database): leg_database=dict() print('Building Leg Datbase...') # Armature Name # | # +leg group index eg. 0 # | # +leg postfix eg. '.001' # | # +status... 0 is down, 1 is up # +speed_multiplier (for learning step rate) # +lift_radius # +frame_state_change print(str(len(armature_list))) for index,each_armature in enumerate(armature_list): print('\tArmature '+each_armature.name+'...') leg_database[each_armature.name]=[] for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): print('\t\tLeg group '+str(each_group_index)+'...') leg_database[each_armature.name].append(dict()) for postfix_index,each_leg_postfix in enumerate(leg_postfixes): print('\t\t\tLeg '+str(each_leg_postfix)+'...') leg_database[each_armature.name][each_group_index][each_leg_postfix]=dict() this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] if leg_grouped_start_phase[each_group_index][postfix_index]<=0.5: this_leg_db['status']=0 #0=down,1=up,2=resting this_leg_db['last_status']=0 this_leg_db['radius_variation']=1-(leg_grouped_start_phase[each_group_index][postfix_index]*2) #lift the leg when this proportion of the radius is exceeded else: this_leg_db['status']=1 #0=down,1=up,2=resting this_leg_db['last_status']=1 this_leg_db['radius_variation']=1-((leg_grouped_start_phase[each_group_index][postfix_index]-0.5)*2) #lift the leg when this proportion of the radius is exceeded #check this... this_leg_db['speed_multiplier']=armature_database[each_group_index][each_leg_postfix]['speed_multiplier'] this_leg_db['frame_state_change']=-1 #the frame the leg last was put down or picked up this_leg_db['idle_count']=0 #save world space positions this_leg_db['this_pos']=each_armature.pose.bones[target_bone+each_leg_postfix].location*get_transform_matrix_bone_world(each_armature,target_bone+each_leg_postfix) this_leg_db['last_pos']=this_leg_db['this_pos'].copy() this_leg_db['this_limit_pos']=each_armature.pose.bones[limit_height_bone+each_leg_postfix].location*get_transform_matrix_bone_world(each_armature,limit_height_bone+each_leg_postfix) this_leg_db['last_limit_pos']=this_leg_db['this_limit_pos'].copy() this_leg_db['placed_limit_pos']=this_leg_db['this_limit_pos'].copy() this_leg_db['leg_distance']=0 #distance of target from limit pos this_leg_db['last_leg_distance']=0 this_leg_db['limit_travel_distance']=0 #distance of limiter from when limiter was last placed this_leg_db['estimated_phase']=0 return leg_database def build_armature_database(armature_object,limit_radius_bone,limit_height_bone,leg_grouped_postfixes,limit_radius_multiplier,limit_height_multiplier,boid_size,leg_grouped_start_phase,leg_grouped_down_min,body_bone): armature_database=[] phases_lookup=[] #do [phase,[group_index,postfix]] print('Building Phases Lookup...') for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): for each_leg_postfix_index,each_leg_postfix in enumerate(leg_postfixes): phases_lookup.append([leg_grouped_start_phase[each_group_index][each_leg_postfix_index],[each_group_index,each_leg_postfix_index]]) phases_lookup=sorted(phases_lookup, key=lambda a: a[0]) # Leg group index # | # +leg postfix # | # +limit_radius # +limit_height print('Building Armature Datbase...') for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): print('\t\tLeg group '+str(each_group_index)+'...') armature_database.append(dict()) for each_leg_postfix_index,each_leg_postfix in enumerate(leg_postfixes): print('\t\t\tLeg '+str(each_leg_postfix)+'...') armature_database[each_group_index][each_leg_postfix]=dict() #limit distances in world space #armature_database[each_group_index][each_leg_postfix]['limit_radius']=boid_size*((armature_object.data.bones[limit_radius_bone+each_leg_postfix].head-armature_object.data.bones[target_bone+each_leg_postfix].head)*armature_object.matrix_world).length*limit_radius_multiplier target_bone_world=armature_object.pose.bones[target_bone+each_leg_postfix].location*get_transform_matrix_bone_world(armature_object,target_bone+each_leg_postfix) limit_radius_bone_world=armature_object.pose.bones[limit_radius_bone+each_leg_postfix].location*get_transform_matrix_bone_world(armature_object,limit_radius_bone+each_leg_postfix) armature_database[each_group_index][each_leg_postfix]['limit_radius']=(limit_radius_bone_world-target_bone_world).length #height is in bone space ########################fixme armature_database[each_group_index][each_leg_postfix]['limit_height']=boid_size*(armature_object.data.bones[limit_height_bone+each_leg_postfix].tail-armature_object.data.bones[limit_height_bone+each_leg_postfix].head).length*limit_height_multiplier #armature_database[each_group_index][each_leg_postfix]['speed_multiplier']=(len(leg_grouped_postfixes)-leg_grouped_down_min[each_group_index])+1 armature_database[each_group_index][each_leg_postfix]['speed_multiplier']=2 armature_database[each_group_index][each_leg_postfix]['body_target_vector']=armature_object.pose.bones[target_bone+each_leg_postfix].location*get_transform_matrix_bones(armature_object,target_bone+each_leg_postfix,body_bone,False) #find it in the sorted phases for each_phase_lookup_index,each_phase_lookup in enumerate(phases_lookup): if each_phase_lookup[1]==[each_group_index,each_leg_postfix_index]: this_phase_lookup_index=each_phase_lookup_index break if this_phase_lookup_index==0: armature_database[each_group_index][each_leg_postfix]['phase_look_to']=-1 else: armature_database[each_group_index][each_leg_postfix]['phase_look_to']=[phases_lookup[each_phase_lookup_index-1][1][0],leg_grouped_postfixes[phases_lookup[each_phase_lookup_index-1][1][0]][phases_lookup[each_phase_lookup_index-1][1][1]],phases_lookup[each_phase_lookup_index-1][1][1]] return armature_database def update_armature_position(each_armature,each_boid,root_bone): #move the armature root and key position and update armature #each_armature.pose.bones[root_bone].location=each_boid.location.copy()*each_armature.matrix_world.copy().inverted()*each_armature.data.bones[root_bone].matrix_local.copy().inverted() each_armature.pose.bones[root_bone].location=each_boid.matrix_world.copy().to_translation()*each_armature.matrix_world.copy().inverted()*each_armature.data.bones[root_bone].matrix_local.copy().inverted() #copy boid rotation #each_armature.pose.bones[root_bone].rotation_quaternion=(each_boid.rotation_euler.to_matrix()*each_armature.matrix_world.copy().to_3x3().inverted()).to_quaternion() each_armature.pose.bones[root_bone].rotation_quaternion=(each_boid.matrix_world.copy()*each_armature.matrix_world.copy().inverted()).to_quaternion() #each_armature.update(refresh='OBJECT') #each_armature.update(refresh='DATA') #each_armature.update(refresh='TIME') bpy.context.scene.update() each_armature.pose.bones[root_bone].keyframe_insert('location') each_armature.pose.bones[root_bone].keyframe_insert('rotation_quaternion') #each_armature.update(refresh='OBJECT') #each_armature.update(refresh='DATA') #each_armature.update(refresh='TIME') bpy.context.scene.update() def sample_lift_profile(lift_profile,position): #position is 0-1 if position>1: position=0.9999 #float safety sample_index=position*(len(lift_profile)-1) sample_lower_index=math.floor(sample_index) sample_upper_index=math.ceil(sample_index) upper_amount=sample_index-sample_lower_index sample=(lift_profile[sample_lower_index]*(1-upper_amount))+(lift_profile[sample_upper_index]*upper_amount) return sample def change_leg_status(this_leg_db,status,this_frame,this_leg_history_database): this_leg_db['last_status']=int(this_leg_db['status']) this_leg_db['status']=status this_leg_db['frame_state_change']=this_frame this_leg_db['placed_limit_pos']=this_leg_db['this_limit_pos'].copy() this_leg_db['limit_travel_distance']=0 this_leg_db['radius_variation']=1-(random.random()*0.1) #for variety this_leg_history_database[this_frame]=status return this_leg_db def build_leg_history_database(armature_list,leg_grouped_postfixes,start_frame): #store by leg... #frame number and status leg_history_database=dict() for each_armature in armature_list: leg_history_database[each_armature.name]=[] for each_leg_postfix_group_index,each_leg_postfix_group in enumerate(leg_grouped_postfixes): leg_history_database[each_armature.name].append(dict()) for each_leg_postfix in each_leg_postfix_group: leg_history_database[each_armature.name][each_leg_postfix_group_index][each_leg_postfix]=dict() leg_history_database[each_armature.name][each_leg_postfix_group_index][each_leg_postfix][start_frame]=0 return leg_history_database #def build_boid_database(armature_list): # boid_database=dict() # for each_armature in armature_list: # boid_database[each_armature.name]=dict() # boid_database[each_armature.name]['last_pos']=mathutils.Vector((0,0,0)) # boid_database[each_armature.name]['last_rot']=mathutils.Euler((0,0,0)) #---------------------------------------------------end defs #collect boids print('Collecting Boids...') boid_list=build_boids_list() armature_object=bpy.data.objects[armature] bpy.context.scene.frame_set(frame=start_frame) armature_list=create_armatures(boid_list,armature_object,root_bone,boid_size,leg_grouped_postfixes,target_bone,limit_height_bone,use_armature_children) armature_database=build_armature_database(armature_object,limit_radius_bone,limit_height_bone, leg_grouped_postfixes,limit_radius_multiplier, limit_height_multiplier,boid_size,leg_grouped_start_phase, leg_grouped_down_min,body_bone) leg_database=build_leg_database(boid_list,armature_list,leg_grouped_postfixes,root_bone,target_bone,limit_radius_bone,limit_height_bone,leg_grouped_down_min,leg_grouped_start_phase,armature_database) leg_history_database=build_leg_history_database(armature_list,leg_grouped_postfixes,start_frame) rewind_keys_list=[] for this_frame in range(start_frame,end_frame+1): if use_key_minimal and len(rewind_keys_list)>0: refreshed_armature_list=[] print(str(len(rewind_keys_list))+' holding keys to add...') bpy.context.scene.frame_set(frame=this_frame-2) for each_rewind_key in rewind_keys_list: if each_rewind_key[0].name not in refreshed_armature_list: #This was removed because it's no longer supported in this form #each_rewind_key[0].update(refresh='OBJECT') #each_rewind_key[0].update(refresh='DATA') #each_rewind_key[0].update(refresh='TIME') refreshed_armature_list.append(each_rewind_key[0].name) each_rewind_key[0].pose.bones[each_rewind_key[1]].location=each_rewind_key[2]*get_transform_matrix_world_bone(each_rewind_key[0],each_rewind_key[1],False) each_rewind_key[0].pose.bones[each_rewind_key[1]].keyframe_insert('location') rewind_keys_list=[] #print('Frame '+str(this_frame)+'...') #--------------------set frame bpy.context.scene.frame_set(frame=this_frame) for index,each_armature in enumerate(armature_list): each_boid=boid_list[index] update_armature_position(each_armature,each_boid,root_bone) legs_down_list_combined=[] legs_up_list_combined=[] #for each leg group for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): legs_down=0 #the leg whose limiter has moved the furthest since it was places leg_max_limit_travel_down_value=-1 leg_max_limit_travel_up_value=-1 leg_max_limit_travel_down_index=-1 leg_max_limit_travel_up_index=-1 legs_down_list=[] legs_up_list=[] #for each leg for each_leg_postfix in leg_postfixes: this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] this_leg_db['last_status']=int(this_leg_db['status']) #update this pos and last pos this_leg_db['last_pos']=this_leg_db['this_pos'].copy() #this_leg_db['this_pos']=each_armature.pose.bones[target_bone+each_leg_postfix].location*get_transform_matrix_bone_world(each_armature,target_bone+each_leg_postfix) this_leg_db['last_limit_pos']=this_leg_db['this_limit_pos'].copy() this_leg_db['this_limit_pos']=each_armature.pose.bones[limit_height_bone+each_leg_postfix].location*get_transform_matrix_bone_world(each_armature,limit_height_bone+each_leg_postfix) this_leg_db['last_leg_distance']=this_leg_db['leg_distance'] this_leg_db['leg_distance']=(this_leg_db['this_pos']-this_leg_db['this_limit_pos']).length this_leg_db['limit_travel_distance']=(this_leg_db['placed_limit_pos']-this_leg_db['this_limit_pos']).length #find out which leg has been on the ground drifting for longest and the reverse if this_leg_db['status']==0: legs_down+=1 legs_down_list.append([each_leg_postfix,this_leg_db['limit_travel_distance'],each_group_index]) elif this_leg_db['status']==1: legs_up_list.append([each_leg_postfix,this_leg_db['limit_travel_distance'],each_group_index]) if use_leg_sequencing: legs_down_list_combined+=legs_down_list legs_up_list_combined+=legs_up_list legs_down_list=sorted(legs_down_list, key=lambda leg_limit_data: leg_limit_data[1]) legs_up_list=sorted(legs_up_list, key=lambda leg_limit_data: leg_limit_data[1]) for each_leg_postfix in leg_postfixes: this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] this_leg_history_db=leg_history_database[each_armature.name][each_group_index][each_leg_postfix] this_limit_radius=armature_database[each_group_index][each_leg_postfix]['limit_radius']*this_leg_db['radius_variation'] #-------------------------------------------------------------------------------------------------------------- #code to force pick up and put down if the IK limit has been exceeded if this_leg_db['leg_distance']>=this_limit_radius: if this_leg_db['status']==0: #is the vector from the foot to the centre the same as the direction of travel direction_of_travel=this_leg_db['this_limit_pos']-this_leg_db['last_limit_pos'] foot_to_centre=this_leg_db['this_limit_pos']-this_leg_db['this_pos'] if direction_of_travel.length>0 and foot_to_centre.length>0: if direction_of_travel.angle(foot_to_centre)0: #print('min exceeded: must place a leg!') #pick the leg with the largest distance travelled since leg_to_set_down=leg_database[each_armature.name][each_group_index][legs_up_list[0][0]] leg_to_set_down_history=leg_history_database[each_armature.name][each_group_index][legs_up_list[0][0]] leg_to_set_down=change_leg_status(leg_to_set_down,0,this_frame,leg_to_set_down_history) #and hurry up the current leg legs_down+=1 elif this_leg_db['status']==1: #print('force place leg') direction_of_travel=this_leg_db['this_limit_pos']-this_leg_db['last_limit_pos'] centre_to_foot=this_leg_db['this_pos']-this_leg_db['this_limit_pos'] if direction_of_travel.length>0 and centre_to_foot.length>0: if direction_of_travel.angle(centre_to_foot)leg_grouped_down_max[each_group_index] and len(legs_down_list)>0: #print('max exceeded: must lift a leg!') #as above but opposite leg_to_pick_up=leg_database[each_armature.name][each_group_index][legs_down_list[0][0]] leg_to_pick_up_history=leg_history_database[each_armature.name][each_group_index][legs_down_list[0][0]] leg_to_pick_up=change_leg_status(leg_to_pick_up,1,this_frame,leg_to_pick_up_history) #the last leg which was set down should be slowed up legs_down-=1 elif this_leg_db['status']==2: #this leg was on its way down, need to quickly change tack this_leg_db=change_leg_status(this_leg_db,0,this_frame,this_leg_history_db) if use_strict_limit: #normalise the position #print(str(this_leg_db['this_pos']-this_leg_db['this_limit_pos'].normalize())) this_leg_db['this_pos']=((this_leg_db['this_pos']-this_leg_db['this_limit_pos']).normalized() *this_limit_radius)+this_leg_db['this_limit_pos'] #leg animation code #--------------------------------------------- if this_leg_db['status']==0: #stick the leg where it is each_armature.pose.bones[target_bone+each_leg_postfix].location=this_leg_db['this_pos']*get_transform_matrix_world_bone(each_armature,target_bone+each_leg_postfix,False) elif this_leg_db['status']==1 or this_leg_db['status']==2: vector_to_centre=this_leg_db['this_limit_pos']-this_leg_db['this_pos'] limit_vector=this_leg_db['this_limit_pos']-this_leg_db['last_limit_pos'] if limit_vector.length>0 and vector_to_centre.length>limit_vector.length and vector_to_centre.angle(limit_vector)leg_grouped_idle_place[each_group_index]: this_leg_db=change_leg_status(this_leg_db,0,this_frame,this_leg_history_db) this_leg_db['this_pos']=this_leg_db['this_pos']+(desired_vector*this_leg_db['speed_multiplier']) each_armature.pose.bones[target_bone+each_leg_postfix].location=this_leg_db['this_pos']*get_transform_matrix_world_bone(each_armature,target_bone+each_leg_postfix,False) if (not use_key_minimal) or (use_key_minimal and this_leg_db['status']!=this_leg_db['last_status']) or this_frame==end_frame or this_frame==start_frame: each_armature.pose.bones[target_bone+each_leg_postfix].keyframe_insert('location') #------------------------------------------- #detect idle if (this_leg_db['this_limit_pos']-this_leg_db['last_limit_pos']).lengthleg_grouped_idle_max[each_group_index] or this_leg_db['idle_count']>1) and this_leg_db['status']==1: this_leg_db=change_leg_status(this_leg_db,2,this_frame,this_leg_history_db) #----------------------- #debug each_armature.pose.bones[target_bone+each_leg_postfix]['aw_status']=this_leg_db['status'] each_armature.pose.bones[target_bone+each_leg_postfix].keyframe_insert('["aw_status"]') #last thing to check #------------------------------------------------------------------------------------- if use_key_minimal and this_leg_db['status']!=this_leg_db['last_status']: #add a holding key on the previous frame rewind_keys_list.append([each_armature,target_bone+each_leg_postfix,this_leg_db['last_pos']]) #---------------------- #leg sequencing if use_leg_sequencing: #calculate the 'estimated phases' of all legs in all leg groups #these are a rough order of all the legs. legs_down_list_combined=sorted(legs_down_list_combined, key=lambda leg_limit_data: leg_limit_data[1]) legs_up_list_combined=sorted(legs_up_list_combined, key=lambda leg_limit_data: leg_limit_data[1]) for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): for each_leg_postfix_index,each_leg_postfix in enumerate(leg_postfixes): #phasing this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] #------------------------------------ #estimate phase if this_leg_db['last_status']==0: for index,each_leg_limit_data in enumerate(legs_down_list_combined): if each_leg_limit_data[0]==each_leg_postfix and each_leg_limit_data[2]==each_group_index: this_leg_db['estimated_phase']=index break elif this_leg_db['last_status']==1: for index,each_leg_limit_data in enumerate(legs_up_list_combined): if each_leg_limit_data[0]==each_leg_postfix and each_leg_limit_data[2]==each_group_index: this_leg_db['estimated_phase']=len(legs_down_list_combined)+index break elif this_leg_db['last_status']==2: this_leg_db['estimated_phase']=len(legs_down_list_combined)+len(legs_up_list_combined) #print(this_leg_db['estimated_phase']) #now see if the leg is behind or in front of where it should be #and adjust its speed accordingly #the '0 phased' leg never gets adjusted for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): for each_leg_postfix_index,each_leg_postfix in enumerate(leg_postfixes): #do not adjust the first leg if armature_database[each_group_index][each_leg_postfix]['phase_look_to']!=-1: this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] target_leg_db=leg_database[each_armature.name][armature_database[each_group_index][each_leg_postfix]['phase_look_to'][0]][armature_database[each_group_index][each_leg_postfix]['phase_look_to'][1]] #find smallest phase difference phase_difference=(target_leg_db['estimated_phase']+1)-this_leg_db['estimated_phase'] phase_difference_backward=phase_difference-(len(legs_down_list_combined)+len(legs_up_list_combined)) phase_difference_forward=phase_difference+(len(legs_down_list_combined)+len(legs_up_list_combined)) #phase difference is what needs to be done to get this_leg in sync with the next leg in the sorted phase list #do the one with the minimum change needed #probably quicker to do lots of ifs than to use min with a lambda key? if math.fabs(phase_difference)<=math.fabs(phase_difference_backward) and math.fabs(phase_difference)<=math.fabs(phase_difference_forward): this_phase_difference=phase_difference elif math.fabs(phase_difference_forward)<=math.fabs(phase_difference_backward) and math.fabs(phase_difference_forward)<=math.fabs(phase_difference): this_phase_difference=phase_difference_forward else: this_phase_difference=phase_difference_backward # <0 indicates it needs to earlier in the ordered list of phasings if this_phase_difference<0: #speedup this_leg_db['speed_multiplier']=armature_database[each_group_index][each_leg_postfix]['speed_multiplier']*1.1 else: #slowdown this_leg_db['speed_multiplier']=armature_database[each_group_index][each_leg_postfix]['speed_multiplier']*0.9 #------------------------------ #body movements and leg height animation based on history db for this_frame in range(start_frame,end_frame+1): #print('Frame '+str(this_frame)+'...') #--------------------set frame bpy.context.scene.frame_set(frame=this_frame) for index,each_armature in enumerate(armature_list): #is this needed? #only if the boid is not on the z plane? #each_armature.update(refresh='OBJECT') #each_armature.update(refresh='DATA') #each_armature.update(refresh='TIME') #body movement body_movement=mathutils.Vector((0.0,0.0,0.0)) #for each leg group for each_group_index,leg_postfixes in enumerate(leg_grouped_postfixes): for each_leg_postfix in leg_postfixes: #find the current status in the db this_leg_history_db=leg_history_database[each_armature.name][each_group_index][each_leg_postfix] this_leg_db=leg_database[each_armature.name][each_group_index][each_leg_postfix] recorded_frames=list(this_leg_history_db.keys()) recorded_frames.sort() for recorded_frames_index in range(0,len(recorded_frames)): if recorded_frames_indexthis_frame: start_phase_frame=recorded_frames[recorded_frames_index] end_phase_frame=recorded_frames[recorded_frames_index+1] break else: start_phase_frame=recorded_frames[recorded_frames_index] end_phase_frame=end_frame this_leg_history_db[end_phase_frame]=0 break if this_leg_history_db[start_phase_frame]==0: #leg is stationary lift_profile_result=0 #print('0-0') elif this_leg_history_db[start_phase_frame]==1: if this_leg_history_db[end_phase_frame]==0: #leg is moving across normal arc lift_profile_position=((this_frame+1)-start_phase_frame)/(end_phase_frame-start_phase_frame) lift_profile_result=sample_lift_profile(lift_profile,lift_profile_position) #print('1-0') elif this_leg_history_db[end_phase_frame]==2: #leg is moving up to peak of arc lift_profile_position=((this_frame+1)-start_phase_frame)/((end_phase_frame-start_phase_frame)*2) lift_profile_result=sample_lift_profile(lift_profile,lift_profile_position) #print('1-2') elif this_leg_history_db[start_phase_frame]==2: lift_profile_position=0.5+(((this_frame+1)-start_phase_frame)/((end_phase_frame-start_phase_frame)*2)) lift_profile_result=sample_lift_profile(lift_profile,lift_profile_position) #leg is on way down from peak of arc #print('2-0') if this_frame==start_phase_frame or this_frame==end_phase_frame or this_frame==int((start_phase_frame+end_phase_frame)/2): this_frame_key_minimal=True else: this_frame_key_minimal=False if (not use_key_minimal) or (use_key_minimal and this_frame_key_minimal): each_armature.pose.bones[target_bone+each_leg_postfix].location[1]+=lift_profile_result each_armature.pose.bones[target_bone+each_leg_postfix].keyframe_insert('location') if use_body_movement: #a lifted leg draws the body away from it body_movement+=lift_profile_result*armature_database[each_group_index][each_leg_postfix]['body_target_vector']*-0.1*body_movement_amount #body movement if use_body_movement: each_armature.pose.bones[body_bone].location=(each_armature.pose.bones[body_bone].location*0.5)+(body_movement*0.5) if this_frame==start_phase_frame or this_frame==end_phase_frame or this_frame==int((start_phase_frame+end_phase_frame)/2): this_frame_key_minimal=True else: this_frame_key_minimal=False if (not use_key_minimal) or (use_key_minimal and this_frame_key_minimal): each_armature.pose.bones[body_bone].keyframe_insert('location')