Skip to content

Instantly share code, notes, and snippets.

@dpogue
Last active January 2, 2020 16:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dpogue/dc69d4f799afacd18e62d41d781767e7 to your computer and use it in GitHub Desktop.
Save dpogue/dc69d4f799afacd18e62d41d781767e7 to your computer and use it in GitHub Desktop.
Put these in a "avimport" folder in your Blender addons folder
bl_info = {
'name': 'Avatar Importer',
'author': 'dpogue & Lontahv & Jrius',
'version': (0, 0, 1),
'blender': (2, 7, 9),
'location': 'File > Import > Avatar Import',
'description': 'Avatar and ATC Anim Importer',
'category': 'Import-Export'
}
import bpy
import avimport.importer
def register():
importer.register()
def unregister():
importer.unregister()
if __name__ == "__main__":
register()
import bpy
import mathutils
import os
import types
from PyHSPlasma import *
from bpy.props import *
def menu_func_import(self, context):
self.layout.operator(AvImport.bl_idname, text=AvImport.bl_label)
self.layout.operator(AnimImport.bl_idname, text=AnimImport.bl_label)
class AnimImport(bpy.types.Operator):
bl_idname = "import.animimport"
bl_label = "ATC Anim Import"
filepath = StringProperty(subtype='FILE_PATH')
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
filePath = self.filepath
armature = None
if '_female' in filePath.lower():
armature = 'Female_Armature'
elif '_male' in filePath.lower():
armature = 'Male_Armature'
else:
raise RuntimeError("Not an avatar animation")
armObj = None
try:
armObj = bpy.data.objects[armature]
except:
raise RuntimeError("Could not locate the armature")
self.rm = plResManager()
page = self.rm.ReadPage(filePath)
atc_anim = self.extractAnim(page)
animdata = armObj.animation_data_create()
action = bpy.data.actions.new(atc_anim.key.name)
action.use_fake_user = True
animdata.action = action
for applicator in atc_anim.applicators:
if not isinstance(applicator, plMatrixChannelApplicator):
raise RuntimeError('Got unexpected applicator type')
channel = applicator.channel
ctrl = channel.controller
name = applicator.channelName
if name == 'handle': # Hack for PotS
name = 'Handle'
try:
bone = armObj.pose.bones[name]
except:
raise RuntimeError('Could not find bone %s' % name)
# I guess we should do something with the affine parts before we
# apply keyframe transforms... or adjust everything by the inverse?
ap = channel.affine
if isinstance(ctrl, plTMController):
pos, rot, scale = ctrl.pos, ctrl.rot, ctrl.scale
elif isinstance(ctrl, plCompoundController):
pos, rot, scale = ctrl.X, ctrl.Y, ctrl.Z
else:
raise RuntimeError('Unknown controller type %s for %s' % (ctrl.ClassName(), bone.name))
if pos:
if isinstance(pos, plLeafController):
self.processSimplePosController(action, bone, pos, ap)
elif isinstance(pos, plSimplePosController):
self.processSimplePosController(action, bone, pos.position, ap)
elif isinstance(pos, (plCompoundPosController, plCompoundController)):
self.processCompoundPosController(action, bone, pos, ap)
else:
raise RuntimeError('Unexpected position controller type %s for %s' % (pos.ClassName(), bone.name))
if rot:
if isinstance(rot, plLeafController):
self.processSimpleRotController(action, bone, rot, ap)
elif isinstance(rot, plSimpleRotController):
self.processSimpleRotController(action, bone, rot.rot, ap)
elif isinstance(rot, (plCompoundRotController, plCompoundController)):
self.processCompoundRotController(action, bone, rot, ap)
else:
raise RuntimeError('Unexpected rotation controller type %s for %s' % (rot.ClassName(), bone.name))
return {'FINISHED'}
def extractAnim(self, page):
sn = self.rm.getSceneNode(page.location)
animname = sn.key.name.split('_')[-1]
for key in sn.poolObjects:
if key.name == animname and isinstance(key.object, plATCAnim):
return key.object
return None
def processSimplePosController(self, action, bone, ctrl, ap):
fcurveX = action.fcurves.new(bone.path_from_id('location'), 0, action_group=bone.name)
fcurveY = action.fcurves.new(bone.path_from_id('location'), 1, action_group=bone.name)
fcurveZ = action.fcurves.new(bone.path_from_id('location'), 2, action_group=bone.name)
for key in ctrl.keys[0]:
kx = fcurveX.keyframe_points.insert(key.frame, key.value.X - ap.T.X)
ky = fcurveY.keyframe_points.insert(key.frame, key.value.Y - ap.T.Y)
kz = fcurveZ.keyframe_points.insert(key.frame, key.value.Z - ap.T.Z)
def processCompoundPosController(self, action, bone, ctrl, ap):
axes = [ctrl.X, ctrl.Y, ctrl.Z]
afns = [ap.T.X, ap.T.Y, ap.T.Z]
i = 0
for axis in axes:
if not axis:
bone.location[i] = afns[i]
i += 1
continue
fcurve = action.fcurves.new(bone.path_from_id('location'), i, action_group=bone.name)
for key in axis.keys[0]:
kf = fcurve.keyframe_points.insert(key.frame, key.value - afns[i])
i += 1
def processSimpleRotController(self, action, bone, ctrl, ap):
bone.rotation_mode = 'QUATERNION'
q = mathutils.Quaternion((ap.Q.W, ap.Q.X, ap.Q.Y, ap.Q.Z)).inverted()
fcurveW = action.fcurves.new(bone.path_from_id('rotation_quaternion'), 0, action_group=bone.name)
fcurveX = action.fcurves.new(bone.path_from_id('rotation_quaternion'), 1, action_group=bone.name)
fcurveY = action.fcurves.new(bone.path_from_id('rotation_quaternion'), 2, action_group=bone.name)
fcurveZ = action.fcurves.new(bone.path_from_id('rotation_quaternion'), 3, action_group=bone.name)
for key in ctrl.keys[0]:
v = q * mathutils.Quaternion((key.value.W, key.value.X, key.value.Y, key.value.Z))
kw = fcurveW.keyframe_points.insert(key.frame, v.w)
kx = fcurveX.keyframe_points.insert(key.frame, v.x)
ky = fcurveY.keyframe_points.insert(key.frame, v.y)
kz = fcurveZ.keyframe_points.insert(key.frame, v.z)
def processCompoundRotController(self, action, bone, ctrl, ap):
print('CompoundRotController %s' % bone.name)
bone.rotation_mode = 'XYZ'
q = mathutils.Quaternion((ap.Q.W, ap.Q.X, ap.Q.Y, ap.Q.Z)).to_euler('XYZ')
axes = [ctrl.X, ctrl.Y, ctrl.Z]
afns = [q.x, q.y, q.z]
i = 0
for axis in axes:
if not axis:
bone.rotation_euler[i] = afns[i]
i += 1
continue
fcurve = action.fcurves.new(bone.path_from_id('rotation_euler'), i, action_group=bone.name)
for key in axis.keys[0]:
print('Axis %d; value %f, afn %f' % (i, key.value, afns[i]))
kf = fcurve.keyframe_points.insert(key.frame, key.value - afns[i])
i += 1
class AvImport(bpy.types.Operator):
bl_idname = "import.avimport"
bl_label = "Avatar Import"
filepath = StringProperty(subtype='FILE_PATH')
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
filePath = self.filepath
if not filePath.lower().endswith('male.prp') and not filePath.lower().endswith('female.prp'):
raise RuntimeError("Not an avatar")
name, boneMap = self.importAvatar(filePath)
self.constructArmature(name, boneMap)
return {'FINISHED'}
def isBone(self, name):
return name.lower().startswith('bone') or name.lower() in ['male', 'female', 'handle', 'convergence', 'bookhandle']
def importAvatar(self, filePath):
rm = plResManager()
page = rm.ReadPage(filePath)
sn = rm.getSceneNode(page.location)
boneMap = {}
for key in sn.sceneObjects:
if not self.isBone(key.name):
continue
so = key.object
if so.coord.object:
ci = so.coord.object
if key.name not in boneMap.keys():
boneMap[key.name] = types.SimpleNamespace(parent=None)
boneMap[key.name].matrix = ci.localToWorld.glMat
boneMap[key.name].inverse = ci.worldToLocal.glMat
boneMap[key.name].l2p = ci.localToParent.glMat
boneMap[key.name].p2l = ci.parentToLocal.glMat
for child in ci.children:
if not self.isBone(child.name):
continue
if child.name not in boneMap.keys():
boneMap[child.name] = types.SimpleNamespace()
boneMap[child.name].parent = key.name
return sn.sceneObjects[0].name, boneMap
def constructArmature(self, name, bones):
armature = bpy.data.armatures.new(name)
armObj = bpy.data.objects.new(name + '_Armature', armature)
bpy.context.scene.objects.link(armObj)
bpy.context.scene.objects.active = armObj
bpy.ops.object.mode_set(mode='EDIT')
if name in bones:
glMat = bones[name].matrix
armObj.matrix_basis = [glMat[:4], glMat[4:8], glMat[8:12], glMat[12:]]
bone = armature.edit_bones.new('Handle')
bone.head = (0, 0, 0)
bone.tail = (0, 0, 0.1)
del bones[name]
for boneName in bones.keys():
armature.edit_bones.new(boneName)
for boneName in bones.keys():
boneObj = bones[boneName]
bone = armature.edit_bones[boneName]
bone.head = (0, 0, 0)
bone.tail = (0, 0, 0.1)
glMat = boneObj.matrix
bone.matrix = [glMat[:4], glMat[4:8], glMat[8:12], glMat[12:]]
if boneObj.parent:
if boneObj.parent != name:
bone.parent = armature.edit_bones[boneObj.parent]
for boneName in bones.keys():
bone = armature.edit_bones[boneName]
x,y,z = [0, 0, 0]
for child in bone.children:
x += child.head[0]
y += child.head[1]
z += child.head[2]
if len(bone.children) and boneName != 'Bone_Root': #hack for root
bone.tail = (x/len(bone.children), y/len(bone.children), z/len(bone.children))
else:
bone.tail = (bone.head[0], bone.head[1] - 0.1, bone.head[2])
bpy.ops.object.mode_set(mode='OBJECT')
"""
for boneName in bones.keys():
if not boneName in armObj.pose.bones:
continue
bone = armObj.pose.bones[boneName]
if boneName in bpy.data.objects:
obj = bpy.data.objects[boneName]
im = bones[boneName].p2l
obj.data.transform([im[:4], im[4:8], im[8:12], im[12:]])
glMat = bones[boneName].l2p
obj.matrix_local = [glMat[:4], glMat[4:8], glMat[8:12], glMat[12:]]
#curmat = mathutils.Matrix(obj.matrix_basis)
##invmat = mathutils.Matrix([im[:4], im[4:8], im[8:12], im[12:]])
#invmat = mathutils.Matrix(bone.matrix)
##resmat = curmat * invmat
#resmat = invmat.inverted_safe()
#obj.matrix_basis = resmat
#bone.custom_shape = obj
bone.use_custom_shape_bone_size = False
"""
def register():
bpy.utils.register_class(AvImport)
bpy.utils.register_class(AnimImport)
bpy.types.INFO_MT_file_import.append(menu_func_import)
def unregister():
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.utils.unregister_class(AvImport)
bpy.utils.unregister_class(AnimImport)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment