OSDN Git Service

implement mqo_export.
[meshio/meshio.git] / swig / blender / bl24.py
1 # coding: utf-8
2 import sys
3 import os
4 import Blender
5 from Blender import Mathutils
6
7
8\e$B%U%!%$%k%7%9%F%`$NJ8;z%3!<%I\e(B
9\e$B2~B$HG$H$N6&MQ$N$?$a\e(B
10 FS_ENCODING=sys.getfilesystemencoding()
11 if os.path.exists(os.path.dirname(sys.argv[0])+"/utf8"):
12     INTERNAL_ENCODING='utf-8'
13 else:
14     INTERNAL_ENCODING=FS_ENCODING
15
16
17 class Writer(object):
18     def __init__(self, path, encoding):
19         self.io=open(path, "wb")
20         self.encoding=encoding
21
22     def write(self, s):
23         self.io.write(s)
24
25     def flush(self):
26         self.io.flush()
27
28     def close(self):
29         self.io.close()
30
31
32 def createEmptyObject(scene, name):
33     empty=scene.objects.new("Empty")
34     empty.setName(name)
35     return empty
36
37
38 def createMqoMaterial(m):
39     material = Blender.Material.New(m.getName().encode(INTERNAL_ENCODING))
40     material.mode |= Blender.Material.Modes.SHADELESS
41     material.rgbCol = [m.color.r, m.color.g, m.color.b]
42     material.alpha = m.color.a
43     material.amb = m.ambient
44     material.spec = m.specular
45     material.hard = int(255 * m.power)
46     return material
47
48
49 def createTexture(path):
50     image = Blender.Image.Load(path.encode(INTERNAL_ENCODING))
51     texture = Blender.Texture.New(path.encode(INTERNAL_ENCODING))
52     texture.type = Blender.Texture.Types.IMAGE
53     texture.image = image
54     return texture, image
55
56
57 def materialAddTexture(material, texture):
58     material.mode = material.mode | Blender.Material.Modes.TEXFACE
59     material.setTexture(0, texture, Blender.Texture.TexCo.UV)
60
61
62 def createMesh(scene, name):
63     mesh = Blender.Mesh.New()
64     mesh_object=scene.objects.new(mesh, name.encode(INTERNAL_ENCODING))
65     return mesh, mesh_object
66
67
68 def objectMakeParent(parent, child):
69     parent.makeParent([child])
70
71
72 def meshAddMqoGeometry(mesh, o, materials, imageMap, scale):
73     # add vertices
74     mesh.verts.extend(Mathutils.Vector(0, 0, 0)) # dummy
75     mesh.verts.extend([(v.x, -v.z, v.y) for v in o.vertices])
76     # add faces
77     mesh_faces=[]
78     for face in o.faces:
79         face_indices=[]
80         for i in xrange(face.index_count):
81             face_indices.append(face.getIndex(i)+1)
82         mesh_faces.append(face_indices)
83     #new_faces=mesh.faces.extend([face.indices for face in o.faces], 
84     new_faces=mesh.faces.extend(mesh_faces,
85             #ignoreDups=True, 
86             indexList=True)
87     mesh.update()
88     
89     # gather used materials
90     materialMap = {}
91     if new_faces:
92         for i in new_faces:
93             if type(i) is int:
94                 materialMap[o.faces[i].material_index]=True
95
96     # blender limits 16 materials per mesh
97     # separate mesh ?
98     for i, material_index in enumerate(materialMap.keys()):
99         if i>=16:
100             print("over 16 materials!")
101             break
102         mesh.materials+=[materials[material_index]]
103         materialMap[material_index]=i
104     
105     # set face params
106     for i, f in enumerate(o.faces):       
107         if not type(new_faces[i]) is int:
108             continue
109
110         face=mesh.faces[new_faces[i]]
111
112         uv_array=[]
113         for i in xrange(f.index_count):
114             uv_array.append(Blender.Mathutils.Vector(
115                 f.getUV(i).x, 
116                 1.0-f.getUV(i).y)
117                 )
118         try:
119             face.uv=uv_array
120         except Exception as msg:
121             #print msg
122             #print face.index, uv_array
123             pass
124     
125         if f.material_index in materialMap:
126             face.mat = materialMap[f.material_index]
127
128         face.smooth = 1
129
130     # rmeove dummy 0 vertex
131     mesh.verts.delete(0)
132         
133     mesh.mode |= Blender.Mesh.Modes.AUTOSMOOTH
134     mesh.maxSmoothAngle = int(o.smoothing)
135     mesh.smooth()
136     mesh.calcNormals()
137     mesh.flipNormals()
138     mesh.update()
139
140     # mirror modifier
141     if o.mirror:
142         mod=mesh_object.modifiers.append(Blender.Modifier.Types.MIRROR)
143
144 ###############################################################################
145 # for mqo mikoto bone.
146 ###############################################################################
147 class MikotoBone(object):
148     __slots__=[
149             'name',
150             'iHead', 'iTail', 'iUp',
151             'vHead', 'vTail', 'vUp',
152             'parent', 'isFloating',
153             'children',
154             ]
155     def __init__(self, face=None, vertices=None, materials=None):
156         self.parent=None
157         self.isFloating=False
158         self.children=[]
159         if not face:
160             self.name='root'
161             return
162
163         self.name=materials[face.material_index].name.encode('utf-8')
164
165         i0=face.indices[0]
166         i1=face.indices[1]
167         i2=face.indices[2]
168         v0=vertices[i0]
169         v1=vertices[i1]
170         v2=vertices[i2]
171         e01=v1-v0
172         e12=v2-v1
173         e20=v0-v2
174         sqNorm0=e01.getSqNorm()
175         sqNorm1=e12.getSqNorm()
176         sqNorm2=e20.getSqNorm()
177         if sqNorm0>sqNorm1:
178             if sqNorm1>sqNorm2:
179                 # e01 > e12 > e20
180                 self.iHead=i2
181                 self.iTail=i1
182                 self.iUp=i0
183             else:
184                 if sqNorm0>sqNorm2:
185                     # e01 > e20 > e12
186                     self.iHead=i2
187                     self.iTail=i0
188                     self.iUp=i1
189                 else:
190                     # e20 > e01 > e12
191                     self.iHead=i1
192                     self.iTail=i0
193                     self.iUp=i2
194         else:
195             # 0 < 1
196             if sqNorm1<sqNorm2:
197                 # e20 > e12 > e01
198                 self.iHead=i1
199                 self.iTail=i2
200                 self.iUp=i0
201             else:
202                 if sqNorm0<sqNorm2:
203                     # e12 > e20 > e01
204                     self.iHead=i0
205                     self.iTail=i2
206                     self.iUp=i1
207                 else:
208                     # e12 > e01 > e20
209                     self.iHead=i0
210                     self.iTail=i1
211                     self.iUp=i2
212         self.vHead=vertices[self.iHead]
213         self.vTail=vertices[self.iTail]
214         self.vUp=vertices[self.iUp]
215
216         if self.name.endswith('[]'):
217             basename=self.name[0:-2]
218             # expand LR name
219             if self.vTail.x>0:
220                 self.name="%s_L" % basename
221             else:
222                 self.name="%s_R" % basename
223
224
225     def setParent(self, parent, floating=False):
226         if floating:
227             self.isFloating=True
228         self.parent=parent
229         parent.children.append(self)
230
231     def printTree(self, indent=''):
232         print("%s%s" % (indent, self.name))
233         for child in self.children:
234             child.printTree(indent+'  ')
235
236
237 def build_armature(armature, mikotoBone, parent=None):
238     """
239     create a armature bone.
240     """
241     bone = Armature.Editbone()
242     bone.name = mikotoBone.name.encode('utf-8')
243     armature.bones[bone.name] = bone
244
245     bone.head = Mathutils.Vector(*mikotoBone.vHead.to_a())
246     bone.tail = Mathutils.Vector(*mikotoBone.vTail.to_a())
247     if parent:
248         bone.parent=parent
249         if mikotoBone.isFloating:
250             pass
251         else:
252             bone.options=[Armature.CONNECTED]
253
254     for child in mikotoBone.children:
255         build_armature(armature, child, bone)
256
257
258 def create_armature(scene, mqo):
259     """
260     create armature
261     """
262     boneObject=None
263     for o in mqo.objects:
264         if o.name.startswith('bone'):
265             boneObject=o
266             break
267     if not boneObject:
268         return
269
270     tailMap={}
271     for f in boneObject.faces:
272         if f.index_count!=3:
273             print("invalid index_count: %d" % f.index_count)
274             continue
275         b=MikotoBone(f, boneObject.vertices, mqo.materials)
276         tailMap[b.iTail]=b
277
278     #################### 
279     # build mikoto bone tree
280     #################### 
281     mikotoRoot=MikotoBone()
282
283     for b in tailMap.values():
284         # each bone has unique parent or is root bone.
285         if b.iHead in tailMap:
286             b.setParent(tailMap[b.iHead])
287         else: 
288             isFloating=False
289             for e in boneObject.edges:
290                 if  b.iHead==e.indices[0]:
291                     # floating bone
292                     if e.indices[1] in tailMap:
293                         b.setParent(tailMap[e.indices[1]], True)
294                         isFloating=True
295                         break
296                 elif b.iHead==e.indices[1]:
297                     # floating bone
298                     if e.indices[0] in tailMap:
299                         b.setParent(tailMap[e.indices[0]], True)
300                         isFloating=True
301                         break
302             if isFloating:
303                 continue
304
305             # no parent bone
306             b.setParent(mikotoRoot, True)
307
308     if len(mikotoRoot.children)==0:
309         print("no root bone")
310         return
311
312     if len(mikotoRoot.children)==1:
313         # single root
314         mikotoRoot=mikotoRoot.children[0]
315         mikotoRoot.parent=None
316     else:
317         mikotoRoot.vHead=Vector3(0, 10, 0)
318         mikotoRoot.vTail=Vector3(0, 0, 0)
319
320     #################### 
321     # create armature
322     #################### 
323     armature = Armature.New()
324     # link to object
325     armature_object = scene.objects.new(armature)
326     # create action
327     act = Armature.NLA.NewAction()
328     act.setActive(armature_object)
329     # set XRAY
330     armature_object.drawMode |= Object.DrawModes.XRAY
331     # armature settings
332     armature.drawType = Armature.OCTAHEDRON
333     armature.envelopes = False
334     armature.vertexGroups = True
335     armature.mirrorEdit = True
336     armature.drawNames=True
337
338     # edit bones
339     armature.makeEditable()
340     build_armature(armature, mikotoRoot)
341     armature.update()
342
343     return armature_object
344         
345
346 class TrianglePlane(object):
347     """
348     mikoto\e$BJ}<0%\!<%s$N%"%s%+!<%&%'%$%H7W;;MQ!#\e(B
349     (\e$BIT40A4\e(B)
350     """
351     __slots__=['normal', 
352             'v0', 'v1', 'v2',
353             ]
354     def __init__(self, v0, v1, v2):
355         self.v0=v0
356         self.v1=v1
357         self.v2=v2
358
359     def isInsideXY(self, p):
360         v0=Vector2(self.v0.x, self.v0.y)
361         v1=Vector2(self.v1.x, self.v1.y)
362         v2=Vector2(self.v2.x, self.v2.y)
363         e01=v1-v0
364         e12=v2-v1
365         e20=v0-v2
366         c0=Vector2.cross(e01, p-v0)
367         c1=Vector2.cross(e12, p-v1)
368         c2=Vector2.cross(e20, p-v2)
369         if c0>=0 and c1>=0 and c2>=0:
370             return True
371         if c0<=0 and c1<=0 and c2<=0:
372             return True
373
374     def isInsideYZ(self, p):
375         v0=Vector2(self.v0.y, self.v0.z)
376         v1=Vector2(self.v1.y, self.v1.z)
377         v2=Vector2(self.v2.y, self.v2.z)
378         e01=v1-v0
379         e12=v2-v1
380         e20=v0-v2
381         c0=Vector2.cross(e01, p-v0)
382         c1=Vector2.cross(e12, p-v1)
383         c2=Vector2.cross(e20, p-v2)
384         if c0>=0 and c1>=0 and c2>=0:
385             return True
386         if c0<=0 and c1<=0 and c2<=0:
387             return True
388
389     def isInsideZX(self, p):
390         v0=Vector2(self.v0.z, self.v0.x)
391         v1=Vector2(self.v1.z, self.v1.x)
392         v2=Vector2(self.v2.z, self.v2.x)
393         e01=v1-v0
394         e12=v2-v1
395         e20=v0-v2
396         c0=Vector2.cross(e01, p-v0)
397         c1=Vector2.cross(e12, p-v1)
398         c2=Vector2.cross(e20, p-v2)
399         if c0>=0 and c1>=0 and c2>=0:
400             return True
401         if c0<=0 and c1<=0 and c2<=0:
402             return True
403
404
405 class MikotoAnchor(object):
406     """
407     mikoto\e$BJ}<0%9%1%k%H%s$N%"%s%+!<!#\e(B
408     """
409     __slots__=[
410             "triangles", "bbox",
411             ]
412     def __init__(self):
413         self.triangles=[]
414         self.bbox=None
415
416     def push(self, face, vertices):
417         if face.index_count==3:
418             self.triangles.append(TrianglePlane(
419                 vertices[face.indices[0]],
420                 vertices[face.indices[1]],
421                 vertices[face.indices[2]]
422                 ))
423         elif face.index_count==4:
424             self.triangles.append(TrianglePlane(
425                 vertices[face.indices[0]],
426                 vertices[face.indices[1]],
427                 vertices[face.indices[2]]
428                 ))
429             self.triangles.append(TrianglePlane(
430                 vertices[face.indices[2]],
431                 vertices[face.indices[3]],
432                 vertices[face.indices[0]]
433                 ))
434         # bounding box
435         if not self.bbox:
436             self.bbox=BoundingBox(vertices[face.indices[0]])
437         for i in face.indices:
438             self.bbox.expand(vertices[i])
439
440
441     def calcWeight(self, v):
442         if not self.bbox.isInside(v):
443             return 0
444
445         if self.anyXY(v.x, v.y) and self.anyYZ(v.y, v.z) and self.anyZX(v.z, v.x):
446             return 1.0
447         else:
448             return 0
449         
450     def anyXY(self, x, y):
451         for t in self.triangles:
452             if t.isInsideXY(Vector2(x, y)):
453                 return True
454         return False
455
456     def anyYZ(self, y, z):
457         for t in self.triangles:
458             if t.isInsideYZ(Vector2(y, z)):
459                 return True
460         return False
461
462     def anyZX(self, z, x):
463         for t in self.triangles:
464             if t.isInsideZX(Vector2(z, x)):
465                 return True
466         return False
467
468
469 def create_bone_weight(scene, mqo, armature_object, objects):
470     """
471     create mikoto bone weight.
472     """
473     anchorMap={}
474     # setup mikoto anchors
475     for o in mqo.objects:
476         if o.name.startswith("anchor"):
477             for f in o.faces:
478                 name=mqo.materials[f.material_index].name
479                 if name.endswith('[]'):
480                     basename=name[0:-2]
481                     v=o.vertices[f.indices[0]]
482                     if(v.x>0):
483                         # L
484                         name_L=basename+'_L'
485                         if not name_L in anchorMap:
486                             anchorMap[name_L]=MikotoAnchor()
487                         anchorMap[name_L].push(f, o.vertices)
488                     elif(v.x<0):
489                         # R
490                         name_R=basename+'_R'
491                         if not name_R in anchorMap:
492                             anchorMap[name_R]=MikotoAnchor()
493                         anchorMap[name_R].push(f, o.vertices)
494                     else:
495                         print("no side", v)
496                 else:
497                     if not name in anchorMap:
498                         anchorMap[name]=MikotoAnchor()
499                     anchorMap[name].push(f, o.vertices)
500
501     for o in objects:
502         # add armature modifier
503         mod=o.modifiers.append(Modifier.Types.ARMATURE)
504         mod[Modifier.Settings.OBJECT] = armature_object
505         mod[Modifier.Settings.ENVELOPES] = False
506         o.makeDisplayList()
507         # create vertex group
508         mesh=o.getData(mesh=True)
509         for name in anchorMap.keys():
510             mesh.addVertGroup(name)
511         mesh.update()
512                  
513     # assing vertices to vertex group
514     for o in objects:
515         mesh=o.getData(mesh=True)
516         for i, mvert in enumerate(mesh.verts):
517             hasWeight=False
518             for name, anchor in anchorMap.items():
519                 weight=anchor.calcWeight(mvert.co)
520                 if weight>0:
521                     mesh.assignVertsToGroup(
522                             name, [i], weight, Mesh.AssignModes.ADD)
523                     hasWeight=True
524             if not hasWeight:
525                 # debug orphan vertex
526                 print('orphan', mvert)
527         mesh.update()
528
529 ###############################################################################
530 def getTexture(m, dirname):
531     tex=""
532     aplane=""
533     # texture
534     for texture in m.getTextures():
535         if texture and texture.tex and texture.tex.getImage():
536             image=texture.tex.getImage()
537             if not image:
538                 continue
539             imagePath=Blender.sys.expandpath(image.getFilename())
540             if len(dirname)>0 and imagePath.startswith(dirname):
541                 # \e$BAjBP%Q%9$KJQ49$9$k\e(B
542                 imagePath=imagePath[len(dirname)+1:len(imagePath)]
543             if texture.mtCol>0:
544                 tex=" tex(\"%s\")" % imagePath
545             elif texture.mtAlpha>0:
546                 aplane=" aplane(\"%s\")" % imagePath
547     return tex, aplane
548
549 def objectDuplicate(scene, obj):
550     mesh, dumy=createMesh(scene, obj.name.decode(INTERNAL_ENCODING))
551     # not apply modifiers
552     mesh.getFromObject(obj.name, 1)
553     # apply matrix
554     dumy.setMatrix(obj.matrixWorld)
555     return mesh, dumy
556
557 def faceVertexCount(face):
558     return len(face.v)
559
560 def faceVertices(face):
561     # flip
562     return [v.index for v in reversed(face.v)]
563
564 def meshHasUV(mesh):
565     return mesh.faceUV
566
567 def faceHasUV(mesh, i, face):
568     return len(face.uv)>0
569
570 def faceGetUV(mesh, i, face, count):
571     # flip
572     return reversed(face.uv)
573
574 def materialToMqo(m):
575     return "\"%s\" shader(3) col(%f %f %f %f)" % (
576             m.name, m.rgbCol[0], m.rgbCol[1], m.rgbCol[2], m.alpha)
577
578 def faceMaterialIndex(face):
579     return face.mat
580