OSDN Git Service

implement pmx bone read
[meshio/pymeshio.git] / pymeshio / pmx.py
1 #!/usr/bin/env python\r
2 # coding: utf-8\r
3 """\r
4 pmx file io library.\r
5 \r
6 pmx file format:\r
7     PMDEditor's Lib/PMX仕様/PMX仕様.txt\r
8 """\r
9 __author__="ousttrue"\r
10 __license__="zlib"\r
11 __versioon__="1.0.0"\r
12 \r
13 \r
14 import io\r
15 import os\r
16 import struct\r
17 from . import common\r
18 \r
19 \r
20 class ParseException(Exception):\r
21     pass\r
22 \r
23 \r
24 class Ik(object):\r
25     """ik info\r
26     """\r
27     __slots__=[\r
28             'target_index',\r
29             'loop',\r
30             'limit_radian',\r
31             'link',\r
32             ]\r
33     def __init__(self, target_index, loop, limit_radian):\r
34         self.target_index=target_index\r
35         self.loop=loop\r
36         self.limit_radian=limit_radian\r
37         self.link=[]\r
38 \r
39 \r
40 class IkLink(object):\r
41     """ik link info\r
42     """\r
43     __slots__=[\r
44             'bone_index',\r
45             'limit_angle',\r
46             'limit_min',\r
47             'limit_max',\r
48             ]\r
49     def __init__(self, bone_index, limit_angle):\r
50         self.bone_index=bone_index\r
51         self.limit_angle=limit_angle\r
52         self.limit_min=None\r
53         self.limit_max=None\r
54 \r
55 \r
56 class Bone(object):\r
57     """material\r
58 \r
59     Bone: see __init__\r
60     """\r
61     __slots__=[\r
62             'name',\r
63             'english_name',\r
64             'position',\r
65             'parent_index',\r
66             'layer',\r
67             'flag',\r
68 \r
69             'tail_positoin',\r
70             'tail_index',\r
71             'effect_index',\r
72             'effect_factor',\r
73             'fixed_axis',\r
74             'local_x_vector',\r
75             'local_z_vector',\r
76             'external_key',\r
77             'ik',\r
78             ]\r
79     def __init__(self,\r
80             name: str,\r
81             english_name: str,\r
82             position: common.Vector3,\r
83             parent_index: int,\r
84             layer: int,\r
85             flag: int\r
86             ):\r
87         self.name=name,\r
88         self.english_name=english_name\r
89         self.position=position\r
90         self.parent_index=parent_index\r
91         self.layer=layer\r
92         self.flag=flag\r
93 \r
94     def getConnectionFlag(self) -> int:\r
95         return self.flag & 0x0001\r
96 \r
97     def getIkFlag(self) -> int:\r
98         return (self.flag & 0x0020) >> 5\r
99 \r
100     def getRotationFlag(self) -> int:\r
101         return (self.flag & 0x0100) >> 8\r
102 \r
103     def getTranslationFlag(self) -> int:\r
104         return (self.flag & 0x0200) >> 9\r
105 \r
106     def getFixedAxisFlag(self) -> int:\r
107         return (self.flag & 0x0400) >> 10\r
108 \r
109     def getLocalCoordinateFlag(self) -> int:\r
110         return (self.flag &  0x0800) >> 11\r
111     \r
112     def getExternalParentDeformFlag(self) -> int:\r
113         return (self.flag &  0x2000) >> 13\r
114 \r
115  \r
116 class Material(object):\r
117     """material\r
118 \r
119     Attributes: see __init__\r
120     """\r
121     __slots__=[\r
122             'name',\r
123             'english_name',\r
124             'diffuse_color',\r
125             'diffuse_alpha',\r
126             'specular_color',\r
127             'specular_factor',\r
128             'ambient_color',\r
129             'flag',\r
130             'edge_color',\r
131             'edge_size',\r
132             'texture_index',\r
133             'sphia_texture_index',\r
134             'sphia_mode',\r
135             'toon_sharing_flag',\r
136             'toon_texture_index',\r
137             'comment',\r
138             'index_count',\r
139             ]\r
140     def __init__(self,\r
141             name: str,\r
142             english_name: str,\r
143             diffuse_color: common.RGB,\r
144             diffuse_alpha: float,\r
145             specular_color: common.RGB,\r
146             specular_factor: float,\r
147             ambient_color: common.RGB,\r
148             flag: int,\r
149             edge_color: common.RGBA,\r
150             edge_size: float,\r
151             texture_index: int,\r
152             sphia_texture_index: int,\r
153             sphia_mode: int,\r
154             toon_sharing_flag: int\r
155             ):\r
156         self.name=name\r
157         self.english_name=english_name\r
158         self.diffuse_color=diffuse_color\r
159         self.diffuse_alpha=diffuse_alpha\r
160         self.specular_color=specular_color\r
161         self.specular_factor=specular_factor\r
162         self.ambient_color=ambient_color\r
163         self.flag=flag\r
164         self.edge_color=edge_color\r
165         self.edge_size=edge_size\r
166         self.texture_index=texture_index\r
167         self.sphia_texture_index=sphia_texture_index\r
168         self.sphia_mode=sphia_mode\r
169         self.toon_sharing_flag=toon_sharing_flag\r
170         #\r
171         self.toon_texture_index=None\r
172         self.comment=''\r
173         self.index_count=0\r
174 \r
175 \r
176 class Deform(object):\r
177     pass\r
178 \r
179 \r
180 class Bdef1(object):\r
181     """bone deform. use a weight\r
182 \r
183     Attributes: see __init__\r
184     """\r
185     __slots__=[ 'bone_index']\r
186     def __init__(self, bone_index: int):\r
187         self.bone_index=bone_index\r
188 \r
189 \r
190 class Bdef2(object):\r
191     """bone deform. use two weights\r
192 \r
193     Attributes: see __init__\r
194     """\r
195     __slots__=[ 'index0', 'index1', 'weight0']\r
196     def __init__(self, \r
197             index0: int,\r
198             index1: int,\r
199             weight0: float):\r
200         self.index0=index0\r
201         self.index1=index1\r
202         self.weight0=weight0\r
203 \r
204 \r
205 class Vertex(object):\r
206     """pmx vertex\r
207 \r
208     Attributes: see __init__\r
209     """\r
210     __slots__=[ 'position', 'normal', 'uv', 'deform', 'edge_factor' ]\r
211     def __init__(self, \r
212             position: common.Vector3, \r
213             normal: common.Vector3, \r
214             uv: common.Vector2, \r
215             deform: Deform, \r
216             edge_factor: float):\r
217         self.position=position \r
218         self.normal=normal\r
219         self.uv=uv\r
220         self.deform=deform\r
221         self.edge_factor=edge_factor\r
222 \r
223 \r
224 class Model(object):\r
225     """pmx data representation\r
226 \r
227     Attributes:\r
228         version: pmx version(expected 2.0)\r
229         name: \r
230         english_name: \r
231         comment: \r
232         english_comment: \r
233         vertices:\r
234         textures:\r
235         materials:\r
236         bones:\r
237     """\r
238     __slots__=[\r
239             'version', # pmx version\r
240             'name', # model name\r
241             'english_name', # model name in english\r
242             'comment', # model comment\r
243             'english_comment', # model comment in english\r
244             'vertices',\r
245             'indices',\r
246             'textures',\r
247             'materials',\r
248             'bones',\r
249             ]\r
250     def __init__(self):\r
251         self.version=0.0\r
252         self.name=''\r
253         self.english_name=''\r
254         self.comment=''\r
255         self.english_comment=''\r
256         self.vertices=[]\r
257         self.indices=[]\r
258         self.textures=[]\r
259         self.materials=[]\r
260         self.bones=[]\r
261 \r
262 \r
263 class IO(object):\r
264     """pmx loader\r
265 \r
266     Attributes:\r
267         text_encoding: \r
268         extended_uv:\r
269         vertex_index_size:\r
270         texture_index_size:\r
271         material_index_size:\r
272         bone_index_size:\r
273         morph_index_size:\r
274         rigidbody_index_size:\r
275     """\r
276     def __init__(self):\r
277         self.__io=None\r
278         self.__pos=-1\r
279         self.__end=-1\r
280         self.__model=Model()\r
281 \r
282     def read(self, path: 'filepath') -> Model:\r
283         size=os.path.getsize(path)\r
284         with open(path, "rb") as f:\r
285             if self.__load(path, f, size):\r
286                 return self.__model\r
287 \r
288     def __load(self, path: 'filepath', io: io.IOBase, size: int) -> bool: \r
289         self.__io=io\r
290         self.__end=size\r
291         self.__pos=0\r
292 \r
293         ####################\r
294         # header\r
295         ####################\r
296         signature=self.__unpack("4s", 4)\r
297         if signature!=b"PMX ":\r
298             print("invalid signature", self.signature)\r
299             return False\r
300         version=self.__read_float()\r
301         if version!=2.0:\r
302             print("unknown version", version)\r
303         self.__model.version=version\r
304         # flags\r
305         flag_bytes=self.__read_uint(1)\r
306         if flag_bytes!=8:\r
307             print("invalid flag length", self.flag_bytes)\r
308             return False\r
309         # text encoding\r
310         self.text_encoding=self.__read_uint(1)\r
311         self.__read_text=self.__get_read_text()\r
312         # uv\r
313         self.extended_uv=self.__read_uint(1)\r
314         if self.extended_uv>0:\r
315             print("extended uv is not supported", self.extended_uv)\r
316             return False\r
317         # index size\r
318         self.vertex_index_size=self.__read_uint(1)\r
319         self.texture_index_size=self.__read_uint(1)\r
320         self.material_index_size=self.__read_uint(1)\r
321         self.bone_index_size=self.__read_uint(1)\r
322         self.morph_index_size=self.__read_uint(1)\r
323         self.rigidbody_index_size=self.__read_uint(1)\r
324 \r
325         ####################\r
326         # model info\r
327         ####################\r
328         self.__model.name = self.__read_text()\r
329         self.__model.english_name = self.__read_text()\r
330         self.__model.comment = self.__read_text()\r
331         self.__model.english_comment = self.__read_text()\r
332 \r
333         ####################\r
334         # vertices\r
335         ####################\r
336         vertex_count=self.__read_uint(4)\r
337         self.__model.vertices=[self.__read_vertex() \r
338                 for i in range(vertex_count)]\r
339 \r
340         ####################\r
341         # indices\r
342         ####################\r
343         index_count=self.__read_uint(4)\r
344         self.__model.indices=[self.__read_uint(self.vertex_index_size) \r
345                 for i in range(index_count)]\r
346 \r
347         ####################\r
348         # textures\r
349         ####################\r
350         texture_count=self.__read_uint(4)\r
351         self.__model.textures=[self.__read_text() \r
352                 for i in range(texture_count)]\r
353 \r
354         ####################\r
355         # materials\r
356         ####################\r
357         material_count=self.__read_uint(4)\r
358         self.__model.materials=[self.__read_material() \r
359                 for i in range(material_count)]\r
360 \r
361         ####################\r
362         # bones\r
363         ####################\r
364         bone_count=self.__read_uint(4)\r
365         self.__model.bones=[self.__read_bone() \r
366                 for i in range(bone_count)]\r
367 \r
368         return True\r
369 \r
370     def __str__(self) -> str:\r
371         return '<PmxIO>'\r
372 \r
373     def __check_position(self):\r
374         self.__pos=self.__io.tell()\r
375 \r
376     def __unpack(self, fmt: str, size: int) -> "read value as format":\r
377         result=struct.unpack(fmt, self.__io.read(size))\r
378         self.__check_position()\r
379         return result[0]\r
380 \r
381     def __get_read_text(self) -> "text process function":\r
382         if self.text_encoding==0:\r
383             def read_text():\r
384                 size=self.__read_uint(4)\r
385                 return self.__unpack("{0}s".format(size), size).decode("UTF16")\r
386             return read_text\r
387         elif self.text_encoding==1:\r
388             def read_text():\r
389                 size=self.__read_uint(4)\r
390                 return self.__unpack("{0}s".format(size), size).decode("UTF8")\r
391             return read_text\r
392         else:\r
393             print("unknown text encoding", self.text_encoding)\r
394 \r
395     def __read_uint(self, size):\r
396         if size==1:\r
397             return self.__unpack("B", size)\r
398         if size==2:\r
399             return self.__unpack("H", size)\r
400         if size==4:\r
401             return self.__unpack("I", size)\r
402         print("not reach here")\r
403         raise ParseException("invalid int size: "+size)\r
404 \r
405     def __read_float(self):\r
406         return self.__unpack("f", 4)\r
407 \r
408     def __read_vector2(self):\r
409         return common.Vector2(\r
410                 self.__read_float(), \r
411                 self.__read_float()\r
412                 )\r
413 \r
414     def __read_vector3(self):\r
415         return common.Vector3(\r
416                 self.__read_float(), \r
417                 self.__read_float(), \r
418                 self.__read_float()\r
419                 )\r
420 \r
421     def __read_rgba(self):\r
422         return common.RGBA(\r
423                 self.__read_float(), \r
424                 self.__read_float(), \r
425                 self.__read_float(),\r
426                 self.__read_float()\r
427                 )\r
428 \r
429     def __read_rgb(self):\r
430         return common.RGB(\r
431                 self.__read_float(), \r
432                 self.__read_float(), \r
433                 self.__read_float()\r
434                 )\r
435 \r
436     def __read_vertex(self):\r
437         return Vertex(\r
438                 self.__read_vector3(), # pos\r
439                 self.__read_vector3(), # normal\r
440                 self.__read_vector2(), # uv\r
441                 self.__read_deform(), # deform(bone weight)\r
442                 self.__read_float() # edge factor\r
443                 )\r
444 \r
445     def __read_deform(self):\r
446         deform_type=self.__read_uint(1)\r
447         if deform_type==0:\r
448             return Bdef1(self.__read_uint(self.bone_index_size))\r
449         if deform_type==1:\r
450             return Bdef2(\r
451                     self.__read_uint(self.bone_index_size),\r
452                     self.__read_uint(self.bone_index_size),\r
453                     self.__read_float()\r
454                     )\r
455         """\r
456         if deform_type==2:\r
457             return Bdef4(\r
458                     self.__read_uint(self.bone_index_size),\r
459                     self.__read_uint(self.bone_index_size),\r
460                     self.__read_uint(self.bone_index_size),\r
461                     self.__read_uint(self.bone_index_size),\r
462                     self.__read_float(), self.__read_float(),\r
463                     self.__read_float(), self.__read_float()\r
464                     )\r
465         """\r
466         raise ParseException("unknown deform type: {0}".format(deform_type))\r
467 \r
468     def __read_material(self):\r
469         material=Material(\r
470                 name=self.__read_text(),\r
471                 english_name=self.__read_text(),\r
472                 diffuse_color=self.__read_rgb(),\r
473                 diffuse_alpha=self.__read_float(),\r
474                 specular_color=self.__read_rgb(),\r
475                 specular_factor=self.__read_float(),\r
476                 ambient_color=self.__read_rgb(),\r
477                 flag=self.__read_uint(1),\r
478                 edge_color=self.__read_rgba(),\r
479                 edge_size=self.__read_float(),\r
480                 texture_index=self.__read_uint(self.texture_index_size),\r
481                 sphia_texture_index=self.__read_uint(self.texture_index_size),\r
482                 sphia_mode=self.__read_uint(1),\r
483                 toon_sharing_flag=self.__read_uint(1),\r
484                 )\r
485         if material.toon_sharing_flag==0:\r
486             material.toon_texture_index=self.__read_uint(self.texture_index_size)\r
487         elif material.toon_sharing_flag==1:\r
488             material.toon_texture_index=self.__read_uint(1)\r
489         else:\r
490             raise ParseException("unknown toon_sharing_flag {0}".format(material.toon_sharing_flag))\r
491         material.comment=self.__read_text()\r
492         material.index_count=self.__read_uint(4)\r
493         return material\r
494 \r
495     def __read_bone(self):\r
496         bone=Bone(\r
497                 name=self.__read_text(),\r
498                 english_name=self.__read_text(),\r
499                 position=self.__read_vector3(),\r
500                 parent_index=self.__read_uint(self.bone_index_size),\r
501                 layer=self.__read_uint(4),\r
502                 flag=self.__read_uint(2)                \r
503                 )\r
504         if bone.getConnectionFlag()==0:\r
505             bone.tail_positoin=self.__read_vector3()\r
506         elif bone.getConnectionFlag()==1:\r
507             bone.tail_index=self.__read_uint(self.bone_index_size)\r
508         else:\r
509             raise ParseException("unknown bone conenction flag: {0}".format(\r
510                 bone.getConnectionFlag()))\r
511 \r
512         if bone.getRotationFlag()==1 or bone.getTranslationFlag()==1:\r
513             bone.effect_index=self.__read_uint(self.bone_index_size)\r
514             bone.effect_factor=self.__read_float()\r
515 \r
516         if bone.getFixedAxisFlag()==1:\r
517             bone.fixed_axis=self.__read_vector3()\r
518 \r
519         if bone.getLocalCoordinateFlag()==1:\r
520             bone.local_x_vector=self.__read_vector3()\r
521             bone.local_z_vector=self.__read_vector3()\r
522 \r
523         if bone.getExternalParentDeformFlag()==1:\r
524             bone.external_key=self.__read_uint(4)\r
525 \r
526         if bone.getIkFlag()==1:\r
527             bone.ik=self.__read_ik()\r
528 \r
529         return bone\r
530 \r
531     def __read_ik(self):\r
532         ik=Ik(\r
533                 target_index=self.__read_uint(self.bone_index_size),\r
534                 loop=self.__read_uint(4),\r
535                 limit_radian=self.__read_float())\r
536         link_size=self.__read_uint(4)\r
537         ik.link=[self.__read_ik_link() for i in range(link_size)]\r
538 \r
539     def __read_ik_link(self):\r
540         link=IkLink(\r
541                 self.__read_uint(self.bone_index_size),\r
542                 self.__read_uint(1))\r
543         if link.limit_angle==0:\r
544             pass\r
545         elif link.limit_angle==1:\r
546             link.limit_min=self.__read_vector3()\r
547             link.limit_max=self.__read_vector3()\r
548         else:\r
549             raise ParseException("invalid ik link limit_angle: {0}".format(\r
550                 link.limit_angle))\r
551         return link\r
552 \r