Package pyffi :: Package formats :: Package tri
[hide private]
[frames] | no frames]

Source Code for Package pyffi.formats.tri

  1  """ 
  2  :mod:`pyffi.formats.tri` --- TRI (.tri) 
  3  ======================================= 
  4   
  5  A .tri file contains facial expression data, that is, morphs for dynamic 
  6  expressions such as smile, frown, and so on. 
  7   
  8  Implementation 
  9  -------------- 
 10   
 11  .. autoclass:: TriFormat 
 12     :show-inheritance: 
 13     :members: 
 14   
 15  Regression tests 
 16  ---------------- 
 17   
 18  Read a TRI file 
 19  ^^^^^^^^^^^^^^^ 
 20   
 21  >>> # check and read tri file 
 22  >>> stream = open('tests/tri/mmouthxivilai.tri', 'rb') 
 23  >>> data = TriFormat.Data() 
 24  >>> data.inspect(stream) 
 25  >>> # do some stuff with header? 
 26  >>> data.num_vertices 
 27  89 
 28  >>> data.num_tri_faces 
 29  215 
 30  >>> data.num_quad_faces 
 31  0 
 32  >>> data.num_uvs 
 33  89 
 34  >>> data.num_morphs 
 35  18 
 36  >>> data.read(stream) # doctest: +ELLIPSIS 
 37  >>> print([str(morph.name.decode("ascii")) for morph in data.morphs]) 
 38  ['Fear', 'Surprise', 'Aah', 'BigAah', 'BMP', 'ChJSh', 'DST', 'Eee', 'Eh', \ 
 39  'FV', 'I', 'K', 'N', 'Oh', 'OohQ', 'R', 'Th', 'W'] 
 40   
 41  Parse all TRI files in a directory tree 
 42  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 43   
 44  >>> for stream, data in TriFormat.walkData('tests/tri'): 
 45  ...     print(stream.name) 
 46  tests/tri/mmouthxivilai.tri 
 47   
 48  Create an TRI file from scratch and write to file 
 49  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 50   
 51  >>> data = TriFormat.Data() 
 52  >>> from tempfile import TemporaryFile 
 53  >>> stream = TemporaryFile() 
 54  >>> data.write(stream) 
 55  """ 
 56   
 57  # ***** BEGIN LICENSE BLOCK ***** 
 58  # 
 59  # Copyright (c) 2007-2011, Python File Format Interface 
 60  # All rights reserved. 
 61  # 
 62  # Redistribution and use in source and binary forms, with or without 
 63  # modification, are permitted provided that the following conditions 
 64  # are met: 
 65  # 
 66  #    * Redistributions of source code must retain the above copyright 
 67  #      notice, this list of conditions and the following disclaimer. 
 68  # 
 69  #    * Redistributions in binary form must reproduce the above 
 70  #      copyright notice, this list of conditions and the following 
 71  #      disclaimer in the documentation and/or other materials provided 
 72  #      with the distribution. 
 73  # 
 74  #    * Neither the name of the Python File Format Interface 
 75  #      project nor the names of its contributors may be used to endorse 
 76  #      or promote products derived from this software without specific 
 77  #      prior written permission. 
 78  # 
 79  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 80  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 81  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 82  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 83  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 84  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 85  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 86  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 87  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 88  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 89  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 90  # POSSIBILITY OF SUCH DAMAGE. 
 91  # 
 92  # ***** END LICENSE BLOCK ***** 
 93   
 94  from itertools import chain, izip 
 95  import struct 
 96  import os 
 97  import re 
 98   
 99  import pyffi.object_models.xml 
100  import pyffi.object_models.common 
101  from pyffi.object_models.xml.basic import BasicBase 
102  import pyffi.object_models 
103  from pyffi.utils.graph import EdgeFilter 
104 105 -class TriFormat(pyffi.object_models.xml.FileFormat):
106 """This class implements the TRI format.""" 107 xml_file_name = 'tri.xml' 108 # where to look for tri.xml and in what order: 109 # TRIXMLPATH env var, or TriFormat module directory 110 xml_file_path = [os.getenv('TRIXMLPATH'), os.path.dirname(__file__)] 111 # file name regular expression match 112 RE_FILENAME = re.compile(r'^.*\.tri$', re.IGNORECASE) 113 114 # basic types 115 int = pyffi.object_models.common.Int 116 uint = pyffi.object_models.common.UInt 117 byte = pyffi.object_models.common.Byte 118 ubyte = pyffi.object_models.common.UByte 119 char = pyffi.object_models.common.Char 120 short = pyffi.object_models.common.Short 121 ushort = pyffi.object_models.common.UShort 122 float = pyffi.object_models.common.Float 123 124 # implementation of tri-specific basic types 125
126 - class SizedStringZ(pyffi.object_models.common.SizedString):
127
128 - def get_size(self, data=None):
129 """Return number of bytes this type occupies in a file. 130 131 :return: Number of bytes. 132 """ 133 return ( 134 1 + 135 pyffi.object_models.common.SizedString.get_size(self, data) 136 )
137
138 - def read(self, stream, data):
139 """Read string from stream. 140 141 :param stream: The stream to read from. 142 :type stream: file 143 """ 144 pyffi.object_models.common.SizedString.read(self, stream, data) 145 self._value = self._value.rstrip(pyffi.object_models.common._b00)
146
147 - def write(self, stream, data):
148 """Write string to stream. 149 150 :param stream: The stream to write to. 151 :type stream: file 152 """ 153 self._value += pyffi.object_models.common._b00 154 pyffi.object_models.common.SizedString.write(self, stream, data) 155 self._value = self._value.rstrip(pyffi.object_models.common._b00)
156
157 - class FileSignature(BasicBase):
158 """Basic type which implements the header of a TRI file."""
159 - def __init__(self, **kwargs):
160 BasicBase.__init__(self, **kwargs)
161
162 - def __str__(self):
163 return 'FRTRI'
164
165 - def get_detail_display(self):
166 return self.__str__()
167
168 - def get_hash(self, data=None):
169 """Return a hash value for this value. 170 171 :return: An immutable object that can be used as a hash. 172 """ 173 return None
174
175 - def read(self, stream, data):
176 """Read header string from stream and check it. 177 178 :param stream: The stream to read from. 179 :type stream: file 180 """ 181 hdrstr = stream.read(5) 182 # check if the string is correct 183 if hdrstr != "FRTRI".encode("ascii"): 184 raise ValueError( 185 "invalid TRI header: expected 'FRTRI' but got '%s'" 186 % hdrstr)
187
188 - def write(self, stream, data):
189 """Write the header string to stream. 190 191 :param stream: The stream to write to. 192 :type stream: file 193 """ 194 stream.write("FRTRI".encode("ascii"))
195
196 - def get_size(self, data=None):
197 """Return number of bytes the header string occupies in a file. 198 199 :return: Number of bytes. 200 """ 201 return 5
202
203 - class FileVersion(BasicBase):
204 _value = 3 205
206 - def get_value(self):
207 return self._value
208
209 - def set_value(self, value):
210 self._value = int(value)
211
212 - def __str__(self):
213 return '%03i' % self._value
214
215 - def get_size(self, data=None):
216 return 3
217
218 - def get_hash(self, data=None):
219 return self._value
220
221 - def read(self, stream, data):
222 self._value = TriFormat.version_number( 223 stream.read(3).decode("ascii"))
224
225 - def write(self, stream, data):
226 stream.write(('%03i' % self._value).encode("ascii"))
227
228 - def get_detail_display(self):
229 return self.__str__()
230 231 @staticmethod
232 - def version_number(version_str):
233 """Converts version string into an integer. 234 235 :param version_str: The version string. 236 :type version_str: str 237 :return: A version integer. 238 239 >>> TriFormat.version_number('003') 240 3 241 >>> TriFormat.version_number('XXX') 242 -1 243 """ 244 try: 245 # note: always '003' in all files seen so far 246 return int(version_str) 247 except ValueError: 248 # not supported 249 return -1
250
251 - class Header(pyffi.object_models.FileFormat.Data):
252 """A class to contain the actual tri data.""" 253
254 - def inspect_quick(self, stream):
255 """Quickly checks if stream contains TRI data, by looking at 256 the first 8 bytes. Reads the signature and the version. 257 258 :param stream: The stream to inspect. 259 :type stream: file 260 """ 261 pos = stream.tell() 262 try: 263 self._signature_value_.read(stream, self) 264 self._version_value_.read(stream, self) 265 finally: 266 stream.seek(pos)
267 268 # overriding pyffi.object_models.FileFormat.Data methods 269
270 - def inspect(self, stream):
271 """Quickly checks if stream contains TRI data, and reads 272 everything up to the arrays. 273 274 :param stream: The stream to inspect. 275 :type stream: file 276 """ 277 pos = stream.tell() 278 try: 279 self.inspect_quick(stream) 280 self._signature_value_.read(stream, self) 281 self._version_value_.read(stream, self) 282 self._num_vertices_value_.read(stream, self) 283 self._num_tri_faces_value_.read(stream, self) 284 self._num_quad_faces_value_.read(stream, self) 285 self._unknown_1_value_.read(stream, self) 286 self._unknown_2_value_.read(stream, self) 287 self._num_uvs_value_.read(stream, self) 288 self._has_uv_value_.read(stream, self) 289 self._num_morphs_value_.read(stream, self) 290 self._num_modifiers_value_.read(stream, self) 291 self._num_modifier_vertices_value_.read(stream, self) 292 finally: 293 stream.seek(pos)
294 295
296 - def read(self, stream):
297 """Read a tri file. 298 299 :param stream: The stream from which to read. 300 :type stream: ``file`` 301 """ 302 self.inspect_quick(stream) 303 pyffi.object_models.xml.struct_.StructBase.read( 304 self, stream, self) 305 306 # check if we are at the end of the file 307 if stream.read(1): 308 raise ValueError( 309 'end of file not reached: corrupt tri file?') 310 311 # copy modifier vertices into modifier records 312 start_index = 0 313 for modifier in self.modifiers: 314 modifier.modifier_vertices.update_size() 315 for src_vert, dst_vert in izip( 316 self.modifier_vertices[ 317 start_index:start_index 318 + modifier.num_vertices_to_modify], 319 modifier.modifier_vertices): 320 dst_vert.x = src_vert.x 321 dst_vert.y = src_vert.y 322 dst_vert.z = src_vert.z 323 start_index += modifier.num_vertices_to_modify
324
325 - def write(self, stream):
326 """Write a tri file. 327 328 :param stream: The stream to which to write. 329 :type stream: ``file`` 330 """ 331 # copy modifier vertices from modifier records to header 332 if self.modifiers: 333 self.num_modifier_vertices = sum( 334 modifier.num_vertices_to_modify 335 for modifier in self.modifiers) 336 self.modifier_vertices.update_size() 337 for self_vert, vert in izip( 338 self.modifier_vertices, 339 chain(*(modifier.modifier_vertices 340 for modifier in self.modifiers))): 341 self_vert.x = vert.x 342 self_vert.y = vert.y 343 self_vert.z = vert.z 344 else: 345 self.num_modifier_vertices = 0 346 self.modifier_vertices.update_size() 347 # write the data 348 pyffi.object_models.xml.struct_.StructBase.write( 349 self, stream, self)
350
351 - def add_morph(self, name=None, relative_vertices=None):
352 """Add a morph.""" 353 self.num_morphs += 1 354 self.morphs.update_size() 355 return self.morphs[-1]
356
357 - def add_modifier(self, name=None, relative_vertices=None):
358 """Add a modifier.""" 359 self.num_modifiers += 1 360 self.modifiers.update_size() 361 return self.modifiers[-1]
362 363 # GlobalNode 364
365 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
366 return ([morph for morph in self.morphs] 367 + [morph for morph in self.modifiers])
368 369 # XXX copied from pyffi.formats.egm.EgmFormat.MorphRecord
370 - class MorphRecord:
371 """ 372 >>> # create morph with 3 vertices. 373 >>> morph = TriFormat.MorphRecord(argument=3) 374 >>> morph.set_relative_vertices( 375 ... [(3, 5, 2), (1, 3, 2), (-9, 3, -1)]) 376 >>> # scale should be 9/32768.0 = 0.0002746... 377 >>> morph.scale # doctest: +ELLIPSIS 378 0.0002746... 379 >>> for vert in morph.get_relative_vertices(): 380 ... print([int(1000 * x + 0.5) for x in vert]) 381 [3000, 5000, 2000] 382 [1000, 3000, 2000] 383 [-8999, 3000, -999] 384 """
385 - def get_relative_vertices(self):
386 for vert in self.vertices: 387 yield (vert.x * self.scale, 388 vert.y * self.scale, 389 vert.z * self.scale)
390
391 - def set_relative_vertices(self, vertices):
392 # copy to list 393 vertices = list(vertices) 394 # check length 395 if len(vertices) != self.arg: 396 raise ValueError("expected %i vertices, but got %i" 397 % (self.arg, len(vertices))) 398 # get extreme values of morph 399 max_value = max(max(abs(value) for value in vert) 400 for vert in vertices) 401 # calculate scale 402 self.scale = max_value / 32767.0 403 inv_scale = 1 / self.scale 404 # set vertices 405 for vert, self_vert in izip(vertices, self.vertices): 406 self_vert.x = int(vert[0] * inv_scale) 407 self_vert.y = int(vert[1] * inv_scale) 408 self_vert.z = int(vert[2] * inv_scale)
409
410 - def apply_scale(self, scale):
411 """Apply scale factor to data. 412 413 >>> # create morph with 3 vertices. 414 >>> morph = TriFormat.MorphRecord(argument=3) 415 >>> morph.set_relative_vertices( 416 ... [(3, 5, 2), (1, 3, 2), (-9, 3, -1)]) 417 >>> morph.apply_scale(2) 418 >>> for vert in morph.get_relative_vertices(): 419 ... print([int(1000 * x + 0.5) for x in vert]) 420 [6000, 10000, 4000] 421 [2000, 6000, 4000] 422 [-17999, 6000, -1999] 423 """ 424 self.scale *= scale
425 426 if __name__=='__main__': 427 import doctest 428 doctest.testmod() 429