only handles bones and bone parenting. (tested in Blender 2.79 please modify use at your own risk)
import bpy
import os
import json
class ArmatureIO(object):
"""class to handle importing and exporting of an armature. only handles bones and bone parenting
import bpy
import sys
#example if wanted to test script without addon part. change to your path here
sys.path.append('/Users/Nathaniel/Documents/src_blender/python/naBlendShape')
from naArmatureIOAddOn import ArmatureIO
#usage:
#export
armatIO = ArmatureIO()
armatIO.exportArmature(armature="Armature", export_dir = '/Users/Nathaniel/Documents/src_blender/python/snippets/tmp', file_name="bones.json")
#import
armatIO = ArmatureIO()
armatIO.importArmature(file_path="/Users/Nathaniel/Documents/src_blender/python/snippets/tmp/bones.json")
"""
def __init__(self):
pass
def exportArmature(self, armature = None, export_dir = '/Users/Nathaniel/Documents/src_blender/python/snippets/tmp', bones = [], file_name = ''):
"""
@param armature - name for armature the data object name
@param export_dir - directory in which to save armature json. if this is a full path to a file name it uses this instead of the file_name argument
@param bones - optional list of bones to export. if empty it exports all bones in armature
@param file_name - optional name for json file to save such as Armature.json. if it is not provided it uses the armature name
"""
if not os.path.isdir(export_dir) and not os.path.isfile(export_dir) :
print("could not find export path %s. enter one that exists" %export_dir)
return
if not armature in bpy.data.objects:
print("requires an armature name. the data object name to exist")
return
dat_dict = {} #what we want to export
#need to have armature selected
self._selectOnlyThing(thing = armature)
#compiling the data for armature
bpy.ops.object.mode_set(mode='EDIT')
bone_names = [ eb.name for eb in bpy.data.objects[armature].data.edit_bones] #default to all bones in armature
if bones:
bone_names = bones
for bone in bone_names:
edit_bone_data = {}
pose_bone_data = {}
#edit bone data
#need to be in edit mode for getting parent - assumes armature is selected
bpy.ops.object.mode_set(mode='EDIT')
eb = bpy.data.objects[armature].data.edit_bones[bone]
#adding edit bone attributes here
edit_bone_data['head'] = tuple( eb.head )#(0,3,5)
edit_bone_data['tail'] = tuple( eb.tail )
edit_bone_data['roll'] = eb.roll #in radians
edit_bone_data['parent'] = eb.parent.name if eb.parent else ''
##
#pose bone data
bpy.ops.object.mode_set(mode='POSE')
pb = bpy.data.objects[armature].pose.bones[bone]
#adding pose bone attributes here
pose_bone_data['location'] = tuple( pb.location ) #(4,5,6)
pose_bone_data['scale'] = tuple( pb.scale )
pose_bone_data['rotation_mode'] = pb.rotation_mode
pose_bone_data['rotation'] = tuple( pb.rotation_euler )
if pb.rotation_mode == 'QUATERNION':
pose_bone_data['rotation'] = tuple( pb.rotation_quaternion )
pose_bone_data['custom_shape'] = pb.custom_shape.name if pb.custom_shape else '' #animator curve for bone - remember on import data bones should have show_wire to True if using curve shape for bone
pose_bone_data['custom_shape_scale'] = pb.custom_shape_scale
##
dat_dict[bone] = {'edit_bone_data':edit_bone_data, 'pose_bone_data':pose_bone_data}
#exporting armature to json
##
#if directory provided is a full path to a file name use it
export_fullpath = ''
if os.path.isfile(export_dir):
export_fullpath = export_dir
else:
#use the file_name if it exists
export_file_name = ''
if file_name:
export_file_name = file_name
else:
#use the armature name to figure out file name
armature_edit = armature.replace(' ','_')
export_file_name = armature_edit+'.json'
export_fullpath = os.path.join(export_dir,export_file_name)
##
outDir = os.path.dirname(export_fullpath)
if not os.path.exists(outDir):
print('Requires an out directory that exists to write armature file %s' %outDir)
return
##
##adding armature name
output_dict = {}
output_dict["armature"] = armature
output_dict["bones"] = dat_dict
##
print("exporting >>> %s to file name: %s" %(output_dict,export_fullpath) )
with open(export_fullpath,"w") as outf:
json.dump(output_dict,outf, indent=4)
def _importMakeArmature(self, armatureName):
"""
@param armatureName - str data object name for armature
"""
if not armatureName:
print("_importMakeArmature requires an armature name")
return
if armatureName in bpy.data.objects:
return
#make the armature
arm_dat = bpy.data.armatures.new(armatureName)
arm_obj = bpy.data.objects.new(armatureName, arm_dat)
arm_obj.data = arm_dat
scene = bpy.context.scene
scene.objects.link(arm_obj) #specific to blender 2.79
def importArmature(self, file_path=''):
"""
@param file_path - str file path to import
"""
if not os.path.exists(file_path):
print("could not find %s skipping" %file_path)
return
info_dict = {}
with open(file_path) as f:
info_dict = json.load(f)
print("read armature info>>",info_dict)
#check if armature exists if it doesnt make an empty one
armature = info_dict.get("armature")
if not armature:
print("using default armature name")
armature = "na_default_armature" #temp for backwards compatibility
if not armature in bpy.data.objects:
print("making armature because it doesnt exist")
self._importMakeArmature(armature)
self.importArmatureFromDict( armature = armature, data_dict = info_dict.get("bones") )
def importArmatureFromDict( self, armature = None, data_dict = None, use_bones = []):
"""
@param armature - armature name - the data object name
@param data_dict - see export for the format it is a dictionary with edit bone and pose bone information
@param use_bones - when specified it limits import to only provide bone names
"""
if not data_dict:
print("requires data dictionary with bone information")
return
if not armature in bpy.data.objects:
print("couldnt find armature - so making one")
self._importMakeArmature(armature)
dat_dict = {}
dat_dict = data_dict
print("using armature info>>",dat_dict)
#ensure in edit mode of armature
bpy.context.scene.objects.active = bpy.data.objects[armature]
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
for bone, dat in dat_dict.items():
#only import specified bones in input parameter
if use_bones:
if bone not in use_bones:
continue
print(bone)
edit_bone_data = dat.get('edit_bone_data') or None #assuming all keys exist
head = edit_bone_data.get('head') or None
tail = edit_bone_data.get('tail') or None
roll = edit_bone_data.get('roll') or 0.0
pose_bone_data = dat.get('pose_bone_data') or None
location = pose_bone_data.get('location') or None
scale = pose_bone_data.get('scale') or None
rotation_mode = pose_bone_data.get('rotation_mode') or None
rotation = pose_bone_data.get('rotation') or None
custom_shape = pose_bone_data.get('custom_shape') or ''
custom_shape_scale = pose_bone_data.get('custom_shape_scale') or 1.0
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bone_obj = None
#if it exists already use it.
if bone in bpy.data.objects[armature].data.edit_bones:
bone_obj = bpy.data.objects[armature].data.edit_bones[bone]
if not bone_obj:
#make edit bone from scratch
bone_obj = bpy.data.objects[armature].data.edit_bones.new(bone)
#position edit bone
bone_obj.head = head
bone_obj.tail = tail
bone_obj.roll = roll
#position pose bone
bpy.ops.object.mode_set(mode='POSE')
pb = bpy.data.objects[armature].pose.bones[bone]
pb.location = location
pb.scale = scale
pb.rotation_mode = rotation_mode
if rotation_mode != 'QUATERNION':
pb.rotation_euler = rotation
else:
pb.rotation_quaternion = rotation
#if custom shape doesnt exist dont try to add it to bone
if custom_shape in bpy.data.objects:
pb.custom_shape = bpy.data.objects[custom_shape]
pb.custom_shape_scale = custom_shape_scale
bpy.data.objects[armature].data.bones[bone].show_wire = True #show wire
#do the bone parenting at end so have all bones created
#ensure in edit mode of armature
bpy.context.scene.objects.active = bpy.data.objects[armature]
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
for bone, dat in dat_dict.items():
#only import specified bones in input parameter
#if use_bones:
# if bone not in use_bones:
# continue
#allowing any existing bone to be considered for parenting to support successive build workflow. like built jaw and head then later neck.
if not bone in bpy.data.objects[armature].data.edit_bones:
continue
bone_obj = bpy.data.objects[armature].data.edit_bones[bone]
edit_bone_data = dat.get('edit_bone_data') or None #assuming all keys exist
parent = edit_bone_data.get('parent') or None
#skip parenting to parent if cannot find it in scene
if not parent:
continue
if not parent in bpy.data.objects[armature].data.edit_bones:
continue
if parent:
bone_obj.parent = bpy.data.objects[armature].data.edit_bones[parent]
####
def _selectOnlyThing(self, thing = None):
#might need to be in object mode
bpy.ops.object.mode_set(mode="OBJECT")
if thing:
thing_obj = bpy.data.objects.get(thing)
if not thing_obj:
print("coulndt find {0} to select".format(thing))
return
#make it only selection
bpy.ops.object.select_all(action='DESELECT')
thing_obj.select = True
bpy.context.scene.objects.active = thing_obj Happy Scripting!