| Home | Trees | Indices | Help |
|
|---|
|
|
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
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
127
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
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
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
158 """Basic type which implements the header of a TRI file."""
161
164
166 return self.__str__()
167
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
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
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
202
204 _value = 3
205
207 return self._value
208
211
213 return '%03i' % self._value
214
217
219 return self._value
220
224
227
229 return self.__str__()
230
231 @staticmethod
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
252 """A class to contain the actual tri data."""
253
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
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
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
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
352 """Add a morph."""
353 self.num_morphs += 1
354 self.morphs.update_size()
355 return self.morphs[-1]
356
358 """Add a modifier."""
359 self.num_modifiers += 1
360 self.modifiers.update_size()
361 return self.modifiers[-1]
362
363 # GlobalNode
364
368
369 # XXX copied from pyffi.formats.egm.EgmFormat.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 """
386 for vert in self.vertices:
387 yield (vert.x * self.scale,
388 vert.y * self.scale,
389 vert.z * self.scale)
390
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
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:13 2011 | http://epydoc.sourceforge.net |