OSDN Git Service

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