Skip to content

Instantly share code, notes, and snippets.

@liyonghelpme
Last active August 29, 2015 14:06
Show Gist options
  • Save liyonghelpme/47389e7bc20bb9ac5323 to your computer and use it in GitHub Desktop.
Save liyonghelpme/47389e7bc20bb9ac5323 to your computer and use it in GitHub Desktop.
使用joint描述骨骼动画,还需要计算每个骨骼的roll值
#!BPY
""" Registration info for Blender menus:
Name: 'OGRE (.mesh.xml)...'
Blender: 236
Group: 'Import'
Tip: 'Import an Ogre-Mesh (.mesh.xml) file.'
"""
'''
__author__ = "Daniel Wickert"
__version__ = "0.4 05/11/05"
__bpydoc__ = """\
This script imports Ogre-Mesh models into Blender.
Supported:<br>
* multiple submeshes (triangle list)
* uvs
* materials (textures only)
* vertex colours
Missing:<br>
* submeshes provided as triangle strips and triangle fans
* materials (diffuse, ambient, specular, alpha mode, etc.)
* skeletons
* animations
Known issues:<br>
* blender only supports a single uv set, always the first is taken
and only the first texture unit in a material, even if it is not for
the first uv set.
* code is a bit hacky in parts.
"""
'''
# Copyright (c) 2005 Daniel Wickert
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# HISTORY:
# 0.1 04/02/05
# ------------
# * initial implementation. single submesh as triangle list, no uvs
#
# 0.2 04/03/05
# ------------
# * uv support added
# * texture loading added
#
# 0.3 04/09/05
# ------------
# * multiple submeshes added
# * scaling added
# * material parsing (only textures yet)
# * mesh -> mesh.xml conversion if IMPORT_OGREXMLCONVERTER defined
# * vertex colours
#
# 0.3.1 04/11/05
# --------------
# * stdout output streamlined
# * missing materials and textures are now handled gracefully
#
# 0.3.1a 04/11/05
# --------------
# * Mesh is assigned to a correctly named object and added
# to the current scene
#
# 0.4. 05/11/05
# --------------
# * shared vertices support
# * multiple texture coordinates in meshes are handled correctly no,
# but only the first coordinate set is used, since Blender doesn't
# allow multiple uvs.
# * only the first texture_unit's texture is used now.
#
# 0.4.1 06/02/05
# --------------
# * fixed bug: pathes invalid under linux
# * fixed bug: shared vertices were duplicated with each submesh
#
# 0.5.0 06/06/05
# --------------
# * consistent logging scheme with adjustable log level
# * render materials
#
# 0.5.1 13/07/05
# --------------
# * fixed bug: meshes without normals and texture coords cannot be imported
#
# 0.5.2 12/02/06
# --------------
# * fixed bug: multiple blender materials created from one ogre material
#
# TODO: implement a consistent naming scheme:
# bxxx for blender classes; oxxx for ogre(well, sorta) classes
bl_info = {
"name": "ogre mesh import",
"author": "Bartek Skorupa, Greg Zaal",
"version": (0, 1, 6),
"blender": (2, 70, 0),
"location": "File > Import > ogre (.mesh)",
"description": "import ogre mesh file",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Nodes/Nodes_Efficiency_Tools",
"category": "Import-Export",
}
__version__ = "1.2.3"
import logging
handle = logging.FileHandler("/Users/liyong/blender.log")
logger = logging.getLogger()
logger.addHandler(handle)
logger.setLevel(logging.INFO)
import bpy
import glob
import os
import re
import xml.sax
import xml.sax.handler
import bmesh
import mathutils
from mathutils import Vector
from mathutils import Matrix
# determines the verbosity of loggin.
# 0 - no logging (fatal errors are still printed)
# 1 - standard logging
# 2 - verbose logging
# 3 - debug level. really boring (stuff like vertex data and verbatim lines)
IMPORT_LOG_LEVEL = 1
IMPORT_SCALE_FACTOR = 1
#IMPORT_OGREXMLCONVERTER = "d:\stuff\Torchlight_modding\orge_tools\OgreXmlConverter.exe"
IMPORT_OGREXMLCONVERTER = "/Users/liyong/Downloads/OgreCommandLineToolsMac_1.8.0/OgreXMLConverter"
#IMPORT_OGREXMLCONVERTER = "F:\\Projekte\\rastullah\checkout\\rl\\branches\python\dependencies\ogre\Tools\Common\\bin\\release\\OgreXmlConverter.exe"
def log(msg):
logger.info(msg)
#if IMPORT_LOG_LEVEL >= 1: print(msg)
def vlog(msg):
if IMPORT_LOG_LEVEL >= 2: print(msg)
def dlog(msg):
if IMPORT_LOG_LEVEL >= 3: print(msg)
class Mesh:
def __init__(self):
self.submeshes = []
self.vertices = []
self.vertexcolours = []
self.normals = []
self.uvs = []
class Submesh:
def __init__(self):
self.vertices = []
self.vertexcolours = []
self.normals = []
self.faces = []
self.uvs = []
self.indextype = ""
self.materialname = ""
self.sharedvertices = 0
self.vertexGroup = {}
class Material:
def __init__(self, name):
self.name = name
self.texname = ""
self.diffuse = (1.0, 1.0, 1.0, 1.0)
self.ambient = (1.0, 1.0, 1.0, 1.0)
self.specular = (0.0, 0.0, 0.0, 0.0)
self.blenderimage = 0
self.loading_failed = 0
def getTexture(self):
if self.blenderimage == 0 and not self.loading_failed:
try:
#f = file(self.texname, 'r')
#f.close()
#self.blenderimage = Blender.Image.Load(self.texname)
if not os.path.exists(self.texname):
self.texname = self.texname.replace('.png', '.dds')
self.blenderimage = bpy.data.images.load(self.texname)
except IOError as err:
errmsg = "Couldn't open %s #%s" \
% (self.texname, err)
log(errmsg)
self.loading_failed = 1;
return self.blenderimage
class OgreMeshSaxHandler(xml.sax.handler.ContentHandler):
global IMPORT_SCALE_FACTOR
def __init__(self):
self.mesh = 0
self.submesh = 0
self.ignore_input = 0
self.load_next_texture_coords = 0
self.skeleton = 0
self.animationlink = 0
#self.boneassignments = 0
#保存player.Skeleton 里面存储的骨骼信息
self.restPose = 0
def startDocument(self):
self.mesh = Mesh()
def startElement(self, name, attrs):
if name == 'boneassignments':
#self.boneassignments = {}
pass
if name == 'vertexboneassignment':
vind = int(attrs.get("vertexindex", ""))
bind = int(attrs.get("boneindex", ""))
weight = float(attrs.get("weight", ""))
if bind not in self.submesh.vertexGroup:
self.submesh.vertexGroup[bind] = []
self.submesh.vertexGroup[bind].append({"vertexIndex":vind,
"boneIndex" :bind, "weight":weight })
if name == 'skeletonlink':
self.skeleton = attrs.get("name", '')
if name == "animationlink":
self.animationlink = attrs.get("name", '')
if name == 'sharedgeometry':
self.submesh = self.mesh
if name == 'submesh':
self.submesh = Submesh()
self.submesh.materialname = attrs.get('material', "")
self.submesh.indextype = attrs.get('operationtype', "")
if attrs.get('usesharedvertices') == 'true':
self.submesh.sharedvertices = 1
if name == 'vertex':
self.load_next_texture_coords = 1
if name == 'face' and self.submesh:
face = (
int(attrs.get('v1',"")),
int(attrs.get('v2',"")),
int(attrs.get('v3',""))
)
self.submesh.faces.append(face)
if name == 'position':
vertex = (
#Ogre x/y/z --> Blender x/-z/y
float(attrs.get('x', "")) * IMPORT_SCALE_FACTOR,
-float(attrs.get('z', "")) * IMPORT_SCALE_FACTOR,
float(attrs.get('y', "")) * IMPORT_SCALE_FACTOR
)
self.submesh.vertices.append(vertex)
if name == 'normal':
normal = (
#Ogre x/y/z --> Blender x/-z/y
float(attrs.get('x', "")),
-float(attrs.get('z', "")),
float(attrs.get('y', ""))
)
self.submesh.normals.append(normal)
if name == 'texcoord' and self.load_next_texture_coords:
uv = (
float(attrs.get('u', "")),
# flip vertical value, Blender's 0/0 is lower left
# whereas Ogre's 0/0 is upper left
1.0 - float(attrs.get('v', ""))
)
self.submesh.uvs.append(uv)
self.load_next_texture_coords = 0
if name == 'colour_diffuse':
self.submesh.vertexcolours.append(attrs.get('value', "").split())
def endElement(self, name):
if name == 'submesh':
self.mesh.submeshes.append(self.submesh)
self.submesh = 0
def setMaterial(ob, mat):
me = ob.data
me.materials.append(mat)
from xml.dom import minidom
def OpenFile(filename):
if filename.find('.xml') == -1:
convert_meshfile(filename)
filename += '.xml'
xml_file = open(filename, 'r')
xml_doc = minidom.parse(xml_file)
def getEle(name):
l = xml_doc.getElementsByTagName(name)
return l[0]
xml_doc.getElementByTagName = getEle
xml_file.close()
return xml_doc
#创建人物的 restpose的骨骼信息
def CreateSkeleton(skeleton, handler):
global dirname
if skeleton != 0:
#scene = bpy.context.scene
filename = os.path.join(dirname, skeleton)
xml_doc = OpenFile(filename)
return CreateBindSkeleton(xml_doc, handler)
#CreateRestPoseAction(xml_doc)
#OGREBoneIDsDic(xml_doc)
def OGREBonesDic(xmldoc):
OGRE_Bones = {}
OGRE_Bone_List = []
OGRE_Bone_ID = {}
for bones in xmldoc.getElementsByTagName('bones'):
for bone in bones.childNodes:
OGRE_Bone = {}
if bone.localName == 'bone':
BoneName = str(bone.getAttributeNode('name').value)
BoneId = int(bone.getAttributeNode('id').value)
OGRE_Bone['name'] = BoneName
OGRE_Bone['id'] = BoneId
for b in bone.childNodes:
if b.localName == 'position':
x = float(b.getAttributeNode('x').value)
y = -float(b.getAttributeNode('z').value)
z = float(b.getAttributeNode('y').value)
OGRE_Bone['position'] = [x,y,z]
if b.localName == 'rotation':
angle = float(b.getAttributeNode('angle').value)
axis = b.childNodes[1]
axisx = float(axis.getAttributeNode('x').value)
axisy = float(axis.getAttributeNode('y').value)
axisz = float(axis.getAttributeNode('z').value)
#convert ogre coordinate to blender coordinate
OGRE_Bone['rotation'] = [axisx, -axisz, axisy]
OGRE_Bone['angle'] = angle
OGRE_Bones[BoneName] = OGRE_Bone
OGRE_Bone_List.append(OGRE_Bone)
OGRE_Bone_ID[OGRE_Bone["id"]] = OGRE_Bone
for bonehierarchy in xmldoc.getElementsByTagName('bonehierarchy'):
for boneparent in bonehierarchy.childNodes:
if boneparent.localName == 'boneparent':
Bone = str(boneparent.getAttributeNode('bone').value)
Parent = str(boneparent.getAttributeNode('parent').value)
OGRE_Bones[Bone]['parent'] = Parent
animations = xmldoc.getElementByTagName("animations")
animation = animations.getElementsByTagName("animation")[0]
animationName = str(animation.getAttributeNode("name").value)
length = float(animation.getAttributeNode("length").value)
tracks = animation.getElementsByTagName("tracks")[0]
TrackData = {}
if length > 0:
for track in tracks.getElementsByTagName("track"):
boneName = str(track.getAttributeNode("bone").value)
TrackData[boneName] = []
for keyframe in track.getElementsByTagName("keyframes")[0].getElementsByTagName("keyframe"):
time = float(keyframe.getAttributeNode("time").value)
translate = keyframe.getElementsByTagName("translate")[0]
x = float(translate.getAttributeNode("x").value)
y = float(translate.getAttributeNode("y").value)
z = float(translate.getAttributeNode("z").value)
rotate = keyframe.getElementsByTagName("rotate")[0]
angle = float(rotate.getAttributeNode("angle").value)
axis = rotate.getElementsByTagName("axis")[0]
rx = float(axis.getAttributeNode("x").value)
ry = float(axis.getAttributeNode("y").value)
rz = float(axis.getAttributeNode("z").value)
#TrackData[boneName].append({"time":time, "translation":[x, -z, y], "angle":angle, "axis":[rx, -rz, ry]})
#相对于parent 的骨骼空间Z 轴相反
#局部空间坐标 z -y
TrackData[boneName].append({"time":time, "translation":[x, -z, y], "angle":angle, "axis":[rx, -rz, ry]})
#return OGRE_Bone_List
return OGRE_Bones, OGRE_Bone_List, OGRE_Bone_ID, TrackData
#骨骼的孩子节点
def ChildList(BonesData):
for bone in BonesData.keys():
childlist = []
for key in BonesData.keys():
if BonesData[key].get('parent'):
parent = BonesData[key]['parent']
if parent == bone:
if key.find('tag') != -1:
childlist.append(BonesData[key])
else:
childlist.insert(0, BonesData[key])
BonesData[bone]['children'] = childlist
#获取所有骨骼的孩子列表缓存起来
#没有孩子节点 或者 有超过1个的孩子节点 的骨骼增加一个Helper骨骼 设置父亲为bone
def HelperBones(BonesData):
ChildList(BonesData)
count = 0
for bone in BonesData.keys():
if (len(BonesData[bone]['children']) == 0) or (len(BonesData[bone]['children']) > 1):
HelperBone = {}
HelperBone['position'] = [0.2,0.0,0.0]
HelperBone['parent'] = bone
HelperBone['rotation'] = [1.0,0.0,0.0,0.0]
HelperBone['flag'] = 'helper'
BonesData['Helper'+str(count)] = HelperBone
count+=1
#### 在 0 0 0 位置的骨骼 增加辅助的 0 骨骼到 parent上面
def ZeroBones(BonesData):
for bone in BonesData.keys():
pos = BonesData[bone]['position']
if (math.sqrt(pos[0]**2+pos[1]**2+pos[2]**2)) == 0:
ZeroBone = {}
ZeroBone['position'] = [0.2,0.0,0.0]
ZeroBone['rotation'] = [1.0,0.0,0.0,0.0]
if (BonesData[bone].get('parent')):
ZeroBone['parent'] = BonesData[bone]['parent']
ZeroBone['flag'] = 'zerobone'
BonesData['Zero'+bone] = ZeroBone
if (BonesData[bone].get('parent')):
BonesData[BonesData[bone]['parent']]['children'].append('Zero'+bone)
#计算骨骼长度
def CalcBoneLength(vec):
return math.sqrt(vec[0]**2+vec[1]**2+vec[2]**2)
'''
通过创建空的正方体对象来模拟joint 最后连接这些正方体对象得到骨骼
位置偏移 和 旋转 都是相对于父亲的 局部空间的变换
'''
#计算每个joint的旋转矩阵 世界坐标中的旋转矩阵
#创建空物体 来摆出pose 接着计算每个骨骼的相对位置
def CreateEmptys(BonesDic, delete=True):
if bpy.context.object != None:
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
if bpy.data.objects.get("Cube"):
modelCube = bpy.data.objects["Cube"]
else:
bpy.ops.mesh.primitive_cube_add()
modelCube = bpy.data.objects["Cube"]
for k in modelCube.data.vertices.values():
k.co *= 0.1
modelCube.location = (4, 0, 0)
modelCube.select = True
modelCube.name = "Cube"
bpy.context.scene.update()
#不存在才创建
if bpy.data.objects.get("root") == None:
for bone in BonesDic.keys():
#bpy.ops.mesh.primitive_cube_add()
bpy.ops.object.select_all(action="DESELECT")
modelCube.select = True
modelCube.name = "Cube"
bpy.ops.object.duplicate()
obj = bpy.data.objects["Cube.001"]
#obj = bpy.data.objects.new(bone, None)
obj.name = bone
#obj.scale = (0.1, 0.1, 0.1)
#bpy.context.scene.objects.link(obj)
log("create Cube: "+obj.name)
for bone in BonesDic.keys():
if BonesDic[bone].get('parent'):
Parent = bpy.data.objects[BonesDic[bone].get('parent')]
obj = bpy.data.objects[bone]
obj.parent = Parent
for bone in BonesDic.keys():
obj = bpy.data.objects[bone]
rot = BonesDic[bone]['rotation']
angle = BonesDic[bone]['angle']
loc = BonesDic[bone]['position']
euler = mathutils.Matrix.Rotation(angle, 3, rot).to_euler()
obj.location = loc
obj.rotation_euler = euler
bpy.ops.object.select_all(action="DESELECT")
bpy.context.scene.update()
for bone in BonesDic.keys():
obj = bpy.data.objects[bone]
#rotmatAS = obj.matrix_world.to_quaternion()
translation = obj.matrix_world.to_translation()
log("obj matrix update "+obj.name+" "+str(translation))
#BonesDic[bone]['rotmatAS'] = rotmatAS.to_matrix()
BonesDic[bone]["translation"] = translation
BonesDic[bone]["matrix_local"] = obj.matrix_local
#bpy.context.scene.objects.unlink(modelCube)
#del modelCube
for bone in BonesDic.keys():
obj = bpy.data.objects[bone]
obj.hide = True
'''
if delete:
for bone in BonesDic.keys():
obj = bpy.data.objects[bone]
obj.name += "_remove"
bpy.context.scene.objects.unlink(obj)
del obj
'''
#没有必要删除empty 只要存在则继续使用
def ClearEmptys():
return
def SetRestMatrix(BonesDic):
#for key in BonesDic.keys():
# bone =
pass
#按照骨骼的依赖关系 来排序 保证每个骨骼的依赖都排在其前面
def SortData(bone, BonesData):
waitToVisit = [bone]
while len(waitToVisit) > 0:
b = waitToVisit.pop(0)
BonesData.append(b)
for c in b['children']:
waitToVisit.append(c)
def FindRoot(BonesList):
for c in BonesList:
if c['name'] == 'root':
return c
def addPos(a, b):
c = [0, 0, 0]
c[0] = a[0]+b[0]
c[1] = a[1]+b[1]
c[2] = a[2]+b[2]
return c
import mathutils
def ParseAnimationSkeleton(xmldoc):
BonesDic, BonesList, BoneID = OGREBonesDic(xmldoc)
#global BoneID2Name
#BoneID2Name = BoneID
def ParseActionAnimation(xmldoc):
Animations = {}
animations = xmldoc.getElementByTagName("animations")
animation = animations.getElementByTagName("animation")
aniname = str(animation.getAttributeNode("name").value)
length = float(animation.getAttributeNode("length").value)
Track = {}
tracks = animation.getElementByTagName("tracks")
for track in tracks:
boneName = str(track.getAttributeNode("bone").value)
Track[boneName] = []
keyframes = track.getElementByTagName("keyframes")
for keyframe in keyframes:
time = float(keyframe.getAttributeNode("time").value)
translate = keyframe.getElementsByTagName("translate")[0]
x = float(translate.getAttributeNode("x").value)
y = float(translate.getAttributeNode("y").value)
z = float(translate.getAttributeNode("z").value)
translate = [x, y, z]
rotate = keyframe.getElementByTagName("rotate")
angle = float(rotate.getAttributeNode("angle").value)
axis = rotate.getElementByTagName("axis")
x = float(axis.getAttributeNode("x").value)
y = float(axis.getAttributeNode("y").value)
z = float(axis.getAttributeNode("z").value)
rotate = [x, y, z, angle]
Track[boneName].append([time, translate, rotate])
Animations[aniname] = Track
return Animations
def CreateRestPoseAction(xmldoc, skname):
pass
#当前选择对象的 pose属性
#创建动画 pose 基本pose 骨骼位置
#只调整骨骼的head tail 位置
#记录骨骼的 旋转 和 head 属性
#得到在 bind 情况下的 pose 数据
#rest pose的数据在 OGREBonesDic 这个结构体中存储着 rest pose 数据
#后面是 动画动作数据 例如 attack pose 初始状态 0 frame 设置pose 每个骨骼位置
#测试:
# 读取一个骨骼数据设置pose状态为这个初始的攻击动作位置
#流程: 读取rest骨骼 读取mesh 绑定骨骼到mesh上
#读取 attack数据 读取初始pose 在pose状态设置每个骨骼的位置
def CreateActions(ActionsDic, armaturename, BonesDic):
armature = bpy.data.armatures[armaturename]
arm_object = bpy.context.object
pose = bpy.context.object.pose
#rest 状态下的骨骼 位置数据
for Action in ActionsDic.keys():
newAction = bpy.data.actions.new(name=Action)
arm_object.animation_data_create()
arm_object.animation_data.action = newAction
for track in ActionsDic[Action].keys():
rpbone = armature.bones[track]
rpbonequat = rpbone.matrix_local.to_quaternion()
rpbonetrans = Vector()
def CreateTestAnimation(handler):
bpy.ops.object.select_all(action="DESELECT")
amt_obj = bpy.data.objects["Armature"]
amt_data = amt_obj.data
amt_obj.select = True
bpy.context.scene.objects.active = amt_obj
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.select_all(action="SELECT")
bpy.context.scene.frame_set(0)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
bpy.context.scene.frame_set(20)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
#amt_obj.animation_data_create()
#action = bpy.data.actions.new(name="Test")
#amt_obj.animation_data.action = action
#针对动画骨架Armature2 制作其对应的 pose动画 只有骨架动画
def CreatePoseAnimation(BonesDic, BonesList, BoneID, TrackData, BonesData, handler):
#TrackData[boneName].append({time=time, translation={x, -z, y}, angle=angle, axis={rx, -rz, ry}})
#每个joint 相对于 初始骨骼位置的偏移 从joint 位置 旋转 计算 旋转 rotation 和 scale属性
#每个joint 影响其所在的骨骼 以及每个子骨骼的位置
#调整 动画中的Empty物体的位置 然后生成一个新的 骨骼位置
#调整一个joint的位置 parent和children 相关联的都会修改矩阵 重新计算矩阵在 edit mode 下
#然后计算edit mode下旋转结果 重新计算这组骨骼的 qua 和 scale信息
#然后设置 一个关键帧在pose mode下面
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
amt_obj = bpy.data.objects["Armature"]
amt_data = amt_obj.data
amt_obj.select = True
bpy.context.scene.objects.active = amt_obj
#bpy.ops.object.duplicate()
#modifyArmature_obj = bpy.data.objects["Armature2.001"]
#modifyArmature_data = modifyArmature_obj.data
#只测试一帧的数据
#第一次frame
#按照父子关系来调整 骨骼的Editor bones 属性 这样 孩子节点的head 也要跟着一起移动
#接着再去修正tail位置 tail的方向不变 但是 head 位置偏移了
#在Pose 状态下 修改 平移 和 旋转属性
#bpy.ops.object.mode_set(mode="OBJECT")
#bpy.ops.object.select_all(action="DESELECT")
#modifyArmature_obj.select = True
#bpy.context.scene.objects.active = modifyArmature_obj
#bpy.ops.object.mode_set(mode="POSE")
#调整pose骨骼旋转继承父亲骨骼的旋转方向
'''
for bone in modifyArmature_data.bones:
log("setPoseBone "+bone.name)
if bone.parent != None:
bone.use_inherit_rotation = True
'''
#进入object mode 调整每个 joint的位置 根据TrackData
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
#modifyArmature_obj.select = True
#bpy.context.scene.objects.active = modifyArmature_obj
#bpy.ops.object.mode_set(mode="POSE")
#需要记录所有joint的 初始化位置 相对于parent的 location 和 rotation_quaternion
RestJoint = {}
for boneName in TrackData.keys():
joint = bpy.data.objects[boneName]
joint.rotation_mode = "QUATERNION"
RestJoint[joint.name] = dict(location=joint.location.copy(), rotation=joint.rotation_quaternion.copy())
#变换每个joint的位置
#骨骼 * frame 建立这么多次的 PoseMode数据
#记录所有出现的关键帧的编号
allFrameData = []
for boneName in TrackData.keys():
tracks = TrackData[boneName]
count = 0
joint = bpy.data.objects[boneName]
joint.select = True
bpy.context.scene.objects.active = joint
jointRestData = RestJoint[joint.name]
for frame in tracks:
time = frame["time"]
fnum = int(round(time*60))
translation = Vector(frame["translation"])
angle = frame["angle"]
axis = frame["axis"]
bpy.context.scene.frame_set(fnum)
#joint = bpy.data.objects[boneName]
joint.location = jointRestData["location"]+translation
joint.rotation_quaternion = jointRestData["rotation"]*Matrix.Rotation(angle, 3, axis).to_quaternion()
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
if fnum not in allFrameData:
allFrameData.append(fnum)
if count >= 8:
break
count += 1
joint.select = False
#生成每一帧的 Pose数据
log("allFrameData is "+str(allFrameData))
for fnum in allFrameData:
bpy.context.scene.frame_set(fnum)
#使用joint初始化一个骨骼Armature3
bpy.ops.object.add(type="ARMATURE", enter_editmode=True)
amt3_obj = bpy.context.object
amt3_obj.location = Vector((0, 0, 0))
amt3_obj.show_x_ray = True
amt3_obj.name = "Armature3"
amt3_data = amt3_obj.data
amt3_data.show_axes = True
#进入Edit模式编辑骨骼对象
#每一个joint对象 生成骨骼
#计算join roll值
FirstBoneData = {}
for k in BonesData:
log("joint pos "+k['name'])
if k.get('parent'):
#骨骼对应的joint对象
#世界位置
rollVal = calculateRoll(k["name"])
boneJoint = bpy.data.objects[k["name"]]
jointTranslation = boneJoint.matrix_world.to_translation()
#roll 具体计算公式了
#qua2 * qua3 = localRotation
parent = BonesDic[k['parent']]
if parent.get('parent'):
parentBone = amt3_data.edit_bones[k['parent']]
bpy.ops.armature.select_all(action="DESELECT")
parentBone.select_tail = True
bpy.ops.armature.extrude()
amt3_data.edit_bones[-1].name = k['name']
selBone = amt3_data.edit_bones[-1]
tp = jointTranslation-selBone.head
bpy.ops.transform.translate(value=tp)
selBone.roll = rollVal
#root--->Bip01
else:
bone = amt3_data.edit_bones.new(k['name'])
#root 的translation是统一
bone.head = parent["translation"]
bone.tail = jointTranslation
bone.roll = rollVal
bone.use_connect = False
#当前root 的第一个骨骼存在则使用这个骨骼作为parent
if FirstBoneData.get(parent["name"]) != None:
bone.parent = amt3_data.edit_bones[FirstBoneData[parent["name"]]]
#if parent.get('firstBone') != None:
# bone.parent = amt.edit_bones[parent['firstBone']]
else:
FirstBoneData[parent["name"]] = bone.name
#parent['firstBone'] = bone.name
bpy.ops.armature.select_all(action="DESELECT")
else:
pass
#计算Armature3 相对于Armature的旋转和缩放变换
#Armature3 Edit Mode下的数据 保存
bpy.context.scene.update()
Armature3Data = {}
for k in BonesData:
Armature3Data[k["name"]] = temp = {}
if amt3_data.edit_bones.get(k["name"]):
bone = amt3_data.edit_bones[k["name"]]
temp["head"] = bone.head.copy()
temp["tail"] = bone.tail.copy()
temp["roll"] = bone.roll
temp["length"] = bone.length
temp["mat"] = bone.matrix.to_3x3().copy()
bone.use_inherit_scale = False
bone.use_inherit_rotation = False
#进入Armature的PoseMode 写入变换数据
if bpy.context.object != None:
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.object.select = False
bpy.ops.object.select_all(action="DESELECT")
#调整 基础骨骼 pose骨骼的 位置 缩放 旋转
bpy.ops.object.select_all(action="DESELECT")
amt_obj.select = True
bpy.context.scene.objects.active = amt_obj
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.select_all(action="SELECT")
#bpy.ops.anim.keyframe_insert_menu(type="Location")
#bpy.ops.anim.keyframe_insert_menu(type="Rotation")
#遍历每个骨骼 计算每个骨骼的 rotation 和 scale
restPoseEdit = handler.restPose
restBone = amt_data.bones
for b in BonesData:
if b.get("parent") and restBone.get(b["name"]) != None:
amt3BoneData = Armature3Data[b["name"]]
amt3Mat = amt3BoneData["mat"]
amt3Length = amt3BoneData["length"]
rbData = restPoseEdit[b["name"]]
#rbDir = rbData["tail"]-rbData["head"]
#myDir = b["tail"]-b["head"]
#逆矩阵是将 世界坐标的向量 转化到 骨骼空间里面去的
#使用edit 模式下的骨骼的 变换矩阵
normalBoneMat = rbData["mat"].inverted() #restBone[b["name"]].matrix.inverted()
#使用动画的 matrix 变换到世界空间 接着从世界空间变换到 rest pose空间
nDir = normalBoneMat*amt3Mat*Vector((0, 1, 0))
rotAxis = Vector.cross(Vector((0, 1, 0)), nDir)
#.normalized()
log("calculate bone data "+b["name"]+" "+str(nDir))
theta = math.asin(rotAxis.length/nDir.length)
#av = min(max(-1, Vector((0, 1, 0))*nDir), 1)
#angle = math.acos(av)
qua = Matrix.Rotation(theta, 3, rotAxis).to_quaternion()
defaultQua = (1, 0, 0, 0)
#还要计算roll值
#difRoll = b["roll"]-rbData["roll"]
#rotY = Matrix.Rotation(difRoll, 3, Vector((0, 1, 0))).to_quaternion()
#先旋转 接着 调整roll 方向
#qua = qua*rotY
scale = amt3Length/restBone[b["name"]].length
modifyScale = (1, scale, 1)
#在第20frame 存储一个 攻击骨骼位置
#b["initQuaternion"] = qua
#b["initScale"] = modifyScale
#bpy.context.scene.frame_set(20)
amtPoseBone = amt_obj.pose.bones[b["name"]]
amtPoseBone.rotation_mode = "QUATERNION"
amtPoseBone.rotation_quaternion = qua
amtPoseBone.scale = Vector(modifyScale)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
bpy.ops.object.mode_set(mode="OBJECT")
amt3_obj.name = "Armature3_remove"
bpy.context.scene.objects.unlink(amt3_obj)
del amt3_obj
return
for boneName in TrackData.keys():
tracks = TrackData[boneName]
count = 0
for frame in tracks:
#if True:
#frame = tracks[1]
time = frame["time"]
fnum = int(time*60)
translation = frame["translation"]
angle = frame["angle"]
axis = frame["axis"]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
modifyArmature_obj.select = True
bpy.context.scene.objects.active = modifyArmature_obj
bpy.ops.object.mode_set(mode="POSE")
#altR altG altS 一下所有的骨骼么?
#BonesDic[boneName] 中保存有tail 和 head信息
#editBone = modifyArmature_data.edit_bones[boneName]
#修正poseBone位置来调整子骨骼位置 scale rotation 位置
#poseBone 的matrix是 旋转缩放后的matrix不能使用
poseBone = modifyArmature_obj.pose.bones[boneName]
#相对于初始骨骼位置的偏移 骨骼空间平移 转化到世界坐标空间
#mat * v1 v2 v3 骨骼空间转化到 世界空间
#pose 空间直接计算 旋转值
traVec = Vector(translation)
targetPos = Vector((0, poseBone.length, 0))+traVec
#检测父亲骨骼是否存在轴旋转
#使用poseBone的parent呢还是使用 BonesDic的parent?
#因为Pelvis 不存在所以导致 Spine 的parent不同
#但是数据是存储在TrackData 里面的
#递归的求得父亲的旋转值 最后 乘以当前的值
#如何做到 骨骼数据的独立性呢? 和父亲节点不相关
'''
ParentQua = mathutils.Quaternion((1, 0, 0, 0))
curBoneName = boneName
while BonesDic[curBoneName].get("parent") != None:
parent = BonesDic[curBoneName]["parent"]
TrackData.get()
if BonesDic[boneName].get("parent") != None:
parent = BonesDic[boneName]["parent"]
TrackData[parent["name"]]
'''
#直接旋转自己骨骼不会影响子骨骼
#存在平移
if traVec.length > 0:
ax = Vector.cross(Vector((0, 1, 0)), targetPos)
the = math.asin(ax.length/targetPos.length)
poseBone.rotation_quaternion = Matrix.Rotation(the, 3, ax).to_quaternion()
else:
poseBone.rotation_quaternion = mathutils.Quaternion((1, 0, 0, 0))
#scale 值
scaleY = targetPos.length/poseBone.length
poseBone.scale = Vector((1, scaleY, 1))
#计算孩子骨骼的旋转参数 joint的旋转会影响孩子骨骼的
#为孩子骨骼设定 rotation_quaternion
#continue
#wt = BonesDic[boneName]["mat"]*Vector(translation)
#editBone.tail = BonesDic[boneName]["tail"]+wt#Vector(translation)
#log("Bone tail in world "+boneName+" "+str(BonesDic[boneName]["tail"])+" "+str(editBone.tail)+" "+str(wt)+" "+str(translation))
#如何调整join的旋转呢?
#放入相关的edit 模式下的骨骼
#父亲骨骼和所有的子骨骼
#调整孩子骨骼的旋转
#update scene之后 保存关键帧
#恢复骨骼状态
if False:
allChildren = BonesDic[boneName]["children"]
#恢复骨骼到默认位置
#for bone in allChildren:
# editBone = modifyArmature_data.edit_bones[bone["name"]]
# editBone.tail = bone["tail"]
#更新一下矩阵变换
#bpy.context.scene.update()
#相对于父亲骨骼的 位置 使用 pose location 来修改而不是 editBone
#tail 调整位置 孩子调整位置
for bone in allChildren:
if modifyArmature_data.edit_bones.get(bone["name"]) != None:
editBone = modifyArmature_data.edit_bones[bone["name"]]
#世界坐标旋转 而不是局部骨骼坐标旋转
rot = Matrix.Rotation(angle, 3, axis)
#父亲骨骼旋转不会旋转子骨骼 方向 只会修正head位置
direction = bone["tail"]-bone["head"]
##editBone.tail-editBone.head
newDir = rot*direction
editBone.tail = editBone.head+newDir
#得到动画骨骼方向的旋转方向数据
rotY = (bone["mat"].inverted()*rot).to_euler().y
editBone.roll = bone["roll"]+rotY
#就按所有骨骼的相对于head tail 的旋转
bpy.context.scene.update()
#计算每个骨骼的旋转 和 scale
allGroups = [BonesDic[boneName]]+BonesDic[boneName]["children"]
animationDic = {}
for bone in allGroups:
if modifyArmature_data.edit_bones.get(bone["name"]) != None:
mb = modifyArmature_data.edit_bones[bone["name"]]
animationDic[bone["name"]] = {
"mat":mb.matrix.to_3x3().copy(),
"length": mb.length,
}
#得到 动画初始骨骼位置 Armature2
#得到 当前帧骨骼位置 Armature2.001
#计算 两者的 qua 和 scale 差值
#修改 Armature2 pose数据
#插入 pose关键帧
bpy.context.scene.frame_set(fnum)
#不能去修改 骨骼的
#qua = Matrix.Rotation(angle, 3, axis)
#loc = Vector(translation)
#amt_obj.pose.bones[boneName].rotation_quaternion = qua.to_quaternion()
#amt_obj.pose.bones[boneName].scale = Vector(modifyScale)
#amt_obj.pose.bones[boneName].location = loc
'''
for bone in allGroups:
if amt_obj.pose.bones.get(bone["name"]) == None:
continue
#restPose
rbData = bone
normalBoneMat = rbData["mat"].inverted() #restBone[b["name"]].matrix.inverted()
#使用动画的 matrix 变换到世界空间 接着从世界空间变换到 rest pose空间
nDir = normalBoneMat*animationDic[bone["name"]]["mat"]*Vector((0, 1, 0))
rotAxis = Vector.cross(Vector((0, 1, 0)), nDir)
#.normalized()
log("calculate bone data "+bone["name"]+" "+str(nDir))
theta = math.asin(rotAxis.length/nDir.length)
#av = min(max(-1, Vector((0, 1, 0))*nDir), 1)
#angle = math.acos(av)
qua = Matrix.Rotation(theta, 3, rotAxis).to_quaternion()
defaultQua = (1, 0, 0, 0)
#还要计算roll值
#difRoll = b["roll"]-rbData["roll"]
#rotY = Matrix.Rotation(difRoll, 3, Vector((0, 1, 0))).to_quaternion()
#先旋转 接着 调整roll 方向
#qua = qua*rotY
scale = animationDic[bone["name"]]["length"]/bone["length"]
modifyScale = (1, scale, 1)
#b["initQuaternion"] = qua
#b["initScale"] = modifyScale
#bpy.context.scene.frame_set(fnum)
#设置这一frame的骨骼的pose数据
amt_obj.pose.bones[bone["name"]].rotation_quaternion = qua
amt_obj.pose.bones[bone["name"]].scale = Vector(modifyScale)
#bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
'''
#modifyArmature_obj.location = Vector((count*4+4, 0, 0))
#modifyArmature_obj.name = "Armature2_"+str(count)
#记录当前所有Armature2 骨骼的位置到关键帧fnum
if False:
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
amt_obj.select = True
bpy.context.scene.objects.active = amt_obj
#if count < len(TrackData.keys()):
# bpy.ops.object.duplicate()
# modifyArmature_obj = bpy.data.objects["Armature2.001"]
# modifyArmature_data = modifyArmature_obj.data
bpy.ops.object.mode_set(mode="POSE")
bpy.context.scene.update()
#只需要选择 parent 和 children 骨骼即可
bpy.ops.pose.select_all(action="SELECT")
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
#数据是下一帧是上一frame的 相对变换pose 还是 相对于初始pose
#如果相对于上一frame
#如果相对于初始则每次只需要按照
'''
amt_obj.pose.bones[b["name"]].rotation_quaternion = qua
amt_obj.pose.bones[b["name"]].scale = Vector(modifyScale)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
'''
#bpy.context.scene.frame_set(0)
#amt_obj.pose.bones[b["name"]].rotation_quaternion = qua
#amt_obj.pose.bones[b["name"]].scale = Vector(modifyScale)
#bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
#读取所有的frame帧数据
#第8帧动作幅度最大
if count >= 8:
break
count += 1
#break
'''
建立动画骨骼 基础形态
'''
#创建一个动画的初始的 pose状态 调整armature的骨骼位置
#进入pose mode 调整每一个每个骨骼的head 和 tail位置
#旋转子骨骼到目标位置 的一个旋转方向 location rotation scale 沿着骨骼的轴方向的
#head ---> tail 之间的一个旋转方向 变化 从原方向 到新方向之间的旋转变化
import math
def CreateAnimationPose(animationlink, handler):
global dirname
if animationlink != 0:
#scene = bpy.context.scene
filename = os.path.join(dirname, animationlink)
xml_doc = OpenFile(filename)
#得到动画pose中每个骨骼的变换矩阵
#分析xml文件得到 攻击动作的初始 骨骼位置 pose
#60frame/s int(time*60) = frame
BonesDic, BonesList, BoneID, TrackData = OGREBonesDic(xml_doc)
BonesData = []
ChildList(BonesDic)
#创建空物体来 计算骨骼的坐标位置
#保存攻击的joint 重新计算每个骨骼的位置相对变化
CreateEmptys(BonesDic, False)
#return
root = FindRoot(BonesList)
SortData(root, BonesData)
if bpy.context.object != None:
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.object.select = False
bpy.ops.object.select_all(action="DESELECT")
amt_obj = bpy.data.objects["Armature"]
amt_data = amt_obj.data
#amt_obj.animation_data_create()
#action = bpy.data.actions.new(name="Test")
#amt_obj.animation_data.action = action
#amt_obj.select = True
#bpy.ops.object.mode_set(mode="POSE")
#提取pose信息 设置初始pose属性 骨骼的location 相对于rest的location 和 rotation数据
#骨骼的location 增加3条曲线 描述 x y z 每个骨骼的位置变化
#modify location is relative position
restPoseEdit = handler.restPose
#使用骨骼的 旋转位置 不要使用 空物体的位置 空物体位置和 骨骼位置不对
restBone = amt_data.bones
#建立一个新的骨架 比较两个骨架的本地旋转信息 Armature2 骨架
#骨架名称可以相同
if bpy.context.object != None:
bpy.ops.object.select_all(action="DESELECT")
arm = bpy.ops.object.add(type="ARMATURE", enter_editmode=True)
ob = bpy.context.object
ob.location = bpy.context.scene.cursor_location
ob.show_x_ray = True
ob.name = "Armature2"
amt = ob.data
amt.show_axes = True
#动画骨骼对象
arm_obj = ob
log("initial Animation Bone ")
#初始化动画骨骼
for k in BonesData:
log("joint pos "+k['name']+" : "+str(k['translation']))
if k.get('parent'):
parent = BonesDic[k['parent']]
#得到对应joint 正方体物体
#matrix_local = k["matrix_local"]
rollVal = calculateRoll(k["name"])
if parent.get('parent'):
parentBone = amt.edit_bones[k['parent']]
bpy.ops.armature.select_all(action="DESELECT")
parentBone.select_tail = True
bpy.ops.armature.extrude()
amt.edit_bones[-1].name = k['name']
selBone = amt.edit_bones[-1]
tp = k["translation"]-selBone.head
bpy.ops.transform.translate(value=tp)
selBone.roll = rollVal
#root--->Bip01
else:
bone = amt.edit_bones.new(k['name'])
bone.head = parent["translation"]
bone.tail = k["translation"]
bone.roll = rollVal
bone.use_connect = False
if parent.get('firstBone') != None:
bone.parent = amt.edit_bones[parent['firstBone']]
else:
parent['firstBone'] = bone.name
bpy.ops.armature.select_all(action="DESELECT")
else:
pass
#存储动画骨骼的基本信息 配置状态
bpy.context.scene.update()
for k in BonesData:
if amt.edit_bones.get(k["name"]):
bone = amt.edit_bones[k["name"]]
k["head"] = bone.head.copy()
k["tail"] = bone.tail.copy()
k["roll"] = bone.roll
k["length"] = bone.length
k["mat"] = bone.matrix.to_3x3().copy()
bone.use_inherit_scale = False
bone.use_inherit_rotation = False
#对比restPose Bone 和 动画 骨骼的 head tail roll 来计算 pose下的rotation
#先制作旋转 再做roll旋转
#将 目标骨骼方向向量转化到 rest骨骼的空间中
#计算 (0, 1, 0) 方向和 转化方向之间的旋转四元式
#设置pose 骨骼的rotation_quaternion旋转数据
if bpy.context.object != None:
bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.object.select = False
bpy.ops.object.select_all(action="DESELECT")
#调整 基础骨骼 pose骨骼的 位置 缩放 旋转
bpy.ops.object.select_all(action="DESELECT")
amt_obj.select = True
bpy.context.scene.objects.active = amt_obj
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.select_all(action="SELECT")
bpy.context.scene.frame_set(0)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
action = amt_obj.animation_data.action
#bpy.context.scene.frame_set(20)
#bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
#bpy.ops.object.mode_set(mode="POSE")
for b in BonesData:
if b.get("parent") and restBone.get(b["name"]) != None:
rbData = restPoseEdit[b["name"]]
#rbDir = rbData["tail"]-rbData["head"]
#myDir = b["tail"]-b["head"]
#逆矩阵是将 世界坐标的向量 转化到 骨骼空间里面去的
#使用edit 模式下的骨骼的 变换矩阵
normalBoneMat = rbData["mat"].inverted() #restBone[b["name"]].matrix.inverted()
#使用动画的 matrix 变换到世界空间 接着从世界空间变换到 rest pose空间
nDir = normalBoneMat*b["mat"]*Vector((0, 1, 0))
rotAxis = Vector.cross(Vector((0, 1, 0)), nDir)
#.normalized()
log("calculate bone data "+b["name"]+" "+str(nDir))
theta = math.asin(rotAxis.length/nDir.length)
#av = min(max(-1, Vector((0, 1, 0))*nDir), 1)
#angle = math.acos(av)
qua = Matrix.Rotation(theta, 3, rotAxis).to_quaternion()
defaultQua = (1, 0, 0, 0)
#还要计算roll值
#difRoll = b["roll"]-rbData["roll"]
#rotY = Matrix.Rotation(difRoll, 3, Vector((0, 1, 0))).to_quaternion()
#先旋转 接着 调整roll 方向
#qua = qua*rotY
scale = b["length"]/restBone[b["name"]].length
modifyScale = (1, scale, 1)
#在第20frame 存储一个 攻击骨骼位置
b["initQuaternion"] = qua
b["initScale"] = modifyScale
bpy.context.scene.frame_set(20)
amt_obj.pose.bones[b["name"]].rotation_quaternion = qua
amt_obj.pose.bones[b["name"]].scale = Vector(modifyScale)
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale")
'''
for axis_i in range(4):
data_path = 'pose.bones["%s"].rotation_quaternion' % (b['name'])
curve = action.fcurves.new(data_path=data_path, index=axis_i)
keyframe_points = curve.keyframe_points
keyframe_points.add(2)
keyframe_points[0].co = (10, defaultQua[axis_i])
keyframe_points[0].co = (20, qua[axis_i])
defaultSca = (1, 1, 1)
#根据长度缩放骨骼
data_path = 'pose.bones["%s"].scale' % (b['name'])
for axis_i in range(3):
curve = action.fcurves.new(data_path=data_path, index=axis_i)
keyframe_points = curve.keyframe_points
keyframe_points.add(2)
keyframe_points[0].co = (10, defaultSca[axis_i])
keyframe_points[0].co = (20, scale)
'''
CreatePoseAnimation(BonesDic, BonesList, BoneID, TrackData, BonesData, handler)
'''
for cu in action.fcurves:
for bez in cu.keyframe_points:
bez.interpolation = 'LINEAR'
'''
'''
for b in BonesData:
#not root
#Pelvis 长度为0的 骨骼不存在所以不用修改状态
if b.get("parent") and restBone.get(b["name"]) != None:
parent = BonesDic[b["parent"]]
#计算两个的方向向量的旋转值
#diff = b["translation"]-parent["translation"]
#diffOld = restPose[b["name"]]["translation"] - restPose[parent["name"]]["translation"]
#从B空间 到 A 空间 MA*MB-1
#matrixA = b["matrix_local"]
#matrixB = restPose[b["name"]]["matrix_local"]
#mat = matrixB.inverted()*matrixA
#qua = mat.to_quaternion()
#qua = matrixA.to_quaternion()-matrixB.to_quaternion()
rpbone = restBone[b["name"]]
rpquat = rpbone.matrix_local.to_quaternion()
sprotAxis = b["rotation"]
sprotAngle = b["angle"]
sprotquat = Matrix.Rotation(sprotAngle, 3, sprotAxis).to_quaternion()
qua = rpquat-sprotquat
#c = Vector.cross(diffOld, diff)
#theta = math.acos(diffOld.normalized()*diff.normalized())
#math.sin(theta)
#rot = Matrix.Rotation(theta, 3, c)
#qua = rot.to_quaternion()
#w x y z
for axis_i in range(4):
data_path = 'pose.bones["%s"].rotation_quaternion' % (b['name'])
curve = action.fcurves.new(data_path=data_path, index=axis_i)
keyframe_points = curve.keyframe_points
keyframe_points.add(1)
keyframe_points[0].co = (5, qua[axis_i])
'''
BoneID2Name = {}
def calculateRoll(jointName):
#return 0
boneJoint = bpy.data.objects[jointName]
#matrix_local = k["matrix_local"]
localTranslation = boneJoint.matrix_local.to_translation()
localQuaternion = boneJoint.matrix_local.to_3x3().to_quaternion()
vec2 = localQuaternion*localTranslation
normalDir = Vector.cross(localTranslation, vec2)
if localTranslation.length == 0 or vec2.length == 0:
rollVal = 0
else:
theta = math.asin(normalDir.length/localTranslation.length/vec2.length)
qua2 = Matrix.Rotation(theta, 3, normalDir).to_quaternion()
qua3 = localQuaternion* qua2.inverted()
rollVal = qua3.angle
return rollVal
#create blender armature submesh multiple mesh use shared vertex or not share
#convert ogre joint to bones
#建立绑定位置的 骨骼位置信息 pose mode下面可以调整微调
def CreateBindSkeleton(xmldoc, handler):
BonesDic, BonesList, BoneID, TrackData = OGREBonesDic(xmldoc)
global BoneID2Name
BoneID2Name = BoneID
#BonesData = BonesDic
BonesData = []
ChildList(BonesDic)
CreateEmptys(BonesDic)
handler.restPose = BonesDic
#return
root = FindRoot(BonesList)
#root = BonesList[0]
if root == None:
log("not find root bone "+str(BonesList))
return
#sort from root
SortData(root, BonesData)
if len(BonesData) != len(BonesList):
log("sort bone not equal "+ str(len(BonesList)))
log("data is "+str(len(BonesData)))
log("lost Number is "+str(len(BonesList) - len(BonesData)))
setA = set([k['name'] for k in BonesList])
setB = set([k['name'] for k in BonesData])
difSet = setA-setB
for k in difSet:
log('lost '+k)
log(BonesDic[k])
return
#HelperBones(BonesDic)
#ZeroBones(BonesDic)
#CreateEmptys(BonesDic)
#SetBonesASPositions(BonesDic)
#BonesData = BonesDic
#scn = bpy.context.scene
#obj = bpy.data.objects.new("obj", objmesh)
#obj.location = bpy.context.scene.cursor_location
#bpy.context.scene.objects.link(obj)
arm = bpy.ops.object.add(type="ARMATURE", enter_editmode=True)
ob = bpy.context.object
ob.location = bpy.context.scene.cursor_location
ob.show_x_ray = True
amt = ob.data
amt.show_axes = True
#joint to bone root-> some bone
#root is an empty Bone joint first Bone is parent
bpy.ops.armature.select_all(action="DESELECT")
log("initial Bone")
for k in BonesData:
log("joint pos "+k['name']+" : "+str(k['translation']))
#posit
#root joint self
if k.get('parent'):
rollVal = calculateRoll(k["name"])
log("bindBone RollValue "+k["name"]+" : "+str(rollVal))
#bpy.context.scene.active = bone
#root ---> Bip01
#Bip01 ----> Pelvis
#root ---> tag_xxx
#parent joint rotation
parent = BonesDic[k['parent']]
#Bip01 ---> Pelvis
#extrude from parent bone tail
if parent.get('parent'):
parentBone = amt.edit_bones[k['parent']]
bpy.ops.armature.select_all(action="DESELECT")
parentBone.select_tail = True
bpy.ops.armature.extrude()
amt.edit_bones[-1].name = k['name']
selBone = amt.edit_bones[-1]
#rot = Matrix.Rotation(parent['angle'], 3, parent['rotation'])
#rot = parent["rotmatAS"]
tp = k["translation"]-selBone.head
#tp = rot*Vector(k['position'])
bpy.ops.transform.translate(value=tp)
selBone.roll = rollVal
#selBone.matrix = rot
#new bone
#bone.head = parentBone.tail
#(trans, rotParent, scale) = parentBone.matrix.decompose()
#rot = Matrix.Rotation(parent['angle'], 3, parent['rotation'])
#bone.tail = Vector(k['position'])+parentBone.tail
#addPos(parentBone.tail, k['position'])
#bone.use_connect = False
#bone.parent = parentBone
#bpy.ops.transform.rotate(value=parent['angle'], axis=parent['rotation'])
#root--->Bip01
else:
bone = amt.edit_bones.new(k['name'])
bone.head = parent["translation"]
#parent['position']
#rot = Matrix.Rotation(parent['angle'], 3, parent['rotation'])
#rot = parent["rotmatAS"]
bone.tail = k["translation"]
bone.roll = rollVal
#bone.tail = rot*Vector(k['position'])+bone.head
#bone.matrix = rot
#bone.tail = rot*Vector(k['position'])+bone.head
#bone.tail = addPos(bone.head, k['position'])
bone.use_connect = False
if parent.get('firstBone') != None:
bone.parent = amt.edit_bones[parent['firstBone']]
else:
parent['firstBone'] = bone.name
bpy.ops.armature.select_all(action="DESELECT")
'''
#first bone from joint root join parent is who?
if k['parent']['firstBone'] == None:
bone.head = k['parent']['position']
bone.tail = addPos(k['parent']['position'], k['position'])
bone.use_connect = False
k['parent']['firstBone'] = bone
if k['parent'].get('parent'):
bone.parent = amt.edit_bones[k['parent']]
#other bone from joint
else:
bone.head = k['parent']['position']
bone.tail = addPos(k['parent']['position'], k['position'])
'''
#parent = amt.edit_bones[k['parent']]
#bone.parent = parent
#bone.head = parent.tail
#bone.use_connect = False
#(trans, rot, scale) = parent.matrix.decompose()
#bone.tail = rot*Vector((0.3, 0, 0))+bone.head
else:
pass
#bone.head = k['position']
#rot = mathutils.Matrix.Translation((0, 0, 0))
#rot = Matrix.Rotation(k['rotation'][3], 3, k['rotation'][:3])
#rot = Matrix.Rotation(k['rotation'][3], 3, k['rotation'][:3])
#bone.head = k['position']
bpy.context.scene.update()
for k in BonesData:
if k.get('parent'):
bone = amt.edit_bones[k['name']]
k["head"] = bone.head.copy()
k["tail"] = bone.tail.copy()
k["roll"] = bone.roll
k["length"] = bone.length
k["mat"] = bone.matrix.to_3x3().copy()
bone.use_inherit_scale = False
bone.use_inherit_rotation = False
log("create bone finish "+str(len(BonesList)))
return arm
#从每个submesh 里面抽取出vertexGroup的数据
#建立一个 vertexGroup
#将对应定点放到group里面
#group 按照数字名称来分组
#def CreateVertexGroup(name, mesh):
#分析完一个子mesh的时候 子物体就添加一个顶点group
def CreateBlenderMesh(name, mesh, materials):
#bmesh = Blender.NMesh.GetRaw()
#bmesh = bpy.data.meshes.new("test")
# dict matname:blender material
bmaterials = {}
bmat_idx = -1
log("Mesh with %d shared vertices." % len(mesh.vertices))
vertex_count = len(mesh.vertices)
shareVert = mesh.vertices
shareNormal = mesh.normals
shareUV = mesh.uvs
submesh_count = len(mesh.submeshes)
vertex_offset = 0
faces = []
#bpy.context.active_object = None
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
for j in range(0, submesh_count):
submesh = mesh.submeshes[j]
vert = submesh.vertices
faces = submesh.faces
uvs = submesh.uvs
if submesh.sharedvertices == 1:
vert = shareVert
uvs = shareUV
objmesh = bpy.data.meshes.new("mesh"+str(j))
obj = bpy.data.objects.new("obj"+str(j), objmesh)
obj.location = bpy.context.scene.cursor_location
bpy.context.scene.objects.link(obj)
objmesh.from_pydata(vert, [], faces)
objmesh.update(calc_edges=True)
obj.select = True
bpy.context.scene.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
#if bpy.context.mode == 'EDIT_MESH':
#for f in bm.faces:
obj = bpy.context.active_object
me = obj.data
bm = bmesh.from_edit_mesh(me)
uv_layer = bm.loops.layers.uv.verify()
bm.faces.layers.tex.verify()
#设置loop 循环上面的每个定点的uv 坐标获得定点的编号来索引到对应的uv坐标
for f in bm.faces:
for l in f.loops:
luv = l[uv_layer]
ind = l.vert.index
luv.uv = Vector(uvs[ind])
bmesh.update_edit_mesh(me)
me.update()
bpy.ops.object.mode_set(mode="OBJECT")
#检测骨骼编号 和 对应的骨骼名称
log("skeleton ID")
allID = set(BoneID2Name.keys())
useID = set(submesh.vertexGroup.keys())
diffID = useID-allID
for k in diffID:
log("lostID "+str(k))
log("all Lost ID "+str(len(allID)))
for k in allID:
log("allID "+str(k)+" "+str(BoneID2Name[k]))
if len(diffID) > 0:
return
#初始化顶点组
#以骨骼快速分析骨骼名称得到实际骨骼名称
#OGREBonesDic
#尝试将一个 joint 上面的定点权重/2 平分到相邻的两个骨骼上面即可
#也就是一个joint vertexGroup 对应两个 bone的 vertexGroup
#分别是 tail 和 别的骨骼的head
#如果这个有多个child 则 平分权重到 多个child+parent上面即可
for k in submesh.vertexGroup:
parentNode = BoneID2Name[k]
allGroups = [parentNode]#+parentNode["children"]
num = float(len(allGroups))
#blenderGroup = []
#将定点 放到每一个 相邻骨骼的 组中 并且权重平分
for g in allGroups:
if obj.vertex_groups.get(g["name"]) == None:
group = obj.vertex_groups.new(g["name"])
else:
group = obj.vertex_groups[g["name"]]
for v in submesh.vertexGroup[k]:
group.add([v["vertexIndex"]], v["weight"]/num, 'ADD')
#blenderGroup.append(group)
#name = realName["name"]
#realName["parent"]
#骨骼是使用 tail joint命名的因此需要 调整为parent 的名称来为 定点group命名
#group = obj.vertex_groups.new(name)
#for v in submesh.vertexGroup[k]:
# group.add([v["vertexIndex"]], v["weight"], 'REPLACE')
#bmesh.from_pydata(vert, [], faces)
#break
if materials.get(submesh.materialname):
omat = materials[submesh.materialname]
bmat = 0
if not bmaterials.get(submesh.materialname):
bmat = create_blender_material(omat)
bmaterials[submesh.materialname] = bmat
else:
bmat = bmaterials[submesh.materialname]
setMaterial(obj, bmat)
#设置模型材质
bpy.ops.object.join()
return bpy.context.object
def convert_meshfile(filename):
log("convert file "+filename)
if IMPORT_OGREXMLCONVERTER != '':
commandline = IMPORT_OGREXMLCONVERTER +' -log /Users/liyong/err.log '+ ' "' + filename + '"' + ' "'+ filename + '.xml' + '"'
log("executing %s" % commandline)
log(os.path.abspath('.'))
status = os.system(commandline)
log('status '+str(status))
log("done.")
#set ambient diffuse specular emissive color
#def collect_materials(dirname):
# matname_pattern = re.compile('^\s*material\s+(.*?)\s*$')
def collect_materials(dirname):
# preparing some patterns
# to collect the material name
matname_pattern = re.compile('^\s*material\s+(.*?)\s*$')
# to collect the texture name
texname_pattern = re.compile('^\s*texture\s+(.*?)\s*$')
# to collect the diffuse colour
diffuse_alpha_pattern = re.compile(\
'^\s*diffuse\s+([^\s]+?)\s+([^\s]+?)\s+([^\s]+?)\s+([^\s]+).*$')
diffuse_pattern = re.compile(\
'^\s*diffuse\s+([^\s]+?)\s+([^\s]+?)\s+([^\s]+).*$')
# to collect the specular colour
specular_pattern = re.compile(\
'^\s*specular\s+([^\s]+?)\s+([^\s]+?)\s+([^\s]+).*$')
ambient_pattern = re.compile(
'^\s*ambient\s+([^\s]+?)\s+([^\s]+?)\s+([^\s]+).*$')
# the dictionary where to put the materials
materials = {}
# for all lines in all material files..
material_files = glob.glob(dirname + '/*.material')
material = 0
for filename in material_files:
f = open(filename, 'r')
line_number = 0
for line in f:
try:
line_number = line_number + 1
dlog("line to be matched: %s" % line)
#m = matname_pattern.match(line)
mat_name = matname_pattern.findall(line)
if len(mat_name) > 0:
material = Material(mat_name[0])
materials[material.name] = material
vlog("parsing material %s" % mat_name[0])
#m = texname_pattern.match(line)
tex_name = texname_pattern.findall(line)
# load only the first texture unit's texture
# TODO change to use the first one using the first uv set
if len(tex_name) > 0 and not material.texname:
#if m and not material.texname:
material.texname = dirname + '/' + tex_name[0]
m = diffuse_alpha_pattern.match(line)
if not m:
m = diffuse_pattern.match(line)
if m:
vlog(" parsing diffuse..")
groups = m.groups()
r = float(groups[0])
g = float(groups[1])
b = float(groups[2])
#TODO: alpha still untested
if len(groups) > 3:
a = float(groups[3])
else:
a = 1.0
material.diffuse = (r, g, b, a)
vlog(" diffuse: %s" % str(material.diffuse))
m = specular_pattern.match(line)
if m:
vlog(" parsing specular..")
groups = m.groups()
r = float(groups[0])
g = float(groups[1])
b = float(groups[2])
material.specular = (r, g, b, 1.0)
vlog(" specular: %s" % str(material.specular))
except Exception as e:
log(" error parsing material %s in %s on line % d: " % \
(material.name, filename, line_number))
log(" exception: %s" % str(e))
return materials
def create_blender_material(omat):
mat = bpy.data.materials.new(omat.name)
mat.diffuse_color = omat.diffuse[:3]
mat.diffuse_shader = 'LAMBERT'
mat.diffuse_intensity = 1.0
mat.specular_color = omat.specular[:3]
mat.specular_shader = 'COOKTORR'
mat.specular_intensity = 1.0
mat.alpha = omat.diffuse[3]
mat.ambient = 1
img = omat.getTexture()
if img:
cTex = bpy.data.textures.new(omat.texname, type="IMAGE")
cTex.image = img
mtex = mat.texture_slots.add()
mtex.texture = cTex
mtex.texture_coords = "UV"
mtex.use_map_color_diffuse = True
#mtex.use_map_color_emission = True
#mtex.emission_color_factor = 0.5
#mtex.use_map_density = True
mtex.mapping = "FLAT"
return mat
dirname = None
basename = None
def fileselection_callback(filename):
log("Reading mesh file %s..." % filename)
filename = os.path.expanduser(filename)
#if fileName:
# (shortName, ext) = os.path.splitext(filename)
# is this a mesh file instead of an xml file?
if (filename.lower().find('.xml') == -1):
# No. Use the xml converter to fix this
log("No mesh.xml file. Trying to convert it from binary mesh format.")
convert_meshfile(filename)
filename += '.xml'
global dirname
global basename
dirname = os.path.dirname(filename)
basename = os.path.basename(filename)
#dirname = sys.dirname(filename)
#basename = Blender.sys.basename(filename)
# parse material files and make up a dictionary: {mat_name:material, ..}
materials = collect_materials(dirname)
#create materials
# prepare the SAX parser and parse the file using our own content handler
parser = xml.sax.make_parser()
handler = OgreMeshSaxHandler()
parser.setContentHandler(handler)
parser.parse(open(filename))
# create the mesh from the parsed data and link it to a fresh object
#scene = Blender.Scene.GetCurrent()
meshname = basename[0:basename.lower().find('.mesh.xml')]
#生成骨骼
CreateSkeleton(handler.skeleton, handler)
amt = bpy.data.objects["Armature"]
#生成蒙皮 和 顶点组
obj = CreateBlenderMesh(meshname, handler.mesh, materials)
#bpy.ops.object.mode_set(mode="OBJECT")
bpy.context.scene.update()
bpy.ops.object.select_all(action="DESELECT")
bpy.context.scene.objects.active = amt
#obj.select = False
#amt.select = False
obj.select = True
amt.select = True
bpy.ops.object.parent_set(type="ARMATURE")
#CreateTestAnimation(handler)
#生成攻击动画基本形态关键帧
CreateAnimationPose(handler.animationlink, handler)
#bpy.data.objects["Armature"].hide = True
bpy.data.objects["Armature2"].hide = True
#bpy.data.objects["Armature2.001"].hide = True
#obj.hide = True
#scene.link(object)
#object.select = True
log("import completed.")
#Blender.Redraw()
def clearScene():
#global toggle
scn = bpy.context.scene
log("clearScene ")
for ob in scn.objects:
if ob.type in ["MESH", "CURVE", "TEXT"]:
scn.objects.active = ob
bpy.ops.object.mode_set(mode='OBJECT')
scn.objects.unlink(ob)
del ob
return scn
#Blender.Window.FileSelector(fileselection_callback, "Import OGRE", "*.xml")
from bpy.props import *
class IMPORT_OT_OGRE(bpy.types.Operator):
bl_idname = "import_scene.ogre_mesh"
bl_description = 'Import from mesh file format (.mesh)'
bl_label = "Import mesh" +' v.'+ __version__
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_options = {'UNDO'}
filepath = StringProperty(
subtype='FILE_PATH',
)
def draw(self, context):
layout0 = self.layout
#layout0.enabled = False
#col = layout0.column_flow(2,align=True)
layout = layout0.box()
col = layout.column()
#col.prop(self, 'KnotType') waits for more knottypes
#col.label(text="import Parameters")
#col.prop(self, 'replace')
#col.prop(self, 'new_scene')
#row = layout.row(align=True)
#row.prop(self, 'curves')
#row.prop(self, 'circleResolution')
#row = layout.row(align=True)
#row.prop(self, 'merge')
#if self.merge:
# row.prop(self, 'mergeLimit')
#row = layout.row(align=True)
#row.label('na')
#row.prop(self, 'draw_one')
#row.prop(self, 'thic_on')
#col = layout.column()
#col.prop(self, 'codec')
#row = layout.row(align=True)
#ow.prop(self, 'debug')
#if self.debug:
# row.prop(self, 'verbose')
def execute(self, context):
'''
global toggle, theMergeLimit, theCodec, theCircleRes
O_Merge = T_Merge if self.merge else 0
#O_Replace = T_Replace if self.replace else 0
O_NewScene = T_NewScene if self.new_scene else 0
O_Curves = T_Curves if self.curves else 0
O_ThicON = T_ThicON if self.thic_on else 0
O_DrawOne = T_DrawOne if self.draw_one else 0
O_Debug = T_Debug if self.debug else 0
O_Verbose = T_Verbose if self.verbose else 0
toggle = O_Merge | O_DrawOne | O_NewScene | O_Curves | O_ThicON | O_Debug | O_Verbose
theMergeLimit = self.mergeLimit*1e-4
theCircleRes = self.circleResolution
theCodec = self.codec
'''
#bpy.ops.wm.read_homefile()
clearScene()
#readAndBuildDxfFile(self.filepath)
fileselection_callback(self.filepath)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
def menu_func(self, context):
self.layout.operator(IMPORT_OT_OGRE.bl_idname, text="ogre (.mesh)")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(menu_func)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment