OSDN Git Service

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