Last active
September 19, 2015 06:03
-
-
Save liyonghelpme/e64b88d6ebb93ca90e20 to your computer and use it in GitHub Desktop.
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.' | |
""" | |
''' | |
2015-3-7 | |
修改导出的动画多一 frame的bug | |
2015-2-5 支持批量导入一个文件夹 某个 模型 所有的 动画 导出为 fbx模型 | |
[已知BUG: ratman 和 主城第一个npc 的idle 动画导出存在旋转问题, ratman的背包的旋转方向反了,npc的手臂旋转方向反了] | |
注意::下面 代码 函数 fileselection_callback 使用了macos 上面的file 命令用于 识别文件格式 是否是UTF-16, 如果在windows上面使用需要 替换为windows命令,或者保证 转化生成的xml 文件为UTF-8 编码去掉文件编码检测也可以 | |
测试::测试了火炬之光 1中的 media/models/warrior/player.mesh 战士 主角的模型 动画导出 | |
0: 将脚本 放到 blender的插件目录中 | |
1:打开一个空场景 去掉 场景中 所有物体 和 相机 以及其它任何东西 | |
2:file import ogre 打开某个目录的 mesh文件 | |
3:导入后 将会 自动生成 这个 模型的 所有动画的 fbx 在 模型目录下面 | |
''' | |
bl_info = { | |
"name": "ogre mesh import", | |
"author": "liyonghelpme", | |
"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 | |
import codecs | |
# 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): | |
log(msg) | |
#if IMPORT_LOG_LEVEL >= 2: print(msg) | |
def dlog(msg): | |
log(msg) | |
#if IMPORT_LOG_LEVEL >= 3: print(msg) | |
''' | |
存放Mesh.xml 中读取的三角面片数据 | |
''' | |
class Mesh: | |
def __init__(self): | |
self.submeshes = [] | |
self.vertices = [] | |
self.vertexcolours = [] | |
self.normals = [] | |
self.uvs = [] | |
self.indextype = "" | |
self.materialname = "" | |
self.sharedvertices = 0 | |
self.vertexGroup = {} | |
''' | |
OGRE 支持共享顶点和 子面片 | |
一个模型可以: | |
只有一个Mesh 使用 sharedvertices | |
多个子Mesh 每个子Mesh 有自己的顶点和面 | |
''' | |
class Submesh: | |
def __init__(self): | |
self.vertices = [] | |
self.vertexcolours = [] | |
self.normals = [] | |
self.faces = [] | |
self.uvs = [] | |
self.indextype = "" | |
self.materialname = "" | |
self.sharedvertices = 0 | |
self.vertexGroup = {} | |
''' | |
OGRE 的材质 | |
diffuse | |
ambient | |
specular | |
Texture | |
''' | |
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 | |
self.matFileCon = '' | |
self.oldTexName = '' | |
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 | |
''' | |
读取Mesh.xml 文件 | |
读取共享Mesh | |
读取SubMesh | |
可以多个SubMesh 共享一个顶点集合 | |
也可以每个SubMesh 有自己的顶点集合 | |
读取基本的骨架文件名称 | |
''' | |
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 | |
#保存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 | |
''' | |
设置Blender 中 对象的材质信息 | |
''' | |
def setMaterial(ob, mat): | |
me = ob.data | |
me.materials.append(mat) | |
''' | |
读取动画的XMl 文件 | |
''' | |
from xml.dom import minidom | |
def OpenFile(filename): | |
if not os.path.exists(filename): | |
return None | |
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的骨骼信息 | |
''' | |
根据骨骼文件名称 skeleton, 读取XML文件,创建基本Pose骨骼 | |
''' | |
def CreateSkeleton(skeleton, handler): | |
global dirname | |
if skeleton != 0: | |
filename = os.path.join(dirname, skeleton) | |
xml_doc = OpenFile(filename) | |
if xml_doc != None: | |
return CreateBindSkeleton(xml_doc, handler) | |
return None | |
''' | |
position | |
angle | |
rotation | |
parent | |
children | |
flag | |
translation 世界空间中的 head 位置 | |
TrackData ={ | |
boneName : { | |
time:xx, | |
translation:[], | |
angle:xx, | |
axis:[], | |
} | |
} | |
''' | |
''' | |
根据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 | |
''' | |
为无孩子的Joint 增加一个Helper孩子 | |
为多于一个孩子的Joint 增加一个Helper孩子 | |
由于构建一个blender中的父亲骨骼 | |
''' | |
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 | |
''' | |
为在同一个点上的两个 Joint 父子关系 创建Helper 骨骼 | |
''' | |
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] | |
''' | |
计算骨骼长度 ZeroBones 中使用 | |
''' | |
def CalcBoneLength(vec): | |
return math.sqrt(vec[0]**2+vec[1]**2+vec[2]**2) | |
''' | |
通过创建空的正方体对象来模拟joint 最后连接这些正方体对象得到骨骼 | |
位置偏移 和 旋转 都是相对于父亲的 局部空间的变换 | |
创建空的正方体,用于计算每个骨骼的blender空间中的位置 | |
''' | |
#计算每个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") | |
#对象存在 但是被unlink 了 可以重新link回来 | |
if bpy.data.objects.get("Cube"): | |
modelCube = bpy.data.objects["Cube"] | |
if bpy.context.scene.objects.find("Cube") == -1: | |
bpy.context.scene.objects.link(modelCube) | |
log("relink 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.name = bone | |
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.name = bone | |
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() | |
''' | |
隐藏空物体joint | |
''' | |
for bone in BonesDic.keys(): | |
obj = bpy.data.objects[bone] | |
obj.hide = True | |
''' | |
将对象从blender中删除 包括对象的孩子 孙子,递归删除 | |
''' | |
def Unlink(obj): | |
if obj == None: | |
return | |
ok = False | |
try: | |
bpy.context.scene.objects.unlink(obj) | |
ok = True | |
except: | |
pass | |
if ok: | |
for c in obj.children: | |
Unlink(c) | |
''' | |
隐藏对象整体 | |
''' | |
def togHide(r): | |
r.hide = not r.hide | |
for c in r.children: | |
togHide(c) | |
import mathutils | |
''' | |
根据读取的动画数据 创建每一帧的骨骼动画 | |
针对动画骨架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 | |
log("restPoseBone is "+str(restPoseEdit)) | |
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-1 | |
return | |
''' | |
建立动画骨骼 基础形态 | |
接着调用其他函数 创建 每一帧骨骼动画 | |
''' | |
#创建一个动画的初始的 pose状态 调整armature的骨骼位置 | |
#进入pose mode 调整每一个每个骨骼的head 和 tail位置 | |
#旋转子骨骼到目标位置 的一个旋转方向 location rotation scale 沿着骨骼的轴方向的 | |
#head ---> tail 之间的一个旋转方向 变化 从原方向 到新方向之间的旋转变化 | |
import math | |
def CreateAnimationPose(animationlink, handler): | |
global dirname | |
if animationlink != 0 and animationlink != None: | |
filename = os.path.join(dirname, animationlink) | |
xml_doc = OpenFile(filename) | |
if xml_doc == None: | |
return False | |
#得到动画pose中每个骨骼的变换矩阵 | |
#分析xml文件得到 攻击动作的初始 骨骼位置 pose | |
#60frame/s int(time*60) = frame | |
''' | |
解析动画骨骼初始位置 数据 | |
创建每个骨骼的孩子列表 | |
创建帮助性骨骼 用于无孩子的Joint 增加一个孩子构成一个骨骼,多于一个孩子的骨骼,增加一个 Helper 孩子用于创建骨骼,其它孩子为正常的骨骼 | |
仅有一个孩子的Joint 可以和孩子连接起来构成一个骨骼 | |
对于0长度的两个Joint 需要创建辅助性骨骼避免掉 长度0 | |
''' | |
BonesDic, BonesList, BoneID, TrackData = OGREBonesDic(xml_doc) | |
#BonesData = [] | |
ChildList(BonesDic) | |
HelperBones(BonesDic) | |
ZeroBones(BonesDic) | |
#创建空物体来 计算骨骼的坐标位置 | |
#保存攻击的joint 重新计算每个骨骼的位置相对变化 | |
''' | |
计算每个joint的 matrix_world 状态变化 设置对应骨骼的状态 | |
创建空的Cube 来调整每个骨骼的初始位置 | |
''' | |
CreateEmptys(BonesDic, 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") | |
''' | |
插入动画第一帧为Pose 形态 | |
''' | |
bpy.context.scene.frame_set(0) | |
bpy.ops.anim.keyframe_insert_menu(type="LocRotScale") | |
BonesData = None | |
CreatePoseAnimation(BonesDic, BonesList, BoneID, TrackData, BonesData, handler) | |
return True | |
BoneID2Name = {} | |
#create blender armature submesh multiple mesh use shared vertex or not share | |
#convert ogre joint to bones | |
#建立绑定位置的 骨骼位置信息 pose mode下面可以调整微调 | |
''' | |
创建Mesh.xml 中连接的Skeleton 骨骼文件的默认骨骼形态 | |
FIX: 清理上个模型的动画数据 | |
''' | |
def CreateBindSkeleton(xmldoc, handler): | |
if GetAmt() != None: | |
log("Armature exist just return") | |
return | |
BonesDic, BonesList, BoneID, TrackData = OGREBonesDic(xmldoc) | |
global BoneID2Name | |
BoneID2Name = BoneID | |
#BonesData = BonesDic | |
BonesData = [] | |
ChildList(BonesDic) | |
HelperBones(BonesDic) | |
ZeroBones(BonesDic) | |
CreateEmptys(BonesDic) | |
#稳定 | |
handler.restPose = BonesDic | |
log("Create New Armature") | |
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.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 | |
#设定roll值 参考Edit mode Y 轴的旋转方向 | |
#joint 相对于父亲的旋转矩阵 和偏移矩阵 | |
#matrix_world 本地 0 0 转化到世界坐标 roll值是使用世界坐标计算的 | |
#世界坐标 0 0 位置物体 移动到 目标的位置 并且旋转 平移世界物体的矩阵 | |
#计算joint沿着 Y 方向的旋转值 | |
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 | |
#从每个submesh 里面抽取出vertexGroup的数据 | |
#建立一个 vertexGroup | |
#将对应定点放到group里面 | |
#group 按照数字名称来分组 | |
#def CreateVertexGroup(name, mesh): | |
#分析完一个子mesh的时候 子物体就添加一个顶点group | |
from mathutils import Color | |
''' | |
根据OgreMeshSaxHandler 生成blender中Mesh 对象 | |
''' | |
def CreateBlenderMesh(name, mesh, materials): | |
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) != None: | |
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: | |
log("vertexGroup is read"+str(k)) | |
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 | |
''' | |
将Mesh 转化成.xml 文件 | |
''' | |
def convert_meshfile(filename): | |
log("convert file "+filename) | |
if not os.path.exists(filename): | |
return | |
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.") | |
''' | |
收集某个目录里面的 .material 文件的材质信息 | |
''' | |
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') | |
for filename in material_files: | |
f = open(filename, 'r') | |
line_number = 0 | |
material = 0 | |
matContent = '' | |
inMat = False | |
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: | |
if material != 0: | |
log("oldMat is "+material.name) | |
log("set Material Content "+matContent) | |
material.matFileCon = matContent | |
matContent = '' | |
material = Material(mat_name[0]) | |
matContent = line | |
inMat = True | |
materials[material.name] = material | |
vlog("parsing material %s" % mat_name[0]) | |
else: | |
matContent += line | |
#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.oldTexName = tex_name[0] | |
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)) | |
if material != 0: | |
log("set LastMat matContent "+material.name) | |
log("last matContent "+matContent) | |
material.matFileCon = matContent | |
log("this mat file con "+material.matFileCon) | |
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 | |
allAnimations = [] | |
skePat = re.compile("\w+\.skeleton") | |
xmlFile = None | |
materials = None | |
''' | |
为每个动画生成对应的FBX文件 | |
[email protected] | |
''' | |
def GenFbxFile(aniFile, handler, export=True): | |
log("GenFbxFile "+str(aniFile)) | |
bpy.ops.object.select_all(action="DESELECT") | |
''' | |
重新连接骨架 | |
''' | |
amt = bpy.data.objects.get("Armature") | |
if amt != None: | |
if amt != None and bpy.context.scene.objects.find("Armature") == -1: | |
bpy.context.scene.objects.link(amt) | |
bpy.context.scene.objects.active = amt | |
amt.select = True | |
''' | |
获取amt 骨架,接着根据AnimationFile 内容生成骨骼Pose信息 | |
''' | |
okFile = CreateAnimationPose(aniFile, handler) | |
''' | |
清理root 等虚拟物体 组成的骨架对象 | |
''' | |
if bpy.data.objects.get("root") != None: | |
if bpy.context.scene.objects.get('root') != None: | |
Unlink(bpy.data.objects["root"]) | |
Unlink(bpy.data.objects["Cube"]) | |
bpy.ops.object.mode_set(mode="OBJECT") | |
''' | |
清空辅助的骨骼 mesh | |
''' | |
clearScene() | |
''' | |
是否导出Fbx 并且正确读入了动画xml文件 | |
''' | |
if export and okFile: | |
global dirname | |
oldDirName = dirname | |
fbxDirName = os.path.join(dirname, "fbx") | |
matDirName = os.path.join(fbxDirName, 'mat') | |
if not os.path.exists(fbxDirName): | |
os.mkdir(fbxDirName) | |
if not os.path.exists(matDirName): | |
os.mkdir(matDirName) | |
global basename | |
log("base name is "+basename) | |
if aniFile != None: | |
aniName = str(aniFile).replace(".skeleton", "") | |
else: | |
aniName = '' | |
basename = basename.lower() | |
objName = basename.replace(".animation", "").replace(".MESH.xml", "").replace(".mesh.xml", "") | |
if aniName != '': | |
fbxName = os.path.join(fbxDirName, objName+"@"+aniName+".fbx") | |
else: | |
fbxName = os.path.join(fbxDirName, objName+".fbx") | |
bpy.ops.export_scene.fbx(filepath=fbxName, use_anim_action_all=False, bake_anim_use_nla_strips=False, bake_anim_use_all_actions=False) | |
#bpy.ops.export_scene.fbx(filepath=fbxName) | |
mesh = handler.mesh | |
submesh_count = len(mesh.submeshes) | |
for j in range(0, submesh_count): | |
submesh = mesh.submeshes[j] | |
ogre_mat = materials.get(submesh.materialname) | |
if ogre_mat != None: | |
log("save Texture file And Material %s" % ogre_mat.oldTexName) | |
newMatName = submesh.materialname.replace("/", "_")+'.mat' | |
matFile = os.path.join(matDirName, newMatName) | |
texName = ogre_mat.oldTexName.replace('dds', 'png') | |
log("") | |
p2 = os.path.join(fbxDirName, texName) | |
p1 = os.path.join(oldDirName, texName) | |
log("save MatFile: "+matFile) | |
log("save Mat Content is: "+ogre_mat.matFileCon) | |
matF = open(matFile, 'w') | |
matF.write(ogre_mat.matFileCon) | |
matF.close() | |
log(p1+" "+p2) | |
cmd = 'cp %s %s' % (p1 , p2) | |
log("copy texture file: "+cmd) | |
os.system(cmd) | |
log("import completed.") | |
''' | |
执行顺序 | |
fileselection_callback | |
GenFbxFile | |
CreateAnimationPose | |
OGREBonesDic | |
ChildList | |
HelperBones | |
ZeroBones | |
CreateEmptys | |
CreatePoseAnimation | |
''' | |
import subprocess | |
def fileselection_callback(filename, clearMesh): | |
''' | |
读取mesh 文件,如果不是xml格式则调用 ogre 工具转化 | |
''' | |
log("Reading mesh file %s..." % filename) | |
filename = os.path.expanduser(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) | |
''' | |
读取Animation文件, 如果存在则存储下animation动画列表 | |
''' | |
global allAnimations | |
aniFileName = filename.replace("MESH.xml", "animation").replace("mesh.xml", "animation").replace("Mesh.xml", "animation") | |
log("readANiFileName "+str(aniFileName)) | |
if os.path.exists(aniFileName): | |
proc = subprocess.Popen(["file", aniFileName], stdout=subprocess.PIPE) | |
(out, err) = proc.communicate() | |
out = out.decode('utf8') | |
if out.find("UTF-16") != -1: | |
aniCon = codecs.open(aniFileName, encoding='utf16', mode='rb').read() | |
else: | |
aniCon = codecs.open(aniFileName, mode='rb').read() | |
allAnimations = skePat.findall(aniCon) | |
else: | |
#No Animation File | |
allAnimations = [] | |
log("aniFileName "+aniFileName) | |
log("Gather all ANimation "+str(allAnimations)) | |
''' | |
读取当前目录的材质文件,收集所有的材质信息 | |
''' | |
# parse material files and make up a dictionary: {mat_name:material, ..} | |
global materials | |
materials = collect_materials(dirname) | |
#create materials | |
# prepare the SAX parser and parse the file using our own content handler | |
''' | |
读取Mesh.xml 文件, 将模型信息存储到OgreMeshSaxHandler 中 | |
''' | |
xmlFile = open(filename, 'r') | |
#解析Mesh 和 Bone文件 | |
parser = xml.sax.make_parser() | |
handler = OgreMeshSaxHandler() | |
parser.setContentHandler(handler) | |
parser.parse(xmlFile) | |
# create the mesh from the parsed data and link it to a fresh object | |
meshname = basename[0:basename.lower().find('.mesh.xml')] | |
#生成骨骼 | |
''' | |
根据OgreMeshSaxHandler 如果Mesh文件中存在骨骼,则创建一个骨骼对象 | |
''' | |
CreateSkeleton(handler.skeleton, handler) | |
#生成蒙皮 和 顶点组 | |
''' | |
根据OgreMeshSaxHandler 构建蒙皮 Mesh对象 设置材质等信息 | |
''' | |
obj = CreateBlenderMesh(meshname, handler.mesh, materials) | |
bpy.context.scene.update() | |
''' | |
获得骨骼对象,如果allAnimations >0 则表示需要生成骨骼动画调用GenFbxFile 生成动画 | |
否则值生成普通的仅包含Mesh的fbx 文件 | |
''' | |
amt = GetAmt() | |
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") | |
if handler.animationlink != 0: | |
GenFbxFile(handler.animationlink, handler, False) | |
elif len(allAnimations) == 0: | |
GenFbxFile(None, handler) | |
else: | |
for a in allAnimations: | |
GenFbxFile(a, handler) | |
log("Clear Animation Frame "+a) | |
#测试 导入mage fidget 动画 | |
#break | |
#2.74 bug 调用这个会导致创建一份新的Action | |
bpy.ops.anim.keyframe_clear_v3d() | |
#bpy.context.scene.objects.active.animation_data_clear() | |
#如何只导出当前使用的AnimationData 里面的actin而不是所有的action呢? | |
#bpy.ops.poselib.unlink() | |
#测试一个动画的生成 | |
#break | |
#clear all scene Object | |
if clearMesh: | |
clearAll() | |
import random | |
oldId = 0 | |
rand = random.Random() | |
def GetAmt(): | |
amt = bpy.data.objects.get("Armature") | |
#这个骨骼已经被删除了 是上一个模型的 | |
if bpy.context.scene.objects.find('Armature') == -1: | |
if amt != None: | |
global rand | |
global oldId | |
amt.name = "Armature_"+str(oldId)+"_"+str(rand.randint(1, 10000)) | |
oldId += 1 | |
return None | |
return amt | |
''' | |
清理场景中所有的 虚拟骨骼对象 | |
不要清除掉Mesh 对象 | |
''' | |
def clearScene(): | |
#global toggle | |
scn = bpy.context.scene | |
log("clearScene ") | |
if bpy.context.object != None: | |
bpy.ops.object.mode_set(mode="OBJECT") | |
for ob in scn.objects: | |
if ob.type in ["MESH", "CURVE", "TEXT"]: | |
if ob.name.find('obj') == -1:#don't delete obj mesh | |
scn.objects.active = ob | |
scn.objects.unlink(ob) | |
del ob | |
return scn | |
''' | |
连同模型Mesh 一起清理,为导入新的模型做准备 | |
连通Armature 一起清理 为下一个动画模型做准备 | |
''' | |
def clearAll(): | |
scn = bpy.context.scene | |
log("clear ALl Scene ") | |
if bpy.context.object != None: | |
bpy.ops.object.mode_set(mode="OBJECT") | |
for ob in scn.objects: | |
#if ob.type in ["MESH", "CURVE", "TEXT"]: | |
#if ob.name.find('obj') == -1:#don't delete obj mesh | |
scn.objects.active = ob | |
scn.objects.unlink(ob) | |
del ob | |
return scn | |
from bpy.props import * | |
from bpy_extras.io_utils import (ExportHelper, | |
path_reference_mode, | |
axis_conversion, | |
) | |
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', | |
) | |
clearMesh = BoolProperty( | |
default=False, | |
) | |
def draw(self, context): | |
layout0 = self.layout | |
layout = layout0.box() | |
col = layout.column() | |
def execute(self, context): | |
fileselection_callback(self.filepath, self.clearMesh) | |
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
支持blender 版本2.7.4 导出fbx bin 数据时候 动画 action只保留一个