OSDN Git Service

fix
[meshio/pymeshio.git] / pymeshio / vmd.py
1 # coding: utf-8
2 """
3 VMDの読み込み
4 http://yumin3123.at.webry.info/200810/article_4.html
5 http://atupdate.web.fc2.com/vmd_format.htm
6 """
7
8
9 class ShapeData(object):
10     """
11     morphing animation data.
12     """
13     __slots__=['name', 'frame', 'ratio']
14     def __init__(self, name):
15         self.name=name
16         self.frame=-1
17         self.ratio=0
18
19     def __cmp__(self, other):
20         return cmp(self.frame, other.frame)
21
22 class MotionData(object):
23     """
24     bone animation data.
25     """
26     __slots__=['name', 'frame', 'pos', 'q', 'complement']
27     def __init__(self, name):
28         self.name=name
29         self.frame=-1
30         self.pos=Vector3()
31         self.q=Quaternion()
32
33     def __cmp__(self, other):
34         return cmp(self.frame, other.frame)
35
36     def __str__(self):
37         return '<MotionData "%s" %d %s%s>' % (self.name, self.frame, self.pos, self.q)
38
39 class VMDLoader(object):
40     __slots__=['io', 'end', 'signature',
41             'model_name', 'last_frame',
42             'motions', 'shapes', 'cameras', 'lights',
43             ]
44     def __init__(self):
45         self.model_name=''
46         self.motions=[]
47         self.shapes=[]
48         self.cameras=[]
49         self.lights=[]
50         self.last_frame=0
51
52     def __str__(self):
53         return '<VMDLoader model: "%s", motion: %d, shape: %d, camera: %d, light: %d>' % (
54             self.model_name, len(self.motions), len(self.shapes),
55             len(self.cameras), len(self.lights))
56
57     def load(self, path, io, end):
58         self.io=io
59         self.end=end
60
61         # signature
62         self.signature=truncate_zero(self.io.read(30))
63         version=self.validate_signature(self.signature)
64         if not version:
65             print("invalid signature", self.signature)
66             return False
67
68         if version==1:
69             if not self.load_verstion_1():
70                 return False
71         elif version==2:
72             if not  self.load_verstion_2():
73                 return False 
74         else:
75             raise Exception("unknown version") 
76
77         # post process
78         motions=self.motions
79         self.motions={}
80         for m in motions:
81             if not m.name in self.motions:
82                 self.motions[m.name]=[]
83             self.motions[m.name].append(m)
84         for name in self.motions.keys():
85             self.motions[name].sort()
86
87         shapes=self.shapes
88         self.shapes={}
89         for s in shapes:
90             if not s.name in self.shapes:
91                 self.shapes[s.name]=[]
92             self.shapes[s.name].append(s)
93         for name in self.shapes.keys():
94             self.shapes[name].sort()
95
96         return True
97
98     def getMotionCount(self):
99         count=0
100         for v in self.motions.values():
101             count+=len(v)
102         return count
103
104     def getShapeCount(self):
105         count=0
106         for v in self.shapes.values():
107             count+=len(v)
108         return count
109
110     def load_verstion_1(self):
111         # model name
112         self.model_name=truncate_zero(self.io.read(10))
113         if not self.loadMotion_1():
114             return False
115         return True
116
117     def loadMotion_1(self):
118         count=struct.unpack('H', self.io.read(2))[0]
119         self.io.read(2)
120         for i in xrange(0, count):
121             self.loadFrameData()
122         return True
123
124     ############################################################
125     def load_verstion_2(self):
126         # model name
127         self.model_name=truncate_zero(self.io.read(20))
128
129         if not self.loadMotion():
130             return False
131         if not self.loadShape():
132             return False
133         if not self.loadCamera():
134             return False
135         if not self.loadLight():
136             return False
137         #assert(self.io.tell()==self.end)
138         #self.motions.sort(lambda l, r: l.name<r.name)
139
140         return True
141
142     def validate_signature(self, signature):
143         if self.signature == "Vocaloid Motion Data 0002":
144             return 2
145         if self.signature == "Vocaloid Motion Data file":
146             return 1
147         else:
148             return None
149
150     def loadMotion(self):
151         count=struct.unpack('I', self.io.read(4))[0]
152         for i in xrange(0, count):
153             self.loadFrameData()
154         return True
155
156     def loadShape(self):
157         count=struct.unpack('I', self.io.read(4))[0]
158         for i in xrange(0, count):
159             self.loadShapeData()
160         return True
161
162     def loadCamera(self):
163         count=struct.unpack('I', self.io.read(4))[0]
164         for i in xrange(0, count):
165             # not implemented
166             assert(False)
167             pass
168         return True
169
170     def loadLight(self):
171         count=struct.unpack('I', self.io.read(4))[0]
172         for i in xrange(0, count):
173             # not implemented
174             assert(False)
175             pass
176         return True
177
178     def loadFrameData(self):
179         """
180         フレームひとつ分を読み込む
181         """
182         data=MotionData(truncate_zero(self.io.read(15)))
183         (data.frame, data.pos.x, data.pos.y, data.pos.z,
184         data.q.x, data.q.y, data.q.z, data.q.w) = struct.unpack(
185                 'I7f', self.io.read(32))
186         # complement data
187         data.complement=''.join(
188                 ['%x' % x for x in struct.unpack('64B', self.io.read(64))])
189         self.motions.append(data)
190         if data.frame>self.last_frame:
191             self.last_frame=data.frame
192
193     def loadShapeData(self):
194         """
195         モーフデータひとつ分を読み込む
196         """
197         data=ShapeData(truncate_zero(self.io.read(15)))
198         (data.frame, data.ratio)=struct.unpack('If', self.io.read(8))
199         self.shapes.append(data)
200         if data.frame>self.last_frame:
201             self.last_frame=data.frame
202
203     # vmd -> csv
204     ############################################################
205     def create_csv_line(m):
206         # quaternion -> euler angle
207         (roll, pitch, yaw)=m.q.getRollPitchYaw()
208         return '%s,%d,%g,%g,%g,%g,%g,%g,0x%s\n' % (
209                 m.name, m.frame, m.pos.x, m.pos.y, m.pos.z,
210                 to_degree(pitch), to_degree(yaw), to_degree(roll), m.complement
211                 )
212
213     def write_csv(l, path):
214         sys.setdefaultencoding('cp932')
215         csv=open(path, "w")
216         csv.write('%s,0\n' % l.signature)
217         csv.write('%s\n' % l.model_name)
218         # motion
219         csv.write('%d\n' % len(l.motions))
220         for m in l.motions:
221             csv.write(create_csv_line(m))
222         # shape
223         csv.write('%d\n' % len(l.shapes))
224         for s in l.shapes:
225             csv.write('%s,%d,%f\n' % ( s.name, s.frame, s.ratio))
226         # camera
227         csv.write('%d\n' % len(l.cameras))
228         for camera in l.cameras:
229             assert(False)
230         # light
231         csv.write('%d\n' % len(l.lights))
232         for light in l.lights:
233             assert(False)
234