From 924bae1667aea0f57dd3de11b1248c12bc9a95d1 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Sat, 15 Oct 2011 10:18:33 +0900 Subject: [PATCH] refactoring export_pmd --- blender26-meshio/bl.py | 1 + blender26-meshio/export_pmd.py | 1398 ++++++++------------------------------- blender26-meshio/export_pmx.py | 12 + blender26-meshio/oneskinmesh.py | 801 ++++++++++++++++++++++ pymeshio/pmd/__init__.py | 2 +- 5 files changed, 1105 insertions(+), 1109 deletions(-) create mode 100644 blender26-meshio/oneskinmesh.py diff --git a/blender26-meshio/bl.py b/blender26-meshio/bl.py index c668fa0..17f7bac 100644 --- a/blender26-meshio/bl.py +++ b/blender26-meshio/bl.py @@ -315,6 +315,7 @@ class texture: try: image=bpy.data.images.load(path) except RuntimeError: + print('fail to create:', path) image=bpy.data.images.new('Image', width=16, height=16) texture.image=image return texture, image diff --git a/blender26-meshio/export_pmd.py b/blender26-meshio/export_pmd.py index dd63db6..9c808da 100644 --- a/blender26-meshio/export_pmd.py +++ b/blender26-meshio/export_pmd.py @@ -32,1130 +32,311 @@ This script exports a pmd model. 20110522: implement RigidBody and Constraint. 20111002: update for pymeshio-2.1.0 """ - -bl_addon_info = { - 'category': 'Import/Export', - 'name': 'Export: MikuMikuDance Model Format (.pmd)', - 'author': 'ousttrue', - 'version': (2, 2), - 'blender': (2, 5, 3), - 'location': 'File > Export', - 'description': 'Export to the MikuMikuDance Model Format (.pmd)', - 'warning': '', # used for warning icon and text in addons panel - 'wiki_url': 'http://sourceforge.jp/projects/meshio/wiki/FrontPage', - 'tracker_url': 'http://sourceforge.jp/ticket/newticket.php?group_id=5081', - } - - -############################################################################### -# import -############################################################################### -import os -import sys import io - -from .pymeshio import englishmap +from . import bl +from . import oneskinmesh from .pymeshio import common from .pymeshio import pmd +from .pymeshio import englishmap from .pymeshio.pmd import writer -# for 2.5 -import bpy -import mathutils - -# wrapper -from . import bl - -xrange=range - -def toCP932(s): - return s.encode('cp932') - - -class Node(object): - __slots__=['o', 'children'] - def __init__(self, o): - self.o=o - self.children=[] - - -############################################################################### -# Blenderのメッシュをワンスキンメッシュ化する -############################################################################### def near(x, y, EPSILON=1e-5): d=x-y return d>=-EPSILON and d<=EPSILON -class VertexAttribute(object): - __slots__=[ - 'nx', 'ny', 'nz', # normal - 'u', 'v', # uv - ] - def __init__(self, nx, ny, nz, u, v): - self.nx=nx - self.ny=ny - self.nz=nz - self.u=u - self.v=v - - def __str__(self): - return "" % ( - self.nx, self.ny, self.nz, self.u, self.v) - - def __hash__(self): - return int(100*(self.nx + self.ny + self.nz + self.u + self.v)) - - def __eq__(self, rhs): - return self.nx==rhs.nx and self.ny==rhs.ny and self.nz==rhs.nz and self.u==rhs.u and self.v==rhs.v - - -class VertexKey(object): - __slots__=[ - 'obj_index', 'index', - ] - - def __init__(self, obj_index, index): - self.obj_index=obj_index - self.index=index - - def __str__(self): - return "" % (self.obj_index, self.index) - - def __hash__(self): - return self.index*100+self.obj_index - - def __eq__(self, rhs): - return self.obj_index==rhs.obj_index and self.index==rhs.index - - -class VertexArray(object): - """ - 頂点配列 - """ - __slots__=[ - 'indexArrays', - 'positions', - 'attributes', # normal and uv - 'b0', 'b1', 'weight', - 'vertexMap', - 'objectMap', - ] - def __init__(self): - # indexArrays split with each material - self.indexArrays={} - - self.positions=[] - self.attributes=[] - self.b0=[] - self.b1=[] - self.weight=[] - - self.vertexMap={} - self.objectMap={} - - def __str__(self): - return "" % ( - len(self.positions), len(self.indexArrays)) - - def zip(self): - return zip( - self.positions, self.attributes, - self.b0, self.b1, self.weight) - - def each(self): - keys=[key for key in self.indexArrays.keys()] - keys.sort() - for key in keys: - yield(key, self.indexArrays[key]) - - def __addOrGetIndex(self, obj_index, base_index, pos, normal, uv, b0, b1, weight0): - key=VertexKey(obj_index, base_index) - attribute=VertexAttribute( - normal[0], normal[1], normal[2], - uv[0], uv[1]) - if key in self.vertexMap: - if attribute in self.vertexMap[key]: - return self.vertexMap[key][attribute] - else: - return self.__addVertex(self.vertexMap[key], - pos, attribute, b0, b1, weight0) - else: - vertexMapKey={} - self.vertexMap[key]=vertexMapKey - return self.__addVertex(vertexMapKey, - pos, attribute, b0, b1, weight0) - - def __addVertex(self, vertexMapKey, pos, attribute, b0, b1, weight0): - index=len(self.positions) - vertexMapKey[attribute]=index - # position - self.positions.append((pos.x, pos.y, pos.z)) - # unique attribute - self.attributes.append(attribute) - # shared attribute - self.b0.append(b0) - self.b1.append(b1) - self.weight.append(weight0) - assert(index<=65535) - return index - - def getMappedIndex(self, obj_name, base_index): - return self.vertexMap[VertexKey(self.objectMap[obj_name], base_index)] - - def addTriangle(self, - object_name, material, - base_index0, base_index1, base_index2, - pos0, pos1, pos2, - n0, n1, n2, - uv0, uv1, uv2, - b0_0, b0_1, b0_2, - b1_0, b1_1, b1_2, - weight0, weight1, weight2 - ): - if object_name in self.objectMap: - obj_index=self.objectMap[object_name] - else: - obj_index=len(self.objectMap) - self.objectMap[object_name]=obj_index - index0=self.__addOrGetIndex(obj_index, base_index0, pos0, n0, uv0, b0_0, b1_0, weight0) - index1=self.__addOrGetIndex(obj_index, base_index1, pos1, n1, uv1, b0_1, b1_1, weight1) - index2=self.__addOrGetIndex(obj_index, base_index2, pos2, n2, uv2, b0_2, b1_2, weight2) - - if not material in self.indexArrays: - self.indexArrays[material]=[] - self.indexArrays[material]+=[index0, index1, index2] - - -class Morph(object): - __slots__=['name', 'type', 'offsets'] - def __init__(self, name, type): - self.name=name - self.type=type - self.offsets=[] - - def add(self, index, offset): - self.offsets.append((index, offset)) - - def sort(self): - self.offsets.sort(key=lambda e: e[0]) - - def __str__(self): - return "" % self.name - -class IKSolver(object): - __slots__=['target', 'effector', 'length', 'iterations', 'weight'] - def __init__(self, target, effector, length, iterations, weight): - self.target=target - self.effector=effector - self.length=length - self.iterations=iterations - self.weight=weight - - -class SSS(object): - def __init__(self): - self.use=1 - - -class DefaultMatrial(object): - def __init__(self): - self.name='default' - # diffuse - self.diffuse_color=[1, 1, 1] - self.alpha=1 - # specular - self.specular_toon_size=0 - self.specular_hardness=5 - self.specular_color=[1, 1, 1] - # ambient - self.mirror_color=[1, 1, 1] - # flag - self.subsurface_scattering=SSS() - # texture - self.texture_slots=[] - - -class OneSkinMesh(object): - __slots__=['vertexArray', 'morphList', 'rigidbodies', 'constraints', ] - def __init__(self): - self.vertexArray=VertexArray() - self.morphList=[] - self.rigidbodies=[] - self.constraints=[] - - def __str__(self): - return "" % ( - self.vertexArray, - len(self.morphList)) - - def addMesh(self, obj): - if not bl.object.isVisible(obj): - return - self.__mesh(obj) - self.__skin(obj) - self.__rigidbody(obj) - self.__constraint(obj) - - def __getWeightMap(self, obj, mesh): - # bone weight - weightMap={} - secondWeightMap={} - def setWeight(i, name, w): - if w>0: - if i in weightMap: - if i in secondWeightMap: - # 上位2つのweightを採用する - if wweightMap[i][1]: - # 多い方をweightMapに - secondWeightMap[i]=weightMap[i] - weightMap[i]=(name, w) - else: - secondWeightMap[i]=(name, w) - else: - weightMap[i]=(name, w) - - # ToDo bone weightと関係ないvertex groupを除外する - for i, v in enumerate(mesh.vertices): - if len(v.groups)>0: - for g in v.groups: - setWeight(i, obj.vertex_groups[g.group].name, g.weight) - else: - try: - setWeight(i, obj.vertex_groups[0].name, 1) - except: - # no vertex_groups - pass - - # 合計値が1になるようにする - for i in xrange(len(mesh.vertices)): - if i in secondWeightMap: - secondWeightMap[i]=(secondWeightMap[i][0], 1.0-weightMap[i][1]) - elif i in weightMap: - weightMap[i]=(weightMap[i][0], 1.0) - secondWeightMap[i]=("", 0) - else: - print("no weight vertex") - weightMap[i]=("", 0) - secondWeightMap[i]=("", 0) - - return weightMap, secondWeightMap - - def __processFaces(self, obj_name, mesh, weightMap, secondWeightMap): - default_material=DefaultMatrial() - # 各面の処理 - for i, face in enumerate(mesh.faces): - faceVertexCount=bl.face.getVertexCount(face) - try: - material=mesh.materials[bl.face.getMaterialIndex(face)] - except IndexError as e: - material=default_material - v=[mesh.vertices[index] for index in bl.face.getVertices(face)] - uv=bl.mesh.getFaceUV( - mesh, i, face, bl.face.getVertexCount(face)) - # flip triangle - if faceVertexCount==3: - # triangle - self.vertexArray.addTriangle( - obj_name, material.name, - v[2].index, - v[1].index, - v[0].index, - v[2].co, - v[1].co, - v[0].co, - bl.vertex.getNormal(v[2]), - bl.vertex.getNormal(v[1]), - bl.vertex.getNormal(v[0]), - uv[2], - uv[1], - uv[0], - weightMap[v[2].index][0], - weightMap[v[1].index][0], - weightMap[v[0].index][0], - secondWeightMap[v[2].index][0], - secondWeightMap[v[1].index][0], - secondWeightMap[v[0].index][0], - weightMap[v[2].index][1], - weightMap[v[1].index][1], - weightMap[v[0].index][1] - ) - elif faceVertexCount==4: - # quadrangle - self.vertexArray.addTriangle( - obj_name, material.name, - v[2].index, - v[1].index, - v[0].index, - v[2].co, - v[1].co, - v[0].co, - bl.vertex.getNormal(v[2]), - bl.vertex.getNormal(v[1]), - bl.vertex.getNormal(v[0]), - uv[2], - uv[1], - uv[0], - weightMap[v[2].index][0], - weightMap[v[1].index][0], - weightMap[v[0].index][0], - secondWeightMap[v[2].index][0], - secondWeightMap[v[1].index][0], - secondWeightMap[v[0].index][0], - weightMap[v[2].index][1], - weightMap[v[1].index][1], - weightMap[v[0].index][1] - ) - self.vertexArray.addTriangle( - obj_name, material.name, - v[0].index, - v[3].index, - v[2].index, - v[0].co, - v[3].co, - v[2].co, - bl.vertex.getNormal(v[0]), - bl.vertex.getNormal(v[3]), - bl.vertex.getNormal(v[2]), - uv[0], - uv[3], - uv[2], - weightMap[v[0].index][0], - weightMap[v[3].index][0], - weightMap[v[2].index][0], - secondWeightMap[v[0].index][0], - secondWeightMap[v[3].index][0], - secondWeightMap[v[2].index][0], - weightMap[v[0].index][1], - weightMap[v[3].index][1], - weightMap[v[2].index][1] - ) - - def __mesh(self, obj): - if bl.RIGID_SHAPE_TYPE in obj: - return - if bl.CONSTRAINT_A in obj: - return - - bl.message("export: %s" % obj.name) - - # メッシュのコピーを生成してオブジェクトの行列を適用する - copyMesh, copyObj=bl.object.duplicate(obj) - if len(copyMesh.vertices)>0: - # apply transform - """ - try: - # svn 36722 - copyObj.scale=obj.scale - bpy.ops.object.transform_apply(scale=True) - copyObj.rotation_euler=obj.rotation_euler - bpy.ops.object.transform_apply(rotation=True) - copyObj.location=obj.location - bpy.ops.object.transform_apply(location=True) - except AttributeError as e: - # 2.57b - copyObj.scale=obj.scale - bpy.ops.object.scale_apply() - copyObj.rotation_euler=obj.rotation_euler - bpy.ops.object.rotation_apply() - copyObj.location=obj.location - bpy.ops.object.location_apply() - """ - copyMesh.transform(obj.matrix_world) - - # apply modifier - for m in [m for m in copyObj.modifiers]: - if m.type=='SOLIDFY': - continue - elif m.type=='ARMATURE': - continue - elif m.type=='MIRROR': - bpy.ops.object.modifier_apply(modifier=m.name) - else: - print(m.type) - - weightMap, secondWeightMap=self.__getWeightMap(copyObj, copyMesh) - self.__processFaces(obj.name, copyMesh, weightMap, secondWeightMap) - bl.object.delete(copyObj) - - def createEmptyBasicSkin(self): - self.__getOrCreateMorph('base', 0) - - def __skin(self, obj): - if not bl.object.hasShapeKey(obj): - return - - indexRelativeMap={} - blenderMesh=bl.object.getData(obj) - baseMorph=None - - # shape keys - vg=bl.object.getVertexGroup(obj, bl.MMD_SHAPE_GROUP_NAME) - - # base - used=set() - for b in bl.object.getShapeKeys(obj): - if b.name==bl.BASE_SHAPE_NAME: - baseMorph=self.__getOrCreateMorph('base', 0) - basis=b - - relativeIndex=0 - for index in vg: - v=bl.shapekey.getByIndex(b, index) - pos=[v[0], v[1], v[2]] - - indices=self.vertexArray.getMappedIndex(obj.name, index) - for attribute, i in indices.items(): - if i in used: - continue - used.add(i) - - baseMorph.add(i, pos) - indexRelativeMap[i]=relativeIndex - relativeIndex+=1 - - break - assert(basis) - #print(basis.name, len(baseMorph.offsets)) - - if len(baseMorph.offsets)==0: - return - - # shape keys - for b in bl.object.getShapeKeys(obj): - if b.name==bl.BASE_SHAPE_NAME: - continue - - #print(b.name) - morph=self.__getOrCreateMorph(b.name, 4) - used=set() - for index, src, dst in zip( - xrange(len(blenderMesh.vertices)), - bl.shapekey.get(basis), - bl.shapekey.get(b)): - offset=[dst[0]-src[0], dst[1]-src[1], dst[2]-src[2]] - if offset[0]==0 and offset[1]==0 and offset[2]==0: - continue - if index in vg: - indices=self.vertexArray.getMappedIndex(obj.name, index) - for attribute, i in indices.items(): - if i in used: - continue - used.add(i) - morph.add(indexRelativeMap[i], offset) - assert(len(morph.offsets)" % (self.name, self.type) - -class BoneBuilder(object): - __slots__=['bones', 'boneMap', 'ik_list', 'bone_groups',] - def __init__(self): - self.bones=[] - self.boneMap={} - self.ik_list=[] - self.bone_groups=[] - - def getBoneGroup(self, bone): - for i, g in enumerate(self.bone_groups): - for b in g[1]: - if b==bone.name: - return i+1 - print('no gorup', bone) - return 0 - - def build(self, armatureObj): - if not armatureObj: - return - - bl.message("build skeleton") - armature=bl.object.getData(armatureObj) - - #################### - # bone group - #################### - for g in bl.object.boneGroups(armatureObj): - self.bone_groups.append((g.name, [])) - - #################### - # get bones - #################### - for b in armature.bones.values(): - if not b.parent: - # root bone - bone=Bone(b.name, - bl.bone.getHeadLocal(b), - bl.bone.getTailLocal(b), - False) - self.__addBone(bone) - self.__getBone(bone, b) - - for b in armature.bones.values(): - if not b.parent: - self.__checkConnection(b, None) - - #################### - # get IK - #################### - pose = bl.object.getPose(armatureObj) - for b in pose.bones.values(): - #################### - # assing bone group - #################### - self.__assignBoneGroup(b, b.bone_group) - for c in b.constraints: - if bl.constraint.isIKSolver(c): - #################### - # IK target - #################### - target=self.__boneByName(bl.constraint.ikTarget(c)) - target.type=2 - - #################### - # IK effector - #################### - # IK 接続先 - link=self.__boneByName(b.name) - link.type=6 - - # IK chain - e=b.parent - chainLength=bl.constraint.ikChainLen(c) - for i in range(chainLength): - # IK影響下 - chainBone=self.__boneByName(e.name) - chainBone.type=4 - chainBone.ik_index=target.index - e=e.parent - self.ik_list.append( - IKSolver(target, link, chainLength, - int(bl.constraint.ikItration(c) * 0.1), - bl.constraint.ikRotationWeight(c) - )) - - #################### - - # boneのsort - self._sortBy() - self._fix() - # IKのsort - def getIndex(ik): - for i, v in enumerate(englishmap.boneMap): - if v[0]==ik.target.name: - return i - return len(englishmap.boneMap) - self.ik_list.sort(key=getIndex) - - def __assignBoneGroup(self, poseBone, boneGroup): - if boneGroup: - for g in self.bone_groups: - if g[0]==boneGroup.name: - g[1].append(poseBone.name) - - def __checkConnection(self, b, p): - if bl.bone.isConnected(b): - parent=self.__boneByName(p.name) - parent.isConnect=True - - for c in b.children: - self.__checkConnection(c, b) - - def _sortBy(self): - """ - boneMap順に並べ替える - """ - boneMap=englishmap.boneMap - original=self.bones[:] - def getIndex(bone): - for i, k_v in enumerate(boneMap): - if k_v[0]==bone.name: - return i - print(bone) - return len(boneMap) - - self.bones.sort(key=getIndex) +def toCP932(s): + return s.encode('cp932') - sortMap={} - for i, b in enumerate(self.bones): - src=original.index(b) - sortMap[src]=i - for b in self.bones: - b.index=sortMap[b.index] - if b.parent_index: - b.parent_index=sortMap[b.parent_index] - if b.tail_index: - b.tail_index=sortMap[b.tail_index] - if b.ik_index>0: - b.ik_index=sortMap[b.ik_index] - def _fix(self): - """ - 調整 - """ - for b in self.bones: - # parent index - if b.parent_index==None: - b.parent_index=0xFFFF +def write(self, path): + model=pmd.Model(1.0) + model.name=self.name.encode('cp932') + model.comment=self.comment.encode('cp932') + + # 頂点 + model.vertices=[pmd.Vertex( + # convert right-handed z-up to left-handed y-up + common.Vector3(pos[0], pos[2], pos[1]), + # convert right-handed z-up to left-handed y-up + common.Vector3(attribute.nx, attribute.nz, attribute.ny), + # reverse vertical + common.Vector2(attribute.u, 1.0-attribute.v), + self.skeleton.indexByName(b0), + self.skeleton.indexByName(b1), + int(100*weight), + # edge flag, 0: enable edge, 1: not edge + 0 + ) + for pos, attribute, b0, b1, weight in self.oneSkinMesh.vertexArray.zip()] + + # 面とマテリアル + vertexCount=self.oneSkinMesh.getVertexCount() + for material_name, indices in self.oneSkinMesh.vertexArray.each(): + #print('material:', material_name) + try: + m=bl.material.get(material_name) + except KeyError as e: + m=DefaultMatrial() + def get_texture_name(texture): + pos=texture.replace("\\", "/").rfind("/") + if pos==-1: + return texture else: - if b.type==6 or b.type==7: - # fix tail bone - parent=self.bones[b.parent_index] - #print('parnet', parent.name) - parent.tail_index=b.index - - for b in self.bones: - if b.tail_index==None: - b.tail_index=0 - elif b.type==9: - b.tail_index==0 - - def getIndex(self, bone): - for i, b in enumerate(self.bones): - if b==bone: - return i - assert(false) - - def indexByName(self, name): - if name=='': - return 0 + return texture[pos+1:] + textures=[get_texture_name(path) + for path in bl.material.eachEnalbeTexturePath(m)] + print(textures) + # マテリアル + model.materials.append(pmd.Material( + # diffuse_color + common.RGB(m.diffuse_color[0], m.diffuse_color[1], m.diffuse_color[2]), + m.alpha, + # specular_factor + 0 if m.specular_toon_size<1e-5 else m.specular_hardness*10, + # specular_color + common.RGB(m.specular_color[0], m.specular_color[1], m.specular_color[2]), + # ambient_color + common.RGB(m.mirror_color[0], m.mirror_color[1], m.mirror_color[2]), + # flag + 1 if m.subsurface_scattering.use else 0, + # toon + 0, + # vertex_count + len(indices), + # texture + ('*'.join(textures) if len(textures)>0 else "").encode('cp932') + )) + # 面 + for i in indices: + assert(i=20: + print(bone_english_name) + #assert(len(bone_english_name)<20) + bone.english_name=bone_english_name + + if len(v)>=3: + # has type + if v[2]==5: + b.ik_index=self.skeleton.indexByName('eyes') + bone.type=v[2] else: - try: - return self.getIndex(self.__boneByName(name)) - except: - return 0 - - def __boneByName(self, name): - return self.boneMap[name] - - def __getBone(self, parent, b): - if len(b.children)==0: - parent.type=7 - return - - for i, c in enumerate(b.children): - bone=Bone(c.name, - bl.bone.getHeadLocal(c), - bl.bone.getTailLocal(c), - bl.bone.isConnected(c)) - self.__addBone(bone) - if parent: - bone.parent_index=parent.index - #if i==0: - if bone.isConnect or (not parent.tail_index and parent.tail==bone.pos): - parent.tail_index=bone.index - self.__getBone(bone, c) - - def __addBone(self, bone): - bone.index=len(self.bones) - self.bones.append(bone) - self.boneMap[bone.name]=bone - - -class PmdExporter(object): - - __slots__=[ - 'armatureObj', - 'oneSkinMesh', - 'englishName', - 'englishComment', - 'name', - 'comment', - 'skeleton', - ] - def setup(self): - self.armatureObj=None - - # 木構造を構築する - object_node_map={} - for o in bl.object.each(): - object_node_map[o]=Node(o) - for o in bl.object.each(): - node=object_node_map[o] - if node.o.parent: - object_node_map[node.o.parent].children.append(node) - - # ルートを得る - root=object_node_map[bl.object.getActive()] - o=root.o - self.englishName=o.name - self.englishComment=o[bl.MMD_COMMENT] if bl.MMD_COMMENT in o else 'blender export\n' - self.name=o[bl.MMD_MB_NAME] if bl.MMD_MB_NAME in o else 'Blenderエクスポート' - self.comment=o[bl.MMD_MB_COMMENT] if bl.MMD_MB_COMMENT in o else 'Blnderエクスポート\n' - - # ワンスキンメッシュを作る - self.oneSkinMesh=OneSkinMesh() - self.__createOneSkinMesh(root) - bl.message(self.oneSkinMesh) - if len(self.oneSkinMesh.morphList)==0: - # create emtpy skin - self.oneSkinMesh.createEmptyBasicSkin() - - # skeleton - self.skeleton=BoneBuilder() - self.skeleton.build(self.armatureObj) - - def __createOneSkinMesh(self, node): - ############################################################ - # search armature modifier - ############################################################ - for m in node.o.modifiers: - if bl.modifier.isType(m, 'ARMATURE'): - armatureObj=bl.modifier.getArmatureObject(m) - if not self.armatureObj: - self.armatureObj=armatureObj - elif self.armatureObj!=armatureObj: - print("warning! found multiple armature. ignored.", - armatureObj.name) - - if node.o.type.upper()=='MESH': - self.oneSkinMesh.addMesh(node.o) - - for child in node.children: - self.__createOneSkinMesh(child) - - def write(self, path): - model=pmd.Model(1.0) - model.name=self.name.encode('cp932') - model.comment=self.comment.encode('cp932') - - # 頂点 - model.vertices=[pmd.Vertex( - # convert right-handed z-up to left-handed y-up - common.Vector3(pos[0], pos[2], pos[1]), + bone.type=b.type + + bone.parent_index=b.parent_index + bone.tail_index=b.tail_index + bone.ik_index=b.ik_index + + # convert right-handed z-up to left-handed y-up + bone.pos.x=b.pos[0] if not near(b.pos[0], 0) else 0 + bone.pos.y=b.pos[2] if not near(b.pos[2], 0) else 0 + bone.pos.z=b.pos[1] if not near(b.pos[1], 0) else 0 + + model.bones.append(bone) + + # IK + for ik in self.skeleton.ik_list: + solver=pmd.IK() + solver.index=self.skeleton.getIndex(ik.target) + solver.target=self.skeleton.getIndex(ik.effector) + solver.length=ik.length + b=self.skeleton.bones[ik.effector.parent_index] + for i in range(solver.length): + solver.children.append(self.skeleton.getIndex(b)) + b=self.skeleton.bones[b.parent_index] + solver.iterations=ik.iterations + solver.weight=ik.weight + model.ik_list.append(solver) + + # 表情 + for i, m in enumerate(self.oneSkinMesh.morphList): + v=englishmap.getUnicodeSkinName(m.name) + if not v: + v=[m.name, m.name, 0] + assert(v) + # morph + morph=pmd.Morph(v[1].encode("cp932")) + morph.english_name=m.name.encode("cp932") + m.type=v[2] + morph.type=v[2] + for index, offset in m.offsets: # convert right-handed z-up to left-handed y-up - common.Vector3(attribute.nx, attribute.nz, attribute.ny), - # reverse vertical - common.Vector2(attribute.u, 1.0-attribute.v), - self.skeleton.indexByName(b0), - self.skeleton.indexByName(b1), - int(100*weight), - # edge flag, 0: enable edge, 1: not edge - 0 - ) - for pos, attribute, b0, b1, weight in self.oneSkinMesh.vertexArray.zip()] - - # 面とマテリアル - vertexCount=self.oneSkinMesh.getVertexCount() - for material_name, indices in self.oneSkinMesh.vertexArray.each(): - #print('material:', material_name) - try: - m=bl.material.get(material_name) - except KeyError as e: - m=DefaultMatrial() - def get_texture_name(texture): - pos=texture.replace("\\", "/").rfind("/") - if pos==-1: - return texture - else: - return texture[pos+1:] - textures=[get_texture_name(path) - for path in bl.material.eachEnalbeTexturePath(m)] - print(textures) - # マテリアル - model.materials.append(pmd.Material( - # diffuse_color - common.RGB(m.diffuse_color[0], m.diffuse_color[1], m.diffuse_color[2]), - m.alpha, - # specular_factor - 0 if m.specular_toon_size<1e-5 else m.specular_hardness*10, - # specular_color - common.RGB(m.specular_color[0], m.specular_color[1], m.specular_color[2]), - # ambient_color - common.RGB(m.mirror_color[0], m.mirror_color[1], m.mirror_color[2]), - # flag - 1 if m.subsurface_scattering.use else 0, - # toon - 0, - # vertex_count - len(indices), - # texture - ('*'.join(textures) if len(textures)>0 else "").encode('cp932') - )) - # 面 - for i in indices: - assert(i=20: - print(bone_english_name) - #assert(len(bone_english_name)<20) - bone.english_name=bone_english_name - - if len(v)>=3: - # has type - if v[2]==5: - b.ik_index=self.skeleton.indexByName('eyes') - bone.type=v[2] + morph.append(index, offset[0], offset[2], offset[1]) + morph.vertex_count=len(m.offsets) + + # 表情枠 + # type==0はbase + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==3: + model.morph_indices.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==2: + model.morph_indices.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==1: + model.morph_indices.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==4: + model.morph_indices.append(i) + + # ボーングループ + for g in self.skeleton.bone_groups: + name=englishmap.getUnicodeBoneGroupName(g[0]) + if not name: + name=g[0] + englishName=g[0] + + model.bone_group_list.append(pmd.BoneGroup( + (name+'\n').encode('cp932'), + (englishName+'\n').encode('cp932') + )) + + # ボーングループメンバー + for i, b in enumerate(self.skeleton.bones): + if i==0: + continue + if b.type in [6, 7]: + continue + model.bone_display_list.append((i, self.skeleton.getBoneGroup(b))) + + # English + model.english_name=self.englishName.encode('cp932') + model.english_comment=self.englishComment.encode('cp932') + + # toon + toonMeshObject=None + for o in bl.object.each(): + try: + if o.name.startswith(bl.TOON_TEXTURE_OBJECT): + toonMeshObject=o + except: + p(o.name) + break + if toonMeshObject: + toonMesh=bl.object.getData(toonMeshObject) + toonMaterial=bl.mesh.getMaterial(toonMesh, 0) + for i in range(10): + t=bl.material.getTexture(toonMaterial, i) + if t: + model.toon_textures[i]=("%s" % t.name).encode('cp932') else: - bone.type=b.type - - bone.parent_index=b.parent_index - bone.tail_index=b.tail_index - bone.ik_index=b.ik_index - - # convert right-handed z-up to left-handed y-up - bone.pos.x=b.pos[0] if not near(b.pos[0], 0) else 0 - bone.pos.y=b.pos[2] if not near(b.pos[2], 0) else 0 - bone.pos.z=b.pos[1] if not near(b.pos[1], 0) else 0 - - model.bones.append(bone) - - # IK - for ik in self.skeleton.ik_list: - solver=pmd.IK() - solver.index=self.skeleton.getIndex(ik.target) - solver.target=self.skeleton.getIndex(ik.effector) - solver.length=ik.length - b=self.skeleton.bones[ik.effector.parent_index] - for i in xrange(solver.length): - solver.children.append(self.skeleton.getIndex(b)) - b=self.skeleton.bones[b.parent_index] - solver.iterations=ik.iterations - solver.weight=ik.weight - model.ik_list.append(solver) - - # 表情 - for i, m in enumerate(self.oneSkinMesh.morphList): - v=englishmap.getUnicodeSkinName(m.name) - if not v: - v=[m.name, m.name, 0] - assert(v) - # morph - morph=pmd.Morph(v[1].encode("cp932")) - morph.english_name=m.name.encode("cp932") - m.type=v[2] - morph.type=v[2] - for index, offset in m.offsets: - # convert right-handed z-up to left-handed y-up - morph.append(index, offset[0], offset[2], offset[1]) - morph.vertex_count=len(m.offsets) - - # 表情枠 - # type==0はbase - for i, m in enumerate(self.oneSkinMesh.morphList): - if m.type==3: - model.morph_indices.append(i) - for i, m in enumerate(self.oneSkinMesh.morphList): - if m.type==2: - model.morph_indices.append(i) - for i, m in enumerate(self.oneSkinMesh.morphList): - if m.type==1: - model.morph_indices.append(i) - for i, m in enumerate(self.oneSkinMesh.morphList): - if m.type==4: - model.morph_indices.append(i) - - # ボーングループ - for g in self.skeleton.bone_groups: - name=englishmap.getUnicodeBoneGroupName(g[0]) - if not name: - name=g[0] - englishName=g[0] - - model.bone_group_list.append(pmd.BoneGroup( - (name+'\n').encode('cp932'), - (englishName+'\n').encode('cp932') - )) - - # ボーングループメンバー - for i, b in enumerate(self.skeleton.bones): - if i==0: - continue - if b.type in [6, 7]: - continue - model.bone_display_list.append((i, self.skeleton.getBoneGroup(b))) - - # English - model.english_name=self.englishName.encode('cp932') - model.english_comment=self.englishComment.encode('cp932') - - # toon - toonMeshObject=None - for o in bl.object.each(): - try: - if o.name.startswith(bl.TOON_TEXTURE_OBJECT): - toonMeshObject=o - except: - p(o.name) - break - if toonMeshObject: - toonMesh=bl.object.getData(toonMeshObject) - toonMaterial=bl.mesh.getMaterial(toonMesh, 0) - for i in range(10): - t=bl.material.getTexture(toonMaterial, i) - if t: - model.toon_textures[i]=("%s" % t.name).encode('cp932') - else: - model.toon_textures[i]=("toon%02d.bmp" % (i+1)).encode('cp932') - else: - for i in range(10): model.toon_textures[i]=("toon%02d.bmp" % (i+1)).encode('cp932') - - # rigid body - rigidNameMap={} - for i, obj in enumerate(self.oneSkinMesh.rigidbodies): - name=obj[bl.RIGID_NAME] if bl.RIGID_NAME in obj else obj.name - print(name) - rigidNameMap[name]=i - boneIndex=boneNameMap[obj[bl.RIGID_BONE_NAME]] - if boneIndex==0: - boneIndex=0xFFFF - bone=self.skeleton.bones[0] - else: - bone=self.skeleton.bones[boneIndex] - rigidBody=pmd.RigidBody( - name.encode('cp932'), - boneIndex, - shape_position=common.Vector3( - obj.location.x-bone.pos[0], - obj.location.z-bone.pos[2], - obj.location.y-bone.pos[1]), - shape_rotation=common.Vector3( - -obj.rotation_euler[0], - -obj.rotation_euler[2], - -obj.rotation_euler[1]), - collision_group=obj[bl.RIGID_GROUP], - no_collision_group=obj[bl.RIGID_INTERSECTION_GROUP], - mode=obj[bl.RIGID_PROCESS_TYPE], - mass=obj[bl.RIGID_WEIGHT], - linear_damping=obj[bl.RIGID_LINEAR_DAMPING], - angular_damping=obj[bl.RIGID_ANGULAR_DAMPING], - restitution=obj[bl.RIGID_RESTITUTION], - friction=obj[bl.RIGID_FRICTION]) - if obj[bl.RIGID_SHAPE_TYPE]==0: - rigidBody.shape_type=pmd.SHAPE_SPHERE - rigidBody.shape_size=common.Vector3(obj.scale[0], 0, 0) - elif obj[bl.RIGID_SHAPE_TYPE]==1: - rigidBody.shape_type=pmd.SHAPE_BOX - rigidBody.shape_size=common.Vector3(obj.scale[0], obj.scale[1], obj.scale[2]) - elif obj[bl.RIGID_SHAPE_TYPE]==2: - rigidBody.shape_type=pmd.SHAPE_CAPSULE - rigidBody.shape_size=common.Vector3(obj.scale[0], obj.scale[2], 0) - model.rigidbodies.append(rigidBody) - - # constraint - model.joints=[pmd.Joint( - name=obj[bl.CONSTRAINT_NAME].encode('cp932'), - rigidbody_index_a=rigidNameMap[obj[bl.CONSTRAINT_A]], - rigidbody_index_b=rigidNameMap[obj[bl.CONSTRAINT_B]], - position=common.Vector3( - obj.location[0], - obj.location[2], - obj.location[1]), - rotation=common.Vector3( - -obj.rotation_euler[0], - -obj.rotation_euler[2], - -obj.rotation_euler[1]), - translation_limit_min=common.Vector3( - obj[bl.CONSTRAINT_POS_MIN][0], - obj[bl.CONSTRAINT_POS_MIN][1], - obj[bl.CONSTRAINT_POS_MIN][2] - ), - translation_limit_max=common.Vector3( - obj[bl.CONSTRAINT_POS_MAX][0], - obj[bl.CONSTRAINT_POS_MAX][1], - obj[bl.CONSTRAINT_POS_MAX][2] - ), - rotation_limit_min=common.Vector3( - obj[bl.CONSTRAINT_ROT_MIN][0], - obj[bl.CONSTRAINT_ROT_MIN][1], - obj[bl.CONSTRAINT_ROT_MIN][2]), - rotation_limit_max=common.Vector3( - obj[bl.CONSTRAINT_ROT_MAX][0], - obj[bl.CONSTRAINT_ROT_MAX][1], - obj[bl.CONSTRAINT_ROT_MAX][2]), - spring_constant_translation=common.Vector3( - obj[bl.CONSTRAINT_SPRING_POS][0], - obj[bl.CONSTRAINT_SPRING_POS][1], - obj[bl.CONSTRAINT_SPRING_POS][2]), - spring_constant_rotation=common.Vector3( - obj[bl.CONSTRAINT_SPRING_ROT][0], - obj[bl.CONSTRAINT_SPRING_ROT][1], - obj[bl.CONSTRAINT_SPRING_ROT][2]) - ) - for obj in self.oneSkinMesh.constraints] - - # 書き込み - bl.message('write: %s' % path) - return writer.write(io.open(path, 'wb'), model) + else: + for i in range(10): + model.toon_textures[i]=("toon%02d.bmp" % (i+1)).encode('cp932') + + # rigid body + rigidNameMap={} + for i, obj in enumerate(self.oneSkinMesh.rigidbodies): + name=obj[bl.RIGID_NAME] if bl.RIGID_NAME in obj else obj.name + print(name) + rigidNameMap[name]=i + boneIndex=boneNameMap[obj[bl.RIGID_BONE_NAME]] + if boneIndex==0: + boneIndex=-1 + bone=self.skeleton.bones[0] + else: + bone=self.skeleton.bones[boneIndex] + if obj[bl.RIGID_SHAPE_TYPE]==0: + shape_type=pmd.SHAPE_SPHERE + shape_size=common.Vector3(obj.scale[0], 0, 0) + elif obj[bl.RIGID_SHAPE_TYPE]==1: + shape_type=pmd.SHAPE_BOX + shape_size=common.Vector3(obj.scale[0], obj.scale[1], obj.scale[2]) + elif obj[bl.RIGID_SHAPE_TYPE]==2: + shape_type=pmd.SHAPE_CAPSULE + shape_size=common.Vector3(obj.scale[0], obj.scale[2], 0) + rigidBody=pmd.RigidBody( + name.encode('cp932'), + collision_group=obj[bl.RIGID_GROUP], + no_collision_group=obj[bl.RIGID_INTERSECTION_GROUP], + bone_index=boneIndex, + shape_position=common.Vector3( + obj.location.x-bone.pos[0], + obj.location.z-bone.pos[2], + obj.location.y-bone.pos[1]), + shape_rotation=common.Vector3( + -obj.rotation_euler[0], + -obj.rotation_euler[2], + -obj.rotation_euler[1]), + shape_type=shape_type, + shape_size=shape_size, + mass=obj[bl.RIGID_WEIGHT], + linear_damping=obj[bl.RIGID_LINEAR_DAMPING], + angular_damping=obj[bl.RIGID_ANGULAR_DAMPING], + restitution=obj[bl.RIGID_RESTITUTION], + friction=obj[bl.RIGID_FRICTION], + mode=obj[bl.RIGID_PROCESS_TYPE] + ) + model.rigidbodies.append(rigidBody) + + # constraint + model.joints=[pmd.Joint( + name=obj[bl.CONSTRAINT_NAME].encode('cp932'), + rigidbody_index_a=rigidNameMap[obj[bl.CONSTRAINT_A]], + rigidbody_index_b=rigidNameMap[obj[bl.CONSTRAINT_B]], + position=common.Vector3( + obj.location[0], + obj.location[2], + obj.location[1]), + rotation=common.Vector3( + -obj.rotation_euler[0], + -obj.rotation_euler[2], + -obj.rotation_euler[1]), + translation_limit_min=common.Vector3( + obj[bl.CONSTRAINT_POS_MIN][0], + obj[bl.CONSTRAINT_POS_MIN][1], + obj[bl.CONSTRAINT_POS_MIN][2] + ), + translation_limit_max=common.Vector3( + obj[bl.CONSTRAINT_POS_MAX][0], + obj[bl.CONSTRAINT_POS_MAX][1], + obj[bl.CONSTRAINT_POS_MAX][2] + ), + rotation_limit_min=common.Vector3( + obj[bl.CONSTRAINT_ROT_MIN][0], + obj[bl.CONSTRAINT_ROT_MIN][1], + obj[bl.CONSTRAINT_ROT_MIN][2]), + rotation_limit_max=common.Vector3( + obj[bl.CONSTRAINT_ROT_MAX][0], + obj[bl.CONSTRAINT_ROT_MAX][1], + obj[bl.CONSTRAINT_ROT_MAX][2]), + spring_constant_translation=common.Vector3( + obj[bl.CONSTRAINT_SPRING_POS][0], + obj[bl.CONSTRAINT_SPRING_POS][1], + obj[bl.CONSTRAINT_SPRING_POS][2]), + spring_constant_rotation=common.Vector3( + obj[bl.CONSTRAINT_SPRING_ROT][0], + obj[bl.CONSTRAINT_SPRING_ROT][1], + obj[bl.CONSTRAINT_SPRING_ROT][2]) + ) + for obj in self.oneSkinMesh.constraints] + + # 書き込み + bl.message('write: %s' % path) + return writer.write(io.open(path, 'wb'), model) def _execute(filepath=''): @@ -1163,10 +344,11 @@ def _execute(filepath=''): if not active: print("abort. no active object.") return - exporter=PmdExporter() + + exporter=oneskinmesh.Exporter() exporter.setup() print(exporter) - exporter.write(filepath) - bl.object.activate(active) + write(exporter, filepath) + bl.object.activate(active) diff --git a/blender26-meshio/export_pmx.py b/blender26-meshio/export_pmx.py index 3f10bac..1f6b165 100644 --- a/blender26-meshio/export_pmx.py +++ b/blender26-meshio/export_pmx.py @@ -1,6 +1,18 @@ # coding: utf-8 +from . import bl + + def _execute(filepath): print(filepath) + active=bl.object.getActive() + if not active: + print("abort. no active object.") + return + exporter=PmdExporter() + exporter.setup() + print(exporter) + exporter.write(filepath) + bl.object.activate(active) return {'FINISHED'} diff --git a/blender26-meshio/oneskinmesh.py b/blender26-meshio/oneskinmesh.py new file mode 100644 index 0000000..6f2d9d2 --- /dev/null +++ b/blender26-meshio/oneskinmesh.py @@ -0,0 +1,801 @@ +# coding: utf-8 +""" +Blenderのメッシュをワンスキンメッシュ化する +""" +import bpy + +from . import bl +from .pymeshio import englishmap + +class VertexAttribute(object): + __slots__=[ + 'nx', 'ny', 'nz', # normal + 'u', 'v', # uv + ] + def __init__(self, nx, ny, nz, u, v): + self.nx=nx + self.ny=ny + self.nz=nz + self.u=u + self.v=v + + def __str__(self): + return "" % ( + self.nx, self.ny, self.nz, self.u, self.v) + + def __hash__(self): + return int(100*(self.nx + self.ny + self.nz + self.u + self.v)) + + def __eq__(self, rhs): + return self.nx==rhs.nx and self.ny==rhs.ny and self.nz==rhs.nz and self.u==rhs.u and self.v==rhs.v + + +class VertexKey(object): + __slots__=[ + 'obj_index', 'index', + ] + + def __init__(self, obj_index, index): + self.obj_index=obj_index + self.index=index + + def __str__(self): + return "" % (self.obj_index, self.index) + + def __hash__(self): + return self.index*100+self.obj_index + + def __eq__(self, rhs): + return self.obj_index==rhs.obj_index and self.index==rhs.index + + +class VertexArray(object): + """ + 頂点配列 + """ + __slots__=[ + 'indexArrays', + 'positions', + 'attributes', # normal and uv + 'b0', 'b1', 'weight', + 'vertexMap', + 'objectMap', + ] + def __init__(self): + # indexArrays split with each material + self.indexArrays={} + + self.positions=[] + self.attributes=[] + self.b0=[] + self.b1=[] + self.weight=[] + + self.vertexMap={} + self.objectMap={} + + def __str__(self): + return "" % ( + len(self.positions), len(self.indexArrays)) + + def zip(self): + return zip( + self.positions, self.attributes, + self.b0, self.b1, self.weight) + + def each(self): + keys=[key for key in self.indexArrays.keys()] + keys.sort() + for key in keys: + yield(key, self.indexArrays[key]) + + def __addOrGetIndex(self, obj_index, base_index, pos, normal, uv, b0, b1, weight0): + key=VertexKey(obj_index, base_index) + attribute=VertexAttribute( + normal[0], normal[1], normal[2], + uv[0], uv[1]) + if key in self.vertexMap: + if attribute in self.vertexMap[key]: + return self.vertexMap[key][attribute] + else: + return self.__addVertex(self.vertexMap[key], + pos, attribute, b0, b1, weight0) + else: + vertexMapKey={} + self.vertexMap[key]=vertexMapKey + return self.__addVertex(vertexMapKey, + pos, attribute, b0, b1, weight0) + + def __addVertex(self, vertexMapKey, pos, attribute, b0, b1, weight0): + index=len(self.positions) + vertexMapKey[attribute]=index + # position + self.positions.append((pos.x, pos.y, pos.z)) + # unique attribute + self.attributes.append(attribute) + # shared attribute + self.b0.append(b0) + self.b1.append(b1) + self.weight.append(weight0) + assert(index<=65535) + return index + + def getMappedIndex(self, obj_name, base_index): + return self.vertexMap[VertexKey(self.objectMap[obj_name], base_index)] + + def addTriangle(self, + object_name, material, + base_index0, base_index1, base_index2, + pos0, pos1, pos2, + n0, n1, n2, + uv0, uv1, uv2, + b0_0, b0_1, b0_2, + b1_0, b1_1, b1_2, + weight0, weight1, weight2 + ): + if object_name in self.objectMap: + obj_index=self.objectMap[object_name] + else: + obj_index=len(self.objectMap) + self.objectMap[object_name]=obj_index + index0=self.__addOrGetIndex(obj_index, base_index0, pos0, n0, uv0, b0_0, b1_0, weight0) + index1=self.__addOrGetIndex(obj_index, base_index1, pos1, n1, uv1, b0_1, b1_1, weight1) + index2=self.__addOrGetIndex(obj_index, base_index2, pos2, n2, uv2, b0_2, b1_2, weight2) + + if not material in self.indexArrays: + self.indexArrays[material]=[] + self.indexArrays[material]+=[index0, index1, index2] + + +class Morph(object): + __slots__=['name', 'type', 'offsets'] + def __init__(self, name, type): + self.name=name + self.type=type + self.offsets=[] + + def add(self, index, offset): + self.offsets.append((index, offset)) + + def sort(self): + self.offsets.sort(key=lambda e: e[0]) + + def __str__(self): + return "" % self.name + +class IKSolver(object): + __slots__=['target', 'effector', 'length', 'iterations', 'weight'] + def __init__(self, target, effector, length, iterations, weight): + self.target=target + self.effector=effector + self.length=length + self.iterations=iterations + self.weight=weight + + +class SSS(object): + def __init__(self): + self.use=1 + + +class DefaultMatrial(object): + def __init__(self): + self.name='default' + # diffuse + self.diffuse_color=[1, 1, 1] + self.alpha=1 + # specular + self.specular_toon_size=0 + self.specular_hardness=5 + self.specular_color=[1, 1, 1] + # ambient + self.mirror_color=[1, 1, 1] + # flag + self.subsurface_scattering=SSS() + # texture + self.texture_slots=[] + + +class OneSkinMesh(object): + __slots__=['vertexArray', 'morphList', 'rigidbodies', 'constraints', ] + def __init__(self): + self.vertexArray=VertexArray() + self.morphList=[] + self.rigidbodies=[] + self.constraints=[] + + def __str__(self): + return "" % ( + self.vertexArray, + len(self.morphList)) + + def addMesh(self, obj): + if not bl.object.isVisible(obj): + return + self.__mesh(obj) + self.__skin(obj) + self.__rigidbody(obj) + self.__constraint(obj) + + def __getWeightMap(self, obj, mesh): + # bone weight + weightMap={} + secondWeightMap={} + def setWeight(i, name, w): + if w>0: + if i in weightMap: + if i in secondWeightMap: + # 上位2つのweightを採用する + if wweightMap[i][1]: + # 多い方をweightMapに + secondWeightMap[i]=weightMap[i] + weightMap[i]=(name, w) + else: + secondWeightMap[i]=(name, w) + else: + weightMap[i]=(name, w) + + # ToDo bone weightと関係ないvertex groupを除外する + for i, v in enumerate(mesh.vertices): + if len(v.groups)>0: + for g in v.groups: + setWeight(i, obj.vertex_groups[g.group].name, g.weight) + else: + try: + setWeight(i, obj.vertex_groups[0].name, 1) + except: + # no vertex_groups + pass + + # 合計値が1になるようにする + for i in range(len(mesh.vertices)): + if i in secondWeightMap: + secondWeightMap[i]=(secondWeightMap[i][0], 1.0-weightMap[i][1]) + elif i in weightMap: + weightMap[i]=(weightMap[i][0], 1.0) + secondWeightMap[i]=("", 0) + else: + print("no weight vertex") + weightMap[i]=("", 0) + secondWeightMap[i]=("", 0) + + return weightMap, secondWeightMap + + def __processFaces(self, obj_name, mesh, weightMap, secondWeightMap): + default_material=DefaultMatrial() + # 各面の処理 + for i, face in enumerate(mesh.faces): + faceVertexCount=bl.face.getVertexCount(face) + try: + material=mesh.materials[bl.face.getMaterialIndex(face)] + except IndexError as e: + material=default_material + v=[mesh.vertices[index] for index in bl.face.getVertices(face)] + uv=bl.mesh.getFaceUV( + mesh, i, face, bl.face.getVertexCount(face)) + # flip triangle + if faceVertexCount==3: + # triangle + self.vertexArray.addTriangle( + obj_name, material.name, + v[2].index, + v[1].index, + v[0].index, + v[2].co, + v[1].co, + v[0].co, + bl.vertex.getNormal(v[2]), + bl.vertex.getNormal(v[1]), + bl.vertex.getNormal(v[0]), + uv[2], + uv[1], + uv[0], + weightMap[v[2].index][0], + weightMap[v[1].index][0], + weightMap[v[0].index][0], + secondWeightMap[v[2].index][0], + secondWeightMap[v[1].index][0], + secondWeightMap[v[0].index][0], + weightMap[v[2].index][1], + weightMap[v[1].index][1], + weightMap[v[0].index][1] + ) + elif faceVertexCount==4: + # quadrangle + self.vertexArray.addTriangle( + obj_name, material.name, + v[2].index, + v[1].index, + v[0].index, + v[2].co, + v[1].co, + v[0].co, + bl.vertex.getNormal(v[2]), + bl.vertex.getNormal(v[1]), + bl.vertex.getNormal(v[0]), + uv[2], + uv[1], + uv[0], + weightMap[v[2].index][0], + weightMap[v[1].index][0], + weightMap[v[0].index][0], + secondWeightMap[v[2].index][0], + secondWeightMap[v[1].index][0], + secondWeightMap[v[0].index][0], + weightMap[v[2].index][1], + weightMap[v[1].index][1], + weightMap[v[0].index][1] + ) + self.vertexArray.addTriangle( + obj_name, material.name, + v[0].index, + v[3].index, + v[2].index, + v[0].co, + v[3].co, + v[2].co, + bl.vertex.getNormal(v[0]), + bl.vertex.getNormal(v[3]), + bl.vertex.getNormal(v[2]), + uv[0], + uv[3], + uv[2], + weightMap[v[0].index][0], + weightMap[v[3].index][0], + weightMap[v[2].index][0], + secondWeightMap[v[0].index][0], + secondWeightMap[v[3].index][0], + secondWeightMap[v[2].index][0], + weightMap[v[0].index][1], + weightMap[v[3].index][1], + weightMap[v[2].index][1] + ) + + def __mesh(self, obj): + if bl.RIGID_SHAPE_TYPE in obj: + return + if bl.CONSTRAINT_A in obj: + return + + bl.message("export: %s" % obj.name) + + # メッシュのコピーを生成してオブジェクトの行列を適用する + copyMesh, copyObj=bl.object.duplicate(obj) + if len(copyMesh.vertices)>0: + # apply transform + """ + try: + # svn 36722 + copyObj.scale=obj.scale + bpy.ops.object.transform_apply(scale=True) + copyObj.rotation_euler=obj.rotation_euler + bpy.ops.object.transform_apply(rotation=True) + copyObj.location=obj.location + bpy.ops.object.transform_apply(location=True) + except AttributeError as e: + # 2.57b + copyObj.scale=obj.scale + bpy.ops.object.scale_apply() + copyObj.rotation_euler=obj.rotation_euler + bpy.ops.object.rotation_apply() + copyObj.location=obj.location + bpy.ops.object.location_apply() + """ + copyMesh.transform(obj.matrix_world) + + # apply modifier + for m in [m for m in copyObj.modifiers]: + if m.type=='SOLIDFY': + continue + elif m.type=='ARMATURE': + continue + elif m.type=='MIRROR': + bpy.ops.object.modifier_apply(modifier=m.name) + else: + print(m.type) + + weightMap, secondWeightMap=self.__getWeightMap(copyObj, copyMesh) + self.__processFaces(obj.name, copyMesh, weightMap, secondWeightMap) + bl.object.delete(copyObj) + + def createEmptyBasicSkin(self): + self.__getOrCreateMorph('base', 0) + + def __skin(self, obj): + if not bl.object.hasShapeKey(obj): + return + + indexRelativeMap={} + blenderMesh=bl.object.getData(obj) + baseMorph=None + + # shape keys + vg=bl.object.getVertexGroup(obj, bl.MMD_SHAPE_GROUP_NAME) + + # base + used=set() + for b in bl.object.getShapeKeys(obj): + if b.name==bl.BASE_SHAPE_NAME: + baseMorph=self.__getOrCreateMorph('base', 0) + basis=b + + relativeIndex=0 + for index in vg: + v=bl.shapekey.getByIndex(b, index) + pos=[v[0], v[1], v[2]] + + indices=self.vertexArray.getMappedIndex(obj.name, index) + for attribute, i in indices.items(): + if i in used: + continue + used.add(i) + + baseMorph.add(i, pos) + indexRelativeMap[i]=relativeIndex + relativeIndex+=1 + + break + assert(basis) + #print(basis.name, len(baseMorph.offsets)) + + if len(baseMorph.offsets)==0: + return + + # shape keys + for b in bl.object.getShapeKeys(obj): + if b.name==bl.BASE_SHAPE_NAME: + continue + + #print(b.name) + morph=self.__getOrCreateMorph(b.name, 4) + used=set() + for index, src, dst in zip( + range(len(blenderMesh.vertices)), + bl.shapekey.get(basis), + bl.shapekey.get(b)): + offset=[dst[0]-src[0], dst[1]-src[1], dst[2]-src[2]] + if offset[0]==0 and offset[1]==0 and offset[2]==0: + continue + if index in vg: + indices=self.vertexArray.getMappedIndex(obj.name, index) + for attribute, i in indices.items(): + if i in used: + continue + used.add(i) + morph.add(indexRelativeMap[i], offset) + assert(len(morph.offsets)" % (self.name, self.type) + +class BoneBuilder(object): + __slots__=['bones', 'boneMap', 'ik_list', 'bone_groups',] + def __init__(self): + self.bones=[] + self.boneMap={} + self.ik_list=[] + self.bone_groups=[] + + def getBoneGroup(self, bone): + for i, g in enumerate(self.bone_groups): + for b in g[1]: + if b==bone.name: + return i+1 + print('no gorup', bone) + return 0 + + def build(self, armatureObj): + if not armatureObj: + return + + bl.message("build skeleton") + armature=bl.object.getData(armatureObj) + + #################### + # bone group + #################### + for g in bl.object.boneGroups(armatureObj): + self.bone_groups.append((g.name, [])) + + #################### + # get bones + #################### + for b in armature.bones.values(): + if not b.parent: + # root bone + bone=Bone(b.name, + bl.bone.getHeadLocal(b), + bl.bone.getTailLocal(b), + False) + self.__addBone(bone) + self.__getBone(bone, b) + + for b in armature.bones.values(): + if not b.parent: + self.__checkConnection(b, None) + + #################### + # get IK + #################### + pose = bl.object.getPose(armatureObj) + for b in pose.bones.values(): + #################### + # assing bone group + #################### + self.__assignBoneGroup(b, b.bone_group) + for c in b.constraints: + if bl.constraint.isIKSolver(c): + #################### + # IK target + #################### + target=self.__boneByName(bl.constraint.ikTarget(c)) + target.type=2 + + #################### + # IK effector + #################### + # IK 接続先 + link=self.__boneByName(b.name) + link.type=6 + + # IK chain + e=b.parent + chainLength=bl.constraint.ikChainLen(c) + for i in range(chainLength): + # IK影響下 + chainBone=self.__boneByName(e.name) + chainBone.type=4 + chainBone.ik_index=target.index + e=e.parent + self.ik_list.append( + IKSolver(target, link, chainLength, + int(bl.constraint.ikItration(c) * 0.1), + bl.constraint.ikRotationWeight(c) + )) + + #################### + + # boneのsort + self._sortBy() + self._fix() + # IKのsort + def getIndex(ik): + for i, v in enumerate(englishmap.boneMap): + if v[0]==ik.target.name: + return i + return len(englishmap.boneMap) + self.ik_list.sort(key=getIndex) + + def __assignBoneGroup(self, poseBone, boneGroup): + if boneGroup: + for g in self.bone_groups: + if g[0]==boneGroup.name: + g[1].append(poseBone.name) + + def __checkConnection(self, b, p): + if bl.bone.isConnected(b): + parent=self.__boneByName(p.name) + parent.isConnect=True + + for c in b.children: + self.__checkConnection(c, b) + + def _sortBy(self): + """ + boneMap順に並べ替える + """ + boneMap=englishmap.boneMap + original=self.bones[:] + def getIndex(bone): + for i, k_v in enumerate(boneMap): + if k_v[0]==bone.name: + return i + print(bone) + return len(boneMap) + + self.bones.sort(key=getIndex) + + sortMap={} + for i, b in enumerate(self.bones): + src=original.index(b) + sortMap[src]=i + for b in self.bones: + b.index=sortMap[b.index] + if b.parent_index: + b.parent_index=sortMap[b.parent_index] + if b.tail_index: + b.tail_index=sortMap[b.tail_index] + if b.ik_index>0: + b.ik_index=sortMap[b.ik_index] + + def _fix(self): + """ + 調整 + """ + for b in self.bones: + # parent index + if b.parent_index==None: + b.parent_index=0xFFFF + else: + if b.type==6 or b.type==7: + # fix tail bone + parent=self.bones[b.parent_index] + #print('parnet', parent.name) + parent.tail_index=b.index + + for b in self.bones: + if b.tail_index==None: + b.tail_index=0 + elif b.type==9: + b.tail_index==0 + + def getIndex(self, bone): + for i, b in enumerate(self.bones): + if b==bone: + return i + assert(false) + + def indexByName(self, name): + if name=='': + return 0 + else: + try: + return self.getIndex(self.__boneByName(name)) + except: + return 0 + + def __boneByName(self, name): + return self.boneMap[name] + + def __getBone(self, parent, b): + if len(b.children)==0: + parent.type=7 + return + + for i, c in enumerate(b.children): + bone=Bone(c.name, + bl.bone.getHeadLocal(c), + bl.bone.getTailLocal(c), + bl.bone.isConnected(c)) + self.__addBone(bone) + if parent: + bone.parent_index=parent.index + #if i==0: + if bone.isConnect or (not parent.tail_index and parent.tail==bone.pos): + parent.tail_index=bone.index + self.__getBone(bone, c) + + def __addBone(self, bone): + bone.index=len(self.bones) + self.bones.append(bone) + self.boneMap[bone.name]=bone + + +class Node(object): + __slots__=['o', 'children'] + def __init__(self, o): + self.o=o + self.children=[] + + + +class Exporter(object): + + __slots__=[ + 'armatureObj', + 'oneSkinMesh', + 'englishName', + 'englishComment', + 'name', + 'comment', + 'skeleton', + ] + def setup(self): + self.armatureObj=None + + # 木構造を構築する + object_node_map={} + for o in bl.object.each(): + object_node_map[o]=Node(o) + for o in bl.object.each(): + node=object_node_map[o] + if node.o.parent: + object_node_map[node.o.parent].children.append(node) + + # ルートを得る + root=object_node_map[bl.object.getActive()] + o=root.o + self.englishName=o.name + self.englishComment=o[bl.MMD_COMMENT] if bl.MMD_COMMENT in o else 'blender export\n' + self.name=o[bl.MMD_MB_NAME] if bl.MMD_MB_NAME in o else 'Blenderエクスポート' + self.comment=o[bl.MMD_MB_COMMENT] if bl.MMD_MB_COMMENT in o else 'Blnderエクスポート\n' + + # ワンスキンメッシュを作る + self.oneSkinMesh=OneSkinMesh() + self.__createOneSkinMesh(root) + bl.message(self.oneSkinMesh) + if len(self.oneSkinMesh.morphList)==0: + # create emtpy skin + self.oneSkinMesh.createEmptyBasicSkin() + + # skeleton + self.skeleton=BoneBuilder() + self.skeleton.build(self.armatureObj) + + def __createOneSkinMesh(self, node): + ############################################################ + # search armature modifier + ############################################################ + for m in node.o.modifiers: + if bl.modifier.isType(m, 'ARMATURE'): + armatureObj=bl.modifier.getArmatureObject(m) + if not self.armatureObj: + self.armatureObj=armatureObj + elif self.armatureObj!=armatureObj: + print("warning! found multiple armature. ignored.", + armatureObj.name) + + if node.o.type.upper()=='MESH': + self.oneSkinMesh.addMesh(node.o) + + for child in node.children: + self.__createOneSkinMesh(child) + diff --git a/pymeshio/pmd/__init__.py b/pymeshio/pmd/__init__.py index afba0ce..8315d2a 100644 --- a/pymeshio/pmd/__init__.py +++ b/pymeshio/pmd/__init__.py @@ -458,7 +458,7 @@ class RigidBody(object): angular_damping, restitution, friction, - mode, + mode ): self.name=name self.bone_index=bone_index -- 2.11.0