OSDN Git Service

implement pmx material 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 Material(object):\r
25     """material\r
26 \r
27     Attributes: see __init__\r
28     """\r
29     __slots__=[\r
30             'name',\r
31             'english_name',\r
32             'diffuse_color',\r
33             'diffuse_alpha',\r
34             'specular_color',\r
35             'specular_factor',\r
36             'ambient_color',\r
37             'flag',\r
38             'edge_color',\r
39             'edge_size',\r
40             'texture_index',\r
41             'sphia_texture_index',\r
42             'sphia_mode',\r
43             'toon_sharing_flag',\r
44             'toon_texture_index',\r
45             'comment',\r
46             'index_count',\r
47             ]\r
48     def __init__(self,\r
49             name: str,\r
50             english_name: str,\r
51             diffuse_color: common.RGB,\r
52             diffuse_alpha: float,\r
53             specular_color: common.RGB,\r
54             specular_factor: float,\r
55             ambient_color: common.RGB,\r
56             flag: int,\r
57             edge_color: common.RGBA,\r
58             edge_size: float,\r
59             texture_index: int,\r
60             sphia_texture_index: int,\r
61             sphia_mode: int,\r
62             toon_sharing_flag: int\r
63             ):\r
64         self.name=name\r
65         self.english_name=english_name\r
66         self.diffuse_color=diffuse_color\r
67         self.diffuse_alpha=diffuse_alpha\r
68         self.specular_color=specular_color\r
69         self.specular_factor=specular_factor\r
70         self.ambient_color=ambient_color\r
71         self.flag=flag\r
72         self.edge_color=edge_color\r
73         self.edge_size=edge_size\r
74         self.texture_index=texture_index\r
75         self.sphia_texture_index=sphia_texture_index\r
76         self.sphia_mode=sphia_mode\r
77         self.toon_sharing_flag=toon_sharing_flag\r
78         #\r
79         self.toon_texture_index=None\r
80         self.comment=''\r
81         self.index_count=0\r
82 \r
83 \r
84 class Deform(object):\r
85     pass\r
86 \r
87 \r
88 class Bdef1(object):\r
89     """bone deform. use a weight\r
90 \r
91     Attributes: see __init__\r
92     """\r
93     __slots__=[ 'bone_index']\r
94     def __init__(self, bone_index: int):\r
95         self.bone_index=bone_index\r
96 \r
97 \r
98 class Bdef2(object):\r
99     """bone deform. use two weights\r
100 \r
101     Attributes: see __init__\r
102     """\r
103     __slots__=[ 'index0', 'index1', 'weight0']\r
104     def __init__(self, \r
105             index0: int,\r
106             index1: int,\r
107             weight0: float):\r
108         self.index0=index0\r
109         self.index1=index1\r
110         self.weight0=weight0\r
111 \r
112 \r
113 class Vertex(object):\r
114     """pmx vertex\r
115 \r
116     Attributes: see __init__\r
117     """\r
118     __slots__=[ 'position', 'normal', 'uv', 'deform', 'edge_factor' ]\r
119     def __init__(self, \r
120             position: common.Vector3, \r
121             normal: common.Vector3, \r
122             uv: common.Vector2, \r
123             deform: Deform, \r
124             edge_factor: float):\r
125         self.position=position \r
126         self.normal=normal\r
127         self.uv=uv\r
128         self.deform=deform\r
129         self.edge_factor=edge_factor\r
130 \r
131 \r
132 class Model(object):\r
133     """pmx data representation\r
134 \r
135     Attributes:\r
136         version: pmx version(expected 2.0)\r
137         name: \r
138         english_name: \r
139         comment: \r
140         english_comment: \r
141         vertices:\r
142         textures:\r
143         materials:\r
144         bones:\r
145     """\r
146     __slots__=[\r
147             'version', # pmx version\r
148             'name', # model name\r
149             'english_name', # model name in english\r
150             'comment', # model comment\r
151             'english_comment', # model comment in english\r
152             'vertices',\r
153             'indices',\r
154             'textures',\r
155             'materials',\r
156             'bones',\r
157             ]\r
158     def __init__(self):\r
159         self.version=0.0\r
160         self.name=''\r
161         self.english_name=''\r
162         self.comment=''\r
163         self.english_comment=''\r
164         self.vertices=[]\r
165         self.indices=[]\r
166         self.textures=[]\r
167         self.materials=[]\r
168         self.bones=[]\r
169 \r
170 \r
171 class IO(object):\r
172     """pmx loader\r
173 \r
174     Attributes:\r
175         text_encoding: \r
176         extended_uv:\r
177         vertex_index_size:\r
178         texture_index_size:\r
179         material_index_size:\r
180         bone_index_size:\r
181         morph_index_size:\r
182         rigidbody_index_size:\r
183     """\r
184     def __init__(self):\r
185         self.__io=None\r
186         self.__pos=-1\r
187         self.__end=-1\r
188         self.__model=Model()\r
189 \r
190     def read(self, path: 'filepath') -> Model:\r
191         size=os.path.getsize(path)\r
192         with open(path, "rb") as f:\r
193             if self.__load(path, f, size):\r
194                 return self.__model\r
195 \r
196     def __load(self, path: 'filepath', io: io.IOBase, size: int) -> bool: \r
197         self.__io=io\r
198         self.__end=size\r
199         self.__pos=0\r
200 \r
201         ####################\r
202         # header\r
203         ####################\r
204         signature=self.__unpack("4s", 4)\r
205         if signature!=b"PMX ":\r
206             print("invalid signature", self.signature)\r
207             return False\r
208         version=self.__unpack("f", 4)\r
209         if version!=2.0:\r
210             print("unknown version", version)\r
211         self.__model.version=version\r
212         # flags\r
213         flag_bytes=self.__unpack("B", 1)\r
214         if flag_bytes!=8:\r
215             print("invalid flag length", self.flag_bytes)\r
216             return False\r
217         # text encoding\r
218         self.text_encoding=self.__unpack("B", 1)\r
219         self.__read_text=self.__get_read_text()\r
220         # uv\r
221         self.extended_uv=self.__unpack("B", 1)\r
222         if self.extended_uv>0:\r
223             print("extended uv is not supported", self.extended_uv)\r
224             return False\r
225         # index size\r
226         self.vertex_index_size=self.__unpack("B", 1)\r
227         self.texture_index_size=self.__unpack("B", 1)\r
228         self.material_index_size=self.__unpack("B", 1)\r
229         self.bone_index_size=self.__unpack("B", 1)\r
230         self.morph_index_size=self.__unpack("B", 1)\r
231         self.rigidbody_index_size=self.__unpack("B", 1)\r
232 \r
233         ####################\r
234         # model info\r
235         ####################\r
236         self.__model.name = self.__read_text()\r
237         self.__model.english_name = self.__read_text()\r
238         self.__model.comment = self.__read_text()\r
239         self.__model.english_comment = self.__read_text()\r
240 \r
241         ####################\r
242         # vertices\r
243         ####################\r
244         vertex_count=self.__read_uint(4)\r
245         self.__model.vertices=[self.__read_vertex() \r
246                 for i in range(vertex_count)]\r
247 \r
248         ####################\r
249         # indices\r
250         ####################\r
251         index_count=self.__read_uint(4)\r
252         self.__model.indices=[self.__read_uint(self.vertex_index_size) \r
253                 for i in range(index_count)]\r
254 \r
255         ####################\r
256         # textures\r
257         ####################\r
258         texture_count=self.__read_uint(4)\r
259         self.__model.textures=[self.__read_text() \r
260                 for i in range(texture_count)]\r
261 \r
262         ####################\r
263         # materials\r
264         ####################\r
265         material_count=self.__read_uint(4)\r
266         self.__model.materials=[self.__read_material() \r
267                 for i in range(material_count)]\r
268 \r
269         ####################\r
270         # bones\r
271         ####################\r
272         bone_count=self.__read_uint(4)\r
273         self.__model.bones=[self.__read_bone() \r
274                 for i in range(bone_count)]\r
275 \r
276         return True\r
277 \r
278     def __str__(self) -> str:\r
279         return '<PmxIO>'\r
280 \r
281     def __check_position(self):\r
282         self.__pos=self.__io.tell()\r
283 \r
284     def __unpack(self, fmt: str, size: int) -> "read value as format":\r
285         result=struct.unpack(fmt, self.__io.read(size))\r
286         self.__check_position()\r
287         return result[0]\r
288 \r
289     def __get_read_text(self) -> "text process function":\r
290         if self.text_encoding==0:\r
291             def read_text():\r
292                 size=self.__unpack("I", 4)\r
293                 return self.__unpack("{0}s".format(size), size).decode("UTF16")\r
294             return read_text\r
295         elif self.text_encoding==1:\r
296             def read_text():\r
297                 size=self.__unpack("I", 4)\r
298                 return self.__unpack("{0}s".format(size), size).decode("UTF8")\r
299             return read_text\r
300         else:\r
301             print("unknown text encoding", self.text_encoding)\r
302 \r
303     def __read_uint(self, size):\r
304         if size==1:\r
305             return self.__unpack("B", size)\r
306         if size==2:\r
307             return self.__unpack("H", size)\r
308         if size==4:\r
309             return self.__unpack("I", size)\r
310         print("not reach here")\r
311         raise ParseException("invalid int size: "+size)\r
312 \r
313     def __read_vertex(self):\r
314         return Vertex(\r
315                 self.__read_vector3(), # pos\r
316                 self.__read_vector3(), # normal\r
317                 self.__read_vector2(), # uv\r
318                 self.__read_deform(), # deform(bone weight)\r
319                 self.__unpack("f", 4) # edge factor\r
320                 )\r
321 \r
322     def __read_vector2(self):\r
323         return common.Vector2(\r
324                 self.__unpack("f", 4), \r
325                 self.__unpack("f", 4)\r
326                 )\r
327 \r
328     def __read_vector3(self):\r
329         return common.Vector3(\r
330                 self.__unpack("f", 4), \r
331                 self.__unpack("f", 4), \r
332                 self.__unpack("f", 4)\r
333                 )\r
334 \r
335     def __read_rgba(self):\r
336         return common.RGBA(\r
337                 self.__unpack("f", 4), \r
338                 self.__unpack("f", 4), \r
339                 self.__unpack("f", 4),\r
340                 self.__unpack("f", 4)\r
341                 )\r
342 \r
343     def __read_rgb(self):\r
344         return common.RGB(\r
345                 self.__unpack("f", 4), \r
346                 self.__unpack("f", 4), \r
347                 self.__unpack("f", 4)\r
348                 )\r
349 \r
350     def __read_deform(self):\r
351         deform_type=self.__unpack("B", 1)\r
352         if deform_type==0:\r
353             return Bdef1(self.__read_uint(self.bone_index_size))\r
354         if deform_type==1:\r
355             return Bdef2(\r
356                     self.__read_uint(self.bone_index_size),\r
357                     self.__read_uint(self.bone_index_size),\r
358                     self.__unpack("f", 4)\r
359                     )\r
360         """\r
361         if deform_type==2:\r
362             return Bdef4(\r
363                     self.__read_uint(self.bone_index_size),\r
364                     self.__read_uint(self.bone_index_size),\r
365                     self.__read_uint(self.bone_index_size),\r
366                     self.__read_uint(self.bone_index_size),\r
367                     self.__unpack("f", 4), self.__unpack("f", 4),\r
368                     self.__unpack("f", 4), self.__unpack("f", 4)\r
369                     )\r
370         """\r
371         raise ParseException("unknown deform type: {0}".format(deform_type))\r
372 \r
373     def __read_material(self):\r
374         material=Material(\r
375                 name=self.__read_text(),\r
376                 english_name=self.__read_text(),\r
377                 diffuse_color=self.__read_rgb(),\r
378                 diffuse_alpha=self.__unpack("f", 4),\r
379                 specular_color=self.__read_rgb(),\r
380                 specular_factor=self.__unpack("f", 4),\r
381                 ambient_color=self.__read_rgb(),\r
382                 flag=self.__read_uint(1),\r
383                 edge_color=self.__read_rgba(),\r
384                 edge_size=self.__unpack("f", 4),\r
385                 texture_index=self.__read_uint(self.texture_index_size),\r
386                 sphia_texture_index=self.__read_uint(self.texture_index_size),\r
387                 sphia_mode=self.__read_uint(1),\r
388                 toon_sharing_flag=self.__read_uint(1),\r
389                 )\r
390         if material.toon_sharing_flag==0:\r
391             material.toon_texture_index=self.__read_uint(self.texture_index_size)\r
392         elif material.toon_sharing_flag==1:\r
393             material.toon_texture_index=self.__read_uint(1)\r
394         else:\r
395             raise ParseException("unknown toon_sharing_flag {0}".format(material.toon_sharing_flag))\r
396         material.comment=self.__read_text()\r
397         material.index_count=self.__read_uint(4)\r
398         return material\r
399 \r
400     def __read_bone(self):\r
401         return None\r
402 \r