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
24 0.8 20100619: fix multibyte object name.
\r
28 ###############################################################################
\r
30 ###############################################################################
\r
35 from meshio import mqo
\r
38 return sys.version_info[0]<3
\r
43 from Blender import Mathutils
\r
51 from bpy.props import *
\r
57 def has_mikoto(mqo):
\r
58 #for o in mqo.objects:
\r
59 # if o.getName().startswith('bone'):
\r
61 # if o.getName().startswith('sdef'):
\r
63 # if o.getName().startswith('anchor'):
\r
68 def __createMaterials(scene, mqo, directory):
\r
70 create blender materials and renturn material list.
\r
75 if len(mqo.materials)>0:
\r
76 for material_index, m in enumerate(mqo.materials):
\r
78 material=bl.createMqoMaterial(m)
\r
79 materials.append(material)
\r
81 texture_name=m.getTexture()
\r
82 if texture_name!='':
\r
83 if texture_name in textureMap:
\r
84 texture=textureMap[texture_name]
\r
86 # load texture image
\r
87 if os.path.isabs(texture_name):
\r
92 path = os.path.join(directory, texture_name)
\r
94 if os.path.exists(path):
\r
95 print("create texture:", path)
\r
96 texture, image=bl.createTexture(path)
\r
97 textureMap[texture_name]=texture
\r
98 imageMap[material_index]=image
\r
100 print("%s not exits" % path)
\r
102 bl.materialAddTexture(material, texture)
\r
106 return materials, imageMap
\r
109 def __createObjects(scene, mqo, root, materials, imageMap, scale):
\r
111 create blender mesh objects.
\r
116 for o in mqo.objects:
\r
117 mesh, mesh_object=bl.createMesh(scene, o.getName())
\r
120 stack_depth=len(stack)-1
\r
121 #print(o.depth, stack_depth)
\r
122 if o.depth<stack_depth:
\r
123 for i in range(stack_depth-o.depth):
\r
125 bl.objectMakeParent(stack[-1], mesh_object)
\r
126 stack.append(mesh_object)
\r
128 if o.getName().startswith('sdef'):
\r
129 objects.append(mesh_object)
\r
130 elif o.getName().startswith('anchor'):
\r
131 bl.objectLayerMask(mesh_object, [0, 1])
\r
132 elif o.getName().startswith('bone'):
\r
133 bl.objectLayerMask(mesh_object, [0, 1])
\r
135 bl.meshAddMqoGeometry(mesh_object, o, materials, imageMap, scale)
\r
139 bl.objectAddMirrorModifier(mesh_object)
\r
142 bl.meshSetSmooth(mesh, o.smoothing)
\r
147 ###############################################################################
\r
148 # for mqo mikoto bone.
\r
149 ###############################################################################
\r
150 class MikotoBone(object):
\r
153 'iHead', 'iTail', 'iUp',
\r
154 'vHead', 'vTail', 'vUp',
\r
155 'parent', 'isFloating',
\r
158 def __init__(self, face=None, vertices=None, materials=None):
\r
160 self.isFloating=False
\r
166 self.name=materials[face.material_index].name.encode('utf-8')
\r
168 i0=face.getIndex(0)
\r
169 i1=face.getIndex(1)
\r
170 i2=face.getIndex(2)
\r
177 sqNorm0=e01.getSqNorm()
\r
178 sqNorm1=e12.getSqNorm()
\r
179 sqNorm2=e20.getSqNorm()
\r
180 if sqNorm0>sqNorm1:
\r
181 if sqNorm1>sqNorm2:
\r
187 if sqNorm0>sqNorm2:
\r
199 if sqNorm1<sqNorm2:
\r
205 if sqNorm0<sqNorm2:
\r
215 self.vHead=vertices[self.iHead]
\r
216 self.vTail=vertices[self.iTail]
\r
217 self.vUp=vertices[self.iUp]
\r
219 if self.name.endswith('[]'):
\r
220 basename=self.name[0:-2]
\r
223 self.name="%s_L" % basename
\r
225 self.name="%s_R" % basename
\r
228 def setParent(self, parent, floating=False):
\r
230 self.isFloating=True
\r
232 parent.children.append(self)
\r
234 def printTree(self, indent=''):
\r
235 print("%s%s" % (indent, self.name))
\r
236 for child in self.children:
\r
237 child.printTree(indent+' ')
\r
240 def build_armature(armature, mikotoBone, parent=None):
\r
242 create a armature bone.
\r
244 bone = Armature.Editbone()
\r
245 bone.name = mikotoBone.name.encode('utf-8')
\r
246 armature.bones[bone.name] = bone
\r
248 bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
\r
249 bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
\r
252 if mikotoBone.isFloating:
\r
255 bone.options=[Armature.CONNECTED]
\r
257 for child in mikotoBone.children:
\r
258 build_armature(armature, child, bone)
\r
261 def create_armature(scene, mqo):
\r
266 for o in mqo.objects:
\r
267 if o.name.startswith('bone'):
\r
274 for f in boneObject.faces:
\r
275 if f.index_count!=3:
\r
276 print("invalid index_count: %d" % f.index_count)
\r
278 b=MikotoBone(f, boneObject.vertices, mqo.materials)
\r
281 ####################
\r
282 # build mikoto bone tree
\r
283 ####################
\r
284 mikotoRoot=MikotoBone()
\r
286 for b in tailMap.values():
\r
287 # each bone has unique parent or is root bone.
\r
288 if b.iHead in tailMap:
\r
289 b.setParent(tailMap[b.iHead])
\r
292 for e in boneObject.edges:
\r
293 if b.iHead==e.indices[0]:
\r
295 if e.indices[1] in tailMap:
\r
296 b.setParent(tailMap[e.indices[1]], True)
\r
299 elif b.iHead==e.indices[1]:
\r
301 if e.indices[0] in tailMap:
\r
302 b.setParent(tailMap[e.indices[0]], True)
\r
309 b.setParent(mikotoRoot, True)
\r
311 if len(mikotoRoot.children)==0:
\r
312 print("no root bone")
\r
315 if len(mikotoRoot.children)==1:
\r
317 mikotoRoot=mikotoRoot.children[0]
\r
318 mikotoRoot.parent=None
\r
320 mikotoRoot.vHead=Vector3(0, 10, 0)
\r
321 mikotoRoot.vTail=Vector3(0, 0, 0)
\r
323 ####################
\r
325 ####################
\r
326 armature = Armature.New()
\r
328 armature_object = scene.objects.new(armature)
\r
330 act = Armature.NLA.NewAction()
\r
331 act.setActive(armature_object)
\r
333 armature_object.drawMode |= Object.DrawModes.XRAY
\r
334 # armature settings
\r
335 armature.drawType = Armature.OCTAHEDRON
\r
336 armature.envelopes = False
\r
337 armature.vertexGroups = True
\r
338 armature.mirrorEdit = True
\r
339 armature.drawNames=True
\r
342 armature.makeEditable()
\r
343 build_armature(armature, mikotoRoot)
\r
346 return armature_object
\r
349 class TrianglePlane(object):
\r
351 mikoto
\e$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#
\e(B
\r
354 __slots__=['normal',
\r
357 def __init__(self, v0, v1, v2):
\r
362 def isInsideXY(self, p):
\r
363 v0=Vector2(self.v0.x, self.v0.y)
\r
364 v1=Vector2(self.v1.x, self.v1.y)
\r
365 v2=Vector2(self.v2.x, self.v2.y)
\r
369 c0=Vector2.cross(e01, p-v0)
\r
370 c1=Vector2.cross(e12, p-v1)
\r
371 c2=Vector2.cross(e20, p-v2)
\r
372 if c0>=0 and c1>=0 and c2>=0:
\r
374 if c0<=0 and c1<=0 and c2<=0:
\r
377 def isInsideYZ(self, p):
\r
378 v0=Vector2(self.v0.y, self.v0.z)
\r
379 v1=Vector2(self.v1.y, self.v1.z)
\r
380 v2=Vector2(self.v2.y, self.v2.z)
\r
384 c0=Vector2.cross(e01, p-v0)
\r
385 c1=Vector2.cross(e12, p-v1)
\r
386 c2=Vector2.cross(e20, p-v2)
\r
387 if c0>=0 and c1>=0 and c2>=0:
\r
389 if c0<=0 and c1<=0 and c2<=0:
\r
392 def isInsideZX(self, p):
\r
393 v0=Vector2(self.v0.z, self.v0.x)
\r
394 v1=Vector2(self.v1.z, self.v1.x)
\r
395 v2=Vector2(self.v2.z, self.v2.x)
\r
399 c0=Vector2.cross(e01, p-v0)
\r
400 c1=Vector2.cross(e12, p-v1)
\r
401 c2=Vector2.cross(e20, p-v2)
\r
402 if c0>=0 and c1>=0 and c2>=0:
\r
404 if c0<=0 and c1<=0 and c2<=0:
\r
408 class MikotoAnchor(object):
\r
410 mikoto
\e$BJ}<0%9%1%k%H%s$N%"%s%+!<!#
\e(B
\r
413 "triangles", "bbox",
\r
415 def __init__(self):
\r
419 def push(self, face, vertices):
\r
420 if face.index_count==3:
\r
421 self.triangles.append(TrianglePlane(
\r
422 vertices[face.indices[0]],
\r
423 vertices[face.indices[1]],
\r
424 vertices[face.indices[2]]
\r
426 elif face.index_count==4:
\r
427 self.triangles.append(TrianglePlane(
\r
428 vertices[face.indices[0]],
\r
429 vertices[face.indices[1]],
\r
430 vertices[face.indices[2]]
\r
432 self.triangles.append(TrianglePlane(
\r
433 vertices[face.indices[2]],
\r
434 vertices[face.indices[3]],
\r
435 vertices[face.indices[0]]
\r
439 self.bbox=BoundingBox(vertices[face.indices[0]])
\r
440 for i in face.indices:
\r
441 self.bbox.expand(vertices[i])
\r
444 def calcWeight(self, v):
\r
445 if not self.bbox.isInside(v):
\r
448 if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
\r
453 def anyXY(self, x, y):
\r
454 for t in self.triangles:
\r
455 if t.isInsideXY(Vector2(x, y)):
\r
459 def anyYZ(self, y, z):
\r
460 for t in self.triangles:
\r
461 if t.isInsideYZ(Vector2(y, z)):
\r
465 def anyZX(self, z, x):
\r
466 for t in self.triangles:
\r
467 if t.isInsideZX(Vector2(z, x)):
\r
472 def create_bone_weight(scene, mqo, armature_object, objects):
\r
474 create mikoto bone weight.
\r
477 # setup mikoto anchors
\r
478 for o in mqo.objects:
\r
479 if o.name.startswith("anchor"):
\r
481 name=mqo.materials[f.material_index].name
\r
482 if name.endswith('[]'):
\r
483 basename=name[0:-2]
\r
484 v=o.vertices[f.indices[0]]
\r
487 name_L=basename+'_L'
\r
488 if not name_L in anchorMap:
\r
489 anchorMap[name_L]=MikotoAnchor()
\r
490 anchorMap[name_L].push(f, o.vertices)
\r
493 name_R=basename+'_R'
\r
494 if not name_R in anchorMap:
\r
495 anchorMap[name_R]=MikotoAnchor()
\r
496 anchorMap[name_R].push(f, o.vertices)
\r
498 print("no side", v)
\r
500 if not name in anchorMap:
\r
501 anchorMap[name]=MikotoAnchor()
\r
502 anchorMap[name].push(f, o.vertices)
\r
505 # add armature modifier
\r
506 mod=o.modifiers.append(Modifier.Types.ARMATURE)
\r
507 mod[Modifier.Settings.OBJECT] = armature_object
\r
508 mod[Modifier.Settings.ENVELOPES] = False
\r
509 o.makeDisplayList()
\r
510 # create vertex group
\r
511 mesh=o.getData(mesh=True)
\r
512 for name in anchorMap.keys():
\r
513 mesh.addVertGroup(name)
\r
516 # assing vertices to vertex group
\r
518 mesh=o.getData(mesh=True)
\r
519 for i, mvert in enumerate(mesh.verts):
\r
521 for name, anchor in anchorMap.items():
\r
522 weight=anchor.calcWeight(mvert.co)
\r
524 mesh.assignVertsToGroup(
\r
525 name, [i], weight, Mesh.AssignModes.ADD)
\r
528 # debug orphan vertex
\r
529 print('orphan', mvert)
\r
533 def __execute(filename, scene, scale=0.1):
\r
536 if not io.read(filename):
\r
537 print("fail to load",filename)
\r
541 materials, imageMap=__createMaterials(scene, io, os.path.dirname(filename))
\r
542 if len(materials)==0:
\r
543 materials.append(bl.createMaterial('default'))
\r
546 root=bl.createEmptyObject(scene, os.path.basename(filename))
\r
547 objects=__createObjects(scene, io, root, materials, imageMap, scale)
\r
550 # create mikoto bone
\r
551 armature_object=create_armature(scene, io)
\r
552 if armature_object:
\r
553 root.makeParent([armature_object])
\r
555 # create bone weight
\r
556 create_bone_weight(scene, io, armature_object, objects)
\r
559 ###############################################################################
\r
561 ###############################################################################
\r
564 def execute_24(filename):
\r
568 filename=filename.decode(bl.INTERNAL_ENCODING)
\r
569 print("##start mqo_import.py##")
\r
570 print(bl.INTERNAL_ENCODING, bl.FS_ENCODING)
\r
571 print("parse mqo file: %s" % (filename))
\r
573 Blender.Window.WaitCursor(1)
\r
574 t = Blender.sys.time()
\r
577 scene = Blender.Scene.GetCurrent()
\r
578 __execute(filename, scene)
\r
581 print('finished in %.2f seconds' % (Blender.sys.time()-t))
\r
584 Blender.Window.WaitCursor(0)
\r
587 Blender.Window.FileSelector(execute_24, 'Import MQO', '*.mqo')
\r
590 def execute_25(*args):
\r
594 class IMPORT_OT_mqo(bpy.types.Operator):
\r
595 '''Import from Metasequoia file format (.mqo)'''
\r
596 bl_idname = "import_scene.mqo"
\r
597 bl_label = 'Import MQO'
\r
599 # List of operator properties, the attributes will be assigned
\r
600 # to the class instance from the operator settings before calling.
\r
602 path = StringProperty(
\r
604 description="File path used for importing the MQO file",
\r
605 maxlen= 1024, default= "")
\r
606 filename = StringProperty(
\r
608 description="Name of the file.")
\r
609 directory = StringProperty(
\r
611 description="Directory of the file.")
\r
613 scale = FloatProperty(
\r
615 description="Scale the MQO by this value",
\r
616 min=0.0001, max=1000000.0,
\r
617 soft_min=0.001, soft_max=100.0, default=0.01)
\r
619 def execute(self, context):
\r
621 self.properties.path,
\r
623 self.properties.scale)
\r
626 def invoke(self, context, event):
\r
628 wm.add_fileselect(self)
\r
629 return 'RUNNING_MODAL'
\r
633 def menu_func(self, context):
\r
634 self.layout.operator(
\r
635 IMPORT_OT_mqo.bl_idname,
\r
636 text="Metasequoia (.mqo)")
\r
639 bpy.types.register(IMPORT_OT_mqo)
\r
640 bpy.types.INFO_MT_file_import.append(menu_func)
\r
643 bpy.types.unregister(IMPORT_OT_mqo)
\r
644 bpy.types.INFO_MT_file_import.remove(menu_func)
\r
646 if __name__=="__main__":
\r