From 8624b41a70d35ec83be65b050a82e5c24bc06ad7 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Sun, 6 Jun 2010 12:15:33 +0900 Subject: [PATCH] integrate 2.4 and 2.5. --- swig/blender/Makefile | 5 + swig/blender/cp.sh | 9 + swig/blender/mqo_import.py | 645 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 659 insertions(+) create mode 100644 swig/blender/Makefile create mode 100755 swig/blender/cp.sh create mode 100644 swig/blender/mqo_import.py diff --git a/swig/blender/Makefile b/swig/blender/Makefile new file mode 100644 index 0000000..176a817 --- /dev/null +++ b/swig/blender/Makefile @@ -0,0 +1,5 @@ +all: copy + +copy: mqo_import.py + ./cp.sh + diff --git a/swig/blender/cp.sh b/swig/blender/cp.sh new file mode 100755 index 0000000..df4e175 --- /dev/null +++ b/swig/blender/cp.sh @@ -0,0 +1,9 @@ +#!/bin/sh +DST="/cygdrive/T//Blender/bf-blender/build2.4git/bin/Release/.blender/scripts" +cp *.py $DST + +DST="/cygdrive/T//Blender/bf-blender/build2.4git/bin/Debug/.blender/scripts" +cp *.py $DST + +DST="T:/Blender/blender-2.49b-windows/.blender/scripts" +cp *.py $DST diff --git a/swig/blender/mqo_import.py b/swig/blender/mqo_import.py new file mode 100644 index 0000000..cb05bc8 --- /dev/null +++ b/swig/blender/mqo_import.py @@ -0,0 +1,645 @@ +#!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__= '0.6 2010/05/05' +__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. +''' +import os +import sys +import math + +# C extension +from meshio import mqo + +def isBlender24(): + return sys.version_info[0]<3 + + +if isBlender24(): + # for 2.4 + import Blender + from Blender import Mathutils + import bpy + + # ファイルシステムの文字コード + # 改造版との共用のため + FS_ENCODING=sys.getfilesystemencoding() + if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"): + INTERNAL_ENCODING='utf-8' + else: + INTERNAL_ENCODING=FS_ENCODING +else: + # for 2.5 + import bpy + from bpy.props import * + + +def has_mikoto(mqo): + return False + + +def create_materials(scene, mqo, directory): + """ + create blender materials and renturn material list. + """ + materials = [] + images = [] + for m in mqo.materials: + material = Blender.Material.New(m.getName().encode(INTERNAL_ENCODING)) + materials.append(material) + + material.mode |= Blender.Material.Modes.SHADELESS + material.rgbCol = [m.color.r, m.color.g, m.color.b] + material.alpha = m.color.a + material.amb = m.ambient + material.spec = m.specular + material.hard = int(255 * m.power) + if m.texture!="": + texture_path=m.getTexture() + + # load texture image + if os.path.isabs(texture_path): + # absolute + path = texture_path + else: + # relative + path = os.path.join(directory, texture_path) + + # backslash to slash + #path = path.replace('\\', '/') + + # texture + if os.path.exists(path): + image = Blender.Image.Load(path.encode(INTERNAL_ENCODING)) + images.append(image) + material.mode = material.mode | Blender.Material.Modes.TEXFACE + tex = Blender.Texture.New(path.encode(INTERNAL_ENCODING)) + tex.type = Blender.Texture.Types.IMAGE + tex.image = image + material.setTexture(0, tex, Blender.Texture.TexCo.UV) + else: + print("%s not exits" % path) + + return materials + + +def create_objects(scene, root, mqo, materials): + """ + create blender mesh objects. + """ + # store hierarchy + stack=[root] + + objects=[] + for o in mqo.objects: + #print "%s:v(%d),f(%d)" % (o.name, len(o.vertices), len(o.faces)) + # create mesh + mesh = Blender.Mesh.New() + mesh_object=scene.objects.new(mesh, o.name.encode('utf-8')) + + # add hierarchy + stack_depth=len(stack)-1 + print(o.depth, stack_depth) + if o.depth=16: + print("over 16 materials!") + break + mesh.materials+=[materials[material_index]] + usedMaterials[material_index]=i + + # set face params + for i, f in enumerate(o.faces): + if not type(new_faces[i]) is int: + continue + + face=mesh.faces[new_faces[i]] + + uv_array=[] + for i in xrange(f.index_count): + uv_array.append(Blender.Mathutils.Vector( + f.getUV(i).x, + 1.0-f.getUV(i).y) + ) + try: + face.uv=uv_array + except Exception, msg: + #print msg + #print face.index, uv_array + pass + + if f.material_index in usedMaterials: + face.mat = usedMaterials[f.material_index] + + face.smooth = 1 + + # rmeove dummy 0 vertex + mesh.verts.delete(0) + + mesh.mode |= Blender.Mesh.Modes.AUTOSMOOTH + mesh.maxSmoothAngle = int(o.smoothing) + mesh.smooth() + mesh.calcNormals() + mesh.flipNormals() + mesh.update() + + # mirror modifier + if o.mirror: + mod=mesh_object.modifiers.append(Blender.Modifier.Types.MIRROR) + + return objects + + +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.indices[0] + i1=face.indices[1] + i2=face.indices[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(scene, 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方式ボーンのアンカーウェイト計算用。 + (不完全) + """ + __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方式スケルトンのアンカー。 + """ + __slots__=[ + "triangles", "bbox", + ] + def __init__(self): + self.triangles=[] + self.bbox=None + + def push(self, face, vertices): + if face.index_count==3: + self.triangles.append(TrianglePlane( + vertices[face.indices[0]], + vertices[face.indices[1]], + vertices[face.indices[2]] + )) + elif face.index_count==4: + self.triangles.append(TrianglePlane( + vertices[face.indices[0]], + vertices[face.indices[1]], + vertices[face.indices[2]] + )) + self.triangles.append(TrianglePlane( + vertices[face.indices[2]], + vertices[face.indices[3]], + vertices[face.indices[0]] + )) + # bounding box + if not self.bbox: + self.bbox=BoundingBox(vertices[face.indices[0]]) + for i in face.indices: + self.bbox.expand(vertices[i]) + + + def calcWeight(self, v): + if not self.bbox.isInside(v): + return 0 + + if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x): + return 1.0 + else: + return 0 + + def anyXY(self, x, y): + for t in self.triangles: + if t.isInsideXY(Vector2(x, y)): + return True + return False + + def anyYZ(self, y, z): + for t in self.triangles: + if t.isInsideYZ(Vector2(y, z)): + return True + return False + + def anyZX(self, z, x): + for t in self.triangles: + if t.isInsideZX(Vector2(z, x)): + return True + return False + + +def create_bone_weight(scene, mqo, armature_object, objects): + """ + create mikoto bone weight. + """ + anchorMap={} + # setup mikoto anchors + for o in mqo.objects: + if o.name.startswith("anchor"): + for f in o.faces: + name=mqo.materials[f.material_index].name + if name.endswith('[]'): + basename=name[0:-2] + v=o.vertices[f.indices[0]] + if(v.x>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_24(filename): + """ + import a mqo file. + """ + filename=filename.decode(INTERNAL_ENCODING) + print "##start mqo_import.py##" + print INTERNAL_ENCODING, FS_ENCODING + print "parse mqo file: %s" % (filename) + + Blender.Window.WaitCursor(1) + t = Blender.sys.time() + + # parse file + io=mqo.IO() + + if not io.read(filename): + return + + # get active scene + scene = Blender.Scene.GetCurrent() + + # create materials + materials=create_materials(scene, io, os.path.dirname(filename)) + + # create objects + root=scene.objects.new("Empty") + root.setName(os.path.basename(filename)) + objects=create_objects(scene, root, io, materials) + + if has_mikoto(io): + # create mikoto bone + armature_object=create_armature(scene, io) + if armature_object: + root.makeParent([armature_object]) + + # create bone weight + create_bone_weight(scene, io, armature_object, objects) + + + print('finished in %.2f seconds' % (Blender.sys.time()-t)) + print('') + Blender.Redraw() + Blender.Window.WaitCursor(0) + + +if isBlender24(): + # for 2.4 + Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo') +else: + # for 2.5 + pass + -- 2.11.0