4 Name: 'Metasequoia(.mqo)...'
\r
7 Tooltip: 'Import from Metasequoia file format (.mqo)'
\r
9 __author__=['ousttrue']
\r
10 __url__ = ["http://gunload.web.fc2.com/blender/"]
\r
11 __version__= '0.6 2010/05/05'
\r
16 This script imports a mqo into Blender for editing.
\r
18 0.2 20080123: update.
\r
19 0.3 20091125: modify for linux.
\r
20 0.4 20100310: rewrite.
\r
21 0.5 20100311: create armature from mikoto bone.
\r
22 0.6 20100505: C extension.
\r
23 0.7 20100606: integrate 2.4 and 2.5.
\r
25 ###############################################################################
\r
27 ###############################################################################
\r
33 from meshio import mqo
\r
36 return sys.version_info[0]<3
\r
41 from Blender import Mathutils
\r
49 FS_ENCODING=sys.getfilesystemencoding()
\r
50 if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):
\r
51 INTERNAL_ENCODING='utf-8'
\r
53 INTERNAL_ENCODING=FS_ENCODING
\r
57 from bpy.props import *
\r
63 ###############################################################################
\r
65 ###############################################################################
\r
66 def has_mikoto(mqo):
\r
70 def create_materials(scene, mqo, directory):
\r
72 create blender materials and renturn material list.
\r
76 for m in mqo.materials:
\r
77 material = Blender.Material.New(m.getName().encode(INTERNAL_ENCODING))
\r
78 materials.append(material)
\r
80 material.mode |= Blender.Material.Modes.SHADELESS
\r
81 material.rgbCol = [m.color.r, m.color.g, m.color.b]
\r
82 material.alpha = m.color.a
\r
83 material.amb = m.ambient
\r
84 material.spec = m.specular
\r
85 material.hard = int(255 * m.power)
\r
87 texture_path=m.getTexture()
\r
89 # load texture image
\r
90 if os.path.isabs(texture_path):
\r
95 path = os.path.join(directory, texture_path)
\r
97 # backslash to slash
\r
98 #path = path.replace('\\', '/')
\r
101 if os.path.exists(path):
\r
102 image = Blender.Image.Load(path.encode(INTERNAL_ENCODING))
\r
103 images.append(image)
\r
104 material.mode = material.mode | Blender.Material.Modes.TEXFACE
\r
105 tex = Blender.Texture.New(path.encode(INTERNAL_ENCODING))
\r
106 tex.type = Blender.Texture.Types.IMAGE
\r
108 material.setTexture(0, tex, Blender.Texture.TexCo.UV)
\r
110 print("%s not exits" % path)
\r
112 return materials, {}
\r
115 def create_objects(scene, mqo, root, materials, imageMap=None, scale=None):
\r
117 create blender mesh objects.
\r
123 for o in mqo.objects:
\r
124 #print "%s:v(%d),f(%d)" % (o.name, len(o.vertices), len(o.faces))
\r
126 mesh = Blender.Mesh.New()
\r
127 mesh_object=scene.objects.new(mesh, o.name.encode('utf-8'))
\r
130 stack_depth=len(stack)-1
\r
131 print(o.depth, stack_depth)
\r
132 if o.depth<stack_depth:
\r
133 for i in range(stack_depth-o.depth):
\r
135 stack[-1].makeParent([mesh_object])
\r
136 stack.append(mesh_object)
\r
138 if o.name.startswith('sdef'):
\r
140 objects.append(mesh_object)
\r
141 elif o.name.startswith('anchor'):
\r
142 #print("hide %s" % o.name)
\r
143 #mesh_object.restrictDisplay=False
\r
144 mesh_object.layers=[2]
\r
145 elif o.name.startswith('bone'):
\r
146 mesh_object.layers=[2]
\r
149 mesh.verts.extend(Mathutils.Vector(0, 0, 0)) # dummy
\r
150 mesh.verts.extend([(v.x, -v.z, v.y) for v in o.vertices])
\r
153 for face in o.faces:
\r
155 for i in xrange(face.index_count):
\r
156 face_indices.append(face.getIndex(i)+1)
\r
157 mesh_faces.append(face_indices)
\r
158 #new_faces=mesh.faces.extend([face.indices for face in o.faces],
\r
159 new_faces=mesh.faces.extend(mesh_faces,
\r
164 # gather used materials
\r
167 for i in new_faces:
\r
169 usedMaterials[o.faces[i].material_index]=True
\r
171 # blender limits 16 materials per mesh
\r
173 for i, material_index in enumerate(usedMaterials.keys()):
\r
175 print("over 16 materials!")
\r
177 mesh.materials+=[materials[material_index]]
\r
178 usedMaterials[material_index]=i
\r
181 for i, f in enumerate(o.faces):
\r
182 if not type(new_faces[i]) is int:
\r
185 face=mesh.faces[new_faces[i]]
\r
188 for i in xrange(f.index_count):
\r
189 uv_array.append(Blender.Mathutils.Vector(
\r
195 except Exception as msg:
\r
197 #print face.index, uv_array
\r
200 if f.material_index in usedMaterials:
\r
201 face.mat = usedMaterials[f.material_index]
\r
205 # rmeove dummy 0 vertex
\r
206 mesh.verts.delete(0)
\r
208 mesh.mode |= Blender.Mesh.Modes.AUTOSMOOTH
\r
209 mesh.maxSmoothAngle = int(o.smoothing)
\r
217 mod=mesh_object.modifiers.append(Blender.Modifier.Types.MIRROR)
\r
222 class MikotoBone(object):
\r
225 'iHead', 'iTail', 'iUp',
\r
226 'vHead', 'vTail', 'vUp',
\r
227 'parent', 'isFloating',
\r
230 def __init__(self, face=None, vertices=None, materials=None):
\r
232 self.isFloating=False
\r
238 self.name=materials[face.material_index].name.encode('utf-8')
\r
249 sqNorm0=e01.getSqNorm()
\r
250 sqNorm1=e12.getSqNorm()
\r
251 sqNorm2=e20.getSqNorm()
\r
252 if sqNorm0>sqNorm1:
\r
253 if sqNorm1>sqNorm2:
\r
259 if sqNorm0>sqNorm2:
\r
271 if sqNorm1<sqNorm2:
\r
277 if sqNorm0<sqNorm2:
\r
287 self.vHead=vertices[self.iHead]
\r
288 self.vTail=vertices[self.iTail]
\r
289 self.vUp=vertices[self.iUp]
\r
291 if self.name.endswith('[]'):
\r
292 basename=self.name[0:-2]
\r
295 self.name="%s_L" % basename
\r
297 self.name="%s_R" % basename
\r
300 def setParent(self, parent, floating=False):
\r
302 self.isFloating=True
\r
304 parent.children.append(self)
\r
306 def printTree(self, indent=''):
\r
307 print("%s%s" % (indent, self.name))
\r
308 for child in self.children:
\r
309 child.printTree(indent+' ')
\r
312 def build_armature(armature, mikotoBone, parent=None):
\r
314 create a armature bone.
\r
316 bone = Armature.Editbone()
\r
317 bone.name = mikotoBone.name.encode('utf-8')
\r
318 armature.bones[bone.name] = bone
\r
320 bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
\r
321 bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
\r
324 if mikotoBone.isFloating:
\r
327 bone.options=[Armature.CONNECTED]
\r
329 for child in mikotoBone.children:
\r
330 build_armature(armature, child, bone)
\r
333 def create_armature(scene, mqo):
\r
338 for o in mqo.objects:
\r
339 if o.name.startswith('bone'):
\r
346 for f in boneObject.faces:
\r
347 if f.index_count!=3:
\r
348 print("invalid index_count: %d" % f.index_count)
\r
350 b=MikotoBone(f, boneObject.vertices, mqo.materials)
\r
353 ####################
\r
354 # build mikoto bone tree
\r
355 ####################
\r
356 mikotoRoot=MikotoBone()
\r
358 for b in tailMap.values():
\r
359 # each bone has unique parent or is root bone.
\r
360 if b.iHead in tailMap:
\r
361 b.setParent(tailMap[b.iHead])
\r
364 for e in boneObject.edges:
\r
365 if b.iHead==e.indices[0]:
\r
367 if e.indices[1] in tailMap:
\r
368 b.setParent(tailMap[e.indices[1]], True)
\r
371 elif b.iHead==e.indices[1]:
\r
373 if e.indices[0] in tailMap:
\r
374 b.setParent(tailMap[e.indices[0]], True)
\r
381 b.setParent(mikotoRoot, True)
\r
383 if len(mikotoRoot.children)==0:
\r
384 print("no root bone")
\r
387 if len(mikotoRoot.children)==1:
\r
389 mikotoRoot=mikotoRoot.children[0]
\r
390 mikotoRoot.parent=None
\r
392 mikotoRoot.vHead=Vector3(0, 10, 0)
\r
393 mikotoRoot.vTail=Vector3(0, 0, 0)
\r
395 ####################
\r
397 ####################
\r
398 armature = Armature.New()
\r
400 armature_object = scene.objects.new(armature)
\r
402 act = Armature.NLA.NewAction()
\r
403 act.setActive(armature_object)
\r
405 armature_object.drawMode |= Object.DrawModes.XRAY
\r
406 # armature settings
\r
407 armature.drawType = Armature.OCTAHEDRON
\r
408 armature.envelopes = False
\r
409 armature.vertexGroups = True
\r
410 armature.mirrorEdit = True
\r
411 armature.drawNames=True
\r
414 armature.makeEditable()
\r
415 build_armature(armature, mikotoRoot)
\r
418 return armature_object
\r
421 class TrianglePlane(object):
\r
423 mikoto方式ボーンのアンカーウェイト計算用。
\r
426 __slots__=['normal',
\r
429 def __init__(self, v0, v1, v2):
\r
434 def isInsideXY(self, p):
\r
435 v0=Vector2(self.v0.x, self.v0.y)
\r
436 v1=Vector2(self.v1.x, self.v1.y)
\r
437 v2=Vector2(self.v2.x, self.v2.y)
\r
441 c0=Vector2.cross(e01, p-v0)
\r
442 c1=Vector2.cross(e12, p-v1)
\r
443 c2=Vector2.cross(e20, p-v2)
\r
444 if c0>=0 and c1>=0 and c2>=0:
\r
446 if c0<=0 and c1<=0 and c2<=0:
\r
449 def isInsideYZ(self, p):
\r
450 v0=Vector2(self.v0.y, self.v0.z)
\r
451 v1=Vector2(self.v1.y, self.v1.z)
\r
452 v2=Vector2(self.v2.y, self.v2.z)
\r
456 c0=Vector2.cross(e01, p-v0)
\r
457 c1=Vector2.cross(e12, p-v1)
\r
458 c2=Vector2.cross(e20, p-v2)
\r
459 if c0>=0 and c1>=0 and c2>=0:
\r
461 if c0<=0 and c1<=0 and c2<=0:
\r
464 def isInsideZX(self, p):
\r
465 v0=Vector2(self.v0.z, self.v0.x)
\r
466 v1=Vector2(self.v1.z, self.v1.x)
\r
467 v2=Vector2(self.v2.z, self.v2.x)
\r
471 c0=Vector2.cross(e01, p-v0)
\r
472 c1=Vector2.cross(e12, p-v1)
\r
473 c2=Vector2.cross(e20, p-v2)
\r
474 if c0>=0 and c1>=0 and c2>=0:
\r
476 if c0<=0 and c1<=0 and c2<=0:
\r
480 class MikotoAnchor(object):
\r
482 mikoto方式スケルトンのアンカー。
\r
485 "triangles", "bbox",
\r
487 def __init__(self):
\r
491 def push(self, face, vertices):
\r
492 if face.index_count==3:
\r
493 self.triangles.append(TrianglePlane(
\r
494 vertices[face.indices[0]],
\r
495 vertices[face.indices[1]],
\r
496 vertices[face.indices[2]]
\r
498 elif face.index_count==4:
\r
499 self.triangles.append(TrianglePlane(
\r
500 vertices[face.indices[0]],
\r
501 vertices[face.indices[1]],
\r
502 vertices[face.indices[2]]
\r
504 self.triangles.append(TrianglePlane(
\r
505 vertices[face.indices[2]],
\r
506 vertices[face.indices[3]],
\r
507 vertices[face.indices[0]]
\r
511 self.bbox=BoundingBox(vertices[face.indices[0]])
\r
512 for i in face.indices:
\r
513 self.bbox.expand(vertices[i])
\r
516 def calcWeight(self, v):
\r
517 if not self.bbox.isInside(v):
\r
520 if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
\r
525 def anyXY(self, x, y):
\r
526 for t in self.triangles:
\r
527 if t.isInsideXY(Vector2(x, y)):
\r
531 def anyYZ(self, y, z):
\r
532 for t in self.triangles:
\r
533 if t.isInsideYZ(Vector2(y, z)):
\r
537 def anyZX(self, z, x):
\r
538 for t in self.triangles:
\r
539 if t.isInsideZX(Vector2(z, x)):
\r
544 def create_bone_weight(scene, mqo, armature_object, objects):
\r
546 create mikoto bone weight.
\r
549 # setup mikoto anchors
\r
550 for o in mqo.objects:
\r
551 if o.name.startswith("anchor"):
\r
553 name=mqo.materials[f.material_index].name
\r
554 if name.endswith('[]'):
\r
555 basename=name[0:-2]
\r
556 v=o.vertices[f.indices[0]]
\r
559 name_L=basename+'_L'
\r
560 if not name_L in anchorMap:
\r
561 anchorMap[name_L]=MikotoAnchor()
\r
562 anchorMap[name_L].push(f, o.vertices)
\r
565 name_R=basename+'_R'
\r
566 if not name_R in anchorMap:
\r
567 anchorMap[name_R]=MikotoAnchor()
\r
568 anchorMap[name_R].push(f, o.vertices)
\r
570 print("no side", v)
\r
572 if not name in anchorMap:
\r
573 anchorMap[name]=MikotoAnchor()
\r
574 anchorMap[name].push(f, o.vertices)
\r
577 # add armature modifier
\r
578 mod=o.modifiers.append(Modifier.Types.ARMATURE)
\r
579 mod[Modifier.Settings.OBJECT] = armature_object
\r
580 mod[Modifier.Settings.ENVELOPES] = False
\r
581 o.makeDisplayList()
\r
582 # create vertex group
\r
583 mesh=o.getData(mesh=True)
\r
584 for name in anchorMap.keys():
\r
585 mesh.addVertGroup(name)
\r
588 # assing vertices to vertex group
\r
590 mesh=o.getData(mesh=True)
\r
591 for i, mvert in enumerate(mesh.verts):
\r
593 for name, anchor in anchorMap.items():
\r
594 weight=anchor.calcWeight(mvert.co)
\r
596 mesh.assignVertsToGroup(
\r
597 name, [i], weight, Mesh.AssignModes.ADD)
\r
600 # debug orphan vertex
\r
601 print('orphan', mvert)
\r
607 def create_texture(directory, texture_name):
\r
608 texture=bpy.data.textures.new(texture_name)
\r
609 texture.type='IMAGE'
\r
610 texture=texture.recast_type()
\r
611 #texturePath="%s/%s" % (directory, texture_name)
\r
612 texturePath=os.path.join(directory, texture_name)
\r
613 print('create_texture', texturePath)
\r
614 image=bpy.data.images.load(texturePath)
\r
615 texture.image=image
\r
616 texture.mipmap = True
\r
617 texture.interpolation = True
\r
618 texture.use_alpha = True
\r
621 def create_materials(scene, mqo, directory):
\r
625 if len(mqo.materials)>0:
\r
626 for material_index, m in enumerate(mqo.materials):
\r
627 material = bpy.data.materials.new(m.getName())
\r
628 materials.append(material)
\r
630 material.diffuse_color=[m.color.r, m.color.g, m.color.b]
\r
631 material.alpha=m.color.a
\r
632 material.diffuse_intensity=m.diffuse
\r
633 texture_name=m.getTexture()
\r
634 if texture_name!='':
\r
635 if texture_name in textureMap:
\r
636 texture=textureMap[texture_name]
\r
638 texture=create_texture(directory, texture_name)
\r
639 textureMap[texture_name]=texture
\r
640 imageMap[material_index]=texture.image
\r
641 #material.add_texture(texture, "UV", {"COLOR", "ALPHA"})
\r
642 material.add_texture(texture, "UV", "COLOR")
\r
646 material = bpy.data.materials.new('Default')
\r
647 materials.append(material)
\r
648 return materials, imageMap
\r
650 def create_objects(scene, mqo, parent, materials, imageMap, scale):
\r
651 for o in mqo.objects:
\r
654 mesh=bpy.data.meshes.new("Mesh")
\r
655 meshObject= bpy.data.objects.new(o.getName(), mesh)
\r
656 scene.objects.link(meshObject)
\r
657 meshObject.parent=parent
\r
659 # count triangle and quadrangle
\r
662 if f.index_count==3 or f.index_count==4:
\r
664 mesh.add_geometry(len(o.vertices), 0, faceCount)
\r
667 unpackedVertices=[]
\r
668 for v in o.vertices:
\r
669 # convert right-handed y-up to right-handed z-up
\r
670 unpackedVertices.extend(
\r
671 (scale*v.x, scale*-v.z, scale*v.y))
\r
672 mesh.verts.foreach_set("co", unpackedVertices)
\r
680 for i in range(f.index_count):
\r
681 face.append(f.getIndex(i))
\r
686 if len(face) != 3 and len(face) != 4:
\r
687 print("{0} vertices in face.".format(len(face)))
\r
692 # rotate indices if the 4th is 0
\r
693 face = [face[3], face[0], face[1], face[2]]
\r
694 elif len(face) == 3:
\r
696 # rotate indices if the 3rd is 0
\r
697 face = [face[2], face[0], face[1], 0]
\r
701 unpackedFaces.extend(face)
\r
702 usedMaterial.add(f.material_index)
\r
704 mesh.faces.foreach_set("verts_raw", unpackedFaces)
\r
706 #print([getFace(f) for f in o.faces])
\r
707 print("fail to mesh.faces.foreach_set")
\r
713 for i in usedMaterial:
\r
714 mesh.add_material(materials[i])
\r
715 meshMaterialMap[i]=materialIndex
\r
719 mesh.add_uv_texture()
\r
720 for mqo_face, blender_face, uv_face in zip(
\r
721 o.faces, mesh.faces, mesh.uv_textures[0].data):
\r
722 if mqo_face.index_count<3:
\r
724 blender_face.material_index=meshMaterialMap[mqo_face.material_index]
\r
725 if mqo_face.index_count>=3:
\r
726 uv_face.uv1=[mqo_face.getUV(0).x, 1.0-mqo_face.getUV(0).y]
\r
727 uv_face.uv2=[mqo_face.getUV(1).x, 1.0-mqo_face.getUV(1).y]
\r
728 uv_face.uv3=[mqo_face.getUV(2).x, 1.0-mqo_face.getUV(2).y]
\r
729 if mqo_face.index_count==4:
\r
731 mqo_face.getUV(3).x, 1.0-mqo_face.getUV(3).y]
\r
732 if materials[mqo_face.material_index] in imageMap:
\r
733 uv_face.image=imageMap[mqo_face.material_index]
\r
739 def __execute(filename, scene, scale=1.0):
\r
742 if not io.read(filename):
\r
743 print("fail to load",filename)
\r
747 materials, imageMap=create_materials(scene, io, os.path.dirname(filename))
\r
750 root=bl.createEmptyObject(scene, os.path.basename(filename))
\r
751 objects=create_objects(scene, io, root, materials, imageMap, scale)
\r
754 # create mikoto bone
\r
755 armature_object=create_armature(scene, io)
\r
756 if armature_object:
\r
757 root.makeParent([armature_object])
\r
759 # create bone weight
\r
760 create_bone_weight(scene, io, armature_object, objects)
\r
763 ###############################################################################
\r
765 ###############################################################################
\r
767 def execute_24(filename):
\r
771 filename=filename.decode(INTERNAL_ENCODING)
\r
772 print("##start mqo_import.py##")
\r
773 print(INTERNAL_ENCODING, FS_ENCODING)
\r
774 print("parse mqo file: %s" % (filename))
\r
776 Blender.Window.WaitCursor(1)
\r
777 t = Blender.sys.time()
\r
780 scene = Blender.Scene.GetCurrent()
\r
781 __execute(filename, scene)
\r
784 print('finished in %.2f seconds' % (Blender.sys.time()-t))
\r
787 Blender.Window.WaitCursor(0)
\r
791 Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo')
\r
793 def execute_25(filename, context, scale):
\r
797 __execute(filename, context.scene, scale)
\r
801 class IMPORT_OT_mqo(bpy.types.Operator):
\r
802 '''Import from Metasequoia file format (.mqo)'''
\r
803 bl_idname = "import_scene.mqo"
\r
804 bl_label = 'Import MQO'
\r
806 # List of operator properties, the attributes will be assigned
\r
807 # to the class instance from the operator settings before calling.
\r
809 path = StringProperty(
\r
811 description="File path used for importing the MQO file",
\r
812 maxlen= 1024, default= "")
\r
813 filename = StringProperty(
\r
815 description="Name of the file.")
\r
816 directory = StringProperty(
\r
818 description="Directory of the file.")
\r
820 scale = FloatProperty(
\r
822 description="Scale the MQO by this value",
\r
823 min=0.0001, max=1000000.0,
\r
824 soft_min=0.001, soft_max=100.0, default=1.0)
\r
826 def execute(self, context):
\r
827 execute_25(self.properties.path, context, self.properties.scale)
\r
830 def invoke(self, context, event):
\r
832 wm.add_fileselect(self)
\r
833 return 'RUNNING_MODAL'
\r
837 def menu_func(self, context):
\r
838 self.layout.operator(IMPORT_OT_mqo.bl_idname,
\r
839 text="Metasequoia (.mqo)")
\r
842 bpy.types.register(IMPORT_OT_mqo)
\r
843 bpy.types.INFO_MT_file_import.append(menu_func)
\r
846 bpy.types.unregister(IMPORT_OT_mqo)
\r
847 bpy.types.INFO_MT_file_import.remove(menu_func)
\r
849 if __name__=="__main__":
\r