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 152378154 """The kfm header string.""" 158 161245 246 # other types with internal implementation163 """Return a hash value for this value. 164 165 :return: An immutable object that can be used as a hash. 166 """ 167 return None168170 """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")196198 """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"))210212 """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 220222 return str(self)223 224 @staticmethod226 """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])255 256 @staticmethod249 """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()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])293295 """A class to contain the actual kfm data.""" 296 version = 0x01024B00 297368299 """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)334336 """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?')349351 """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 361363 return (anim for anim in self.animations)364
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:14 2011 | http://epydoc.sourceforge.net |