| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 :mod:`pyffi.formats.kfm` --- NetImmerse/Gamebryo Keyframe Motion (.kfm)
3 =======================================================================
4
5 Implementation
6 --------------
7
8 .. autoclass:: KfmFormat
9 :show-inheritance:
10 :members:
11
12 Regression tests
13 ----------------
14
15 Read a KFM file
16 ^^^^^^^^^^^^^^^
17
18 >>> # read kfm file
19 >>> stream = open('tests/kfm/test.kfm', 'rb')
20 >>> data = KfmFormat.Data()
21 >>> data.inspect(stream)
22 >>> print(data.nif_file_name.decode("ascii"))
23 Test.nif
24 >>> data.read(stream)
25 >>> stream.close()
26 >>> # get all animation file names
27 >>> for anim in data.animations:
28 ... print(anim.kf_file_name.decode("ascii"))
29 Test_MD_Idle.kf
30 Test_MD_Run.kf
31 Test_MD_Walk.kf
32 Test_MD_Die.kf
33
34 Parse all KFM files in a directory tree
35 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36
37 >>> for stream, data in KfmFormat.walkData('tests/kfm'):
38 ... print(stream.name)
39 tests/kfm/test.kfm
40
41 Create a KFM model from scratch and write to file
42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
44 >>> data = KfmFormat.Data()
45 >>> data.nif_file_name = "Test.nif"
46 >>> data.num_animations = 4
47 >>> data.animations.update_size()
48 >>> data.animations[0].kf_file_name = "Test_MD_Idle.kf"
49 >>> data.animations[1].kf_file_name = "Test_MD_Run.kf"
50 >>> data.animations[2].kf_file_name = "Test_MD_Walk.kf"
51 >>> data.animations[3].kf_file_name = "Test_MD_Die.kf"
52 >>> from tempfile import TemporaryFile
53 >>> stream = TemporaryFile()
54 >>> data.write(stream)
55 >>> stream.close()
56
57 Get list of versions and games
58 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59
60 >>> for vnum in sorted(KfmFormat.versions.values()):
61 ... print('0x%08X' % vnum)
62 0x01000000
63 0x01024B00
64 0x0200000B
65 0x0201000B
66 0x0202000B
67 >>> for game, versions in sorted(KfmFormat.games.items(),
68 ... key=lambda x: x[0]):
69 ... print("%s " % game + " ".join('0x%08X' % vnum for vnum in versions))
70 Civilization IV 0x01000000 0x01024B00 0x0200000B
71 Emerge 0x0201000B 0x0202000B
72 Loki 0x01024B00
73 Megami Tensei: Imagine 0x0201000B
74 Oblivion 0x01024B00
75 Prison Tycoon 0x01024B00
76 Pro Cycling Manager 0x01024B00
77 Red Ocean 0x01024B00
78 Sid Meier's Railroads 0x0200000B
79 The Guild 2 0x01024B00
80 """
81
82 # ***** BEGIN LICENSE BLOCK *****
83 #
84 # Copyright (c) 2007-2011, NIF File Format Library and Tools.
85 # All rights reserved.
86 #
87 # Redistribution and use in source and binary forms, with or without
88 # modification, are permitted provided that the following conditions
89 # are met:
90 #
91 # * Redistributions of source code must retain the above copyright
92 # notice, this list of conditions and the following disclaimer.
93 #
94 # * Redistributions in binary form must reproduce the above
95 # copyright notice, this list of conditions and the following
96 # disclaimer in the documentation and/or other materials provided
97 # with the distribution.
98 #
99 # * Neither the name of the NIF File Format Library and Tools
100 # project nor the names of its contributors may be used to endorse
101 # or promote products derived from this software without specific
102 # prior written permission.
103 #
104 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
105 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
106 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
107 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
108 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
109 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
110 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
111 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
112 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
113 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
114 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
115 # POSSIBILITY OF SUCH DAMAGE.
116 #
117 # ***** END LICENSE BLOCK *****
118
119 import struct, os, re
120
121 import pyffi.object_models.xml
122 import pyffi.object_models.common
123 from pyffi.object_models.xml.basic import BasicBase
124 from pyffi.utils.graph import EdgeFilter
125 import pyffi.object_models
126 import pyffi.object_models.xml.struct_
129 """This class implements the kfm file format."""
130 xml_file_name = 'kfm.xml'
131 # where to look for kfm.xml and in what order:
132 # KFMXMLPATH env var, or KfmFormat module directory
133 xml_file_path = [os.getenv('KFMXMLPATH'),
134 os.path.join(os.path.dirname(__file__), "kfmxml")]
135 # file name regular expression match
136 RE_FILENAME = re.compile(r'^.*\.kfm$', re.IGNORECASE)
137 # used for comparing floats
138 _EPSILON = 0.0001
139
140 # basic types
141 int = pyffi.object_models.common.Int
142 uint = pyffi.object_models.common.UInt
143 byte = pyffi.object_models.common.UByte # not a typo
144 char = pyffi.object_models.common.Char
145 short = pyffi.object_models.common.Short
146 ushort = pyffi.object_models.common.UShort
147 float = pyffi.object_models.common.Float
148 SizedString = pyffi.object_models.common.SizedString
149 TextString = pyffi.object_models.common.UndecodedData # for text (used by older kfm versions)
150
151 # implementation of kfm-specific basic types
152
154 """The kfm header string."""
158
161
163 """Return a hash value for this value.
164
165 :return: An immutable object that can be used as a hash.
166 """
167 return None
168
170 """Read header string from stream and check it.
171
172 :param stream: The stream to read from.
173 :type stream: file
174 :keyword version: The file version.
175 :type version: int
176 """
177 # get the string we expect
178 version_string = self.version_string(data.version)
179 # read string from stream
180 hdrstr = stream.read(len(version_string))
181 # check if the string is correct
182 if hdrstr != version_string.encode("ascii"):
183 raise ValueError(
184 "invalid KFM header: expected '%s' but got '%s'"
185 % (version_string, hdrstr))
186 # check eol style
187 nextchar = stream.read(1)
188 if nextchar == '\x0d'.encode("ascii"):
189 nextchar = stream.read(1)
190 self._doseol = True
191 else:
192 self._doseol = False
193 if nextchar != '\x0a'.encode("ascii"):
194 raise ValueError(
195 "invalid KFM header: string does not end on \\n or \\r\\n")
196
198 """Write the header string to stream.
199
200 :param stream: The stream to write to.
201 :type stream: file
202 """
203 # write the version string
204 stream.write(self.version_string(data.version).encode("ascii"))
205 # write \n (or \r\n for older versions)
206 if self._doseol:
207 stream.write('\x0d\x0a'.encode("ascii"))
208 else:
209 stream.write('\x0a'.encode("ascii"))
210
212 """Return number of bytes the header string occupies in a file.
213
214 :return: Number of bytes.
215 """
216 return len(self.version_string(data.version)) \
217 + (1 if not self._doseol else 2)
218
219 # DetailNode
220
222 return str(self)
223
224 @staticmethod
226 """Transforms version number into a version string.
227
228 :param version: The version number.
229 :type version: int
230 :return: A version string.
231
232 >>> KfmFormat.HeaderString.version_string(0x0202000b)
233 ';Gamebryo KFM File Version 2.2.0.0b'
234 >>> KfmFormat.HeaderString.version_string(0x01024b00)
235 ';Gamebryo KFM File Version 1.2.4b'
236 """
237 if version == -1 or version is None:
238 raise RuntimeError('no string for version %s'%version)
239 return ";Gamebryo KFM File Version %s"%({
240 0x01000000 : "1.0",
241 0x01024b00 : "1.2.4b",
242 0x0200000b : "2.0.0.0b",
243 0x0201000b : "2.1.0.0b",
244 0x0202000b : "2.2.0.0b" } [version])
245
246 # other types with internal implementation
249 """Return a hash value for this value.
250 For file paths, the hash value is case insensitive.
251
252 :return: An immutable object that can be used as a hash.
253 """
254 return self.get_value().lower()
255
256 @staticmethod
258 """Converts version string into an integer.
259
260 :param version_str: The version string.
261 :type version_str: str
262 :return: A version integer.
263
264 >>> hex(KfmFormat.version_number('1.0'))
265 '0x1000000'
266 >>> hex(KfmFormat.version_number('1.2.4b'))
267 '0x1024b00'
268 >>> hex(KfmFormat.version_number('2.2.0.0b'))
269 '0x202000b'
270 """
271
272 if not '.' in version_str:
273 return int(version_str)
274
275 try:
276 ver_list = [int(x, 16) for x in version_str.split('.')]
277 except ValueError:
278 # version not supported (i.e. version_str '10.0.1.3z' would
279 # trigger this)
280 return -1
281 if len(ver_list) > 4 or len(ver_list) < 1:
282 # version not supported
283 return -1
284 for ver_digit in ver_list:
285 if (ver_digit | 0xff) > 0xff:
286 return -1 # version not supported
287 while len(ver_list) < 4:
288 ver_list.append(0)
289 return ((ver_list[0] << 24)
290 + (ver_list[1] << 16)
291 + (ver_list[2] << 8)
292 + ver_list[3])
293
295 """A class to contain the actual kfm data."""
296 version = 0x01024B00
297
299 """Quick heuristic check if stream contains KFM data,
300 by looking at the first 64 bytes. Sets version and reads
301 header string.
302
303 :param stream: The stream to inspect.
304 :type stream: file
305 """
306 pos = stream.tell()
307 try:
308 hdrstr = stream.readline(64).rstrip()
309 finally:
310 stream.seek(pos)
311 if hdrstr.startswith(";Gamebryo KFM File Version ".encode("ascii")):
312 version_str = hdrstr[27:].decode("ascii")
313 else:
314 # not a kfm file
315 raise ValueError("Not a KFM file.")
316 try:
317 ver = KfmFormat.version_number(version_str)
318 except:
319 # version not supported
320 raise ValueError("KFM version not supported.")
321 if not ver in KfmFormat.versions.values():
322 # unsupported version
323 raise ValueError("KFM version not supported.")
324 # store version
325 self.version = ver
326 # read header string
327 try:
328 self._header_string_value_.read(stream, self)
329 self._unknown_byte_value_.read(stream, self)
330 self._nif_file_name_value_.read(stream, self)
331 self._master_value_.read(stream, self)
332 finally:
333 stream.seek(pos)
334
336 """Read a kfm file.
337
338 :param stream: The stream from which to read.
339 :type stream: ``file``
340 """
341 # read the file
342 self.inspect(stream) # quick check
343 pyffi.object_models.xml.struct_.StructBase.read(
344 self, stream, self)
345
346 # check if we are at the end of the file
347 if stream.read(1):
348 raise ValueError('end of file not reached: corrupt kfm file?')
349
351 """Write a kfm file.
352
353 :param stream: The stream to which to write.
354 :type stream: ``file``
355 """
356 # write the file
357 pyffi.object_models.xml.struct_.StructBase.write(
358 self, stream, self)
359
360 # GlobalNode
361
363 return (anim for anim in self.animations)
364
368
378
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:14 2011 | http://epydoc.sourceforge.net |