home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / mayabinary.py < prev    next >
Encoding:
Python Source  |  2008-02-24  |  12.8 KB  |  352 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id$
  36.  
  37. """This module contains the MBReader base class to parse Maya binary files (~IFF files).
  38. """
  39.  
  40. import struct, os.path
  41.  
  42. class Chunk:
  43.     """Chunk class.
  44.     
  45.     This class stores information about a chunk. It is passed to the handler
  46.     methods which can also use this class to read the actual chunk data.
  47.     
  48.     The class has the following attributes:
  49.     
  50.     - tag: The chunk name (four characters)
  51.     - size: The size in bytes of the data part of the chunk
  52.     - pos: The absolute position of the data part within the input file
  53.     - parent: The GroupChunk object of the parent chunk
  54.     - depth: The depth of the node (i.e. how deep it is nested). The root
  55.              has a depth of 0.
  56.  
  57.     The binary chunk data can be read by using the read() method. You
  58.     can specify the number of bytes to read or read the entire data
  59.     at once. It is not possible to read data that lies outside this
  60.     chunk.
  61.     """
  62.     
  63.     def __init__(self, file, parent, tag, size, pos, depth):
  64.         """Constructor.
  65.         """
  66.         # The file handle that currently points to the start of the chunk data
  67.         self.file = file
  68.         # The parent chunk object
  69.         self.parent = parent
  70.         # The chunk name (four characters)
  71.         self.tag = tag
  72.         # The chunk size in bytes (only the data part)
  73.         self.size = size
  74.         # The absolute position (of the data) within the input file
  75.         self.pos = pos
  76.         # The depth of the node (i.e. how deep it is nested)
  77.         self.depth = depth
  78.         
  79.         # The number of bytes read so far
  80.         self._bytesRead = 0
  81.  
  82.     def __str__(self):
  83.         return "<Chunk %s at pos %d (%d bytes)>"%(self.tag, self.pos, self.size)
  84.  
  85.     def chunkPath(self):
  86.         """Return the full path to this chunk.
  87.  
  88.         The result is a concatenation of all chunk names that lead to this chunk.
  89.         """
  90.         name = "%s"%(self.tag)
  91.         if self.parent is None:
  92.             return name
  93.         else:
  94.             return self.parent.chunkPath()+".%s"%name
  95.  
  96.     def read(self, bytes=-1):
  97.         """Read the specified number of bytes from the chunk.
  98.         
  99.         If bytes is -1 the entire chunk data is read.
  100.         """
  101.         if self.file is None:
  102.             raise RuntimeError, "This chunk is not active anymore"
  103.  
  104.         maxbytes = self.size-self._bytesRead
  105.         if bytes<0:
  106.             bytes = maxbytes
  107.         else:
  108.             bytes = min(bytes, maxbytes)
  109.         self._bytesRead += bytes
  110.         return self.file.read(bytes)
  111.     
  112. class GroupChunk(Chunk):
  113.     """Specialized group chunk class.
  114.  
  115.     In addition to the Chunk class this class has an attribute "type"
  116.     that contains the group type (the first four characters of the data part).
  117.     """
  118.     
  119.     def __init__(self, file, parent, tag, size, pos, type, depth):
  120.         Chunk.__init__(self, file=file, parent=parent, tag=tag, size=size, pos=pos, depth=depth)
  121.  
  122.         # The group type
  123.         self.type = type
  124.  
  125.         # Start with 4 because the type was already read
  126.         self._bytesRead = 4
  127.         
  128.     def __str__(self):
  129.         return "<GroupChunk %s (%s) at pos %d (%d bytes)>"%(self.tag, self.type, self.pos, self.size)
  130.  
  131.     def chunkPath(self):
  132.         """Return the full path to this chunk.
  133.         """
  134.         name = "%s[%s]"%(self.tag, self.type)
  135.         if self.parent is None:
  136.             return name
  137.         else:
  138.             return self.parent.chunkPath()+".%s"%name
  139.    
  140.  
  141. class MBReader:
  142.     """Read Maya IFF files and call an appropriate handler for each chunk.
  143.     
  144.     This is the base class for a .mb reader class. Derived classes can implement
  145.     the chunk handler methods onBeginGroup(), onEndGroup() and onDataChunk().
  146.     These handlers receive a Chunk (or GroupChunk) object as input that
  147.     contains information about the current chunk and that can also be used
  148.     to read the actual chunk data.
  149.     """
  150.     
  151.     def __init__(self):
  152.         pass
  153.  
  154.     def abort(self):
  155.         """Aborts reading the current file.
  156.         
  157.         This method can be called in handler functions to abort reading
  158.         the file.
  159.         """
  160.         self._abortFlag = True
  161.  
  162.     def onBeginGroup(self, chunk):
  163.         """Callback that is called whenever a new group tag begins.
  164.         
  165.         chunk is a GroupChunk object containing information about the group chunk.
  166.         """
  167.         pass
  168. #        print "BEGIN", chunk
  169.         
  170.     def onEndGroup(self, chunk):
  171.         """Callback that is called whenever a group goes out of scope.
  172.         
  173.         chunk is a GroupChunk object containing information about the group chunk
  174.         (it is the same instance that was passed to onBeginGroup()).
  175.         """
  176.         pass
  177. #        print "END", chunk
  178.         
  179.     def onDataChunk(self, chunk):
  180.         """Callback that is called for each data chunk.
  181.  
  182.         chunk is a Chunk object that contains information about the chunk
  183.         and that can be used to read the actual chunk data.
  184.         """
  185.         pass
  186. #        print " ", chunk
  187.  
  188.     def read(self, file):
  189.         """Read the binary file.
  190.         
  191.         This method reads all chunks sequentially and invokes appropriate
  192.         callback methods. 
  193.         file is a file-like object or the name of a file.
  194.         """
  195.         if isinstance(file, basestring):
  196.             self.filename = file
  197.             file = open(file, "rb")
  198.         else:
  199.             self.filename = getattr(file, "name", "?")
  200.         
  201.         # Check if this actually is a Maya file
  202.         # (and that it starts with a group tag)
  203.         header = file.read(12)
  204.         file.seek(0)
  205.         if len(header)!=12 or header[0:4]!="FOR4" or header[8:12]!="Maya":
  206.             raise ValueError, 'The file "%s" is not a Maya binary file.'%self.filename 
  207.         
  208.         self._file = file
  209.         self._abortFlag = False
  210.         
  211.         # The current byte position inside the file
  212.         pos = 0
  213.         # A stack with alignment values. Each group tag pushes a new value
  214.         # which is popped when the group goes out of scope
  215.         alignments = []
  216.         # A stack with the currently open group chunks. The items are
  217.         # 2-tuples (endpos, groupchunk).
  218.         pendingGroups = []
  219.         # The current depth of the chunks
  220.         depth = 0
  221.         while not self._abortFlag:
  222.             tag,size = self.readChunkHeader()
  223.             if tag==None:
  224.                 break
  225.             pos += 8
  226.             if self.isGroupChunk(tag):
  227.                 # Group chunk...
  228.                 type = file.read(4)
  229.                 if len(pendingGroups)==0:
  230.                     parent = None
  231.                 else:
  232.                     parent = pendingGroups[-1][1]
  233.                 chunk = GroupChunk(file=file, parent=parent, tag=tag, size=size, pos=pos, type=type, depth=depth) 
  234.                 self.onBeginGroup(chunk)
  235.                 av = self.alignmentValue(tag)
  236.                 alignments.append(av)
  237.                 end = pos+self.paddedSize(size, av)
  238.                 if len(pendingGroups)>0 and end>pendingGroups[-1][0]:
  239.                     raise ValueError, 'Chunk %s at position %s in file "%s" has an invalid size (%d) that goes beyond its contained group chunk.'%(tag,pos-8,os.path.basename(self.filename),size)
  240.                 pendingGroups.append((end, chunk))
  241.                 pos += 4
  242.                 depth += 1
  243.             else:
  244.                 # Data chunk...
  245.                 chunk = Chunk(file=file, parent=pendingGroups[-1][1], tag=tag, size=size, pos=pos, depth=depth)
  246.                 self.onDataChunk(chunk)
  247.                 pos += self.paddedSize(size, alignments[-1])
  248.  
  249.             # Check which groups are to be closed...
  250.             while len(pendingGroups)>0 and pos>=pendingGroups[-1][0]:
  251.                 end,chunk = pendingGroups.pop()
  252.                 self.onEndGroup(chunk)
  253.                 depth -= 1
  254.                 
  255.             # Seek to the next chunk position. This is done here (even though it
  256.             # wouldn't be necessary in some cases) so that the callbacks have
  257.             # no chance to mess with the file handle and bring the reader
  258.             # out of sync.
  259.             file.seek(pos)
  260.  
  261.     def readChunkHeader(self):
  262.         """Read the tag and size of the next chunk.
  263.         
  264.         Returns a tuple (tag, size) where tag is the four character
  265.         chunk name and size is an integer containing the size of the
  266.         data part of the chunk.
  267.         Returns None,None if the end of the file has been reached.
  268.         Throws an exception when an incomplete tag/size was read.
  269.         """
  270.         header = self._file.read(8)
  271.         if len(header)==0:
  272.             return None,None
  273.         if len(header)!=8:
  274.             raise ValueError, 'Premature end of file "%s" (chunk tag & size expected)'%os.path.basename(self.filename)
  275.         return (header[:4], struct.unpack(">L", header[4:])[0])
  276.         
  277.     def isGroupChunk(self, tag):
  278.         """Check if the given tag refers to a group chunk.
  279.  
  280.         tag is the chunk name. Returns True when tag is the name
  281.         of a group chunk.
  282.         """
  283.         return tag in ["FORM", "CAT ", "LIST", "PROP",
  284.                        "FOR4", "CAT4", "LIS4", "PRO4",
  285.                        "FOR8", "CAT8", "LIS8", "PRO8"]
  286.     
  287.     def alignmentValue(self, tag):
  288.         """Return the alignment value for a group chunk.
  289.         
  290.         Returns 2, 4 or 8.
  291.         """
  292.         if tag in ["FORM", "CAT ", "LIST", "PROP"]:
  293.             return 2
  294.         elif tag in ["FOR4", "CAT4", "LIS4", "PRO4"]:
  295.             return 4
  296.         elif tag in ["FOR8", "CAT8", "LIS8", "PRO8"]:
  297.             return 8
  298.         else:
  299.             return 2
  300.         
  301.     def paddedSize(self, size, alignment):
  302.         """Return the padded size that is aligned to the given value.
  303.         
  304.         size is an arbitrary chunk size and alignment an integer
  305.         containing the alignment value (2,4,8). If size is already
  306.         aligned it is returned unchanged, otherwise an appropriate
  307.         number of padding bytes is added and the aligned size is
  308.         returned.
  309.         """
  310.         # Padding required?
  311.         if size%alignment!=0:
  312.             padding = alignment-size%alignment
  313.             size += padding
  314.         return size
  315.       
  316.     def dump(self, buf):
  317.         """Helper method to do a hex dump of chunk data.
  318.         
  319.         buf is a string containing the data to dump.
  320.         """
  321.         offset = 0
  322.         while len(buf)>0:
  323.             data = buf[:16]
  324.             buf = buf[16:]
  325.             s = "%04x: "%offset
  326.             s += " ".join(map(lambda x: "%02x"%ord(x), data))
  327.             s += (55-len(s))*' '
  328.             for c in data:
  329.                 if ord(c)<32:
  330.                     c = '.'
  331.                 s += c
  332.             print s
  333.             offset += 16
  334.             
  335.             
  336. if __name__=="__main__":
  337.     
  338.     import sys
  339.     
  340.     class MBDumper(MBReader):
  341.         def onBeginGroup(self, chunk):
  342.             print "GRP BEGIN", chunk
  343.         
  344.         def onEndGroup(self, chunk):
  345.             print "GRP END  ", chunk
  346.         
  347.         def onDataChunk(self, chunk):
  348.             print "CHUNK    ",chunk
  349.             
  350.     rd = MBDumper()
  351.     rd.read(open(sys.argv[1], "rb"))
  352.