Last active
August 29, 2015 14:07
-
-
Save liyonghelpme/a099f706c01b0928e48e to your computer and use it in GitHub Desktop.
before support vertexcolours
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!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. | |
""" | |
''' | |
#Known Bug: | |
#骨骼 旋转存在问题, 需要调整骨骼的 四元式 将轴和 角度分别取负值 | |
# 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 = [] | |
self.indextype = "" | |
self.materialname = "" | |
self.sharedvertices = 0 | |
self.vertexGroup = {} | |
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 = 0 | |
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): | |
log("texname is "+str(self.texname)) | |
if self.texname == 0: | |
return None | |
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 self.texname == 0: | |
return None | |
if not os.path.exists(self.texname): | |
self.texname = self.texname.replace('.png', '.dds') | |
if os.path.exists(self.texname): | |
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): | |
log("read element "+name) | |
if name == 'boneassignments': | |
#self.boneassignments = {} | |
pass | |
if name == 'vertexboneassignment': | |
log("current submesh is "+str(self.submesh)) | |
vind = int(attrs.get("vertexindex", "")) | |
bind = int(attrs.get("boneindex", "")) | |
weight = float(attrs.get("weight", "")) | |
#共享的骨骼数据 设定子Mesh | |
if self.submesh == 0: | |
self.submesh = self.mesh | |
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': | |
vc = attrs.get('value', "").split() | |
vc[0] = float(vc[0]) | |
vc[1] = float(vc[1]) | |
vc[2] = float(vc[2]) | |
vc[3] = float(vc[3]) | |
self.submesh.vertexcolours.append(vc) | |
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) | |
''' | |
position | |
angle | |
rotation | |
parent | |
children | |
flag | |
translation 世界空间中的 head 位置 | |
TrackData ={ | |
boneName : { | |
time:xx, | |
translation:[], | |
angle:xx, | |
axis:[], | |
} | |
} | |
''' | |
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 | |
allHelpBones = {} | |
for bone in BonesData.keys(): | |
if (len(BonesData[bone]['children']) == 0) or (len(BonesData[bone]['children']) > 1): | |
HelperBone = {} | |
HelperBone['position'] = [0,0.2,0.0] | |
HelperBone['parent'] = bone | |
HelperBone['rotation'] = [1.0,0.0,0.0] | |
HelperBone["angle"] = 0 | |
HelperBone["name"] = "Helper"+bone | |
HelperBone['flag'] = 'helper' | |
HelperBone["children"] = [] | |
#BonesData['Helper'+str(count)] = HelperBone | |
allHelpBones['Helper'+bone] = HelperBone | |
count+=1 | |
for b in allHelpBones.keys(): | |
BonesData[b] = allHelpBones[b] | |
#### 在 0 0 0 位置的骨骼 增加辅助的 0 骨骼到 parent上面 | |
#零长度 骨骼 Pelvis | |
def ZeroBones(BonesData): | |
allZeroBone = {} | |
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,0.2,0.0] | |
ZeroBone['rotation'] = [1.0,0.0,0.0] | |
ZeroBone["angle"] = 0 | |
ZeroBone["name"] = "Zero"+bone | |
ZeroBone["children"] = [] | |
if (BonesData[bone].get('parent')): | |
ZeroBone['parent'] = BonesData[bone]['parent'] | |
ZeroBone['flag'] = 'zerobone' | |
#BonesData['Zero'+bone] = ZeroBone | |
allZeroBone['Zero'+bone] = ZeroBone | |
if (BonesData[bone].get('parent')): | |
BonesData[BonesData[bone]['parent']]['children'].append('Zero'+bone) | |
for k in allZeroBone.keys(): | |
BonesData[k] = allZeroBone[k] | |
#计算骨骼长度 | |
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) | |
else: | |
for bone in BonesDic.keys(): | |
if not bpy.data.objects.get(bone): | |
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'): | |
if bpy.data.objects.get(bone) != None: | |
Parent = bpy.data.objects[BonesDic[bone].get('parent')] | |
obj = bpy.data.objects[bone] | |
obj.parent = Parent | |
''' | |
设定空物体的位置和旋转 相对于父物体 | |
helper 骨骼不用调整数据 | |
''' | |
for bone in BonesDic.keys(): | |
#if BonesDic[bone].get("flag") == None: | |
#if bpy.data.objects.get(bone) != None: | |
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() | |
''' | |
保存空物体的相对于父亲物体的 matrix_local 和 转化到世界空间的 | |
''' | |
for bone in BonesDic.keys(): | |
#if bpy.data.objects.get(bone) != None: | |
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.copy() | |
BonesDic[bone]["matrix_world"] = obj.matrix_world.copy() | |
#bpy.context.scene.objects.unlink(modelCube) | |
#del modelCube | |
''' | |
隐藏空物体joint | |
''' | |
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 | |
''' | |
def Unlink(obj): | |
bpy.context.scene.objects.unlink(obj) | |
for c in obj.children: | |
Unlink(c) | |
#没有必要删除empty 只要存在则继续使用 | |
def ClearEmptys(): | |
return | |
def togHide(r): | |
r.hide = not r.hide | |
for c in r.children: | |
togHide(c) | |
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.mode_set(mode="POSE") | |
bpy.ops.pose.select_all(action="DESELECT") | |
maxFrame = 0 | |
restPoseEdit = handler.restPose | |
for boneName in TrackData.keys(): | |
tracks = TrackData[boneName] | |
count = 0 | |
bone = BonesDic[boneName] | |
restBone = restPoseEdit[boneName] | |
rp = Vector(restBone["position"]) | |
bp = Vector(bone["position"]) | |
poseBone = amt_obj.pose.bones[boneName] | |
normalBone = amt_data.bones[boneName] | |
quaRest = Matrix.Rotation(restBone["angle"], 3, restBone["rotation"]).to_quaternion() | |
quaAni = Matrix.Rotation(bone["angle"], 3, bone["rotation"]).to_quaternion() | |
normalBone.select = True | |
for frame in tracks: | |
time = frame["time"] | |
fnum = int(round(time*60)) | |
maxFrame = max(maxFrame, fnum) | |
translation = Vector(frame["translation"]) | |
angle = frame["angle"] | |
axis = frame["axis"] | |
FrameTranslate = translation | |
FrameQua = Matrix.Rotation(angle, 3, axis).to_quaternion() | |
#poseBone.select = True | |
#需要操作对应的 bones 选择不能操作pose选择 | |
bpy.context.scene.frame_set(fnum) | |
poseBone.location = bp-rp+FrameTranslate | |
poseBone.rotation_mode = "QUATERNION" | |
#比较restPose 和 动作状态下的骨骼旋转 | |
#poseBone.rotation_quaternion = FrameQua*quaAni*quaRest.inverted() #quaRest.rotation_difference(quaAni) | |
poseBone.rotation_quaternion = quaAni*FrameQua *quaRest.inverted() #quaRest.rotation_difference(quaAni) | |
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale") | |
#poseBone.select = False | |
normalBone.select = False | |
bpy.data.scenes["Scene"].frame_end = maxFrame | |
return | |
#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) | |
HelperBones(BonesDic) | |
ZeroBones(BonesDic) | |
#创建空物体来 计算骨骼的坐标位置 | |
#保存攻击的joint 重新计算每个骨骼的位置相对变化 | |
''' | |
计算每个joint的 matrix_world 状态变化 设置对应骨骼的状态 | |
''' | |
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") | |
amt_obj = bpy.data.objects["Armature"] | |
amt_data = amt_obj.data | |
#调整 基础骨骼 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") | |
''' | |
调整每个骨骼的 Pose下的 location rotation scale 信息 | |
location head 位置信息 相对于restBone的偏移 相对于自己的joint坐标轴的位置移动 qua 的旋转 | |
rotation joint的世界旋转值 | |
''' | |
bpy.context.scene.frame_set(20) | |
restPoseEdit = handler.restPose | |
for b in BonesDic.keys(): | |
bone = BonesDic[b] | |
#不是Helper 骨骼则比较数据 | |
if bone.get("flag") == None: | |
log("get RestBone "+b) | |
#if b == "master_root": | |
restBone = restPoseEdit[b] | |
#相对于父亲空间的位置 | |
#位移相对于 父亲空间 | |
#旋转 相对于 自己空间的轴旋转吗? | |
rp = Vector(restBone["position"]) | |
bp = Vector(bone["position"]) | |
#从世界坐标空间偏移转化到pose 骨骼空间 | |
#相对于 父亲骨骼空间的 位置移动 直接设定 | |
poseBone = amt_obj.pose.bones[b] | |
poseBone.location = bp-rp | |
poseBone.rotation_mode = "QUATERNION" | |
#比较restPose 和 动作状态下的骨骼旋转 | |
quaRest = Matrix.Rotation(restBone["angle"], 3, restBone["rotation"]).to_quaternion() | |
quaAni = Matrix.Rotation(bone["angle"], 3, bone["rotation"]).to_quaternion() | |
poseBone.rotation_quaternion = quaAni * quaRest.inverted() #quaRest.rotation_difference(quaAni) | |
#poseBone.location = poseBone.matrix.inverted()*bone["translation"] | |
log("Bone location is "+bone["name"]+" "+str(bone["translation"])+" "+str(poseBone.location)) | |
#amt_obj.pose.bones[b].location = bp-rp | |
#amt_obj.pose.bones[b].rotation_quaternion = qua | |
#amt_obj.pose.bones[b].scale = Vector(modifyScale) | |
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale") | |
''' | |
得到每一帧动画 插入到Pose模式 类似于 这里设置location和rotation_quaternion | |
每一帧位置 | |
location = bp-rp+ FrameTranslate | |
旋转 quaDiff * quaRest = FrameQua*quaAni | |
rotation_quaternion = FrameQua*quaAni*quaRest.inverted() | |
''' | |
BonesData = None | |
CreatePoseAnimation(BonesDic, BonesList, BoneID, TrackData, BonesData, handler) | |
return | |
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 | |
#生成无孩子joint 和 多孩子joint root 的 辅助骨骼用于骨骼变换控制 mesh | |
''' | |
def HelperBones(BonesDic): | |
count = 0 | |
for bone in BonesDic.keys(): | |
if (len(BonesDic[bone]["children"]) == 0) or (len(BonesDic[bone]["children"]) > 1): | |
HelperBone = { | |
"position" : [0.2, 0, 0], | |
"parent" : bone, | |
"rotation" : [1.0, 0, 0, 0], | |
"flag" : "helper", | |
} | |
BonesDic["Helper"+str(count)] = HelperBone | |
count += 1 | |
''' | |
''' | |
设定每个骨骼的head 位置 tail 位置默认沿着Z轴向上 一定长度 | |
每个joint都是一个独立的骨骼 | |
''' | |
def SetBonesASPositions(BonesDic): | |
for key in BonesDic.keys(): | |
bone = BonesDic[key] | |
bone["head"] = bone["matrix_world"].to_translation() | |
#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) | |
HelperBones(BonesDic) | |
ZeroBones(BonesDic) | |
CreateEmptys(BonesDic) | |
#SetBonesASPositions(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) | |
bpy.ops.object.add(type="ARMATURE", enter_editmode=True) | |
amt_obj = bpy.context.object | |
amt_obj.location = Vector((0, 0, 0)) | |
amt_obj.show_x_ray = True | |
amt_data = amt_obj.data | |
amt_data.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 BonesDic.keys(): | |
boneData = BonesDic[k] | |
bone = amt_data.edit_bones.new(boneData['name']) | |
#bone.use_connect = False | |
#bone.use_inherit_rotation = False | |
#bone.use_inherit_scale = False | |
bone.head = boneData["translation"] | |
children = boneData["children"] | |
if len(children) == 1: | |
childname = children[0]["name"] | |
vectailadd = CalcBoneLength(children[0]["position"]) | |
else: | |
vectailadd = 0.2 | |
#qua = boneData["matrix_world"].to_3x3().to_quaternion() | |
#设定骨骼的矩阵为 joint的矩阵 | |
''' | |
骨骼使用parent的旋转矩阵 来计算 location 位置 | |
但是应该使用本地的矩阵来计算 rotation数值 | |
''' | |
bone.tail = bone.head + Vector((0, vectailadd, 0)) | |
if boneData.get("parent") != None: | |
parent = BonesDic[boneData["parent"]] | |
rotMat = parent["matrix_world"].to_3x3().to_4x4() | |
rotMat[0][3] = bone.head[0] | |
rotMat[1][3] = bone.head[1] | |
rotMat[2][3] = bone.head[2] | |
bone.matrix = rotMat | |
else: | |
#如果没有父亲则 旋转为 单位矩阵 | |
pass | |
#bone.matrix = Matrix.Identity(4) | |
#bone.tail = bone.head+ qua*Vector((0, vectailadd, 0)) | |
#设定roll值 参考Edit mode Y 轴的旋转方向 | |
#joint 相对于父亲的旋转矩阵 和偏移矩阵 | |
#matrix_world 本地 0 0 转化到世界坐标 roll值是使用世界坐标计算的 | |
#世界坐标 0 0 位置物体 移动到 目标的位置 并且旋转 平移世界物体的矩阵 | |
#计算joint沿着 Y 方向的旋转值 | |
''' | |
v1 = Vector((0, 1, 0)) | |
v2 = bone.tail-bone.head | |
nor = Vector.cross(v1, v2) | |
theta = math.asin(nor.length/v1.length/v2.length) | |
qua2 = Matrix.Rotation(theta, 3, nor.normalized()).to_quaternion() | |
qua3 = qua2.inverted()*qua | |
bone.roll = qua3.angle | |
''' | |
for bone in amt_data.edit_bones: | |
if BonesDic[bone.name].get("parent") != None: | |
bone.parent = amt_data.edit_bones[BonesDic[bone.name]["parent"]] | |
bpy.ops.armature.select_all(action="DESELECT") | |
for bone in amt_data.edit_bones: | |
if BonesDic[bone.name].get("flag") != None: | |
bone.select = True | |
bpy.ops.armature.delete() | |
bpy.context.scene.update() | |
return | |
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 | |
from mathutils import Color | |
def CreateBlenderMesh(name, mesh, materials): | |
#bmesh = Blender.NMesh.GetRaw() | |
#bmesh = bpy.data.meshes.new("test") | |
if bpy.context.object != None: | |
bpy.ops.object.mode_set(mode="OBJECT") | |
bpy.ops.object.select_all(action="DESELECT") | |
# 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 | |
shareColour = mesh.vertexcolours | |
submesh_count = len(mesh.submeshes) | |
vertex_offset = 0 | |
faces = [] | |
#bpy.context.active_object = None | |
log("read len submesh "+str(submesh_count)) | |
for j in range(0, submesh_count): | |
submesh = mesh.submeshes[j] | |
log("createMesh "+submesh.materialname) | |
vert = submesh.vertices | |
faces = submesh.faces | |
uvs = submesh.uvs | |
vertexGroup = submesh.vertexGroup | |
vertexcolours = submesh.vertexcolours | |
if submesh.sharedvertices == 1: | |
vert = shareVert | |
uvs = shareUV | |
vertexGroup = mesh.vertexGroup | |
vertexcolours = shareColour | |
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() | |
colLayer = None | |
if len(vertexcolours) > 0: | |
colLayer = bm.loops.layers.color.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]) | |
if colLayer: | |
l[colLayer] = Color(vertexcolours[ind][:3]) | |
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 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) | |
#lost some bones don't create vertex group | |
if len(diffID) == 0: | |
#初始化顶点组 | |
#以骨骼快速分析骨骼名称得到实际骨骼名称 | |
#OGREBonesDic | |
#尝试将一个 joint 上面的定点权重/2 平分到相邻的两个骨骼上面即可 | |
#也就是一个joint vertexGroup 对应两个 bone的 vertexGroup | |
#分别是 tail 和 别的骨骼的head | |
#如果这个有多个child 则 平分权重到 多个child+parent上面即可 | |
for k in vertexGroup: | |
parentNode = BoneID2Name[k] | |
#有孩子骨骼则权重分配到孩子身上 没有则自己吞掉 | |
group = obj.vertex_groups.new(parentNode["name"]) | |
for v in vertexGroup[k]: | |
group.add([v["vertexIndex"]], v["weight"], 'REPLACE') | |
log("after create vertexGroup") | |
#设置模型材质 | |
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() | |
log("getTexture is "+str(img)) | |
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.get("Armature") | |
#togHide(bpy.data.objects["root"]) | |
#生成蒙皮 和 顶点组 | |
obj = CreateBlenderMesh(meshname, handler.mesh, materials) | |
#bpy.ops.object.mode_set(mode="OBJECT") | |
bpy.context.scene.update() | |
bpy.ops.object.select_all(action="DESELECT") | |
if amt != None: | |
bpy.context.scene.objects.active = amt | |
obj.select = True | |
amt.select = True | |
bpy.ops.object.parent_set(type="ARMATURE") | |
CreateAnimationPose(handler.animationlink, handler) | |
if bpy.data.objects.get("root") != None: | |
Unlink(bpy.data.objects["root"]) | |
Unlink(bpy.data.objects["Cube"]) | |
bpy.ops.object.mode_set(mode="OBJECT") | |
''' | |
#obj.select = False | |
#amt.select = False | |
#CreateTestAnimation(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