From: U-theremin\focke Date: Fri, 29 Apr 2011 09:28:14 +0000 (+0900) Subject: import from old repository X-Git-Url: http://git.osdn.jp/view?a=commitdiff_plain;h=f67a43c20545138262b46c4a866a37f43ba1c27b;p=meshio%2Fpymeshio.git import from old repository --- f67a43c20545138262b46c4a866a37f43ba1c27b diff --git a/pymeshio/bl25.py b/pymeshio/bl25.py new file mode 100755 index 0000000..eaec64f --- /dev/null +++ b/pymeshio/bl25.py @@ -0,0 +1,651 @@ +# coding: utf-8 +import os +import sys +import time +import functools + +try: + import bpy + import mathutils +except: + pass + +FS_ENCODING=sys.getfilesystemencoding() +if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"): + INTERNAL_ENCODING='utf-8' +else: + INTERNAL_ENCODING=FS_ENCODING + +def register(): + pass + +def unregister(): + pass + +SCENE=None +def initialize(name, scene): + global SCENE + SCENE=scene + progress_start(name) + +def finalize(): + scene.update(SCENE) + progress_finish() + +def message(msg): + print(msg) + +def enterEditMode(): + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + +def enterObjectMode(): + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + +def enterPoseMode(): + bpy.ops.object.mode_set(mode='POSE', toggle=False) + +def createVector(x, y, z): + return mathutils.Vector([x, y, z]) + + +class Writer(object): + ''' + io wrapper + ''' + def __init__(self, path, encoding): + self.io=open(path, "wb") + self.encoding=encoding + + def write(self, s): + self.io.write(s.encode(self.encoding)) + + def flush(self): + self.io.flush() + + def close(self): + self.io.close() + + +class ProgressBar(object): + ''' + progress bar wrapper + ''' + def __init__(self, base): + print("#### %s ####" % base) + self.base=base + self.start=time.time() + self.set('', 0) + + def advance(self, message, progress): + self.progress+=float(progress) + self._print(message) + + def set(self, message, progress): + self.progress=float(progress) + self._print(message) + + def _print(self, message): + print(message) + message="%s: %s" % (self.base, message) + #Blender.Window.DrawProgressBar(self.progress, message) + + def finish(self): + self.progress=1.0 + message='finished in %.2f sec' % (time.time()-self.start) + self.set(message, 1.0) + +def progress_start(base): + global progressBar + progressBar=ProgressBar(base) + +def progress_finish(): + global progressBar + progressBar.finish() + +def progress_print(message, progress=0.05): + global progressBar + progressBar.advance(message, progress) + +def progress_set(message, progress): + global progressBar + progressBar.set(message, progress) + + +class scene: + @staticmethod + def update(scene): + scene.update() + + +class object: + @staticmethod + def createEmpty(name): + global SCENE + empty=bpy.data.objects.new(name, None) + SCENE.objects.link(empty) + return empty + + @staticmethod + def each(): + for o in SCENE.objects: + yield o + + @staticmethod + def makeParent(parent, child): + child.parent=parent + + @staticmethod + def duplicate(o): + global SCENE + bpy.ops.object.select_all(action='DESELECT') + o.select=True + SCENE.objects.active=o + bpy.ops.object.duplicate() + dumy=SCENE.objects.active + #bpy.ops.object.rotation_apply() + #bpy.ops.object.scale_apply() + #bpy.ops.object.location_apply() + return dumy.data, dumy + + @staticmethod + def delete(o): + global SCENE + SCENE.objects.unlink(o) + + @staticmethod + def getData(o): + return o.data + + @staticmethod + def select(o): + o.select=True + + @staticmethod + def activate(o): + global SCENE + o.select=True + SCENE.objects.active=o + + @staticmethod + def getActive(): + global SCENE + return SCENE.objects.active + + @staticmethod + def deselectAll(): + bpy.ops.object.select_all(action='DESELECT') + + @staticmethod + def setLayerMask(object, layers): + layer=[] + for i in range(20): + try: + layer.append(True if layers[i]!=0 else False) + except IndexError: + layer.append(False) + object.layers=layer + + @staticmethod + def isVisible(o): + return False if o.hide else True + + @staticmethod + def getShapeKeys(o): + return o.data.shape_keys.keys + + @staticmethod + def addShapeKey(o, name): + try: + return o.shape_key_add(name) + except: + return o.add_shape_key(name) + + @staticmethod + def hasShapeKey(o): + return o.data.shape_keys + + @staticmethod + def pinShape(o, enable): + o.show_only_shape_key=enable + + @staticmethod + def setActivateShapeKey(o, index): + o.active_shape_key_index=index + + @staticmethod + def getPose(o): + return o.pose + + @staticmethod + def getVertexGroup(o, name): + indices=[] + for i, v in enumerate(o.data.vertices): + for g in v.groups: + if o.vertex_groups[g.group].name==name: + indices.append(i) + return indices + + @staticmethod + def getVertexGroupNames(o): + for g in o.vertex_groups: + yield g.name + + @staticmethod + def addVertexGroup(o, name): + o.vertex_groups.new(name) + + @staticmethod + def assignVertexGroup(o, name, index, weight): + g=o.vertex_groups[name] + o.vertex_groups.assign([index], g, weight, 'ADD') + + @staticmethod + def createBoneGroup(o, name, color_set='DEFAULT'): + # create group + object.activate(o) + enterPoseMode() + bpy.ops.pose.group_add() + # set name + pose=object.getPose(o) + g=pose.bone_groups.active + g.name=name + g.color_set=color_set + + @staticmethod + def boneGroups(o): + return object.getPose(o).bone_groups + + +class modifier: + @staticmethod + def addMirror(mesh_object): + return mesh_object.modifiers.new("Modifier", "MIRROR") + + @staticmethod + def addArmature(mesh_object, armature_object): + mod=mesh_object.modifiers.new("Modifier", "ARMATURE") + mod.object = armature_object + mod.use_bone_envelopes=False + + @staticmethod + def hasType(mesh_object, type_name): + for mod in mesh_object.modifiers: + if mod.type==type_name.upper(): + return True + + @staticmethod + def isType(m, type_name): + return m.type==type_name.upper() + + @staticmethod + def getArmatureObject(m): + return m.object + + +class shapekey: + @staticmethod + def assign(shapeKey, index, pos): + shapeKey.data[index].co=pos + + @staticmethod + def getByIndex(b, index): + return b.data[index].co + + @staticmethod + def get(b): + for k in b.data: + yield k.co + + +class texture: + @staticmethod + def create(path): + texture=bpy.data.textures.new(os.path.basename(path), 'IMAGE') + texture.use_mipmap=True + texture.use_interpolation=True + texture.use_alpha=True + try: + image=bpy.data.images.load(path) + except SystemError: + image=bpy.data.images.new('Image') + texture.image=image + return texture, image + + @staticmethod + def getPath(t): + if t.type=="IMAGE": + image=t.image + if image: + return image.filepath + + +class material: + @staticmethod + def create(name): + return bpy.data.materials.new(name) + + @staticmethod + def get(material_name): + return bpy.data.materials[material_name] + + @staticmethod + def addTexture(material, texture, enable=True): + # search free slot + index=None + for i, slot in enumerate(material.texture_slots): + if not slot: + index=i + break + if index==None: + return + # + #material.add_texture(texture, "UV", "COLOR") + #slot=material.texture_slots.add() + slot=material.texture_slots.create(index) + slot.texture=texture + slot.texture_coords='UV' + slot.blend_type='MULTIPLY' + slot.use_map_alpha=True + slot.use=enable + return index + + @staticmethod + def getTexture(m, index): + return m.texture_slots[index].texture + + @staticmethod + def hasTexture(m): + return m.texture_slots[0] + + @staticmethod + def setUseTexture(m, index, enable): + m.use_textures[index]=enable + + @staticmethod + def eachTexturePath(m): + for slot in m.texture_slots: + if slot and slot.texture: + texture=slot.texture + if texture.type=="IMAGE": + image=texture.image + if not image: + continue + yield image.filepath + + @staticmethod + def eachEnalbeTexturePath(m): + for i, slot in enumerate(m.texture_slots): + if m.use_textures[i] and slot and slot.texture: + texture=slot.texture + if texture.type=="IMAGE": + image=texture.image + if not image: + continue + yield image.filepath + + +class mesh: + @staticmethod + def create(name): + global SCENE + mesh=bpy.data.meshes.new("Mesh") + mesh_object= bpy.data.objects.new(name, mesh) + SCENE.objects.link(mesh_object) + return mesh, mesh_object + + @staticmethod + def addGeometry(mesh, vertices, faces): + mesh.from_pydata(vertices, [], faces) + """ + mesh.add_geometry(len(vertices), 0, len(faces)) + # add vertex + unpackedVertices=[] + for v in vertices: + unpackedVertices.extend(v) + mesh.vertices.foreach_set("co", unpackedVertices) + # add face + unpackedFaces = [] + for face in faces: + if len(face) == 4: + if face[3] == 0: + # rotate indices if the 4th is 0 + face = [face[3], face[0], face[1], face[2]] + elif len(face) == 3: + if face[2] == 0: + # rotate indices if the 3rd is 0 + face = [face[2], face[0], face[1], 0] + else: + face.append(0) + unpackedFaces.extend(face) + mesh.faces.foreach_set("verts_raw", unpackedFaces) + """ + assert(len(vertices)==len(mesh.vertices)) + assert(len(faces)==len(mesh.faces)) + + @staticmethod + def hasUV(mesh): + return mesh.active_uv_texture + + @staticmethod + def useVertexUV(mesh): + pass + + @staticmethod + def addUV(mesh): + mesh.uv_textures.new() + + @staticmethod + def hasFaceUV(mesh, i, face): + return mesh.active_uv_texture and mesh.active_uv_texture.data[i] + + @staticmethod + def getFaceUV(mesh, i, faces, count=3): + if mesh.active_uv_texture and mesh.active_uv_texture.data[i]: + uvFace=mesh.active_uv_texture.data[i] + if count==3: + return (uvFace.uv1, uvFace.uv2, uvFace.uv3) + elif count==4: + return (uvFace.uv1, uvFace.uv2, uvFace.uv3, uvFace.uv4) + else: + print(count) + assert(False) + else: + return ((0, 0), (0, 0), (0, 0), (0, 0)) + + @staticmethod + def setFaceUV(m, i, face, uv_array, image): + uv_face=m.uv_textures[0].data[i] + uv_face.uv=uv_array + if image: + uv_face.image=image + uv_face.use_image=True + + @staticmethod + def vertsDelete(m, remove_vertices): + enterEditMode() + bpy.ops.mesh.select_all(action='DESELECT') + enterObjectMode() + + for i in remove_vertices: + m.vertices[i].select=True + + enterEditMode() + bpy.ops.mesh.delete(type='VERT') + enterObjectMode() + + @staticmethod + def setSmooth(m, smoothing): + m.auto_smooth_angle=int(smoothing) + m.use_auto_smooth=True + + @staticmethod + def recalcNormals(mesh_object): + bpy.ops.object.select_all(action='DESELECT') + object.activate(mesh_object) + enterEditMode() + bpy.ops.mesh.normals_make_consistent() + enterObjectMode() + + @staticmethod + def flipNormals(m): + m.flipNormals() + + @staticmethod + def addMaterial(m, material): + m.materials.append(material) + + @staticmethod + def getMaterial(m, index): + return m.materials[index] + + +class vertex: + @staticmethod + def setNormal(v, normal): + v.normal=mathutils.Vector(normal) + + @staticmethod + def getNormal(v): + return v.normal + + @staticmethod + def setUv(v, uv): + # sticky ? + pass + + +class face: + @staticmethod + def getVertexCount(face): + return len(face.vertices) + + @staticmethod + def getVertices(face): + return face.vertices[:] + + @staticmethod + def getIndices(face, count=3): + if count==3: + return [face.vertices[0], face.vertices[1], face.vertices[2]] + elif count==4: + return [face.vertices[0], face.vertices[1], face.vertices[2], face.vertices[3]] + else: + assert(False) + + @staticmethod + def setMaterial(face, material_index): + face.material_index=material_index + + @staticmethod + def getMaterialIndex(face): + return face.material_index + + @staticmethod + def setNormal(face, normal): + face.normal=normal + + @staticmethod + def getNormal(face): + return face.normal + + @staticmethod + def setSmooth(face, isSmooth): + face.use_smooth=True if isSmooth else False + + +class armature: + @staticmethod + def create(): + global SCENE + armature = bpy.data.armatures.new('Armature') + armature_object=bpy.data.objects.new('Armature', armature) + SCENE.objects.link(armature_object) + + armature_object.show_x_ray=True + armature.show_names=True + #armature.draw_type='OCTAHEDRAL' + armature.draw_type='STICK' + armature.use_deform_envelopes=False + armature.use_deform_vertex_groups=True + armature.use_mirror_x=True + + return armature, armature_object + + @staticmethod + def makeEditable(armature_object): + global SCENE + # select only armature object and set edit mode + SCENE.objects.active=armature_object + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + + @staticmethod + def createIkConstraint(armature_object, p_bone, effector_name, ik): + constraint = p_bone.constraints.new('IK') + constraint.chain_count=len(ik.children) + constraint.target=armature_object + constraint.subtarget=effector_name + constraint.use_tail=False + # not used. place folder when export. + constraint.weight=ik.weight + constraint.iterations=ik.iterations * 10 + return constraint + + @staticmethod + def createBone(armature, name): + return armature.edit_bones.new(name) + + @staticmethod + def update(armature): + pass + + +class bone: + @staticmethod + def setConnected(bone): + bone.use_connect=True + + @staticmethod + def isConnected(b): + return b.connected + + @staticmethod + def setLayerMask(bone, layers): + layer=[] + for i in range(32): + try: + layer.append(True if layers[i]!=0 else False) + except IndexError: + layer.append(False) + bone.layers=layer + + @staticmethod + def getHeadLocal(b): + return b.head_local[0:3] + + @staticmethod + def getTailLocal(b): + return b.tail_local[0:3] + + +class constraint: + @staticmethod + def ikChainLen(c): + return c.chain_length + + @staticmethod + def ikTarget(c): + return c.subtarget + + @staticmethod + def ikItration(c): + return c.iterations + + @staticmethod + def ikRotationWeight(c): + return c.weight + + @staticmethod + def isIKSolver(c): + return c.type=='IK' + diff --git a/pymeshio/io_export_scene_mqo.py b/pymeshio/io_export_scene_mqo.py new file mode 100755 index 0000000..2ea8c64 --- /dev/null +++ b/pymeshio/io_export_scene_mqo.py @@ -0,0 +1,399 @@ +#!BPY +# coding: utf-8 + +""" +Name: 'Metasequoia (.mqo)...' +Blender: 245 +Group: 'Export' +Tooltip: 'Save as Metasequoia MQO File' +""" +__author__= 'ousttrue' +__url__ = ["http://gunload.web.fc2.com/blender/"] +__version__= '2.2' +__bpydoc__ = """\ +This script is an exporter to MQO file format. + +Usage: + +Run this script from "File->Export" menu. + +0.1 20080128: +0.2 20100518: refactoring. +0.3 20100606: integrate 2.4 and 2.5. +0.4 20100626: refactoring. +0.5 20100710: add [apply_modifier] option(2.5 only). +0.6 20100714: remove shape_key when apply_modifier. fix material. +2.0 20100724: update for Blender2.53. +2.1 20101005: update for Blender2.54. +2.2 20101228: update for Blender2.55. +""" + +bl_addon_info = { + 'category': 'Import/Export', + 'name': 'Export: Metasequioa Model Format (.mqo)', + 'author': 'ousttrue', + 'version': (2, 1), + 'blender': (2, 5, 3), + 'location': 'File > Export', + 'description': 'Export to the Metasequioa Model Format (.mqo)', + '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 os +import sys + +def isBlender24(): + return sys.version_info[0]<3 + + +class MQOMaterial(object): + __slots__=[ + 'name', 'shader', 'r', 'g', 'b', 'a', + 'dif', 'amb', 'emi', + ] + def __init__(self, name, shader=3): + self.name=name + self.shader=shader + self.r=0.5 + self.g=0.5 + self.b=0.5 + self.a=1 + self.dif=1 + self.amb=0 + self.emi=0 + + def __str__(self): + return "\"%s\" shader(%d) col(%f %f %f %f) dif(%f) amb(%f) emi(%f)" % ( + self.name, self.shader, self.r, self.g, self.b, self.a, + self.dif, self.amb, self.emi + ) + + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # wrapper + import bl24 as bl + + def materialToMqo(m): + material=MQOMaterial(m.name, 3) + material.r=m.rgbCol[0] + material.g=m.rgbCol[1] + material.b=m.rgbCol[2] + material.a=m.alpha + return material + +else: + # for 2.5 + import bpy + + # wrapper + import bl25 as bl + + def materialToMqo(m): + material=MQOMaterial(m.name, 3) + material.r=m.diffuse_color[0] + material.g=m.diffuse_color[1] + material.b=m.diffuse_color[2] + material.a=m.alpha + material.amb=m.ambient + material.emi=m.emit + return material + +def apply_transform(vec, matrix): + x, y, z = vec + xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2] + return x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,\ + x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,\ + x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc + +def convert_to_mqo(vec): + return vec.x, vec.z, -vec.y + + +class OutlineNode(object): + __slots__=['o', 'children'] + def __init__(self, o): + self.o=o + self.children=[] + + def __str__(self): + return "" % self.o + + +class ObjectInfo(object): + __slots__=['object', 'depth', 'material_map'] + def __init__(self, o, depth): + self.object=o + self.depth=depth + self.material_map={} + + def __str__(self): + return "" % (self.depth, self.object) + + +class MqoExporter(object): + __slots__=["materials", "objects", 'scale', 'apply_modifier',] + def __init__(self, scale, apply_modifier): + self.objects=[] + self.materials=[] + self.scale=scale + self.apply_modifier=apply_modifier + + def setup(self, scene): + # 木構造を構築する + object_node_map={} + for o in scene.objects: + object_node_map[o]=OutlineNode(o) + for node in object_node_map.values(): + if node.o.parent: + object_node_map[node.o.parent].children.append(node) + + # ルートを得る + root=object_node_map[scene.objects.active] + + # 情報を集める + if root.o.type.upper()=='EMPTY': + # depth調整 + for node in root.children: + self.__setup(node) + else: + self.__setup(root) + + def __setup(self, node, depth=0): + info=ObjectInfo(node.o, depth) + self.objects.append(info) + if node.o.type.upper()=='MESH': + # set material index + for i, m in enumerate(node.o.data.materials): + info.material_map[i]=self.__getOrAddMaterial(m) + # recursive + for child in node.children: + self.__setup(child, depth+1) + + def __getOrAddMaterial(self, material): + for i, m in enumerate(self.materials): + if m==material: + return i + index=len(self.materials) + self.materials.append(material) + return index + + def write(self, path): + bl.message("open: "+path) + io=bl.Writer(path, 'cp932') + self.__write_header(io) + self.__write_scene(io) + print("Writing MaterialChunk") + self.__write_materials(io, os.path.dirname(path)) + print("Writing ObjectChunk") + for info in self.objects: + self.__write_object(io, info) + io.write("Eof\r\n") + io.flush() + io.close() + + def __write_header(self, io): + io.write("Metasequoia Document\r\n") + io.write("Format Text Ver 1.0\r\n") + io.write("\r\n") + + def __write_scene(self, io): + print("Writing SceneChunk") + io.write("Scene {\r\n") + io.write("}\r\n") + + def __write_materials(self, io, dirname): + # each material + io.write("Material %d {\r\n" % (len(self.materials))) + for m in self.materials: + io.write(str(materialToMqo(m))) + # ToDo separated alpha texture + for filename in bl.material.eachTexturePath(m): + if len(dirname)>0 and filename.startswith(dirname): + # 相対パスに変換する + filename=filename[len(dirname)+1:] + io.write(" tex(\"%s\")" % filename) + break + io.write("\r\n") + # end of chunk + io.write("}\r\n") + + def __write_object(self, io, info): + print(info) + + obj=info.object + if obj.type.upper()=='MESH' or obj.type.upper()=='EMPTY': + pass + else: + print(obj.type) + return + + io.write("Object \""+obj.name+"\" {\r\n") + + # depth + io.write("\tdepth %d\r\n" % info.depth) + + # mirror + if not self.apply_modifier: + if bl.modifier.hasType(obj, 'MIRROR'): + io.write("\tmirror 1\r\n") + io.write("\tmirror_axis 1\r\n") + + if obj.type.upper()=='MESH': + # duplicate and applyMatrix + copyMesh, copyObj=bl.object.duplicate(obj) + # apply transform + 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() + # apply modifier + if self.apply_modifier: + # remove shape key + while bl.object.hasShapeKey(copyObj): + bpy.ops.object.shape_key_remove() + for m in [m for m in copyObj.modifiers]: + if m.type=='SOLIDFY': + continue + elif m.type=='ARMATURE': + bpy.ops.object.modifier_apply(modifier=m.name) + elif m.type=='MIRROR': + bpy.ops.object.modifier_apply(modifier=m.name) + else: + print(m.type) + # write mesh + self.__write_mesh(io, copyMesh, info.material_map) + bl.object.delete(copyObj) + + io.write("}\r\n") # end of object + + def __write_mesh(self, io, mesh, material_map): + # vertices + io.write("\tvertex %d {\r\n" % len(mesh.verts)) + for vert in mesh.verts: + x, y, z = convert_to_mqo(vert.co) + io.write("\t\t%f %f %f\r\n" % + (x*self.scale, y*self.scale, z*self.scale)) # rotate to y-up + io.write("\t}\r\n") + + # faces + io.write("\tface %d {\r\n" % len(mesh.faces)) + for i, face in enumerate(mesh.faces): + count=bl.face.getVertexCount(face) + # V + io.write("\t\t%d V(" % count) + for j in reversed(bl.face.getVertices(face)): + io.write("%d " % j) + io.write(")") + # mat + if len(mesh.materials): + io.write(" M(%d)" % + material_map[bl.face.getMaterialIndex(face)]) + # UV + if bl.mesh.hasUV(mesh) and bl.mesh.hasFaceUV(mesh, i, face): + io.write(" UV(") + for uv in reversed(bl.mesh.getFaceUV(mesh, i, face, count)): + # reverse vertical value + io.write("%f %f " % (uv[0], 1.0-uv[1])) + io.write(")") + io.write("\r\n") + io.write("\t}\r\n") # end of faces + + +def __execute(filename, scene, scale=10, apply_modifier=False): + if scene.objects.active: + exporter=MqoExporter(scale, apply_modifier) + exporter.setup(scene) + exporter.write(filename) + else: + bl.message('no active object !') + + +if isBlender24(): + # for 2.4 + def execute_24(filename): + scene=Blender.Scene.GetCurrent() + bl.initialize('mqo_export', scene) + __execute( + filename.decode(bl.INTERNAL_ENCODING), + scene, False) + bl.finalize() + + # execute + Blender.Window.FileSelector( + execute_24, + 'Export Metasequoia MQO', + Blender.sys.makename(ext='.mqo')) + +else: + # for 2.5 + def execute_25(path, scene, scale, apply_modifier): + bl.initialize('mqo_export', scene) + __execute(path, scene, scale, apply_modifier) + bl.finalize() + + # operator + class EXPORT_OT_mqo(bpy.types.Operator): + '''Save a Metasequoia MQO file.''' + bl_idname = "export_scene.mqo" + bl_label = 'Export MQO' + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + filepath = bpy.props.StringProperty() + filename = bpy.props.StringProperty() + directory = bpy.props.StringProperty() + + scale = bpy.props.FloatProperty( + name="Scale", + description="Scale the MQO by this value", + min=0.0001, max=1000000.0, + soft_min=0.001, soft_max=100.0, default=10.0) + + apply_modifier = bpy.props.BoolProperty( + name="ApplyModifier", + description="Would apply modifiers", + default=False) + + def execute(self, context): + execute_25( + self.properties.filepath, + context.scene, + self.properties.scale, + self.properties.apply_modifier) + return 'FINISHED' + + def invoke(self, context, event): + wm=context.window_manager + try: + wm.fileselect_add(self) + except: + wm.add_fileselect(self) + return 'RUNNING_MODAL' + + # register menu + def menu_func(self, context): + default_path=bpy.data.filepath.replace(".blend", ".mqo") + self.layout.operator( + EXPORT_OT_mqo.bl_idname, + text="Metasequoia (.mqo)", + icon='PLUGIN' + ).filepath=default_path + + def register(): + bpy.types.INFO_MT_file_export.append(menu_func) + + def unregister(): + bpy.types.INFO_MT_file_export.remove(menu_func) + + if __name__ == "__main__": + register() + diff --git a/pymeshio/io_export_scene_pmd.py b/pymeshio/io_export_scene_pmd.py new file mode 100755 index 0000000..8666bd6 --- /dev/null +++ b/pymeshio/io_export_scene_pmd.py @@ -0,0 +1,1274 @@ +#!BPY +# coding: utf-8 +""" + Name: 'MikuMikuDance model (.pmd)...' + Blender: 248 + Group: 'Export' + Tooltip: 'Export PMD file for MikuMikuDance.' +""" +__author__= ["ousttrue"] +__version__= "2.2" +__url__=() +__bpydoc__=""" +pmd Importer + +This script exports a pmd model. + +0.1 20100318: first implementation. +0.2 20100519: refactoring. use C extension. +1.0 20100530: implement basic features. +1.1 20100612: integrate 2.4 and 2.5. +1.2 20100616: implement rigid body. +1.3 20100619: fix rigid body, bone weight. +1.4 20100626: refactoring. +1.5 20100629: sphere map. +1.6 20100710: toon texture & bone group. +1.7 20100711: separate vertex with normal or uv. +2.0 20100724: update for Blender2.53. +2.1 20100731: add full python module. +2.2 20101005: update for Blender2.54. +2.3 20101228: update for Blender2.55. +""" + +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', + } + +MMD_SHAPE_GROUP_NAME='_MMD_SHAPE' +MMD_MB_NAME='mb_name' +MMD_MB_COMMENT='mb_comment' +MMD_COMMENT='comment' +BASE_SHAPE_NAME='Basis' +RIGID_NAME='rigid_name' +RIGID_SHAPE_TYPE='rigid_shape_type' +RIGID_PROCESS_TYPE='rigid_process_type' +RIGID_BONE_NAME='rigid_bone_name' +#RIGID_LOCATION='rigid_loation' +RIGID_GROUP='ribid_group' +RIGID_INTERSECTION_GROUP='rigid_intersection_group' +RIGID_WEIGHT='rigid_weight' +RIGID_LINEAR_DAMPING='rigid_linear_damping' +RIGID_ANGULAR_DAMPING='rigid_angular_damping' +RIGID_RESTITUTION='rigid_restitution' +RIGID_FRICTION='rigid_friction' +CONSTRAINT_NAME='constraint_name' +CONSTRAINT_A='const_a' +CONSTRAINT_B='const_b' +CONSTRAINT_POS_MIN='const_pos_min' +CONSTRAINT_POS_MAX='const_pos_max' +CONSTRAINT_ROT_MIN='const_rot_min' +CONSTRAINT_ROT_MAX='const_rot_max' +CONSTRAINT_SPRING_POS='const_spring_pos' +CONSTRAINT_SPRING_ROT='const_spring_rot' +TOON_TEXTURE_OBJECT='ToonTextures' + + +############################################################################### +# import +############################################################################### +import os +import sys + +try: + # C extension + from meshio import pmd, englishmap + print('use meshio C module') +except ImportError: + # full python + from pymeshio import englishmap + from pymeshio import mmd as pmd + pmd.IO=pmd.PMDLoader + +def isBlender24(): + return sys.version_info[0]<3 + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # wrapper + import bl24 as bl + + def setMaterialParams(material, m): + # diffuse + material.diffuse.r=m.R + material.diffuse.g=m.G + material.diffuse.b=m.B + material.diffuse.a=m.alpha + # specular + material.shinness=0 if m.spec<1e-5 else m.spec*10 + material.specular.r=m.specR + material.specular.g=m.specG + material.specular.b=m.specB + # ambient + material.ambient.r=m.mirR + material.ambient.g=m.mirG + material.ambient.b=m.mirB + # flag + material.flag=1 if m.enableSSS else 0 + + def toCP932(s): + return s + + +else: + # for 2.5 + import bpy + import mathutils + + # wrapper + import bl25 as bl + + xrange=range + + def setMaterialParams(material, m): + # diffuse + material.diffuse.r=m.diffuse_color[0] + material.diffuse.g=m.diffuse_color[1] + material.diffuse.b=m.diffuse_color[2] + material.diffuse.a=m.alpha + # specular + material.shinness=0 if m.specular_toon_size<1e-5 else m.specular_hardness*10 + material.specular.r=m.specular_color[0] + material.specular.g=m.specular_color[1] + material.specular.b=m.specular_color[2] + # ambient + material.ambient.r=m.mirror_color[0] + material.ambient.g=m.mirror_color[1] + material.ambient.b=m.mirror_color[2] + # flag + material.flag=1 if m.subsurface_scattering.enabled else 0 + # toon + material.toon_index=7 + + 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 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): + if isBlender24(): + self.offsets.sort(lambda l, r: l[0]-r[0]) + else: + 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 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を除外する + if isBlender24(): + for name in bl.object.getVertexGroupNames(obj): + for i, w in mesh.getVertsFromGroup(name, 1): + setWeight(i, name, w) + else: + for i, v in enumerate(mesh.verts): + if len(v.groups)>0: + for g in v.groups: + setWeight(i, obj.vertex_groups[g.group].name, g.weight) + else: + setWeight(i, obj.vertex_groups[0].name, 1) + + # 合計値が1になるようにする + for i in xrange(len(mesh.verts)): + 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): + # 各面の処理 + for i, face in enumerate(mesh.faces): + faceVertexCount=bl.face.getVertexCount(face) + material=mesh.materials[bl.face.getMaterialIndex(face)] + v=[mesh.verts[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 isBlender24(): + pass + else: + if RIGID_SHAPE_TYPE in obj: + return + if CONSTRAINT_A in obj: + return + + #if not bl.modifier.hasType(obj, 'ARMATURE'): + # return + + bl.message("export: %s" % obj.name) + + # メッシュのコピーを生成してオブジェクトの行列を適用する + copyMesh, copyObj=bl.object.duplicate(obj) + if len(copyMesh.verts)>0: + # apply transform + 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() + # 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, MMD_SHAPE_GROUP_NAME) + + # base + used=set() + for b in bl.object.getShapeKeys(obj): + if b.name==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==BASE_SHAPE_NAME: + continue + + print(b.name) + morph=self.__getOrCreateMorph(b.name, 4) + used=set() + for index, src, dst in zip( + xrange(len(blenderMesh.verts)), + 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) + if isBlender24(): + self.ik_list.sort(lambda l, r: getIndex(l)-getIndex(r)) + else: + 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) + + if isBlender24(): + self.bones.sort(lambda l, r: getIndex(l)-getIndex(r)) + else: + 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: + return self.getIndex(self.__boneByName(name)) + + 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[MMD_COMMENT] if MMD_COMMENT in o else 'blender export\n' + self.name=o[MMD_MB_NAME] if MMD_MB_NAME in o else 'Blenderエクスポート' + self.comment=o[MMD_MB_COMMENT] if 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): + io=pmd.IO() + io.setName(toCP932(self.name)) + io.setComment(toCP932(self.comment)) + io.version=1.0 + + # 頂点 + for pos, attribute, b0, b1, weight in self.oneSkinMesh.vertexArray.zip(): + # convert right-handed z-up to left-handed y-up + v=io.addVertex() + v.pos.x=pos[0] + v.pos.y=pos[2] + v.pos.z=pos[1] + v.normal.x=attribute.nx + v.normal.y=attribute.ny + v.normal.z=attribute.nz + v.uv.x=attribute.u + v.uv.y=1.0-attribute.v # reverse vertical + v.bone0=self.skeleton.indexByName(b0) + v.bone1=self.skeleton.indexByName(b1) + v.weight0=int(100*weight) + v.edge_flag=0 # edge flag, 0: enable edge, 1: not edge + + # 面とマテリアル + vertexCount=self.oneSkinMesh.getVertexCount() + for material_name, indices in self.oneSkinMesh.vertexArray.each(): + #print('material:', material_name) + m=bl.material.get(material_name) + # マテリアル + material=io.addMaterial() + setMaterialParams(material, m) + + material.vertex_count=len(indices) + material.toon_index=0 + textures=[os.path.basename(path) + for path in bl.material.eachEnalbeTexturePath(m)] + if len(textures)>0: + material.setTexture(toCP932('*'.join(textures))) + else: + material.setTexture(toCP932("")) + # 面 + for i in indices: + assert(i=3: + # has type + if v[2]==5: + b.ik_index=self.skeleton.indexByName('eyes') + bone.type=v[2] + 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 + + # IK + for ik in self.skeleton.ik_list: + solver=io.addIK() + 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 + + # 表情 + for i, m in enumerate(self.oneSkinMesh.morphList): + # morph + morph=io.addMorph() + + v=englishmap.getUnicodeSkinName(m.name) + if not v: + v=[m.name, m.name, 0] + assert(v) + cp932=v[1].encode('cp932') + morph.setName(cp932) + morph.setEnglishName(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: + io.face_list.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==2: + io.face_list.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==1: + io.face_list.append(i) + for i, m in enumerate(self.oneSkinMesh.morphList): + if m.type==4: + io.face_list.append(i) + + # ボーングループ + for g in self.skeleton.bone_groups: + boneDisplayName=io.addBoneGroup() + # name + name=englishmap.getUnicodeBoneGroupName(g[0]) + if not name: + name=g[0] + boneDisplayName.setName(toCP932(name+'\n')) + # english + englishName=g[0] + boneDisplayName.setEnglishName(toCP932(englishName+'\n')) + + # ボーングループメンバー + for i, b in enumerate(self.skeleton.bones): + if i==0: + continue + if b.type in [6, 7]: + continue + io.addBoneDisplay(i, self.skeleton.getBoneGroup(b)) + + #assert(len(io.bones)==len(io.bone_display_list)+1) + + # English + io.setEnglishName(toCP932(self.englishName)) + io.setEnglishComment(toCP932(self.englishComment)) + + # toon + toonMeshObject=None + for o in bl.object.each(): + try: + if o.name.startswith(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: + io.getToonTexture(i).setName(toCP932(t.name)) + else: + io.getToonTexture(i).setName(toCP932("toon%02d.bmp\n" % i)) + else: + for i in range(10): + io.getToonTexture(i).setName(toCP932("toon%02d.bmp\n" % i)) + + # rigid body + rigidNameMap={} + for i, obj in enumerate(self.oneSkinMesh.rigidbodies): + name=obj[RIGID_NAME] if RIGID_NAME in obj else obj.name + print(name) + rigidBody=io.addRigidBody() + rigidBody.setName(name.encode('cp932')) + rigidNameMap[name]=i + boneIndex=boneNameMap[obj[RIGID_BONE_NAME]] + if boneIndex==0: + boneIndex=0xFFFF + bone=self.skeleton.bones[0] + else: + bone=self.skeleton.bones[boneIndex] + rigidBody.boneIndex=boneIndex + #rigidBody.position.x=obj[RIGID_LOCATION][0] + #rigidBody.position.y=obj[RIGID_LOCATION][1] + #rigidBody.position.z=obj[RIGID_LOCATION][2] + rigidBody.position.x=obj.location.x-bone.pos[0] + rigidBody.position.y=obj.location.z-bone.pos[2] + rigidBody.position.z=obj.location.y-bone.pos[1] + rigidBody.rotation.x=-obj.rotation_euler[0] + rigidBody.rotation.y=-obj.rotation_euler[2] + rigidBody.rotation.z=-obj.rotation_euler[1] + rigidBody.processType=obj[RIGID_PROCESS_TYPE] + rigidBody.group=obj[RIGID_GROUP] + rigidBody.target=obj[RIGID_INTERSECTION_GROUP] + rigidBody.weight=obj[RIGID_WEIGHT] + rigidBody.linearDamping=obj[RIGID_LINEAR_DAMPING] + rigidBody.angularDamping=obj[RIGID_ANGULAR_DAMPING] + rigidBody.restitution=obj[RIGID_RESTITUTION] + rigidBody.friction=obj[RIGID_FRICTION] + if obj[RIGID_SHAPE_TYPE]==0: + rigidBody.shapeType=pmd.SHAPE_SPHERE + rigidBody.w=obj.scale[0] + elif obj[RIGID_SHAPE_TYPE]==1: + rigidBody.shapeType=pmd.SHAPE_BOX + rigidBody.w=obj.scale[0] + rigidBody.d=obj.scale[1] + rigidBody.h=obj.scale[2] + elif obj[RIGID_SHAPE_TYPE]==2: + rigidBody.shapeType=pmd.SHAPE_CAPSULE + rigidBody.w=obj.scale[0] + rigidBody.h=obj.scale[2] + + # constraint + for obj in self.oneSkinMesh.constraints: + constraint=io.addConstraint() + constraint.setName(obj[CONSTRAINT_NAME].encode('cp932')) + constraint.rigidA=rigidNameMap[obj[CONSTRAINT_A]] + constraint.rigidB=rigidNameMap[obj[CONSTRAINT_B]] + constraint.pos.x=obj.location[0] + constraint.pos.y=obj.location[2] + constraint.pos.z=obj.location[1] + constraint.rot.x=-obj.rotation_euler[0] + constraint.rot.y=-obj.rotation_euler[2] + constraint.rot.z=-obj.rotation_euler[1] + constraint.constraintPosMin.x=obj[CONSTRAINT_POS_MIN][0] + constraint.constraintPosMin.y=obj[CONSTRAINT_POS_MIN][1] + constraint.constraintPosMin.z=obj[CONSTRAINT_POS_MIN][2] + constraint.constraintPosMax.x=obj[CONSTRAINT_POS_MAX][0] + constraint.constraintPosMax.y=obj[CONSTRAINT_POS_MAX][1] + constraint.constraintPosMax.z=obj[CONSTRAINT_POS_MAX][2] + constraint.constraintRotMin.x=obj[CONSTRAINT_ROT_MIN][0] + constraint.constraintRotMin.y=obj[CONSTRAINT_ROT_MIN][1] + constraint.constraintRotMin.z=obj[CONSTRAINT_ROT_MIN][2] + constraint.constraintRotMax.x=obj[CONSTRAINT_ROT_MAX][0] + constraint.constraintRotMax.y=obj[CONSTRAINT_ROT_MAX][1] + constraint.constraintRotMax.z=obj[CONSTRAINT_ROT_MAX][2] + constraint.springPos.x=obj[CONSTRAINT_SPRING_POS][0] + constraint.springPos.y=obj[CONSTRAINT_SPRING_POS][1] + constraint.springPos.z=obj[CONSTRAINT_SPRING_POS][2] + constraint.springRot.x=obj[CONSTRAINT_SPRING_ROT][0] + constraint.springRot.y=obj[CONSTRAINT_SPRING_ROT][1] + constraint.springRot.z=obj[CONSTRAINT_SPRING_ROT][2] + + # 書き込み + bl.message('write: %s' % path) + return io.write(path) + + +def __execute(filename): + active=bl.object.getActive() + if not active: + print("abort. no active object.") + return + exporter=PmdExporter() + exporter.setup() + print(exporter) + exporter.write(filename) + bl.object.activate(active) + + +if isBlender24(): + # for 2.4 + def execute_24(filename): + bl.initialize('pmd_export', bpy.data.scenes.active) + __execute(filename.decode(bl.INTERNAL_ENCODING)) + bl.finalize() + + Blender.Window.FileSelector( + execute_24, + 'Export Metasequoia PMD', + Blender.sys.makename(ext='.pmd')) + +else: + # for 2.5 + def execute_25(filename, scene): + bl.initialize('pmd_export', scene) + __execute(filename) + bl.finalize() + + # operator + class EXPORT_OT_pmd(bpy.types.Operator): + '''Save a Metasequoia PMD file.''' + bl_idname = "export_scene.pmd" + bl_label = 'Export PMD' + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + filepath = bpy.props.StringProperty() + filename = bpy.props.StringProperty() + directory = bpy.props.StringProperty() + + def execute(self, context): + execute_25( + self.properties.filepath, + context.scene + ) + return 'FINISHED' + + def invoke(self, context, event): + wm=context.window_manager + try: + wm.fileselect_add(self) + except: + wm.add_fileselect(self) + return 'RUNNING_MODAL' + + # register menu + def menu_func(self, context): + default_path=bpy.data.filepath.replace(".blend", ".pmd") + self.layout.operator( + EXPORT_OT_pmd.bl_idname, + text="Miku Miku Dance Model(.pmd)", + icon='PLUGIN' + ).filepath=default_path + + def register(): + bpy.types.INFO_MT_file_export.append(menu_func) + + def unregister(): + bpy.types.INFO_MT_file_export.remove(menu_func) + + if __name__ == "__main__": + register() + diff --git a/pymeshio/io_import_scene_mqo.py b/pymeshio/io_import_scene_mqo.py new file mode 100755 index 0000000..e4addea --- /dev/null +++ b/pymeshio/io_import_scene_mqo.py @@ -0,0 +1,722 @@ +#!BPY +# coding: utf-8 +""" +Name: 'Metasequoia(.mqo)...' +Blender: 245 +Group: 'Import' +Tooltip: 'Import from Metasequoia file format (.mqo)' +""" +__author__=['ousttrue'] +__url__ = ["http://gunload.web.fc2.com/blender/"] +__version__= '2.2' +__bpydoc__= '''\ + +MQO Importer + +This script imports a mqo into Blender for editing. + +0.2 20080123: update. +0.3 20091125: modify for linux. +0.4 20100310: rewrite. +0.5 20100311: create armature from mikoto bone. +0.6 20100505: C extension. +0.7 20100606: integrate 2.4 and 2.5. +0.8 20100619: fix multibyte object name. +0.9 20100626: refactoring. +2.0 20100724: update for Blender2.53. +2.1 20100731: add full python module. +2.2 20101005: update for Blender2.54. +2.3 20101228: update for Blender2.55. +''' + +bl_addon_info = { + 'category': 'Import/Export', + 'name': 'Import: Metasequioa Model Format (.mqo)', + 'author': 'ousttrue', + 'version': (2, 0), + 'blender': (2, 5, 3), + 'location': 'File > Import', + 'description': 'Import from the Metasequioa Model Format (.mqo)', + '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 os +import sys + +try: + # C extension + from meshio import mqo + print('use meshio C module') +except ImportError: + # full python + from pymeshio import mqo + +def isBlender24(): + return sys.version_info[0]<3 + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # wrapper + import bl24 as bl + + def createMqoMaterial(m): + material = Blender.Material.New( + m.getName().encode(bl.INTERNAL_ENCODING)) + #material.mode |= Blender.Material.Modes.SHADELESS + # diffuse + material.rgbCol = [m.color.r, m.color.g, m.color.b] + material.alpha = m.color.a + # other + material.amb=m.ambient + material.spec=m.specular + material.hard=int(255 * m.power) + material.emit=m.emit + return material + +else: + # for 2.5 + import bpy + + # wrapper + import bl25 as bl + + def createMqoMaterial(m): + material = bpy.data.materials.new(m.getName()) + # shader + if m.shader==1: + material.diffuse_shader='FRESNEL' + else: + material.diffuse_shader='LAMBERT' + # diffuse + material.diffuse_color=[m.color.r, m.color.g, m.color.b] + material.diffuse_intensity=m.diffuse + material.alpha=m.color.a + # other + material.ambient = m.ambient + #material.specular = m.specular + material.emit=m.emit + material.use_shadeless=True + return material + + +def has_mikoto(mqo): + #for o in mqo.objects: + # if o.getName().startswith('bone'): + # return True + # if o.getName().startswith('sdef'): + # return True + # if o.getName().startswith('anchor'): + # return True + return False + + +def __createMaterials(mqo, directory): + """ + create blender materials and renturn material list. + """ + materials = [] + textureMap={} + imageMap={} + if len(mqo.materials)>0: + for material_index, m in enumerate(mqo.materials): + # material + material=createMqoMaterial(m) + materials.append(material) + # texture + texture_name=m.getTexture() + if texture_name!='': + if texture_name in textureMap: + texture=textureMap[texture_name] + else: + # load texture image + if os.path.isabs(texture_name): + # absolute + path = texture_name + else: + # relative + path = os.path.join(directory, texture_name) + # texture + path=path.replace("\\", "/") + if os.path.exists(path): + print("create texture:", path) + texture, image=bl.texture.create(path) + textureMap[texture_name]=texture + imageMap[material_index]=image + else: + print("%s not exits" % path) + continue + bl.material.addTexture(material, texture) + else: + # default material + pass + return materials, imageMap + + +def __createObjects(mqo, root, materials, imageMap, scale): + """ + create blender mesh objects. + """ + # tree stack + stack=[root] + objects=[] + for o in mqo.objects: + mesh, mesh_object=bl.mesh.create(o.getName()) + + # add hierarchy + stack_depth=len(stack)-1 + #print(o.depth, stack_depth) + if o.depth=16: + # split a mesh ? + print("over 16 materials!") + break + bl.mesh.addMaterial(mesh, materials[material_index]) + materialMap[material_index]=i + + # set face params + assert(len(o.faces)==len(mesh.faces)) + bl.mesh.addUV(mesh) + for i, (f, face) in enumerate(zip(o.faces, mesh.faces)): + uv_array=[] + # ToDo FIX + # flip face + for j in reversed(range(f.index_count)): + uv_array.append((f.getUV(j).x, 1.0-f.getUV(j).y)) + bl.mesh.setFaceUV(mesh, i, face, uv_array, + imageMap.get(f.material_index, None)) + if f.material_index in materialMap: + bl.face.setMaterial(face, materialMap[f.material_index]) + bl.face.setSmooth(face, True) + + # mirror modifier + if o.mirror: + bl.modifier.addMirror(mesh_object) + + # set smoothing + bl.mesh.setSmooth(mesh, o.smoothing) + + # calc normal + bl.mesh.recalcNormals(mesh_object) + + return objects + + +############################################################################### +# for mqo mikoto bone. +############################################################################### +class MikotoBone(object): + __slots__=[ + 'name', + 'iHead', 'iTail', 'iUp', + 'vHead', 'vTail', 'vUp', + 'parent', 'isFloating', + 'children', + ] + def __init__(self, face=None, vertices=None, materials=None): + self.parent=None + self.isFloating=False + self.children=[] + if not face: + self.name='root' + return + + self.name=materials[face.material_index].name.encode('utf-8') + + i0=face.getIndex(0) + i1=face.getIndex(1) + i2=face.getIndex(2) + v0=vertices[i0] + v1=vertices[i1] + v2=vertices[i2] + e01=v1-v0 + e12=v2-v1 + e20=v0-v2 + sqNorm0=e01.getSqNorm() + sqNorm1=e12.getSqNorm() + sqNorm2=e20.getSqNorm() + if sqNorm0>sqNorm1: + if sqNorm1>sqNorm2: + # e01 > e12 > e20 + self.iHead=i2 + self.iTail=i1 + self.iUp=i0 + else: + if sqNorm0>sqNorm2: + # e01 > e20 > e12 + self.iHead=i2 + self.iTail=i0 + self.iUp=i1 + else: + # e20 > e01 > e12 + self.iHead=i1 + self.iTail=i0 + self.iUp=i2 + else: + # 0 < 1 + if sqNorm1 e12 > e01 + self.iHead=i1 + self.iTail=i2 + self.iUp=i0 + else: + if sqNorm0 e20 > e01 + self.iHead=i0 + self.iTail=i2 + self.iUp=i1 + else: + # e12 > e01 > e20 + self.iHead=i0 + self.iTail=i1 + self.iUp=i2 + self.vHead=vertices[self.iHead] + self.vTail=vertices[self.iTail] + self.vUp=vertices[self.iUp] + + if self.name.endswith('[]'): + basename=self.name[0:-2] + # expand LR name + if self.vTail.x>0: + self.name="%s_L" % basename + else: + self.name="%s_R" % basename + + + def setParent(self, parent, floating=False): + if floating: + self.isFloating=True + self.parent=parent + parent.children.append(self) + + def printTree(self, indent=''): + print("%s%s" % (indent, self.name)) + for child in self.children: + child.printTree(indent+' ') + + +def build_armature(armature, mikotoBone, parent=None): + """ + create a armature bone. + """ + bone = Armature.Editbone() + bone.name = mikotoBone.name.encode('utf-8') + armature.bones[bone.name] = bone + + bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a()) + bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a()) + if parent: + bone.parent=parent + if mikotoBone.isFloating: + pass + else: + bone.options=[Armature.CONNECTED] + + for child in mikotoBone.children: + build_armature(armature, child, bone) + + +def create_armature(mqo): + """ + create armature + """ + boneObject=None + for o in mqo.objects: + if o.name.startswith('bone'): + boneObject=o + break + if not boneObject: + return + + tailMap={} + for f in boneObject.faces: + if f.index_count!=3: + print("invalid index_count: %d" % f.index_count) + continue + b=MikotoBone(f, boneObject.vertices, mqo.materials) + tailMap[b.iTail]=b + + #################### + # build mikoto bone tree + #################### + mikotoRoot=MikotoBone() + + for b in tailMap.values(): + # each bone has unique parent or is root bone. + if b.iHead in tailMap: + b.setParent(tailMap[b.iHead]) + else: + isFloating=False + for e in boneObject.edges: + if b.iHead==e.indices[0]: + # floating bone + if e.indices[1] in tailMap: + b.setParent(tailMap[e.indices[1]], True) + isFloating=True + break + elif b.iHead==e.indices[1]: + # floating bone + if e.indices[0] in tailMap: + b.setParent(tailMap[e.indices[0]], True) + isFloating=True + break + if isFloating: + continue + + # no parent bone + b.setParent(mikotoRoot, True) + + if len(mikotoRoot.children)==0: + print("no root bone") + return + + if len(mikotoRoot.children)==1: + # single root + mikotoRoot=mikotoRoot.children[0] + mikotoRoot.parent=None + else: + mikotoRoot.vHead=Vector3(0, 10, 0) + mikotoRoot.vTail=Vector3(0, 0, 0) + + #################### + # create armature + #################### + armature = Armature.New() + # link to object + armature_object = scene.objects.new(armature) + # create action + act = Armature.NLA.NewAction() + act.setActive(armature_object) + # set XRAY + armature_object.drawMode |= Object.DrawModes.XRAY + # armature settings + armature.drawType = Armature.OCTAHEDRON + armature.envelopes = False + armature.vertexGroups = True + armature.mirrorEdit = True + armature.drawNames=True + + # edit bones + armature.makeEditable() + build_armature(armature, mikotoRoot) + armature.update() + + return armature_object + + +class TrianglePlane(object): + """ + mikoto$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#(B + ($BIT40A4(B) + """ + __slots__=['normal', + 'v0', 'v1', 'v2', + ] + def __init__(self, v0, v1, v2): + self.v0=v0 + self.v1=v1 + self.v2=v2 + + def isInsideXY(self, p): + v0=Vector2(self.v0.x, self.v0.y) + v1=Vector2(self.v1.x, self.v1.y) + v2=Vector2(self.v2.x, self.v2.y) + e01=v1-v0 + e12=v2-v1 + e20=v0-v2 + c0=Vector2.cross(e01, p-v0) + c1=Vector2.cross(e12, p-v1) + c2=Vector2.cross(e20, p-v2) + if c0>=0 and c1>=0 and c2>=0: + return True + if c0<=0 and c1<=0 and c2<=0: + return True + + def isInsideYZ(self, p): + v0=Vector2(self.v0.y, self.v0.z) + v1=Vector2(self.v1.y, self.v1.z) + v2=Vector2(self.v2.y, self.v2.z) + e01=v1-v0 + e12=v2-v1 + e20=v0-v2 + c0=Vector2.cross(e01, p-v0) + c1=Vector2.cross(e12, p-v1) + c2=Vector2.cross(e20, p-v2) + if c0>=0 and c1>=0 and c2>=0: + return True + if c0<=0 and c1<=0 and c2<=0: + return True + + def isInsideZX(self, p): + v0=Vector2(self.v0.z, self.v0.x) + v1=Vector2(self.v1.z, self.v1.x) + v2=Vector2(self.v2.z, self.v2.x) + e01=v1-v0 + e12=v2-v1 + e20=v0-v2 + c0=Vector2.cross(e01, p-v0) + c1=Vector2.cross(e12, p-v1) + c2=Vector2.cross(e20, p-v2) + if c0>=0 and c1>=0 and c2>=0: + return True + if c0<=0 and c1<=0 and c2<=0: + return True + + +class MikotoAnchor(object): + """ + mikoto$BJ}<0%9%1%k%H%s$N%"%s%+!0): + # L + name_L=basename+'_L' + if not name_L in anchorMap: + anchorMap[name_L]=MikotoAnchor() + anchorMap[name_L].push(f, o.vertices) + elif(v.x<0): + # R + name_R=basename+'_R' + if not name_R in anchorMap: + anchorMap[name_R]=MikotoAnchor() + anchorMap[name_R].push(f, o.vertices) + else: + print("no side", v) + else: + if not name in anchorMap: + anchorMap[name]=MikotoAnchor() + anchorMap[name].push(f, o.vertices) + + for o in objects: + # add armature modifier + mod=o.modifiers.append(Modifier.Types.ARMATURE) + mod[Modifier.Settings.OBJECT] = armature_object + mod[Modifier.Settings.ENVELOPES] = False + o.makeDisplayList() + # create vertex group + mesh=o.getData(mesh=True) + for name in anchorMap.keys(): + mesh.addVertGroup(name) + mesh.update() + + # assing vertices to vertex group + for o in objects: + mesh=o.getData(mesh=True) + for i, mvert in enumerate(mesh.verts): + hasWeight=False + for name, anchor in anchorMap.items(): + weight=anchor.calcWeight(mvert.co) + if weight>0: + mesh.assignVertsToGroup( + name, [i], weight, Mesh.AssignModes.ADD) + hasWeight=True + if not hasWeight: + # debug orphan vertex + print('orphan', mvert) + mesh.update() + + +def __execute(filename, scene, scale=0.1): + # parse file + io=mqo.IO() + if not io.read(filename): + bl.message("fail to load %s" % filename) + return + + # create materials + materials, imageMap=__createMaterials(io, os.path.dirname(filename)) + if len(materials)==0: + materials.append(bl.material.create('default')) + + # create objects + root=bl.object.createEmpty(os.path.basename(filename)) + objects=__createObjects(io, root, materials, imageMap, scale) + + if has_mikoto(io): + # create mikoto bone + armature_object=create_armature(io) + if armature_object: + root.makeParent([armature_object]) + + # create bone weight + create_bone_weight(io, armature_object, objects) + + +############################################################################### +# register +############################################################################### +if isBlender24(): + # for 2.4 + def execute_24(filename): + scene=Blender.Scene.GetCurrent() + bl.initialize('mqo_import', scene) + __execute( + filename.decode(bl.INTERNAL_ENCODING), + scene) + bl.finalize() + + # execute + Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo') + +else: + # for 2.5 + def execute_25(filename, scene, scale): + bl.initialize('mqo_import', scene) + __execute(filename, scene, scale) + bl.finalize() + + # operator + class IMPORT_OT_mqo(bpy.types.Operator): + '''Import from Metasequoia file format (.mqo)''' + bl_idname = "import_scene.mqo" + bl_label = 'Import MQO' + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + filepath = bpy.props.StringProperty() + filename = bpy.props.StringProperty() + directory = bpy.props.StringProperty() + + scale = bpy.props.FloatProperty( + name="Scale", + description="Scale the MQO by this value", + min=0.0001, max=1000000.0, + soft_min=0.001, soft_max=100.0, default=0.1) + + def execute(self, context): + execute_25( + self.properties.filepath, + context.scene, + self.properties.scale) + return 'FINISHED' + + def invoke(self, context, event): + wm=context.window_manager + try: + wm.fileselect_add(self) + except: + wm.add_fileselect(self) + return 'RUNNING_MODAL' + + + # register menu + def menu_func(self, context): + self.layout.operator( + IMPORT_OT_mqo.bl_idname, + text="Metasequoia (.mqo)", + icon='PLUGIN' + ) + + def register(): + bpy.types.INFO_MT_file_import.append(menu_func) + + def unregister(): + bpy.types.INFO_MT_file_import.remove(menu_func) + + if __name__=="__main__": + register() + diff --git a/pymeshio/io_import_scene_pmd.py b/pymeshio/io_import_scene_pmd.py new file mode 100755 index 0000000..14c056d --- /dev/null +++ b/pymeshio/io_import_scene_pmd.py @@ -0,0 +1,950 @@ +#!BPY +# coding:utf-8 +""" + Name: 'MikuMikuDance model (.pmd)...' + Blender: 248 + Group: 'Import' + Tooltip: 'Import PMD file for MikuMikuDance.' +""" +__author__= ["ousttrue"] +__version__= "2.2" +__url__=() +__bpydoc__=""" +pmd Importer + +This script imports a pmd into Blender for editing. + +0.1 20091126: first implement. +0.2 20091209: implement IK. +0.3 20091210: implement morph target. +0.4 20100305: use english name. +0.5 20100408: cleanup not used vertices. +0.6 20100416: fix fornt face. texture load fail safe. add progress. +0.7 20100506: C extension. +0.8 20100521: add shape_key group. +1.0 20100530: add invisilbe bone tail(armature layer 2). +1.1 20100608: integrate 2.4 and 2.5. +1.2 20100616: implement rigid body. +1.3 20100619: fix for various models. +1.4 20100623: fix constraint name. +1.5 20100626: refactoring. +1.6 20100629: sphere map. +1.7 20100703: implement bone group. +1.8 20100710: implement toon texture. +1.9 20100718: keep model name, comment. +2.0 20100724: update for Blender2.53. +2.1 20100731: add full python module. +2.2 20101005: update for Blender2.54. +2.3 20101228: update for Blender2.55. +""" +bl_addon_info = { + 'category': 'Import/Export', + 'name': 'Import: MikuMikuDance Model Format (.pmd)', + 'author': 'ousttrue', + 'version': (2, 2), + 'blender': (2, 5, 3), + 'location': 'File > Import', + 'description': 'Import from 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', + } + +MMD_SHAPE_GROUP_NAME='_MMD_SHAPE' +MMD_MB_NAME='mb_name' +MMD_MB_COMMENT='mb_comment' +MMD_COMMENT='comment' +BASE_SHAPE_NAME='Basis' +RIGID_NAME='rigid_name' +RIGID_SHAPE_TYPE='rigid_shape_type' +RIGID_PROCESS_TYPE='rigid_process_type' +RIGID_BONE_NAME='rigid_bone_name' +#RIGID_LOCATION='rigid_loation' +RIGID_GROUP='ribid_group' +RIGID_INTERSECTION_GROUP='rigid_intersection_group' +RIGID_WEIGHT='rigid_weight' +RIGID_LINEAR_DAMPING='rigid_linear_damping' +RIGID_ANGULAR_DAMPING='rigid_angular_damping' +RIGID_RESTITUTION='rigid_restitution' +RIGID_FRICTION='rigid_friction' +CONSTRAINT_NAME='constraint_name' +CONSTRAINT_A='const_a' +CONSTRAINT_B='const_b' +CONSTRAINT_POS_MIN='const_pos_min' +CONSTRAINT_POS_MAX='const_pos_max' +CONSTRAINT_ROT_MIN='const_rot_min' +CONSTRAINT_ROT_MAX='const_rot_max' +CONSTRAINT_SPRING_POS='const_spring_pos' +CONSTRAINT_SPRING_ROT='const_spring_rot' +TOON_TEXTURE_OBJECT='ToonTextures' + + +############################################################################### +# import +############################################################################### +import os +import sys +import math + +try: + # C extension + from meshio import pmd, englishmap + print('use meshio C module') +except ImportError: + # full python + from pymeshio import englishmap + from pymeshio import mmd as pmd + pmd.IO=pmd.PMDLoader + +def isBlender24(): + return sys.version_info[0]<3 + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # wrapper + import bl24 as bl + + def createPmdMaterial(m, index): + material=Blender.Material.New() + # fresnelが無いw + material.setDiffuseShader(Blender.Material.Shaders.DIFFUSE_TOON) + material.setRGBCol([m.diffuse.r, m.diffuse.g, m.diffuse.b]) + material.setAlpha(m.diffuse.a) + # specular + material.setSpecShader(Blender.Material.Shaders.SPEC_TOON) + material.setSpec(m.shinness*0.1) + material.setSpecCol([m.specular.r, m.specular.g, m.specular.b]) + # ambient + material.setMirCol([m.ambient.r, m.ambient.g, m.ambient.b]) + # flag + material.enableSSS=True if m.flag==1 else False + # name + material.name="m_%02d" % index + return material + + def poseBoneLimit(n, b): + if n.endswith("_t"): + return + if n.startswith("knee_"): + b.lockYRot=True + b.lockZRot=True + b.limitX=True + b.limitMin=[0, 0, 0] + b.limitMax=[180, 0, 0] + elif n.startswith("ankle_"): + b.lockYRot=True + + def setSphereMap(material, index, blend_type='MULTIPLY'): + slot=material.textures[index] + slot.mapto=Blender.Texture.MapTo.NOR + slot.mapping=Blender.Texture.Mappings.SPHERE + if blend_type=='MULTIPLY': + slot.blendmode=Blender.Texture.BlendModes.MULTIPLY + elif blend_type=='ADD': + slot.blendmode=Blender.Texture.BlendModes.ADD + +else: + # for 2.5 + import bpy + import mathutils + + # wrapper + import bl25 as bl + + xrange=range + + def createPmdMaterial(m, index): + material = bpy.data.materials.new("Material") + # diffuse + material.diffuse_shader='FRESNEL' + material.diffuse_color=([m.diffuse.r, m.diffuse.g, m.diffuse.b]) + material.alpha=m.diffuse.a + # specular + material.specular_shader='TOON' + material.specular_color=([m.specular.r, m.specular.g, m.specular.b]) + material.specular_toon_size=int(m.shinness) + # ambient + material.mirror_color=([m.ambient.r, m.ambient.g, m.ambient.b]) + # flag + material.subsurface_scattering.use=True if m.flag==1 else False + # other + material.name="m_%02d" % index + material.preview_render_type='FLAT' + material.use_transparency=True + return material + + def poseBoneLimit(n, b): + if n.endswith("_t"): + return + if n.startswith("knee_"): + b.lock_ik_y=True + b.lock_ik_z=True + b.lock_ik_x=False + # IK limit + b.use_ik_limit_x=True + b.ik_min_x=0 + b.ik_max_x=180 + elif n.startswith("ankle_"): + #b.ik_dof_y=False + pass + + def setSphereMap(material, index, blend_type='MULTIPLY'): + slot=material.texture_slots[index] + slot.texture_coords='NORMAL' + slot.mapping='SPHERE' + slot.blend_type=blend_type + + +############################################################################### +def VtoV(v): + return bl.createVector(v.x, v.y, v.z) + + +def convert_coord(pos): + """ + Left handed y-up to Right handed z-up + """ + return (pos.x, pos.z, pos.y) + + +def to_radian(degree): + return math.pi * degree / 180 + + +def get_bone_name(l, index): + if index==0xFFFF: + return l.bones[0].getName() + + if index < len(l.bones): + name=englishmap.getEnglishBoneName(l.bones[index].getName()) + if name: + return name + return l.bones[index].getName() + print('invalid bone index', index) + return l.bones[0].getName() + + +def get_group_name(g): + group_name=englishmap.getEnglishBoneGroupName(g.getName().strip()) + if not group_name: + group_name=g.getName().strip() + return group_name + + +def __importToonTextures(io, tex_dir): + mesh, meshObject=bl.mesh.create(TOON_TEXTURE_OBJECT) + material=bl.material.create(TOON_TEXTURE_OBJECT) + bl.mesh.addMaterial(mesh, material) + for i in range(10): + t=io.getToonTexture(i) + path=os.path.join(tex_dir, t.getName()) + texture, image=bl.texture.create(path) + bl.material.addTexture(material, texture, False) + return meshObject, material + + +def __importShape(obj, l, vertex_map): + if len(l.morph_list)==0: + return + + # set shape_key pin + bl.object.pinShape(obj, True) + + # find base + base=None + for s in l.morph_list: + if s.type==0: + base=s + + # create vertex group + bl.object.addVertexGroup(obj, MMD_SHAPE_GROUP_NAME) + hasShape=False + for i in s.indices: + if i in vertex_map: + hasShape=True + bl.object.assignVertexGroup( + obj, MMD_SHAPE_GROUP_NAME, vertex_map[i], 0) + if not hasShape: + return + assert(base) + + # create base key + baseShapeBlock=bl.object.addShapeKey(obj, BASE_SHAPE_NAME) + # mesh + mesh=bl.object.getData(obj) + mesh.update() + + # each skin + for s in l.morph_list: + if s.type==0: + continue + + # name + name=englishmap.getEnglishSkinName(s.getName()) + if not name: + name=s.getName() + + if isBlender24(): + # 24 + for index, offset in zip(s.indices, s.pos_list): + try: + vertex_index=vertex_map[base.indices[index]] + v=mesh.vertices[vertex_index].co + offset=convert_coord(offset) + v[0]+=offset[0] + v[1]+=offset[1] + v[2]+=offset[2] + except IndexError as msg: + print(msg) + print(index, len(base.indices), len(vertex_map)) + print(len(mesh.vertices)) + print(base.indices[index]) + print(vertex_index) + break + except KeyError: + #print 'this mesh not has shape vertices' + break + + # create shapekey block + new_shape_key=bl.object.addShapeKey(obj, name) + + # copy vertex to shape key + mesh.update() + + # restore + for mv, v in zip(mesh.vertices, baseShapeBlock.getData()): + mv.co[0] = v[0] + mv.co[1] = v[1] + mv.co[2] = v[2] + mesh.update() + + else: + # 25 + new_shape_key=bl.object.addShapeKey(obj, name) + + for index, offset in zip(s.indices, s.pos_list): + try: + vertex_index=vertex_map[base.indices[index]] + bl.shapekey.assign(new_shape_key, vertex_index, + mesh.vertices[vertex_index].co+ + bl.createVector(*convert_coord(offset))) + except IndexError as msg: + print(msg) + print(index, len(base.indices), len(vertex_map)) + print(len(mesh.vertices)) + print(base.indices[index]) + print(vertex_index) + break + except KeyError: + #print 'this mesh not has shape vertices' + break + + # select base shape + bl.object.setActivateShapeKey(obj, 0) + + +def __build(armature, b, p, parent): + name=englishmap.getEnglishBoneName(b.getName()) + if not name: + name=b.getName() + + bone=bl.armature.createBone(armature, name) + + if parent and (b.tail_index==0 or b.type==6 or b.type==7 or b.type==9): + # 先端ボーン + bone.head = bl.createVector(*convert_coord(b.pos)) + bone.tail=bone.head+bl.createVector(0, 1, 0) + bone.parent=parent + if bone.name=="center_t": + # センターボーンは(0, 1, 0)の方向を向いていないと具合が悪い + parent.tail=parent.head+bl.createVector(0, 1, 0) + bone.head=parent.tail + bone.tail=bone.head+bl.createVector(0, 1, 0) + else: + if parent.tail==bone.head: + pass + else: + print('diffurence with parent.tail and head', name) + + if b.type!=9: + bl.bone.setConnected(bone) + # armature layer 2 + bl.bone.setLayerMask(bone, [0, 1]) + else: + # 通常ボーン + bone.head = bl.createVector(*convert_coord(b.pos)) + bone.tail = bl.createVector(*convert_coord(b.tail)) + if parent: + bone.parent=parent + if parent.tail==bone.head: + bl.bone.setConnected(bone) + + if bone.head==bone.tail: + bone.tail=bone.head+bl.createVector(0, 1, 0) + + for c in b.children: + __build(armature, c, b, bone) + + +def __importArmature(l): + armature, armature_object=bl.armature.create() + + # build bone + bl.armature.makeEditable(armature_object) + for b in l.bones: + if not b.parent: + __build(armature, b, None, None) + bl.armature.update(armature) + bl.enterObjectMode() + + # IK constraint + pose = bl.object.getPose(armature_object) + for ik in l.ik_list: + target=l.bones[ik.target] + name = englishmap.getEnglishBoneName(target.getName()) + if not name: + name=target.getName() + p_bone = pose.bones[name] + if not p_bone: + print('not found', name) + continue + if len(ik.children) >= 16: + print('over MAX_CHAINLEN', ik, len(ik.children)) + continue + effector_name=englishmap.getEnglishBoneName( + l.bones[ik.index].getName()) + if not effector_name: + effector_name=l.bones[ik.index].getName() + + constraint=bl.armature.createIkConstraint(armature_object, + p_bone, effector_name, ik) + + bl.armature.makeEditable(armature_object) + bl.armature.update(armature) + bl.enterObjectMode() + + if isBlender24(): + pass + else: + # create bone group + for i, g in enumerate(l.bone_group_list): + name=get_group_name(g) + bl.object.createBoneGroup(armature_object, name, "THEME%02d" % (i+1)) + + # assign bone to group + for b_index, g_index in l.bone_display_list: + # bone + b=l.bones[b_index] + bone_name=englishmap.getEnglishBoneName(b.getName()) + if not bone_name: + bone_name=b.getName() + # group + g=l.bone_group_list[g_index-1] + group_name=get_group_name(g) + + # assign + pose.bones[bone_name].bone_group=pose.bone_groups[group_name] + + bl.enterObjectMode() + + return armature_object + + +def __import16MaerialAndMesh(meshObject, l, + material_order, face_map, tex_dir, toon_material): + + mesh=bl.object.getData(meshObject) + ############################################################ + # material + ############################################################ + bl.progress_print('create materials') + mesh_material_map={} + textureMap={} + imageMap={} + index=0 + + for material_index in material_order: + try: + m=l.materials[material_index] + mesh_material_map[material_index]=index + except KeyError: + break + + material=createPmdMaterial(m, material_index) + + # main texture + texture_name=m.getTexture() + if texture_name!='': + for i, t in enumerate(texture_name.split('*')): + if t in textureMap: + texture=textureMap[t] + else: + path=os.path.join(tex_dir, t) + texture, image=bl.texture.create(path) + textureMap[texture_name]=texture + imageMap[material_index]=image + texture_index=bl.material.addTexture(material, texture) + if t.endswith('sph'): + # sphere map + setSphereMap(material, texture_index) + elif t.endswith('spa'): + # sphere map + setSphereMap(material, texture_index, 'ADD') + + # toon texture + toon_index=bl.material.addTexture( + material, + bl.material.getTexture( + toon_material, + 0 if m.toon_index==0xFF else m.toon_index + ), + False) + + bl.mesh.addMaterial(mesh, material) + + index+=1 + + ############################################################ + # vertex + ############################################################ + bl.progress_print('create vertices') + # create vertices + vertices=[] + for v in l.each_vertex(): + vertices.append(convert_coord(v.pos)) + + ############################################################ + # face + ############################################################ + bl.progress_print('create faces') + # create faces + mesh_face_indices=[] + mesh_face_materials=[] + used_vertices=set() + + for material_index in material_order: + face_offset=face_map[material_index] + m=l.materials[material_index] + material_faces=l.indices[face_offset:face_offset+m.vertex_count] + + def degenerate(i0, i1, i2): + """ + 縮退しているか? + """ + return i0==i1 or i1==i2 or i2==i0 + + for j in xrange(0, len(material_faces), 3): + i0=material_faces[j] + i1=material_faces[j+1] + i2=material_faces[j+2] + # flip + triangle=[i2, i1, i0] + if degenerate(*triangle): + continue + mesh_face_indices.append(triangle[0:3]) + mesh_face_materials.append(material_index) + used_vertices.add(i0) + used_vertices.add(i1) + used_vertices.add(i2) + + ############################################################ + # create vertices & faces + ############################################################ + bl.mesh.addGeometry(mesh, vertices, mesh_face_indices) + + ############################################################ + # vertex bone weight + ############################################################ + # create vertex group + vertex_groups={} + for v in l.each_vertex(): + vertex_groups[v.bone0]=True + vertex_groups[v.bone1]=True + for i in vertex_groups.keys(): + bl.object.addVertexGroup(meshObject, get_bone_name(l, i)) + + # vertex params + bl.mesh.useVertexUV(mesh) + for i, v, mvert in zip(xrange(len(l.vertices)), + l.each_vertex(), mesh.vertices): + # normal, uv + bl.vertex.setNormal(mvert, convert_coord(v.normal)) + # bone weight + w1=float(v.weight0)/100.0 + w2=1.0-w1 + bl.object.assignVertexGroup(meshObject, get_bone_name(l, v.bone0), + i, w1) + bl.object.assignVertexGroup(meshObject, get_bone_name(l, v.bone1), + i, w2) + + ############################################################ + # face params + ############################################################ + used_map={} + bl.mesh.addUV(mesh) + for i, (face, material_index) in enumerate( + zip(mesh.faces, mesh_face_materials)): + try: + index=mesh_material_map[material_index] + except KeyError as message: + print(message, mesh_material_map, m) + assert(False) + bl.face.setMaterial(face, index) + material=mesh.materials[index] + used_map[index]=True + if bl.material.hasTexture(material): + uv_array=[l.getUV(i) for i in bl.face.getIndices(face)] + bl.mesh.setFaceUV(mesh, i, face, + # fix uv + [(uv.x, 1.0-uv.y) for uv in uv_array], + imageMap.get(index, None)) + + # set smooth + bl.face.setSmooth(face, True) + + mesh.update() + + ############################################################ + # clean up not used vertices + ############################################################ + bl.progress_print('clean up vertices not used') + remove_vertices=[] + vertex_map={} + for i, v in enumerate(l.each_vertex()): + if i in used_vertices: + vertex_map[i]=len(vertex_map) + else: + remove_vertices.append(i) + + bl.mesh.vertsDelete(mesh, remove_vertices) + + bl.progress_print('%s created' % mesh.name) + return vertex_map + + +def __importMaterialAndMesh(io, tex_dir, toon_material): + """ + @param l[in] mmd.PMDLoader + @param filename[in] + """ + ############################################################ + # shpaeキーで使われるマテリアル優先的に前に並べる + ############################################################ + # shapeキーで使われる頂点インデックスを集める + shape_key_used_vertices=set() + if len(io.morph_list)>0: + # base + base=None + for s in io.morph_list: + if s.type!=0: + continue + base=s + break + assert(base) + + for index in base.indices: + shape_key_used_vertices.add(index) + + # マテリアルに含まれる頂点がshape_keyに含まれるか否か? + def isMaterialUsedInShape(offset, m): + for i in xrange(offset, offset+m.vertex_count): + if io.indices[i] in shape_key_used_vertices: + return True + + material_with_shape=set() + + # 各マテリアルの開始頂点インデックスを記録する + face_map={} + face_count=0 + for i, m in enumerate(io.materials): + face_map[i]=face_count + if isMaterialUsedInShape(face_count, m): + material_with_shape.add(i) + face_count+=m.vertex_count + + # shapeキーで使われる頂点のあるマテリアル + material_with_shape=list(material_with_shape) + material_with_shape.sort() + + # shapeキーに使われていないマテリアル + material_without_shape=[] + for i in range(len(io.materials)): + if not i in material_with_shape: + material_without_shape.append(i) + + # メッシュの生成 + def __splitList(l, length): + for i in range(0, len(l), length): + yield l[i:i+length] + + def __importMeshAndShape(material16, name): + mesh, meshObject=bl.mesh.create(name) + + # activate object + bl.object.deselectAll() + bl.object.activate(meshObject) + + # shapeキーで使われる順に並べなおしたマテリアル16個分の + # メッシュを作成する + vertex_map=__import16MaerialAndMesh( + meshObject, io, material16, face_map, tex_dir, toon_material) + + # crete shape key + __importShape(meshObject, io, vertex_map) + + mesh.update() + return meshObject + + mesh_objects=[__importMeshAndShape(material16, 'with_shape') + for material16 in __splitList(material_with_shape, 16)] + + mesh_objects+=[__importMeshAndShape(material16, 'mesh') + for material16 in __splitList(material_without_shape, 16)] + + return mesh_objects + + +def __importConstraints(io): + if isBlender24(): + return + print("create constraint") + container=bl.object.createEmpty('Constraints') + layer=[ + True, False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, False, + ] + material=bl.material.create('constraint') + material.diffuse_color=(1, 0, 0) + constraintMeshes=[] + for i, c in enumerate(io.constraints): + bpy.ops.mesh.primitive_uv_sphere_add( + segments=8, + rings=4, + size=0.1, + location=(c.pos.x, c.pos.z, c.pos.y), + layer=layer + ) + meshObject=bl.object.getActive() + constraintMeshes.append(meshObject) + mesh=bl.object.getData(meshObject) + bl.mesh.addMaterial(mesh, material) + meshObject.name='c_%d' % i + #meshObject.draw_transparent=True + #meshObject.draw_wire=True + meshObject.max_draw_type='SOLID' + rot=c.rot + meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y) + + meshObject[CONSTRAINT_NAME]=c.getName() + meshObject[CONSTRAINT_A]=io.rigidbodies[c.rigidA].getName() + meshObject[CONSTRAINT_B]=io.rigidbodies[c.rigidB].getName() + meshObject[CONSTRAINT_POS_MIN]=VtoV(c.constraintPosMin) + meshObject[CONSTRAINT_POS_MAX]=VtoV(c.constraintPosMax) + meshObject[CONSTRAINT_ROT_MIN]=VtoV(c.constraintRotMin) + meshObject[CONSTRAINT_ROT_MAX]=VtoV(c.constraintRotMax) + meshObject[CONSTRAINT_SPRING_POS]=VtoV(c.springPos) + meshObject[CONSTRAINT_SPRING_ROT]=VtoV(c.springRot) + + for meshObject in reversed(constraintMeshes): + bl.object.makeParent(container, meshObject) + + return container + + +def __importRigidBodies(io): + if isBlender24(): + return + print("create rigid bodies") + + container=bl.object.createEmpty('RigidBodies') + layer=[ + True, False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, False, + ] + material=bl.material.create('rigidBody') + rigidMeshes=[] + for i, rigid in enumerate(io.rigidbodies): + if rigid.boneIndex==0xFFFF: + # no reference bone + bone=io.bones[0] + else: + bone=io.bones[rigid.boneIndex] + pos=bone.pos+rigid.position + + if rigid.shapeType==pmd.SHAPE_SPHERE: + bpy.ops.mesh.primitive_ico_sphere_add( + location=(pos.x, pos.z, pos.y), + layer=layer + ) + bpy.ops.transform.resize( + value=(rigid.w, rigid.w, rigid.w)) + elif rigid.shapeType==pmd.SHAPE_BOX: + bpy.ops.mesh.primitive_cube_add( + location=(pos.x, pos.z, pos.y), + layer=layer + ) + bpy.ops.transform.resize( + value=(rigid.w, rigid.d, rigid.h)) + elif rigid.shapeType==pmd.SHAPE_CAPSULE: + bpy.ops.mesh.primitive_tube_add( + location=(pos.x, pos.z, pos.y), + layer=layer + ) + bpy.ops.transform.resize( + value=(rigid.w, rigid.w, rigid.h)) + else: + assert(False) + + meshObject=bl.object.getActive() + mesh=bl.object.getData(meshObject) + rigidMeshes.append(meshObject) + bl.mesh.addMaterial(mesh, material) + meshObject.name='r_%d' % i + meshObject[RIGID_NAME]=rigid.getName() + #meshObject.draw_transparent=True + #meshObject.draw_wire=True + meshObject.max_draw_type='WIRE' + rot=rigid.rotation + meshObject.rotation_euler=(-rot.x, -rot.z, -rot.y) + + # custom properties + meshObject[RIGID_SHAPE_TYPE]=rigid.shapeType + meshObject[RIGID_PROCESS_TYPE]=rigid.processType + + bone_name = englishmap.getEnglishBoneName(bone.getName()) + if not bone_name: + bone_name=bone.getName() + meshObject[RIGID_BONE_NAME]=bone_name + + meshObject[RIGID_GROUP]=rigid.group + meshObject[RIGID_INTERSECTION_GROUP]=rigid.target + meshObject[RIGID_WEIGHT]=rigid.weight + meshObject[RIGID_LINEAR_DAMPING]=rigid.linearDamping + meshObject[RIGID_ANGULAR_DAMPING]=rigid.angularDamping + meshObject[RIGID_RESTITUTION]=rigid.restitution + meshObject[RIGID_FRICTION]=rigid.friction + + for meshObject in reversed(rigidMeshes): + bl.object.makeParent(container, meshObject) + + return container + + +def _execute(filename): + """ + load pmd file to context. + """ + + # load pmd + bl.progress_set('load %s' % filename, 0.0) + + io=pmd.IO() + if not io.read(filename): + bl.message("fail to load %s" % filename) + return + bl.progress_set('loaded', 0.1) + + # create root object + model_name=io.getEnglishName() + if len(model_name)==0: + model_name=io.getName() + root=bl.object.createEmpty(model_name) + root[MMD_MB_NAME]=io.getName() + root[MMD_MB_COMMENT]=io.getComment() + root[MMD_COMMENT]=io.getEnglishComment() + + # toon textures + tex_dir=os.path.dirname(filename) + toonTextures, toonMaterial=__importToonTextures(io, tex_dir) + bl.object.makeParent(root, toonTextures) + + # import mesh + mesh_objects=__importMaterialAndMesh(io, tex_dir, toonMaterial) + for o in mesh_objects: + bl.object.makeParent(root, o) + + # import armature + armature_object=__importArmature(io) + if armature_object: + bl.object.makeParent(root, armature_object) + armature = bl.object.getData(armature_object) + + # add armature modifier + for o in mesh_objects: + bl.modifier.addArmature(o, armature_object) + + # Limitation + for n, b in bl.object.getPose(armature_object).bones.items(): + poseBoneLimit(n, b) + + # import rigid bodies + rigidBodies=__importRigidBodies(io) + if rigidBodies: + bl.object.makeParent(root, rigidBodies) + + # import constraints + constraints=__importConstraints(io) + if constraints: + bl.object.makeParent(root, constraints) + + bl.object.activate(root) + + +if isBlender24(): + # for 2.4 + def execute_24(filename): + bl.initialize('pmd_import', bpy.data.scenes.active) + _execute(filename.decode(bl.INTERNAL_ENCODING)) + bl.finalize() + + Blender.Window.FileSelector( + execute_24, + 'Import PMD file', + Blender.sys.makename(ext='.pmd')) + +else: + # import operator + class IMPORT_OT_pmd(bpy.types.Operator): + bl_idname = "import_scene.pmd" + bl_label = 'Import PMD' + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + filepath = bpy.props.StringProperty() + filename = bpy.props.StringProperty() + directory = bpy.props.StringProperty() + + def execute(self, context): + bl.initialize('pmd_import', context.scene) + _execute(self.properties.filepath) + bl.finalize() + return 'FINISHED' + + def invoke(self, context, event): + wm = context.window_manager + try: + wm.fileselect_add(self) + except: + wm.add_fileselect(self) + return 'RUNNING_MODAL' + + # register menu + def menu_func(self, context): + self.layout.operator(IMPORT_OT_pmd.bl_idname, + text="MikuMikuDance model (.pmd)", + icon='PLUGIN' + ) + + def register(): + bpy.types.INFO_MT_file_import.append(menu_func) + + def unregister(): + bpy.types.INFO_MT_file_import.remove(menu_func) + + if __name__=="__main__": + register() + diff --git a/pymeshio/pymeshio/__init__.py b/pymeshio/pymeshio/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/pymeshio/pymeshio/englishmap.py b/pymeshio/pymeshio/englishmap.py new file mode 100755 index 0000000..301f3bc --- /dev/null +++ b/pymeshio/pymeshio/englishmap.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python +# coding: utf8 +""" +日本語名との変換マップ +""" +import sys + +""" +ボーン名変換 +""" +boneMap=[ +("center", "センター", 1), +("upper body", "上半身"), +("neck", "首"), +("head", "頭"), +("eye_L", "左目", 5), +("eye_R", "右目", 5), +("necktie1", "ネクタイ1"), +("necktie2", "ネクタイ2"), +("necktie3", "ネクタイ3"), +("lower body", "下半身"), +("waist accessory", "腰飾り"), +("hair1_L", "左髪1"), +("hair2_L", "左髪2"), +("hair3_L", "左髪3"), +("hair4_L", "左髪4"), +("hair5_L", "左髪5"), +("hair6_L", "左髪6"), +("shoulder_L", "左肩"), +("arm_L", "左腕"), +("arm twist_L", "左腕捩", 8), +("elbow_L", "左ひじ"), +("wrist twist_L", "左手捩", 8), +("wrist_L", "左手首"), +("sleeve_L", "左袖", 1), +("thumb1_L", "左親指1"), +("thumb2_L", "左親指2"), +("fore1_L", "左人指1"), +("fore2_L", "左人指2"), +("fore3_L", "左人指3"), +("middle1_L", "左中指1"), +("middle2_L", "左中指2"), +("middle3_L", "左中指3"), +("third1_L", "左薬指1"), +("third2_L", "左薬指2"), +("third3_L", "左薬指3"), +("little1_L", "左小指1"), +("little2_L", "左小指2"), +("little3_L", "左小指3"), +("front skirt_L", "左スカート前"), +("back skirt_L", "左スカート後"), +("leg_L", "左足"), +("knee_L", "左ひざ"), +("ankle_L", "左足首"), +("hair1_R", "右髪1"), +("hair2_R", "右髪2"), +("hair3_R", "右髪3"), +("hair4_R", "右髪4"), +("hair5_R", "右髪5"), +("hair6_R", "右髪6"), +("shoulder_R", "右肩"), +("arm_R", "右腕"), +("arm twist_R", "右腕捩", 8), +("elbow_R", "右ひじ"), +("wrist twist_R", "右手捩", 8), +("wrist_R", "右手首"), +("sleeve_R", "右袖", 1), +("thumb1_R", "右親指1"), +("thumb2_R", "右親指2"), +("fore1_R", "右人指1"), +("fore2_R", "右人指2"), +("fore3_R", "右人指3"), +("middle1_R", "右中指1"), +("middle2_R", "右中指2"), +("middle3_R", "右中指3"), +("third1_R", "右薬指1"), +("third2_R", "右薬指2"), +("third3_R", "右薬指3"), +("little1_R", "右小指1"), +("little2_R", "右小指2"), +("little3_R", "右小指3"), +("front skirt_R", "右スカート前"), +("back skirt_R", "右スカート後"), +("leg_R", "右足"), +("knee_R", "右ひざ"), +("ankle_R", "右足首"), +("eyes", "両目"), +("front hair1", "前髪1"), +("front hair2", "前髪2"), +("front hair3", "前髪3"), +("eyelight_L", "左目光"), +("eyelight_R", "右目光"), +("necktie3_t", "ネクタイ4"), +("hair6_L_t", "左髪7"), +("hair6_R_t", "右髪7"), +("ankle_L_t", "左つま先"), +("ankle_R_t", "右つま先"), +("necktie IK", "ネクタイIK"), +("hair IK_L", "左髪IK"), +("hair IK_R", "右髪IK"), +("leg IK_L", "左足IK"), +("leg IK_R", "右足IK"), +("toe IK_L", "左つま先IK"), +("toe IK_R", "右つま先IK"), + +("lower body_t", "下半身先"), +("head_t", "頭先"), +("eye_L_t", "左目先"), +("eye_R_t", "右目先"), +("waist accessory_t", "腰飾り先"), + +("sleeve_L_t", "左袖先"), +("wrist_L_t", "左手先"), +("thumb2_L_t", "左親指先"), +("fore3_L_t", "左人差指先"), +("middle3_L_t", "左中指先"), +("third3_L_t", "左薬指先"), +("little3_L_t", "左小指先"), +("front skirt_L_t", "左スカート前先"), +("back skirt_L_t", "左スカート後先"), + +("sleeve_R_t", "右袖先"), +("wrist_R_t", "右手先"), +("thumb2_R_t", "右親指先"), +("fore3_R_t", "右人差指先"), +("middle3_R_t", "右中指先"), +("third3_R_t", "右薬指先"), +("little3_R_t", "右小指先"), +("front skirt_R_t", "右スカート前先"), +("back skirt_R_t", "右スカート後先"), + +("center_t", "センター先"), +("eyes_t", "両目先"), +("necktie IK_t", "ネクタイIK先"), +("hair IK_L_t", "左髪IK先"), +("hair IK_R_t", "右髪IK先"), +("leg IK_L_t", "左足IK先"), +("leg IK_R_t", "右足IK先"), +("toe IK_L_t", "左つま先IK先"), +("toe IK_R_t", "右つま先IK先"), +("front hair1_t", "前髪1先"), +("front hair2_t", "前髪2先"), +("front hair3_t", "前髪3先"), +("eyelight_L_t", "左目光先"), +("eyelight_R_t", "右目光先"), +("arm twist_L_t", "左腕捩先"), +("wrist twist_L_t", "左手捩先"), +("arm twist_R_t", "右腕捩先"), +("wrist twist_R_t", "右手捩先"), +("arm twist1_L", "左腕捩1", 9), +("arm twist2_L", "左腕捩2", 9), +("arm twist3_L", "左腕捩3", 9), +("arm twist1_R", "右腕捩1", 9), +("arm twist2_R", "右腕捩2", 9), +("arm twist3_R", "右腕捩3", 9), +# +("arm twist1_L_t", "左腕捩1先"), +("arm twist2_L_t", "左腕捩2先"), +("arm twist3_L_t", "左腕捩3先"), +("arm twist1_R_t", "右腕捩1先"), +("arm twist2_R_t", "右腕捩2先"), +("arm twist3_R_t", "右腕捩3先"), + +# 追加ボーン +("root", "全ての親"), +("root_t", "全ての親先"), +("group", "グループ"), +("group_t", "グループ先"), +("front_shirt_L", "左シャツ前"), +("front_shirt_R", "右シャツ前"), +("back_shirt_L", "左シャツ後"), +("back_shirt_R", "右シャツ後"), +] +def getEnglishBoneName(name): + for v in boneMap: + if v[1]==name: + return v[0] + +def getIndexByEnglish(name): + for i, v in enumerate(boneMap): + if v[0]==name: + return i + +def getUnicodeBoneName(name): + for v in boneMap: + if v[0]==name: + return v + +""" +モーフ名変換 +""" +skinMap=[ +("base", "base", 0), +("serious", "真面目", 1), +("sadness", "困る", 1), +("cheerful", "にこり", 1), +("anger", "怒り", 1), +("go up", "上", 1), +("go down", "下", 1), +("blink", "まばたき", 2), +("smile", "笑い", 2), +("wink", "ウィンク", 2), +("wink2", "ウィンク2", 2), +("wink_R", "ウィンク右", 2), +("wink2_R", "ウィンク2右", 2), +("close><", "はぅ", 2), +("calm", "なごみ", 2), +("surprise", "びっくり", 2), +("doubt", "じと目", 2), +("confuse", "なぬ!", 2), +("pupil", "瞳小", 4), +("a", "あ", 3), +("i", "い", 3), +("u", "う", 3), +("o", "お", 3), +("triangle", "▲", 3), +("regret", "∧", 3), +("omega", "ω", 3), +("omegabox", "ω□", 3), +("fool", "はんっ!", 3), +("tongue", "ぺろっ", 4), +("e-", "えー", 3), +("grin", "にやり", 3), +] +def getEnglishSkinName(name): + for v in skinMap: + if v[1]==name: + return v[0] + +def getUnicodeSkinName(name): + for v in skinMap: + if v[0]==name: + return v + +""" +ボーングループ名変換 +""" +boneGroupMap=[ + ("IK", "IK"), + ("Body[u]", "体(上)"), + ("Hair", "髪"), + ("Arms", "腕"), + ("Fingers", "指"), + ("Body[l]", "体(下)"), + ("Legs", "足"), + ] +def getEnglishBoneGroupName(name): + for v in boneGroupMap: + if v[1]==name: + return v[0] + +def getUnicodeBoneGroupName(name): + for v in boneGroupMap: + if v[0]==name: + return v[1] + + +############################################################################### +# blender2.4 str to unicode +############################################################################### +if sys.version_info[0]<3: + print('convert boneMap and skinMap to unicode...') + # python2.x + # unicodeに変換 + for i, l in enumerate(boneMap): + replace=[] + for j, m in enumerate(l): + if j==1: + replace.append(m.decode('utf-8')) + else: + replace.append(m) + boneMap[i]=replace + + for i, l in enumerate(skinMap): + replace=[] + for j, m in enumerate(l): + if j==1: + replace.append(m.decode('utf-8')) + else: + replace.append(m) + skinMap[i]=replace + print('done') + diff --git a/pymeshio/pymeshio/mmd.py b/pymeshio/pymeshio/mmd.py new file mode 100755 index 0000000..00d9ecf --- /dev/null +++ b/pymeshio/pymeshio/mmd.py @@ -0,0 +1,1341 @@ +#!/usr/bin/python +# coding: utf-8 +""" +20091202: VPD読み込みを追加 +20100318: PMD書き込みを追加 +20100731: meshioと互換になるように改造 + +VMDの読み込み +http://yumin3123.at.webry.info/200810/article_4.html +http://atupdate.web.fc2.com/vmd_format.htm + +PMDの読み込み +http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4 + +VPDの読み込み + +ToDo: + rigdid bodies + constraints +""" +import sys +import codecs +import os.path +import struct +import math +import re +#import numpy +from decimal import * + +ENCODING='cp932' + +if sys.version_info[0]>=3: + xrange=range + +############################################################################### +# utility +############################################################################### +def truncate_zero(src): + """ + 0x00以降を捨てる + """ + pos = src.find(b"\x00") + assert(type(src)==bytes) + if pos >= 0: + return src[:pos] + else: + return src + +def radian_to_degree(x): + return x/math.pi * 180.0 + + +############################################################################### +# geometry +############################################################################### +class Vector2(object): + __slots__=['x', 'y'] + def __init__(self, x=0, y=0): + self.x=x + self.y=y + + def __str__(self): + return "<%f %f>" % (self.x, self.y) + + def __getitem__(self, key): + if key==0: + return self.x + elif key==1: + return self.y + else: + assert(False) + + def to_tuple(self): + return (self.x, self.y) + + +class Vector3(object): + __slots__=['x', 'y', 'z'] + def __init__(self, x=0, y=0, z=0): + self.x=x + self.y=y + self.z=z + + def __str__(self): + return "<%f %f %f>" % (self.x, self.y, self.z) + + def __getitem__(self, key): + if key==0: + return self.x + elif key==1: + return self.y + elif key==2: + return self.z + else: + assert(False) + + def to_tuple(self): + return (self.x, self.y, self.z) + +class Quaternion(object): + __slots__=['x', 'y', 'z', 'w'] + def __init__(self, x=0, y=0, z=0, w=1): + self.x=x + self.y=y + self.z=z + self.w=w + + def __str__(self): + return "<%f %f %f %f>" % (self.x, self.y, self.z, self.w) + + def __mul__(self, rhs): + u=numpy.array([self.x, self.y, self.z], 'f') + v=numpy.array([rhs.x, rhs.y, rhs.z], 'f') + xyz=self.w*v+rhs.w*u+numpy.cross(u, v) + q=Quaternion(xyz[0], xyz[1], xyz[2], self.w*rhs.w-numpy.dot(u, v)) + return q + + def dot(self, rhs): + return self.x*rhs.x+self.y*rhs.y+self.z*rhs.z+self.w*rhs.w + + def getMatrix(self): + sqX=self.x*self.x + sqY=self.y*self.y + sqZ=self.z*self.z + xy=self.x*self.y + xz=self.x*self.z + yz=self.y*self.z + wx=self.w*self.x + wy=self.w*self.y + wz=self.w*self.z + return numpy.array([ + # 1 + [1-2*sqY-2*sqZ, 2*xy+2*wz, 2*xz-2*wy, 0], + # 2 + [2*xy-2*wz, 1-2*sqX-2*sqZ, 2*yz+2*wx, 0], + # 3 + [2*xz+2*wy, 2*yz-2*wx, 1-2*sqX-2*sqY, 0], + # 4 + [0, 0, 0, 1]], + 'f') + + def getRHMatrix(self): + x=-self.x + y=-self.y + z=self.z + w=self.w + sqX=x*x + sqY=y*y + sqZ=z*z + xy=x*y + xz=x*z + yz=y*z + wx=w*x + wy=w*y + wz=w*z + return numpy.array([ + # 1 + [1-2*sqY-2*sqZ, 2*xy+2*wz, 2*xz-2*wy, 0], + # 2 + [2*xy-2*wz, 1-2*sqX-2*sqZ, 2*yz+2*wx, 0], + # 3 + [2*xz+2*wy, 2*yz-2*wx, 1-2*sqX-2*sqY, 0], + # 4 + [0, 0, 0, 1]], + 'f') + + def getRollPitchYaw(self): + m=self.getMatrix() + + roll = math.atan2(m[0, 1], m[1, 1]) + pitch = math.asin(-m[2, 1]) + yaw = math.atan2(m[2, 0], m[2, 2]) + + if math.fabs(math.cos(pitch)) < 1.0e-6: + roll += m[0, 1] > math.pi if 0.0 else -math.pi + yaw += m[2, 0] > math.pi if 0.0 else -math.pi + + return roll, pitch, yaw + + def getSqNorm(self): + return self.x*self.x+self.y*self.y+self.z*self.z+self.w*self.w + + def getNormalized(self): + f=1.0/self.getSqNorm() + q=Quaternion(self.x*f, self.y*f, self.z*f, self.w*f) + return q + + def getRightHanded(self): + "swap y and z axis" + return Quaternion(-self.x, -self.z, -self.y, self.w) + + @staticmethod + def createFromAxisAngle(axis, rad): + q=Quaternion() + half_rad=rad/2.0 + c=math.cos(half_rad) + s=math.sin(half_rad) + return Quaternion(axis[0]*s, axis[1]*s, axis[2]*s, c) + + +class RGBA(object): + __slots__=['r', 'g', 'b', 'a'] + def __init__(self, r=0, g=0, b=0, a=1): + self.r=r + self.g=g + self.b=b + self.a=a + + def __getitem__(self, key): + if key==0: + return self.r + elif key==1: + return self.g + elif key==2: + return self.b + elif key==3: + return self.a + else: + assert(False) + + +############################################################################### +# VMD +############################################################################### +class ShapeData(object): + __slots__=['name', 'frame', 'ratio'] + def __init__(self, name): + self.name=name + self.frame=-1 + self.ratio=0 + + def __cmp__(self, other): + return cmp(self.frame, other.frame) + +class MotionData(object): + __slots__=['name', 'frame', 'pos', 'q', 'complement'] + def __init__(self, name): + self.name=name + self.frame=-1 + self.pos=Vector3() + self.q=Quaternion() + + def __cmp__(self, other): + return cmp(self.frame, other.frame) + + def __str__(self): + return '' % (self.name, self.frame, self.pos, self.q) + +class VMDLoader(object): + __slots__=['io', 'end', 'signature', + 'model_name', 'last_frame', + 'motions', 'shapes', 'cameras', 'lights', + ] + def __init__(self): + self.model_name='' + self.motions=[] + self.shapes=[] + self.cameras=[] + self.lights=[] + self.last_frame=0 + + def __str__(self): + return '' % ( + self.model_name, len(self.motions), len(self.shapes), + len(self.cameras), len(self.lights)) + + def load(self, path, io, end): + self.io=io + self.end=end + + # signature + self.signature=truncate_zero(self.io.read(30)) + version=self.validate_signature(self.signature) + if not version: + print("invalid signature", self.signature) + return False + + if version==1: + if not self.load_verstion_1(): + return False + elif version==2: + if not self.load_verstion_2(): + return False + else: + raise Exception("unknown version") + + # post process + motions=self.motions + self.motions={} + for m in motions: + if not m.name in self.motions: + self.motions[m.name]=[] + self.motions[m.name].append(m) + for name in self.motions.keys(): + self.motions[name].sort() + + shapes=self.shapes + self.shapes={} + for s in shapes: + if not s.name in self.shapes: + self.shapes[s.name]=[] + self.shapes[s.name].append(s) + for name in self.shapes.keys(): + self.shapes[name].sort() + + return True + + def getMotionCount(self): + count=0 + for v in self.motions.values(): + count+=len(v) + return count + + def getShapeCount(self): + count=0 + for v in self.shapes.values(): + count+=len(v) + return count + + def load_verstion_1(self): + # model name + self.model_name=truncate_zero(self.io.read(10)) + if not self.loadMotion_1(): + return False + return True + + def loadMotion_1(self): + count=struct.unpack('H', self.io.read(2))[0] + self.io.read(2) + for i in xrange(0, count): + self.loadFrameData() + return True + + ############################################################ + def load_verstion_2(self): + # model name + self.model_name=truncate_zero(self.io.read(20)) + + if not self.loadMotion(): + return False + if not self.loadShape(): + return False + if not self.loadCamera(): + return False + if not self.loadLight(): + return False + #assert(self.io.tell()==self.end) + #self.motions.sort(lambda l, r: l.nameself.last_frame: + self.last_frame=data.frame + + def loadShapeData(self): + """ + モーフデータひとつ分を読み込む + """ + data=ShapeData(truncate_zero(self.io.read(15))) + (data.frame, data.ratio)=struct.unpack('If', self.io.read(8)) + self.shapes.append(data) + if data.frame>self.last_frame: + self.last_frame=data.frame + + # vmd -> csv + ############################################################ + def create_csv_line(m): + # quaternion -> euler angle + (roll, pitch, yaw)=m.q.getRollPitchYaw() + return '%s,%d,%g,%g,%g,%g,%g,%g,0x%s\n' % ( + m.name, m.frame, m.pos.x, m.pos.y, m.pos.z, + to_degree(pitch), to_degree(yaw), to_degree(roll), m.complement + ) + + def write_csv(l, path): + sys.setdefaultencoding('cp932') + csv=open(path, "w") + csv.write('%s,0\n' % l.signature) + csv.write('%s\n' % l.model_name) + # motion + csv.write('%d\n' % len(l.motions)) + for m in l.motions: + csv.write(create_csv_line(m)) + # shape + csv.write('%d\n' % len(l.shapes)) + for s in l.shapes: + csv.write('%s,%d,%f\n' % ( s.name, s.frame, s.ratio)) + # camera + csv.write('%d\n' % len(l.cameras)) + for camera in l.cameras: + assert(False) + # light + csv.write('%d\n' % len(l.lights)) + for light in l.lights: + assert(False) + + +############################################################################### +# PMD +############################################################################### +class Vertex(object): + __slots__=['pos', 'normal', 'uv', 'bone0', 'bone1', 'weight0', 'edge_flag'] + def __init__(self, x=0, y=0, z=0, nx=0, ny=0, nz=0, u=0, v=0, + bone0=0, bone1=0, weight0=0, edge_flag=0): + self.pos=Vector3(x, y, z) + self.normal=Vector3(nx, ny, nz) + self.uv=Vector2(u, v) + self.bone0=bone0 + self.bone1=bone1 + self.weight0=weight0 + self.edge_flag=edge_flag + + def __str__(self): + return "<%s %s %s, (%d, %d, %d)>" % (str(self.pos), str(self.normal), str(self.uv), self.bone0, self.bone1, self.weight0) + + def __getitem__(self, key): + if key==0: + return self.pos.x + elif key==1: + return self.pos.y + elif key==2: + return self.pos.z + else: + assert(False) + +class Material(object): + __slots__=[ + 'diffuse', 'shinness', 'specular', + 'ambient', 'vertex_count', 'texture', 'toon_index', 'flag', + ] + + def __init__(self, dr=0, dg=0, db=0, alpha=1, + specular=0, sr=0, sg=0, sb=0, ar=0, ag=0, ab=0): + self.diffuse=RGBA(dr, dg, db, alpha) + self.specular=RGBA(sr, sg, sb) + self.shinness=specular + self.ambient=RGBA(ar, ag, ab) + self.vertex_count=0 + self.texture='' + self.toon_index=0 + self.flag=0 + + def __str__(self): + return "" % ( + self.diffuse[0], self.diffuse[1], + self.diffuse[2], self.diffuse[3], + ) + + def getTexture(self): return self.texture.decode('cp932') + def setTexture(self, u): self.texture=u + +# @return 各マテリアルについて、そのマテリアルが保持する面の回数だけ +# マテリアル自身を返す +def material_per_face(materials): + for m in materials: + for x in xrange(int(m.vertex_count/3)): + yield m + +class Bone(object): + # kinds + ROTATE = 0 + ROTATE_MOVE = 1 + IK = 2 + IK_ROTATE_INFL = 4 + ROTATE_INFL = 5 + IK_TARGET = 6 + UNVISIBLE = 7 + # since v4.0 + ROLLING=8 # ? + TWEAK=9 + __slots__=['name', 'index', 'type', 'parent', 'ik', 'pos', + 'children', 'english_name', 'ik_index', + 'parent_index', 'tail_index', 'tail', + ] + def __init__(self, name='bone', type=0): + self.name=name + self.index=0 + self.type=type + self.parent_index=0xFFFF + self.tail_index=0 + self.tail=Vector3(0, 0, 0) + self.parent=None + self.ik_index=0xFFFF + self.pos=Vector3(0, 0, 0) + self.children=[] + self.english_name='' + + def getName(self): return self.name.decode('cp932') + def setName(self, u): self.name=u + def setEnglishName(self, u): self.english_name=u + + def hasParent(self): + return self.parent_index!=0xFFFF + + def hasChild(self): + return self.tail_index!=0 + + def display(self, indent=[]): + if len(indent)>0: + prefix='' + for i, is_end in enumerate(indent): + if i==len(indent)-1: + break + else: + prefix+=' ' if is_end else ' |' + uni='%s +%s(%s)' % (prefix, unicode(self), self.english_name) + print(uni.encode(ENCODING)) + else: + uni='%s(%s)' % (unicode(self), self.english_name) + print(uni.encode(ENCODING)) + + child_count=len(self.children) + for i in xrange(child_count): + child=self.children[i] + if i' % (self.name) +# 1 +class Bone_RotateMove(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_RotateMove, self).__init__(name, 1) + def __str__(self): + return '' % (self.name) +# 2 +class Bone_IK(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_IK, self).__init__(name, 2) + def __str__(self): + return '' % (self.name) +# 4 +class Bone_IKRotateInfl(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_IKRotateInfl, self).__init__(name, 4) + def __str__(self): + return '' % (self.name) +# 5 +class Bone_RotateInfl(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_RotateInfl, self).__init__(name, 5) + def __str__(self): + return '' % (self.name) +# 6 +class Bone_IKTarget(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_IKTarget, self).__init__(name, 6) + def __str__(self): + return '' % (self.name) +# 7 +class Bone_Unvisible(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_Unvisible, self).__init__(name, 7) + def __str__(self): + return '' % (self.name) +# 8 +class Bone_Rolling(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_Rolling, self).__init__(name, 8) + def __str__(self): + return '' % (self.name) +# 9 +class Bone_Tweak(Bone): + __slots__=[] + def __init__(self, name): + super(Bone_Tweak, self).__init__(name, 9) + def __str__(self): + return '' % (self.name) + + +def createBone(name, type): + if type==0: + return Bone_Rotate(name) + elif type==1: + return Bone_RotateMove(name) + elif type==2: + return Bone_IK(name) + elif type==3: + raise Exception("no used bone type: 3(%s)" % name) + elif type==4: + return Bone_IKRotateInfl(name) + elif type==5: + return Bone_RotateInfl(name) + elif type==6: + return Bone_IKTarget(name) + elif type==7: + return Bone_Unvisible(name) + elif type==8: + return Bone_Rolling(name) + elif type==9: + return Bone_Tweak(name) + else: + raise Exception("unknown bone type: %d(%s)", type, name) + + +class IK(object): + __slots__=['index', 'target', 'iterations', 'weight', 'length', 'children'] + def __init__(self, index=0, target=0): + self.index=index + self.target=target + self.iterations=None + self.weight=None + self.children=[] + + def __str__(self): + return "" %(self.index, self.target, self.iterations, self.weight, '-'.join([str(i) for i in self.children]), len(self.children)) + +class Skin(object): + __slots__=['name', 'type', 'indices', 'pos_list', 'english_name', + 'vertex_count'] + def __init__(self, name='skin'): + self.name=name + self.type=None + self.indices=[] + self.pos_list=[] + self.english_name='' + self.vertex_count=0 + + def getName(self): return self.name.decode('cp932') + def setName(self, u): self.name=u + def setEnglishName(self, u): self.english_name=u + + def append(self, index, x, y, z): + self.indices.append(index) + self.pos_list.append(Vector3(x, y, z)) + + def __str__(self): + return '' % ( + self.name, self.type, len(self.indices)) + +class ToonTexture(object): + __slots__=['name'] + def __init__(self, name): self.name=name + def getName(self): return self.name.decode('cp932') + def setName(self, u): self.name=u + +class BoneGroup(object): + __slots__=['name', 'english_name'] + def __init__(self, name='group'): self.name=name; self.english_name='center' + def getName(self): return self.name.decode('cp932') + def setName(self, u): self.name=u + def getEnglishName(self): return self.english_name.decode('cp932') + def setEnglishName(self, u): self.english_name=u + +class PMDLoader(object): + __slots__=['io', 'end', 'pos', + 'version', 'model_name', 'comment', + 'english_model_name', 'english_comment', + 'vertices', 'indices', 'materials', 'bones', + 'ik_list', 'morph_list', + 'face_list', 'bone_group_list', 'bone_display_list', + 'toon_textures', + 'no_parent_bones', + 'rigidbodies', 'constraints', + ] + def __init__(self): + self.version=1.0 + self.model_name=b"default" + self.comment=b"default" + self.english_model_name=b'default' + self.english_comment=b'default' + self.vertices=[] + self.indices=[] + self.materials=[] + self.bones=[] + self.ik_list=[] + self.morph_list=[] + + self.face_list=[] + self.bone_group_list=[] + self.bone_display_list=[] + + self.toon_textures=[ + ToonTexture(b'toon'), ToonTexture(b'toon'), + ToonTexture(b'toon'), ToonTexture(b'toon'), + ToonTexture(b'toon'), ToonTexture(b'toon'), + ToonTexture(b'toon'), ToonTexture(b'toon'), + ToonTexture(b'toon'), ToonTexture(b'toon'), + ] + + self.no_parent_bones=[] + + self.rigidbodies=[] + self.constraints=[] + + def getName(self): return self.model_name.decode('cp932') + def setName(self, u): self.model_name=u + def getComment(self): return self.comment.decode('cp932') + def setComment(self, u): self.comment=u + def getEnglishName(self): return self.english_model_name.decode('cp932') + def setEnglishName(self, u): self.english_model_name=u + def getEnglishComment(self): return self.english_comment.decode('cp932') + def setEnglishComment(self, u): self.english_comment=u + + def getToonTexture(self, i): return self.toon_textures[i] + def each_vertex(self): return self.vertices + def getUV(self, i): return self.vertices[i].uv + def addVertex(self): + v=Vertex() + self.vertices.append(v) + return v + def addMaterial(self): + m=Material() + self.materials.append(m) + return m + def addBone(self): + b=Bone() + self.bones.append(b) + return b + def addIK(self): + ik=IK() + self.ik_list.append(ik) + return ik + def addMorph(self): + s=Skin() + self.morph_list.append(s) + return s + def addBoneGroup(self): + g=BoneGroup() + self.bone_group_list.append(g) + return g + def addBoneDisplay(self, b, g): + self.bone_display_list.append((b, g)) + + def __str__(self): + return '' % ( + self.version, self.model_name, len(self.vertices), len(self.indices), + len(self.materials), len(self.bones), len(self.ik_list), len(self.morph_list)) + + def _check_position(self): + """ + if self.pos: + print(self.pos, self.io.tell()-self.pos) + """ + self.pos=self.io.tell() + pass + + def read(self, path): + size=os.path.getsize(path) + f=open(path, "rb") + return self.load(path, f, size) + + def load(self, path, io, end): + self.io=io + self.pos=self.io.tell() + self.end=end + self._check_position() + + if not self._loadHeader(): + return False + self._check_position() + + if not self._loadVertex(): + return False + self._check_position() + + if not self._loadFace(): + return False + self._check_position() + + if not self._loadMaterial(): + return False + self._check_position() + + if not self._loadBone(): + return False + self._check_position() + + if not self._loadIK(): + return False + self._check_position() + + if not self._loadSkin(): + return False + self._check_position() + + if not self._loadSkinIndex(): + return False + self._check_position() + + if not self._loadBoneName(): + return False + self._check_position() + + if not self._loadBoneIndex(): + return False + self._check_position() + + if not self._loadExtend(): + print('fail to loadExtend') + return False + + # 終端 + if self.io.tell()!=self.end: + print("can not reach eof.") + print("current: %d, end: %d, remain: %d" % ( + self.io.tell(), self.end, self.end-self.io.tell())) + + # build bone tree + for i, child in enumerate(self.bones): + if child.parent_index==0xFFFF: + # no parent + self.no_parent_bones.append(child) + child.parent=None + else: + # has parent + parent=self.bones[child.parent_index] + child.parent=parent + parent.children.append(child) + # 後位置 + if child.hasChild(): + child.tail=self.bones[child.tail_index].pos + + return True + + def write(self, path): + io=open(path, 'wb') + if not io: + return False + # Header + io.write(b"Pmd") + io.write(struct.pack("f", self.version)) + io.write(struct.pack("20s", self.model_name)) + io.write(struct.pack("256s", self.comment)) + + # Vertices + io.write(struct.pack("I", len(self.vertices))) + sVertex=struct.Struct("=8f2H2B") # 38byte + assert(sVertex.size==38) + for v in self.vertices: + data=sVertex.pack( + v.pos[0], v.pos[1], v.pos[2], + v.normal[0], v.normal[1], v.normal[2], + v.uv[0], v.uv[1], + v.bone0, v.bone1, v.weight0, v.edge_flag) + io.write(data) + + # Faces + io.write(struct.pack("I", len(self.indices))) + io.write(struct.pack("=%dH" % len(self.indices), *self.indices)) + + # material + io.write(struct.pack("I", len(self.materials))) + sMaterial=struct.Struct("=3fff3f3fBBI20s") # 70byte + assert(sMaterial.size==70) + for m in self.materials: + io.write(sMaterial.pack( + m.diffuse[0], m.diffuse[1], m.diffuse[2], m.diffuse[3], + m.shinness, + m.specular[0], m.specular[1], m.specular[2], + m.ambient[0], m.ambient[1], m.ambient[2], + m.toon_index, m.flag, + m.vertex_count, + m.texture + )) + + # bone + io.write(struct.pack("H", len(self.bones))) + sBone=struct.Struct("=20sHHBH3f") + assert(sBone.size==39) + for b in self.bones: + io.write(sBone.pack( + b.name, + b.parent_index, b.tail_index, b.type, b.ik_index, + b.pos[0], b.pos[1], b.pos[2])) + + # IK + io.write(struct.pack("H", len(self.ik_list))) + for ik in self.ik_list: + io.write(struct.pack("=2HBHf", + ik.index, ik.target, ik.length, ik.iterations, ik.weight + )) + for c in ik.children: + io.write(struct.pack("H", c)) + + # skin + io.write(struct.pack("H", len(self.morph_list))) + for s in self.morph_list: + io.write(struct.pack("20sIB", + s.name, len(s.indices), s.type)) + for i, v in zip(s.indices, s.pos_list): + io.write(struct.pack("I3f", i, v[0], v[1], v[2])) + + # skin list + io.write(struct.pack("B", len(self.face_list))) + for i in self.face_list: + io.write(struct.pack("H", i)) + + # bone name + io.write(struct.pack("B", len(self.bone_group_list))) + for g in self.bone_group_list: + io.write(struct.pack("50s", g.name)) + + # bone list + io.write(struct.pack("I", len(self.bone_display_list))) + for l in self.bone_display_list: + io.write(struct.pack("=HB", *l)) + + # ToDo + # Extend Data + + return True + + + def _loadExtend(self): + ############################################################ + # extend1: english name + ############################################################ + if self.io.tell()>=self.end: + return True + if struct.unpack("B", self.io.read(1))[0]==1: + if not self.loadEnglishName(): + return False + self._check_position() + + ############################################################ + # extend2: toon texture list + ############################################################ + if self.io.tell()>=self.end: + return True + if not self.loadToonTexture(): + return False + self._check_position() + + ############################################################ + # extend3: physics + ############################################################ + if self.io.tell()>=self.end: + return True + #if not self.loadPhysics(): + # return False + self._check_position() + + return True + + def _loadHeader(self): + signature=struct.unpack("3s", self.io.read(3))[0] + print(signature) + if signature!=b"Pmd": + print("invalid signature", signature) + return False + self.version=struct.unpack("f", self.io.read(4))[0] + self.model_name = truncate_zero(struct.unpack("20s", self.io.read(20))[0]) + self.comment = truncate_zero( + struct.unpack("256s", self.io.read(256))[0]) + return True + + def _loadVertex(self): + count = struct.unpack("I", self.io.read(4))[0] + for i in xrange(count): + self.vertices.append(Vertex(*struct.unpack("8f2H2B", self.io.read(38)))) + return True + + def _loadFace(self): + count = struct.unpack("I", self.io.read(4))[0] + for i in xrange(0, count, 3): + self.indices+=struct.unpack("HHH", self.io.read(6)) + return True + + def _loadMaterial(self): + count = struct.unpack("I", self.io.read(4))[0] + for i in xrange(count): + material=Material(*struct.unpack("4ff3f3f", self.io.read(44))) + material.toon_index=struct.unpack("B", self.io.read(1))[0] + material.flag=struct.unpack("B", self.io.read(1))[0] + material.vertex_count=struct.unpack("I", self.io.read(4))[0] + texture=truncate_zero(struct.unpack("20s", self.io.read(20))[0]) + # todo sphere map + #material.texture=texture.split('*')[0] + material.texture=texture + self.materials.append(material) + return True + + def _loadBone(self): + size = struct.unpack("H", self.io.read(2))[0] + for i in xrange(size): + name=truncate_zero(struct.unpack("20s", self.io.read(20))[0]) + parent_index, tail_index = struct.unpack("HH", self.io.read(4)) + type = struct.unpack("B", self.io.read(1))[0] + bone=createBone(name, type) + bone.parent_index=parent_index + bone.tail_index=tail_index + bone.ik_index = struct.unpack("H", self.io.read(2))[0] + bone.pos = Vector3(*struct.unpack("3f", self.io.read(12))) + bone.english_name="bone%03d" % len(self.bones) + self.bones.append(bone) + return True + + def _loadIK(self): + size = struct.unpack("H", self.io.read(2))[0] + for i in xrange(size): + ik=IK(*struct.unpack("2H", self.io.read(4))) + ik.length = struct.unpack("B", self.io.read(1))[0] + ik.iterations = struct.unpack("H", self.io.read(2))[0] + ik.weight = struct.unpack("f", self.io.read(4))[0] + for j in xrange(ik.length): + ik.children.append(struct.unpack("H", self.io.read(2))[0]) + self.ik_list.append(ik) + return True + + def _loadSkin(self): + size = struct.unpack("H", self.io.read(2))[0] + for i in xrange(size): + skin=Skin(truncate_zero(struct.unpack("20s", self.io.read(20))[0])) + skin_size = struct.unpack("I", self.io.read(4))[0] + skin.type = struct.unpack("B", self.io.read(1))[0] + for j in xrange(skin_size): + skin.indices.append(struct.unpack("I", self.io.read(4))[0]) + skin.pos_list.append( + Vector3(*struct.unpack("3f", self.io.read(12)))) + skin.english_name="skin%03d" % len(self.morph_list) + self.morph_list.append(skin) + return True + + def _loadSkinIndex(self): + size = struct.unpack("B", self.io.read(1))[0] + for i in xrange(size): + self.face_list.append(struct.unpack("H", self.io.read(2))[0]) + return True + + def _loadBoneName(self): + size = struct.unpack("B", self.io.read(1))[0] + for i in xrange(size): + self.bone_group_list.append(BoneGroup( + truncate_zero(struct.unpack("50s", self.io.read(50))[0]))) + return True + + def _loadBoneIndex(self): + size = struct.unpack("I", self.io.read(4))[0] + for i in xrange(size): + self.bone_display_list.append(struct.unpack("HB", self.io.read(3))) + return True + + def loadToonTexture(self): + """ + 100bytex10 + """ + for i in xrange(10): + self.toon_textures.append(ToonTexture( + truncate_zero(struct.unpack("100s", self.io.read(100))[0]))) + return True + + def loadEnglishName(self): + # english name + self.english_model_name=truncate_zero( + struct.unpack("20s", self.io.read(20))[0]) + self.english_comment=truncate_zero( + struct.unpack("256s", self.io.read(256))[0]) + # english bone list + for bone in self.bones: + english_name=truncate_zero( + struct.unpack("20s", self.io.read(20))[0]) + if english_name!=bone.name: + bone.english_name=english_name + # english skin list + #for index in self.face_list: + for skin in self.morph_list: + if skin.name=='base': + continue + english_name=truncate_zero( + struct.unpack("20s", self.io.read(20))[0]) + #skin=self.morph_list[index] + if english_name!=skin.name: + skin.english_name=english_name + # english bone list + for i in xrange(0, len(self.bone_group_list)): + self.bone_group_list[i].english_name=truncate_zero( + struct.unpack("50s", self.io.read(50))[0]) + return True + + def loadPhysics(self): + # 剛体リスト + count = struct.unpack("I", self.io.read(4))[0] + for i in xrange(count): + struct.unpack("83s", self.io.read(83))[0] + # ジョイントリスト + count = struct.unpack("I", self.io.read(4))[0] + for i in xrange(count): + struct.unpack("124s", self.io.read(124))[0] + return True + + +############################################################################### +# VPD +############################################################################### +class LineLoader(object): + """ + 行指向の汎用ローダ + """ + __slots__=['path', 'io', 'end'] + def __str__(self): + return "<%s current:%d, end:%d>" % ( + self.__class__, self.getPos(), self.getEnd()) + + def getPos(self): + return self.io.tell() + + def getEnd(self): + return self.end + + def readline(self): + return (self.io.readline()).strip() + + def isEnd(self): + return self.io.tell()>=self.end + + def load(self, path, io, end): + self.path=path + self.io=io + self.end=end + return self.process() + + def process(self): + """ + dummy. read to end. + """ + while not self.isEnd(): + self.io.readline() + return True + + +class VPDLoader(LineLoader): + __slots__=['pose'] + def __init__(self): + super(VPDLoader, self).__init__() + self.pose=[] + + def __str__(self): + return "" % len(self.pose) + + def process(self): + if self.readline()!="Vocaloid Pose Data file": + return + + RE_OPEN=re.compile('^(\w+){(.*)') + RE_OSM=re.compile('^\w+\.osm;') + RE_COUNT=re.compile('^(\d+);') + + bone_count=-1 + while not self.isEnd(): + line=self.readline() + if line=='': + continue + m=RE_OPEN.match(line) + if m: + if not self.parseBone(m.group(2)): + raise Exception("invalid bone") + continue + + m=RE_OSM.match(line) + if m: + continue + + m=RE_COUNT.match(line) + if m: + bone_count=int(m.group(1)) + continue + + return len(self.pose)==bone_count + + def parseBone(self, name): + bone=MotionData(name) + self.pose.append(bone) + bone.pos=Vector3(*[float(token) for token in self.readline().split(';')[0].split(',')]) + bone.q=Quaternion(*[float(token) for token in self.readline().split(';')[0].split(',')]) + return self.readline()=="}" + + +############################################################################### +# interface +############################################################################### +def load_pmd(path): + size=os.path.getsize(path) + f=open(path, "rb") + l=PMDLoader() + if l.load(path, f, size): + return l + +def load_vmd(path): + size=os.path.getsize(path) + f=open(path, "rb") + l=VMDLoader() + if l.load(path, f, size): + return l + +def load_vpd(path): + f=open(path, 'rb') + if not f: + return; + size=os.path.getsize(path) + l=VPDLoader() + if l.load(path, f, size): + return l + + +############################################################################### +# debug +############################################################################### +def debug_pmd(path): + l=load_pmd(path) + if not l: + print("fail to load") + sys.exit() + + print(unicode(l).encode(ENCODING)) + print(l.comment.encode(ENCODING)) + print("<ボーン>".decode('utf-8').encode(ENCODING)) + for bone in l.no_parent_bones: + print(bone.name.encode(ENCODING)) + bone.display() + #for bone in l.bones: + # uni="%s:%s" % (bone.english_name, bone.name) + # print uni.encode(ENCODING) + #for skin in l.morph_list: + # uni="%s:%s" % (skin.english_name, skin.name) + # print uni.encode(ENCODING) + #for i, v in enumerate(l.vertices): + # print i, v + #for i, f in enumerate(l.indices): + # print i, f + for m in l.materials: + print(m) + +def debug_pmd_write(path, out): + l=load_pmd(path) + if not l: + print("fail to load") + sys.exit() + + if not l.write(out): + print("fail to write") + sys.exit() + +def debug_vmd(path): + l=load_vmd(path) + if not l: + print("fail to load") + sys.exit() + print(unicode(l).encode(ENCODING)) + + #for m in l.motions[u'センター']: + # print m.frame, m.pos + for n, m in l.shapes.items(): + print(unicode(n).encode(ENCODING), getEnglishSkinName(n)) + +def debug_vpd(path): + l=load_vpd(path) + if not l: + print("fail to load") + sys.exit() + for bone in l.pose: + print(unicode(bone).encode(ENCODING)) + +if __name__=="__main__": + if len(sys.argv)<2: + print("usage: %s {pmd file}" % sys.argv[0]) + print("usage: %s {vmd file}" % sys.argv[0]) + print("usage: %s {vpd file}" % sys.argv[0]) + print("usage: %s {pmd file} {export pmdfile}" % sys.argv[0]) + sys.exit() + + path=sys.argv[1] + if not os.path.exists(path): + print("no such file: %s" % path) + + if path.lower().endswith('.pmd'): + if len(sys.argv)==2: + debug_pmd(path) + else: + debug_pmd_write(path, sys.argv[2]) + elif path.lower().endswith('.vmd'): + debug_vmd(path) + elif path.lower().endswith('.vpd'): + debug_vpd(path) + else: + print("unknown file type: %s" % path) + sys.exit() + diff --git a/pymeshio/pymeshio/mqo.py b/pymeshio/pymeshio/mqo.py new file mode 100755 index 0000000..e9eee56 --- /dev/null +++ b/pymeshio/pymeshio/mqo.py @@ -0,0 +1,473 @@ +#!BPY +""" +Name: 'Metasequoia(.mqo)...' +Blender: 245 +Group: 'Import' +Tooltip: 'Import from Metasequoia file format (.mqo)' +""" +__author__= 'ousttrue' +__url__ = ["http://gunload.web.fc2.com/blender/"] +__version__= '0.4 2009/11/25' +__bpydoc__= '''\ + +MQO Importer + +This script imports a mqo file. + +0.2 20080123: update. +0.3 20091125: modify for linux. +0.4 20100310: rewrite. +0.5 20100311: create armature from mikoto bone. +''' + +import os +import sys +import math + + +class RGBA(object): + __slots__=['r', 'g', 'b', 'a'] + def __init__(self, r=0, g=0, b=0, a=0): + self.r=r + self.g=g + self.b=b + self.a=a + +class Vector3(object): + __slots__=['x', 'y', 'z'] + def __init__(self, x=0, y=0, z=0): + self.x=x + self.y=y + self.z=z + + def __str__(self): + return "[%f, %f, %f]" % (self.x, self.y, self.z) + + def __sub__(self, rhs): + return Vector3(self.x-rhs.x, self.y-rhs.y, self.z-rhs.z) + + def getSqNorm(self): + return self.x*self.x + self.y*self.y + self.z*self.z + + def getNorm(self): + return math.sqrt(self.getSqNorm()) + + def normalize(self): + factor=1.0/self.getNorm() + self.x*=factor + self.y*=factor + self.z*=factor + return self + + def to_a(self): + return [self.x, self.y, self.z] + + @staticmethod + def dot(lhs, rhs): + return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z + + @staticmethod + def cross(lhs, rhs): + return Vector3( + lhs.y*rhs.z - rhs.y*lhs.z, + lhs.z*rhs.x - rhs.z*lhs.x, + lhs.x*rhs.y - rhs.x*lhs.y, + ) + + +class Vector2(object): + __slots__=['x', 'y'] + def __init__(self, x=0, y=0): + self.x=x + self.y=y + + def __str__(self): + return "[%f, %f]" % (self.x, self.y) + + def __sub__(self, rhs): + return Vector3(self.x-rhs.x, self.y-rhs.y) + + @staticmethod + def cross(lhs, rhs): + return lhs.x*rhs.y-lhs.y*rhs.x + + +############################################################################### +# MQO loader +############################################################################### +class Material(object): + __slots__=[ + "name", "shader", "color", "diffuse", + "ambient", "emit", "specular", "power", + "tex", + ] + def __init__(self, name): + self.name=name + self.shader=3 + self.color=RGBA(0.5, 0.5, 0.5, 1.0) + self.diffuse=1.0 + self.ambient=0.0 + self.emit=0.0 + self.specular=0.0 + self.power=5.0 + self.tex="" + + def getName(self): return self.name + def getTexture(self): return self.tex + + def parse(self, line): + offset=0 + while True: + leftParenthesis=line.find("(", offset) + if leftParenthesis==-1: + break + key=line[offset:leftParenthesis] + rightParenthesis=line.find(")", leftParenthesis+1) + if rightParenthesis==-1: + raise ValueError("assert") + + param=line[leftParenthesis+1:rightParenthesis] + if key=="shader": + self.shader=int(param) + elif key=="col": + self.color=RGBA(*[float(e) for e in param.split()]) + elif key=="dif": + self.diffuse=float(param) + elif key=="amb": + self.ambient=float(param) + elif key=="emi": + self.emit=float(param) + elif key=="spc": + self.specular=float(param) + elif key=="power": + self.power=float(param) + elif key=="tex": + self.tex=param[1:-1] + else: + print( + "%s#parse" % self.name, + "unknown key: %s" % key + ) + + offset=rightParenthesis+2 + + def __str__(self): + return "" % ( + self.name, self.shader, + self.color[0], self.color[1], self.color[2], self.color[3], + self.diffuse) + + +class Obj(object): + __slots__=["name", "depth", "folding", + "scale", "rotation", "translation", + "visible", "locking", "shading", "facet", + "color", "color_type", "mirror", "mirror_axis", + "vertices", "faces", "edges", "smoothing", + ] + + def __init__(self, name): + self.name=name + self.vertices=[] + self.faces=[] + self.edges=[] + self.depth=0 + self.folding=0 + self.scale=[1, 1, 1] + self.rotation=[0, 0, 0] + self.translation=[0, 0, 0] + self.visible=15 + self.locking=0 + self.shading=0 + self.facet=59.5 + self.color=[1, 1, 1] + self.color_type=0 + self.mirror=0 + self.smoothing=0 + + def getName(self): return self.name + + def addVertex(self, x, y, z): + self.vertices.append(Vector3(x, y, z)) + + def addFace(self, face): + if face.index_count==2: + self.edges.append(face) + else: + self.faces.append(face) + + def __str__(self): + return "" % ( + self.name, len(self.vertices), len(self.faces)) + + +class Face(object): + __slots__=[ + "index_count", + "indices", "material_index", "col", "uv", + ] + def __init__(self, index_count, line): + if index_count<2 or index_count>4: + raise ValueError("invalid vertex count: %d" % index_count) + self.material_index=0 + self.col=[] + self.uv=[Vector2(0, 0)]*4 + self.index_count=index_count + offset=0 + while True: + leftParenthesis=line.find("(", offset) + if leftParenthesis==-1: + break + key=line[offset:leftParenthesis] + rightParenthesis=line.find(")", leftParenthesis+1) + if rightParenthesis==-1: + raise ValueError("assert") + params=line[leftParenthesis+1:rightParenthesis].split() + if key=="V": + self.indices=[int(e) for e in params] + elif key=="M": + self.material_index=int(params[0]) + elif key=="UV": + uv_list=[float(e) for e in params] + self.uv=[] + for i in range(0, len(uv_list), 2): + self.uv.append(Vector2(uv_list[i], uv_list[i+1])) + elif key=="COL": + for n in params: + d=int(n) + # R + d, m=divmod(d, 256) + self.col.append(m) + # G + d, m=divmod(d, 256) + self.col.append(m) + # B + d, m=divmod(d, 256) + self.col.append(m) + # A + d, m=divmod(d, 256) + self.col.append(m) + else: + print("Face#__init__:unknown key: %s" % key) + + offset=rightParenthesis+2 + + def getIndex(self, i): return self.indices[i] + def getUV(self, i): return self.uv[i] if i" % ( + self.lines, len(self.materials), len(self.objects)) + + def getline(self): + line=self.io.readline() + self.lines+=1 + if line=="": + self.eof=True + return None + return line.strip() + + def printError(self, method, msg): + print("%s:%s:%d" % (method, msg, self.lines)) + + @withio + def read(self): + line=self.getline() + if line!="Metasequoia Document": + print("invalid signature") + return False + + line=self.getline() + if line!="Format Text Ver 1.0": + print("unknown version: %s" % line) + + while True: + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + tokens=line.split() + key=tokens[0] + + if key=="Eof": + return True + elif key=="Scene": + if not self.readChunk(): + return False + elif key=="Material": + if not self.readMaterial(): + return False + elif key=="Object": + firstQuote=line.find('"') + secondQuote=line.find('"', firstQuote+1) + if not self.readObject(line[firstQuote+1:secondQuote]): + return False + elif key=="BackImage": + if not self.readChunk(): + return False + elif key=="IncludeXml": + firstQuote=line.find('"') + secondQuote=line.find('"', firstQuote+1) + print("IncludeXml", line[firstQuote+1:secondQuote]) + else: + print("unknown key: %s" % key) + if not self.readChunk(): + return False + + self.printError("parse", "invalid eof") + return False + + def readObject(self, name): + obj=Obj(name) + if name.startswith('bone'): + self.has_mikoto=True + self.objects.append(obj) + while(True): + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + if line=="}": + return True + else: + tokens=line.split() + key=tokens[0] + if key=="vertex": + if not self.readVertex(obj): + return False + elif key=="face": + if not self.readFace(obj): + return False + elif key=="depth": + obj.depth=int(tokens[1]) + else: + print( + "%s#readObject" % name, + "unknown key: %s" % name + ) + + self.printError("readObject", "invalid eof") + return False + + def readFace(self, obj): + while(True): + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + if line=="}": + return True + else: + # face + tokens=line.split(' ', 1) + try: + obj.addFace(Face(int(tokens[0]), tokens[1])) + except ValueError as ex: + self.printError("readFace", ex) + #return False + + self.printError("readFace", "invalid eof") + return False + + def readVertex(self, obj): + while(True): + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + if line=="}": + return True + else: + # vertex + obj.addVertex(*[float(v) for v in line.split()]) + + self.printError("readVertex", "invalid eof") + return False + + def readMaterial(self): + while(True): + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + if line=="}": + return True + else: + # material + secondQuaote=line.find('"', 1) + material=Material(line[1:secondQuaote]) + try: + material.parse(line[secondQuaote+2:]) + except ValueError as ex: + self.printError("readMaterial", ex) + + self.materials.append(material) + + self.printError("readMaterial", "invalid eof") + return False + + def readChunk(self): + level=1 + while(True): + line=self.getline() + if line==None: + # eof + break; + if line=="": + # empty line + continue + + if line=="}": + level-=1 + if level==0: + return True + elif line.find("{")!=-1: + level+=1 + + self.printError("readChunk", "invalid eof") + return False +