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 / mayaascii.py < prev    next >
Encoding:
Python Source  |  2008-02-21  |  60.6 KB  |  1,701 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: mayaascii.py,v 1.10 2005/06/15 19:18:46 mbaas Exp $
  36.  
  37. import sys, types, simplecpp
  38.  
  39. # The keywords that may be used for the value True
  40. _true_keywords = ["true", "on", "yes"]
  41. # and for the value False
  42. _false_keywords = ["false", "off", "no"]
  43.  
  44. # splitDAGPath
  45. def splitDAGPath(path):
  46.     """Split a Maya DAG path into its components.
  47.  
  48.     The path is given as a string that may have the form
  49.     <namespace>:<path> where <path> is a sequence of strings
  50.     separated by '|'.
  51.     The return value is a 2-tuple (namespace, names) where namespace
  52.     is None if the path did not contain a ':'. names is a list of
  53.     individual path names.
  54.  
  55.     Examples:
  56.  
  57.     :time1   ->  ('', ['time1'])
  58.     |foo|bar ->  (None, ['', 'foo', 'bar'])
  59.     foo|bar  ->  (None, ['foo', 'bar'])
  60.     """
  61.     if not isinstance(path, types.StringTypes):
  62.         raise ValueError, "string type expected as path argument, got %s"%type(path)
  63.         
  64.     namespace = None
  65.     n = path.find(":")
  66.     if n!=-1:
  67.         namespace = path[:n]
  68.         path = path[n+1:]
  69.     return namespace, path.split("|")
  70.  
  71. # stripQuotes
  72. def stripQuotes(s):
  73.     """Remove surrounding quotes if there are any.
  74.  
  75.     The function returns the string without surrounding quotes
  76.     (i.e. '"foo"' -> 'foo'). If there are no quotes the string
  77.     is returned unchanged.
  78.     """
  79.     if s[0]=='"':
  80.         return s[1:-1]
  81.     else:
  82.         return s
  83.     
  84.  
  85. # MAPreProcessor
  86. class MAPreProcessor(simplecpp.PreProcessor):
  87.     """Preprocess the source file and invoke a callback for each line.
  88.     """
  89.     
  90.     def __init__(self, linehandler):
  91.         simplecpp.PreProcessor.__init__(self)
  92.         self.linehandler = linehandler
  93.         
  94.     def output(self, s):
  95.         # Ignore the preprocessor lines
  96.         if s[0:1]!="#":
  97.             continue_flag = self.linehandler(s)
  98.             if not continue_flag:
  99.                 self.abort()
  100.  
  101. # PolyFace
  102. class PolyFace:
  103.     """Stores the data of a polyFace value.
  104.  
  105.     PolyFace objects are returned by the getValue() method of the
  106.     Attribute class when the type was "polyFaces".
  107.     """
  108.     def __init__(self):
  109.         # face (list of ints)
  110.         self.f = None
  111.         # holes (list of list of ints)
  112.         # There's a list if vertex ids for each hole
  113.         self.h = []
  114.         # face tex coords (list of list of ints)
  115.         self.mf = []
  116.         # hole tex coords (list of list of ints)
  117.         self.mh = []
  118.         # tex coords (list of list of 2-tuple (uvset, list of ints))
  119.         # There's a list for each loop (#f + #h).
  120.         # Each list contains a list of 2-tuples (as there can be more than
  121.         # one uvset per face)
  122.         self.mu = []
  123.         # face colors (list of list of ints)
  124.         # There's a list for each loop (#f + #h)
  125.         self.fc = []
  126.  
  127.     def __str__(self):
  128.         return "<PolyFace %s #holes:%d>"%(self.f, len(self.h))
  129.  
  130.     # hasValidTexCoords
  131.     def hasValidTexCoords(self):
  132.         loops = [self.f]+self.h
  133.         if len(self.mu)!=len(loops):
  134.             return False
  135.         for mus,loop in zip(self.mu, loops):
  136.             if len(mus)==0:
  137.                 return False
  138.             if len(mus[0][1])!=len(loop):
  139.                 return False
  140.  
  141.         return True
  142.  
  143.     # newLoop
  144.     def newLoop(self):
  145.         """This is an internal method used during construction of a poly face.
  146.  
  147.         The method has to be called after a new 'f' or 'h' attribute was
  148.         encountered. The method will then allocate a new empty list for the
  149.         texture coords and face colors. This list is then filled during
  150.         construction.
  151.         This ensures that the number of tex coords and color lists matches
  152.         the number of loops.
  153.         """
  154.         self.mf.append([])
  155.         self.mh.append([])
  156.         self.mu.append([])
  157.         self.fc.append([])
  158.  
  159. # NurbsCurve
  160. class NurbsCurve:
  161.     """Stores the data of a nurbsCurve value.
  162.  
  163.     NurbsCurve objects are returned by the getValue() method of the
  164.     Attribute class when the type was "nurbsCurve".
  165.     """
  166.     def __init__(self):
  167.         # Degree
  168.         self.degree = 0
  169.         # Spans
  170.         self.spans = 0
  171.         # Form attribute (0=open, 1=closed, 2=periodic)
  172.         self.form = 0
  173.         # Is the curve rational?
  174.         self.isrational = False
  175.         # Dimension (2 or 3)
  176.         self.dimension = 0
  177.         # Knots
  178.         self.knots = []
  179.         # Control vertices
  180.         self.cvs = []
  181.  
  182. #    def __str__(self):
  183. #        return "<NurbsCurve>"
  184.  
  185. # NurbsSurface
  186. class NurbsSurface:
  187.     """Stores the data of a nurbsSurface value.
  188.  
  189.     NurbsSurface objects are returned by the getValue() method of the
  190.     Attribute class when the type was "nurbsSurface".
  191.     """
  192.     def __init__(self):
  193.         # Degree in u and v
  194.         self.udegree = 0
  195.         self.vdegree = 0
  196.         # Form attribute in u and v (0=open, 1=closed, 2=periodic)
  197.         self.uform = 0
  198.         self.vform = 0
  199.         # Is the surface rational?
  200.         self.isrational = False
  201.         # Knots in u and v
  202.         self.uknots = []
  203.         self.vknots = []
  204.         # Control vertices
  205.         self.cvs = []
  206.  
  207. #    def __str__(self):
  208. #        return "<NurbsSurface>"
  209.  
  210. # Attribute
  211. class Attribute:
  212.     """This class stores an attribute (i.e. the name and its value).
  213.  
  214.     An Attribute object is initialized with the arguments that were passed
  215.     to the onSetAttr() callback of the reader class. The main purpose
  216.     of an Attribute object is to convert the value into an appropriate
  217.     Python value.
  218.     """
  219.     
  220.     def __init__(self, attr, vals, opts):
  221.         """Constructor.
  222.  
  223.         attr, vals and opts are the parameters of the onSetAttr() callback.
  224.         """
  225.         self._attr = stripQuotes(attr)
  226.         self._vals = vals
  227.         self._opts = opts
  228.  
  229.     def __str__(self):
  230.         val = str(self._vals)
  231.         if len(val)>10:
  232.             val = val[:10]+"..."
  233.         return "<Attribute %s %s %s>"%(self._attr, val, self._opts)
  234.  
  235.     # getBaseName
  236.     def getBaseName(self):
  237.         """Return the base name of the attribute.
  238.  
  239.         This is the first part of the attribute name (and may actually
  240.         refer to another attribute).
  241.  
  242.         ".t"            -> "t"
  243.         ".ed[0:11]"     -> "ed"
  244.         ".uvst[0].uvsn" -> "uvst"
  245.         """
  246.         a = self._attr.split(".")
  247.         b = a[1].split("[")
  248.         return b[0]
  249.  
  250.     # getFullName
  251.     def getFullName(self):
  252.         return self._attr
  253.  
  254.     # getValue
  255.     def getValue(self, type=None, n=None):
  256.         """Return the converted value.
  257.  
  258.         type is a string containing the required type of the value.
  259.  
  260.         Valid types are "bool", "int", "float"
  261.         "short2", "short3", "long2", "long3",
  262.         "double2", "double3", "float2", "float3", "string", "int32Array",
  263.         "doubleArray", "polyFaces", "nurbsSurface", "nurbsCurve",
  264.         "double4", "float4" (for colors).
  265.         """
  266.         
  267.         # Check if the value type was specified in the setAttr call
  268.         valtype = self._opts.get("type", [None])[0]
  269.         if valtype==None and type==None:
  270.             if filter(lambda x: x in _true_keywords+_false_keywords, self._vals)!=[]:
  271.                 valtype = "bool"
  272.             else:
  273.                 valtype = "float"
  274.  
  275.         # Use the real attribute type if no required type was specified
  276.         if type==None:
  277.             type = valtype
  278.  
  279.         # Use the provided type if the attribute had no 'type' option
  280.         if valtype==None:
  281.             valtype = type
  282.  
  283.         # No type information given?
  284.         if valtype==None:
  285.             raise ValueError, "No type information available for attribute '%s'"%self.getBaseName()
  286.  
  287.         # Check if the type matches the required type...
  288.         if valtype!=type:
  289.             raise ValueError, "Attribute of type %s expected, got %s"%(type, valtype)
  290.  
  291.         # Convert the values..
  292.         convertername = "convert"+type[0].upper()+type[1:]
  293.         f = getattr(self, convertername)
  294.         try:
  295.             vs = f()
  296.         except ValueError, e:
  297.             print >>sys.stderr, e
  298.             # Try a string conversion when no type was specified...
  299.             if type==None and convertername!="convertString":
  300.                 vs = self.convertString()
  301.             else:
  302.                 raise
  303.         if n==None:
  304.             return vs
  305.         if len(vs)!=n:
  306.             raise ValueError, "%s: %d values expected, got %d"%(self._attr, n, len(vs))
  307.         if n==1:
  308.             return vs[0]
  309.         else:
  310.             return vs
  311.  
  312.     # The following convert methods have to convert the value list (self._vals)
  313.     # into the appropriate type. The return value is always a list of values.
  314.  
  315.     def convertInt(self):
  316.         return map(lambda x: int(x), self._vals)
  317.  
  318.     def convertFloat(self):
  319.         return map(lambda x: float(x), self._vals)
  320.  
  321.     def convertBool(self):
  322.         res = []
  323.         for v in self._vals:
  324.             res.append(v in _true_keywords)
  325.         return res
  326.  
  327.     def convertLong2(self):
  328.         res = []
  329.         vs = self._vals
  330.         for i in range(0,len(vs), 2):
  331.             res.append((int(vs[i]), int(vs[i+1])))
  332.         return res
  333.  
  334.     convertShort2 = convertLong2
  335.  
  336.     def convertLong3(self):
  337.         res = []
  338.         vs = self._vals
  339.         for i in range(0,len(vs), 3):
  340.             res.append((int(vs[i]), int(vs[i+1]), int(vs[i+2])))
  341.         return res
  342.  
  343.     convertShort3 = convertLong3
  344.  
  345.     def convertDouble2(self):
  346.         res = []
  347.         vs = self._vals
  348.         for i in range(0,len(vs), 2):
  349.             res.append((float(vs[i]), float(vs[i+1])))
  350.         return res
  351.  
  352.     convertFloat2 = convertDouble2
  353.  
  354.     def convertDouble3(self):
  355.         res = []
  356.         vs = self._vals
  357.         for i in range(0,len(vs), 3):
  358.             res.append((float(vs[i]), float(vs[i+1]), float(vs[i+2])))
  359.         return res
  360.  
  361.     convertFloat3 = convertDouble3
  362.  
  363.     def convertDouble4(self):
  364.         res = []
  365.         vs = self._vals
  366.         for i in range(0,len(vs), 4):
  367.             res.append((float(vs[i]), float(vs[i+1]), float(vs[i+2]), float(vs[i+3])))
  368.         return res
  369.  
  370.     convertFloat4 = convertDouble4
  371.  
  372.     def convertString(self):
  373.         return map(lambda x: str(x), self._vals)
  374.  
  375.     def convertInt32Array(self):
  376.         n = int(self._vals[0])
  377.         return map(lambda x: int(x), self._vals[1:n+1])
  378.  
  379.     def convertDoubleArray(self):
  380.         n = int(self._vals[0])
  381.         return map(lambda x: float(x), self._vals[1:n+1])
  382.  
  383.     def convertPolyFaces(self):
  384.         res = []
  385.         vs = self._vals
  386.         i=0
  387.         pf = None
  388.         while i<len(vs):
  389.             c = vs[i]
  390.             i+=1
  391.             if c not in ["f", "h", "mf", "mh", "mu", "fc"]:
  392.                 raise ValueError, "Unknown polyFace data: %s"%c
  393.             
  394.             if c=="mu":
  395.                 uvset = int(vs[i])
  396.                 i+=1
  397.                 
  398.             n = int(vs[i])
  399.             ids = map(lambda x: int(x), vs[i+1:i+n+1])
  400.             i+=n+1
  401.             
  402.             # Is that already a new polyFace? Then store the previous one
  403.             # and start a new face
  404.             if c=="f":
  405.                 if pf!=None:
  406.                     res.append(pf)
  407.                 pf = PolyFace()
  408.                 pf.f = ids
  409.                 pf.newLoop()
  410.             elif c=="h":
  411.                 pf.h.append(ids)
  412.                 pf.newLoop()
  413.             elif c=="mu":
  414.                 pf.mu[-1].append((uvset, ids))
  415.             else:
  416.                 # Set the IDs into the corresponding list
  417.                 lst = getattr(pf, c)
  418.                 lst[-1] = ids
  419.  
  420.         # Append the last face
  421.         if pf!=None:
  422.             res.append(pf)
  423.             
  424.         return res
  425.  
  426.     def convertNurbsCurve(self):
  427.         res = []
  428.         vs = self._vals
  429.  
  430.         while vs!=[]:
  431.             nc = NurbsCurve()
  432.             nc.degree = int(vs[0])
  433.             nc.spans = int(vs[1])
  434.             nc.form = int(vs[2])
  435.             nc.isrational = vs[3] in _true_keywords
  436.             nc.dimension = int(vs[4])
  437.             # Get knots
  438.             n = int(vs[5])
  439.             nc.knots = map(lambda x: float(x), vs[6:n+6])
  440.             vs = vs[n+6:]
  441.             # Get CVs
  442.             n = int(vs[0])
  443.             dim = nc.dimension
  444.             if nc.isrational:
  445.                 dim += 1
  446.             cvs = []
  447.             for i in range(n):
  448.                 cvs.append( tuple(map(lambda x: float(x), vs[1+i*dim:1+(i+1)*dim])) )
  449.             nc.cvs = cvs
  450.             vs = vs[1+n*dim:]
  451.             res.append(nc)
  452.  
  453.         return res
  454.  
  455.     def convertNurbsSurface(self):
  456.         res = []
  457.         vs = self._vals
  458.  
  459.         while vs!=[]:
  460.             ns = NurbsSurface()
  461.             ns.udegree = int(vs[0])
  462.             ns.vdegree = int(vs[1])
  463.             ns.uform = int(vs[2])
  464.             ns.vform = int(vs[3])
  465.             ns.isrational = vs[4] in _true_keywords
  466.             vs = vs[5:]
  467.             # Get u knots
  468.             n = int(vs[0])
  469.             ns.uknots = map(lambda x: float(x), vs[1:n+1])
  470.             vs = vs[n+1:]
  471.             # Get v knots
  472.             n = int(vs[0])
  473.             ns.vknots = map(lambda x: float(x), vs[1:n+1])
  474.             vs = vs[n+1:]
  475.             # Skip TRIM|NOTRIM
  476.             if vs[0] in ["TRIM", "NOTRIM"]:
  477.                 vs = vs[1:]
  478.             # Get CVs
  479.             n = int(vs[0])
  480.             if ns.isrational:
  481.                 dim = 4
  482.             else:
  483.                 dim = 3
  484.             cvs = []
  485.             for i in range(n):
  486.                 cvs.append( tuple(map(lambda x: float(x), vs[1+i*dim:1+(i+1)*dim])) )
  487.             ns.cvs = cvs
  488.             vs = vs[1+n*dim:]
  489.             res.append(ns)
  490.  
  491.         return res
  492.         
  493.     # setValue
  494.     def setValue(self, value):
  495.         pass
  496.  
  497. # MultiAttrStorage
  498. class MultiAttrStorage:
  499.     """This helper class serves as MEL style array.
  500.  
  501.     You can assign values to arbitrary indices which will automatically
  502.     enlarge the array (filling missing values with None).
  503.     The slicing operation is different than Python as the stop index is
  504.     inclusive. The actual array is stored in the _array attribute.
  505.  
  506.     Example:
  507.  
  508.     a = MultiAttrStorage()
  509.     a[2] = 5               # -> [None, None, 5]
  510.     a[4:6] = [1,2,3]       # -> [None, None, 5, None, 1, 2, 3]
  511.  
  512.  
  513.     Reading an arbitrary attribute from an object of this class will
  514.     automatically create this attribute which will in turn be of type
  515.     MultiAttrStorage. So this class can also be used for compound objects.
  516.     If you want to check such a compound for a particular attribute you
  517.     must not use hasattr() as this would silently create the attribute if
  518.     it didn't already exist and always return True. Instead you have to
  519.     do the check as follows:
  520.  
  521.     if <attrname> in dir(compound):
  522.        ...
  523.     """
  524.     def __init__(self):
  525.         self._array = []
  526.  
  527.     def __str__(self):
  528.         namedattrs = filter(lambda x: x[0:1]!="_", self.__dict__.keys())
  529.         # Not an array but a 'struct' with named attributes...
  530.         if namedattrs!=[] and self._array==[]:
  531.             a = []
  532.             for name in namedattrs:
  533.                 a.append(".%s:%s"%(name, getattr(self, name)))
  534.             return ", ".join(a)
  535.         else:
  536.             return "["+", ".join(map(lambda x: str(x), self._array))+"]"
  537.  
  538.     def __iter__(self):
  539.         return iter(self._array)
  540.  
  541.     def __len__(self):
  542.         return len(self._array)
  543.  
  544.     def __getattr__(self, name):
  545.         if name[:2]=="__":
  546.             raise AttributeError, name
  547.         ma = MultiAttrStorage()
  548.         setattr(self, name, ma)
  549.         return ma
  550.  
  551.     def __getitem__(self, key):
  552.         if key>=len(self._array):
  553.             ma = MultiAttrStorage()
  554.             self.__setitem__(key, ma)
  555.             return ma
  556.         else:
  557.             return self._array[key]
  558.  
  559.     def __setitem__(self, key, value):
  560.         al = len(self._array)
  561.         if type(key)==slice:
  562.             if key.stop>=al:
  563.                 self._array += (key.stop-al+1)*[None]
  564.             if len(value)!=(key.stop-key.start+1):
  565.                 raise ValueError, "%d values expected, got %d"%(key.stop-key.start+1, len(value))
  566.             self._array[key.start:key.stop+1] = value
  567. #            print key.start,key.stop
  568.         else:
  569.             if key<al:
  570.                 self._array[key] = value
  571.             else:
  572.                 self._array += (key-al)*[None] + [value]
  573.  
  574. # Node
  575. class Node:
  576.     """A generic Maya node class.
  577.  
  578.     This is a helper class which may be used in a concrete implementation
  579.     of the MAReader class to represent Maya nodes.
  580.  
  581.     This class does not implement the actual functionality of a
  582.     particular Maya node, it just tracks attribute changes and
  583.     connections which can later be retrieved once the entire file was
  584.     read. So this class can be used for all Maya nodes in a file. 
  585.     """
  586.     
  587.     def __init__(self, nodetype, opts, parent=None):
  588.         """Constructor.
  589.  
  590.         nodetype and opts are the arguments of the onCreateNode()
  591.         callback of the MAReader class.
  592.         parent is the parent Node object or None.
  593.         """
  594.  
  595.         # Do some type checking...
  596.         if not isinstance(nodetype, types.StringTypes):
  597.             raise ValueError, "Argument 'nodetype' must be a string, got %s."%(type(nodetype))
  598.         if type(opts)!=dict:
  599.             raise ValueError, "Argument 'opts' must be a dict, got %s."%(type(opts))
  600.         if parent!=None and not isinstance(parent, Node):
  601.             raise ValueError, "Argument 'parent' must be a Node object or None, got %s."%(type(parent))
  602.  
  603.         # A string containing the node type
  604.         self.nodetype = nodetype
  605.         # The options dictionary
  606.         self.opts = opts
  607.  
  608.         # Parent Node object
  609.         self._parent = None
  610.  
  611.         # Children Node objects
  612.         self._children = []
  613.         
  614.         # Attribute values.
  615.         # Key: Attribute base name / Value: List of Attribute objects
  616.         self._setattr = {}
  617.  
  618.         # If True, accessing attributes will automatically create a new
  619.         # attribute that contains a MultiAttrStorage object
  620.         self._create_attributes = False
  621.  
  622.         # Key: Local attribute / Value: (nodename, attrname) of source
  623.         self.in_connections = {}
  624.         # Key: Local attribute / Value: List of (node, nodename, attrname) tuples
  625.         self.out_connections = {}
  626.  
  627.         # Set the parent
  628.         self.setParent(parent)
  629.  
  630.     def __str__(self):
  631.         return '<Node "%s" %s>'%(self.getFullName(), self.nodetype)
  632.  
  633.     def __getattr__(self, name):
  634.         if self._create_attributes:
  635.             ma = MultiAttrStorage()
  636.             setattr(self, name, ma)
  637.             return ma
  638.         raise AttributeError, name
  639.  
  640.     # getName
  641.     def getName(self):
  642.         """Return the node name.
  643.  
  644.         If no node name was specified during the creation of the object,
  645.         the dummy name 'MayaNode' is returned.
  646.         """
  647.         return self.opts.get("name", ["MayaNode"])[0]
  648.  
  649.     # getFullName
  650.     def getFullName(self):
  651.         """Return the full node name.
  652.         """
  653.         name = self.getName()
  654.         if self._parent==None:
  655.             return "|%s"%name
  656.         else:
  657.             return "%s|%s"%(self._parent.getFullName(), name)
  658.  
  659.     # getParentName
  660.     def getParentName(self):
  661.         """Return the parent's node name or None.
  662.         """
  663.         if self._parent==None:
  664.             return None
  665.         else:
  666.             return self._parent.getFullName()
  667.  
  668.     # getParent
  669.     def getParent(self):
  670.         """Return the parent Node object or None.
  671.         """
  672.         return self._parent
  673.  
  674.     # setParent
  675.     def setParent(self, parent):
  676.         """Reparent the Node object.
  677.         """
  678.         # Remove self from the previous parent's children list...
  679.         if self._parent!=None:
  680.             self._parent._children.remove(self)
  681.  
  682.         # Set the new parent...
  683.         self._parent = parent
  684.         if parent!=None:
  685.             parent._children.append(self)
  686.  
  687.     # iterChildren
  688.     def iterChildren(self):
  689.         """Return an iterator that yields all children Node objects.
  690.         """
  691.         return iter(self._children)
  692.  
  693.     # setAttr
  694.     def setAttr(self, attr, vals, opts):
  695.         """Store the attribute value.
  696.  
  697.         The arguments are the same than the arguments of the onSetAttr()
  698.         callback in the MAReader class.
  699.         The final Python value can be retrieved with the getAttrValue()
  700.         method.
  701.         """
  702.         # Is this setattr call only used to "declare" the size of an array?
  703.         # then ignore the call
  704.         if vals==[] and "size" in opts:
  705.             return
  706.         
  707.         a = Attribute(attr, vals, opts)
  708.         basename = a.getBaseName()
  709.         if basename in self._setattr:
  710.             self._setattr[basename].append(a)
  711.         else:
  712.             self._setattr[basename] = [a]
  713.  
  714.     # addAttr
  715.     def addAttr(self, opts):
  716.         pass
  717.  
  718.     # addInConnection
  719.     def addInConnection(self, localattr, nodename, attrname):
  720.         """Add an 'in' connection.
  721.  
  722.         'in' = node.attr is connected to localattr
  723.         nodename is the name of a Node object and attrname the full
  724.         attribute name.
  725.         """
  726.         self.in_connections[localattr] = (nodename, attrname)
  727.  
  728.     # addutConnection
  729.     def addOutConnection(self, localattr, node, nodename, attrname):
  730.         """Add an 'out' connection.
  731.  
  732.         'out' = localattr is connected to node.attr
  733.         node is a Node object, nodename the name of the node and attrname
  734.         the full attribute name.
  735.         """
  736.         if not isinstance(node, Node):
  737.             raise ValueError, "Argument 'node' must be a Node instance."
  738.         if self.out_connections.has_key(localattr):
  739.             self.out_connections[localattr].append((node, nodename, attrname))
  740.         else:
  741.             self.out_connections[localattr] = [(node, nodename, attrname)]
  742.         
  743.  
  744.     # getAttrValue
  745.     def getAttrValue(self, lname, sname, type, n=1, default=None):
  746.         """Get the Python value of an attribute.
  747.  
  748.         lname is the long name, sname the short name. type is the
  749.         required type and n the required number of elements. type and
  750.         n may be None.
  751.         The return value is either a normal Python type (int, float, sequence)
  752.         or a MultiAttrStorage object in cases where the setAttr command
  753.         contained the index operator.
  754.         When no attribute with the given long or short name could be
  755.         found the provided default value is returned.
  756.  
  757.         This method will only return a value other than the default
  758.         value when the \method{setAttr()} was called with the corresponding
  759.         attribute.
  760.         """
  761.         # 'Execute' the stored setAttr commands for the long name and
  762.         # the short name. This will store the value as an attribute
  763.         # of self.
  764.         self._executeAttrs(lname, type, n)
  765.         self._executeAttrs(sname, type, n)
  766.         # Check if the long name is available, otherwise try the short name...
  767.         if hasattr(self, lname):
  768.             return getattr(self, lname)
  769.         return getattr(self, sname, default)
  770.  
  771.     # getInNode
  772.     def getInNode(self, localattr_long, localattr_short):
  773.         """Return the node and attribute that serves as input for localattr.
  774.  
  775.         The return value is a 2-tuple (nodename, attrname) that specifies
  776.         the input connection for localattr. (None, None) is returned if there
  777.         is no connection.
  778.         """
  779.         node, attr = self.in_connections.get(localattr_long, (None, None))
  780.         if node==None:
  781.             node, attr = self.in_connections.get(localattr_short, (None, None))
  782.         return node, attr
  783.  
  784.     # getOutNodes
  785.     def getOutNodes(self, localattr_long, localattr_short):
  786.         """Return the nodes and attributes that this attribute connects to.
  787.  
  788.         The return value is a list of 3-tuples (node, nodename, attrname) that
  789.         specify the output connections for localattr.
  790.         """
  791.         lst = self.out_connections.get(localattr_long, None)
  792.         if lst==None:
  793.             lst = self.out_connections.get(localattr_short, [])
  794.         return lst
  795.  
  796.     # getOutAttr
  797.     def getOutAttr(self, localattr_long, localattr_short, dstnodetype):
  798.         """Check if a local attribute is connected to a particular type of node.
  799.  
  800.         Returns a tuple (node, attrname) where node is the Node object
  801.         of the destination node and attrname the name of the destination
  802.         attribute. If there is no connection with a node of type dstnodetype,
  803.         the method returns (None,None).
  804.         If the attribute is connected to more than one node with the
  805.         given type or to several attributes of the same node then only
  806.         the first connection encountered is returned.
  807.         """
  808.         cs = self.getOutNodes(localattr_long, localattr_short)
  809.  
  810.         # Search the connections for one that goes to a node with
  811.         # the specified type...
  812.         for node,nodename,attrname in cs:
  813.             if node.nodetype==dstnodetype:
  814.                 return node, attrname
  815.         return None,None
  816.  
  817.     # _executeAttrs
  818.     def _executeAttrs(self, basename, type, n):
  819.         """Retrieve the Python values out of an attribute.
  820.  
  821.         All stored attributes with the given basename will be 'executed'.
  822.         This means, their Python value will be obtained and stored
  823.         in the node under the same name than the original attribute.
  824.         type is the required type of the attribute (may be None) and
  825.         n the required number of elements (or None = Dynamic array).
  826.         """
  827.         self._create_attributes = True
  828.         efftype = type
  829.         for attr in self._setattr.get(basename, []):
  830.             # Keep the type information once an attribute explicitly
  831.             # provided some info. Later attributes that don't specify
  832.             # the type (as can happen with the texture coordinates) can
  833.             # then be read properly.
  834.             if type==None:
  835.                 valtype = attr._opts.get("type", [None])[0]
  836.                 if valtype!=None:
  837.                     efftype = valtype
  838.             v = attr.getValue(efftype, n)
  839.             # Unpack the value if the number of elements wasn't known and
  840.             # it turned out to be only one element (in this case, the below
  841.             # assignment will be of the form a[i]=v instead of a[i:j]=v)
  842.             if n==None and len(v)==1:
  843.                 v = v[0]
  844.             if attr.getBaseName() in ["in", "from", "for", "if", "while",
  845.                                       "else", "elif", "exec"]:
  846.                 cmd = "setattr(self, '%s', v)"%attr.getFullName()[1:]
  847.             else:
  848.                 cmd = "self%s = v"%attr.getFullName()
  849.             if v!=[]:
  850.                 exec cmd
  851.         self._create_attributes = False
  852.  
  853. ######################################################################
  854.  
  855. # MAReader
  856. class MAReader:
  857.     """Low level MA (Maya ASCII) reader.
  858.  
  859.     The MAReader class reads Maya ASCII files and calls handler
  860.     methods which have to be implemented in a derived class. The
  861.     content of the file is actually a subset of the Maya Embedded
  862.     Language (MEL) which is the scripting language implemented inside
  863.     Maya. The MAReader parses the file, breaks down the content of the
  864.     file in commands and their arguments and options (expressions are
  865.     not evaluated). Each MEL command will then trigger a callback
  866.     method that has to execute the command. These callback methods
  867.     have to be implemented in a derived class.
  868.  
  869.     There are 12 MEL commands that can appear in a Maya ASCII file: 
  870.  
  871.     - file 
  872.     - requires 
  873.     - fileInfo 
  874.     - currentUnit 
  875.     - createNode 
  876.     - setAttr 
  877.     - addAttr 
  878.     - connectAttr 
  879.     - disconnectAttr 
  880.     - parent 
  881.     - select
  882.     - lockNode
  883.  
  884.     Each command has a number of arguments and can also take
  885.     options. The callback methods receive the arguments as regular
  886.     arguments to the method and the options as an additional argument
  887.     opts which is a dictionary containing the options that were
  888.     specified in the file. The key is the long name of the option
  889.     (without leading dash) and the value is a list of strings
  890.     containing the option values. The number of values and how they
  891.     have to be interpreted depend on the actual option.
  892.     
  893.     The callbacks may access a few instance variables that carry
  894.     further information:
  895.     
  896.     - filename: The name of the ma file
  897.     - cmd_start_linenr: The line number where the current command began
  898.     - cmd_end_linenr: The line number where the current command ended 
  899.     """
  900.     
  901.     def __init__(self):
  902.  
  903.         # createNode options
  904.         self.createNode_name_dict = { "n":"name", "p":"parent", "s":"shared" }
  905.         self.createNode_opt_def = { "name" : (1, None),
  906.                                     "parent" : (1, None),
  907.                                     "shared" : (0, None) }
  908.  
  909.         # setAttr options
  910.         self.setAttr_name_dict = { "k":"keyable",
  911.                                    "l":"lock",
  912.                                    "s":"size",
  913.                                    "typ":"type",
  914.                                    "av":"alteredValue",
  915.                                    "c":"clamp" }
  916.         self.setAttr_opt_def = { "keyable" : (1, None),
  917.                                  "lock" : (1, None),
  918.                                  "size" : (1, None),
  919.                                  "type" : (1, None),
  920.                                  "alteredValue" : (0, None),
  921.                                  "clamp" : (0, None)}
  922.  
  923.         # fileInfo options
  924.         self.fileInfo_name_dict = { "rm":"remove" }
  925.         self.fileInfo_opt_def = { "remove" : (1, None)}
  926.  
  927.         # currentUnit options
  928.         self.currentUnit_name_dict = { "l":"linear",
  929.                                        "a":"angle",
  930.                                        "t":"time",
  931.                                        "f":"fullName",
  932.                                        "ua":"updateAnimation"}
  933.         self.currentUnit_opt_def = { "linear" : (1, None),
  934.                                      "angle" : (1, None),
  935.                                      "time" : (1, None),
  936.                                      "fullName" : (0, None),
  937.                                      "updateAnimation" : (1, None)}
  938.  
  939.         # connectAttr options
  940.         self.connectAttr_name_dict = { "l":"lock",
  941.                                        "f":"force",
  942.                                        "na":"nextAvailable",
  943.                                        "rd":"referenceDest" }
  944.         self.connectAttr_opt_def = { "lock" : (1, None),
  945.                                      "force" : (0, None),
  946.                                      "nextAvailable" : (0, None),
  947.                                      "referenceDest" : (1, None) }
  948.  
  949.         # disconnectAttr options
  950.         self.fileInfo_name_dict = { "na":"nextAvailable" }
  951.         self.fileInfo_opt_def = { "nextAvailable" : (0, None)}
  952.  
  953.         # parent options
  954.         self.parent_name_dict = { "w":"world",
  955.                                   "r":"relative",
  956.                                   "a":"absolute",
  957.                                   "add":"addObject",
  958.                                   "rm":"removeObject",
  959.                                   "s":"shape",
  960.                                   "nc":"noConnections" }
  961.         self.parent_opt_def = { "world" : (0, None),
  962.                                 "relative" : (0, None),
  963.                                 "absolute" : (0, None),
  964.                                 "addObject" : (0, None),
  965.                                 "removeObject" : (0, None),
  966.                                 "shape" : (0, None),
  967.                                 "noConnections" : (0, None) }
  968.  
  969.         # select options
  970.         self.select_name_dict = { "adn":"allDependencyNodes",
  971.                                   "ado":"allDagObjects",
  972.                                   "vis":"visible",
  973.                                   "hi":"hierarchy",
  974.                                   "af":"addFirst",
  975.                                   "r":"replace",
  976.                                   "d":"deselect",
  977.                                   "tgl":"toggle",
  978.                                   "cl":"clear",
  979.                                   "ne":"noExpand" }
  980.         self.select_opt_def = { "all" : (0, None),
  981.                                 "allDependencyNodes" : (0, None),
  982.                                 "allDagObjects" : (0, None),
  983.                                 "visible" : (0, None),
  984.                                 "hierarchy" : (0, None),
  985.                                 "add" : (0, None),
  986.                                 "addFirst" : (0, None),
  987.                                 "replace" : (0, None),
  988.                                 "deselect" : (0, None),
  989.                                 "toggle" : (0, None),
  990.                                 "clear" : (0, None),
  991.                                 "noExpand" : (0, None) }
  992.  
  993.         # addAttr options
  994.         self.addAttr_name_dict = { "ln":"longName",
  995.                                    "sn":"shortName",
  996.                                    "bt":"binaryTag",
  997.                                    "at":"attributeType",
  998.                                    "dt":"dataType",
  999.                                    "dv":"defaultValue",
  1000.                                    "m":"multi",
  1001.                                    "im":"indexMatters",
  1002.                                    "min":"minValue",
  1003.                                    "hnv":"hasMinValue",
  1004.                                    "max":"maxValue",
  1005.                                    "hxv":"hasMaxValue",
  1006.                                    "ci":"cachedInternally",
  1007.                                    "is":"internalSet",
  1008.                                    "p":"parent",
  1009.                                    "nc":"numberOfChildren",
  1010.                                    "uac":"usedAsColor",
  1011.                                    "h":"hidden",
  1012.                                    "r":"readable",
  1013.                                    "w":"writable",
  1014.                                    "s":"storable",
  1015.                                    "k":"keyable",
  1016.                                    "smn":"softMinValue",
  1017.                                    "hsn":"hasSoftMinValue",
  1018.                                    "smx":"softMaxValue",
  1019.                                    "hsx":"hasSoftMaxValue",
  1020.                                    "en":"enumName" }
  1021.         self.addAttr_opt_def = { "longName" : (1, None),
  1022.                                  "shortName" : (1, None),
  1023.                                  "binaryTag" : (1, None),
  1024.                                  "attributeType" : (1, None),
  1025.                                  "dataType" : (1, None),
  1026.                                  "defaultValue" : (1, None),
  1027.                                  "multi" : (0, None),
  1028.                                  "indexMatters" : (1, None),
  1029.                                  "minValue" : (1, None),
  1030.                                  "hasMinValue" : (1, None),
  1031.                                  "maxValue" : (1, None),
  1032.                                  "hasMaxValue" : (1, None),
  1033.                                  "cachedInternally" : (1, None),
  1034.                                  "internalSet" : (1, None),
  1035.                                  "parent" : (1, None),
  1036.                                  "numberOfChildren" : (1, None),
  1037.                                  "usedAsColor" : (0, None),
  1038.                                  "hidden" : (1, None),
  1039.                                  "readable" : (1, None),
  1040.                                  "writable" : (1, None),
  1041.                                  "storable" : (1, None),
  1042.                                  "keyable" : (1, None),
  1043.                                  "softMinValue" : (1, None),
  1044.                                  "hasSoftMinValue" : (1, None),
  1045.                                  "softMaxValue" : (1, None),
  1046.                                  "hasSoftMaxValue" : (1, None),
  1047.                                  "enumName" : (1, None) }
  1048.  
  1049.         # file options (incomplete)
  1050.         self.file_name_dict = { "bls":"buildLoadSettings",
  1051.                                 "c":"command",
  1052.                                 "dns":"defaultNamespace",
  1053.                                 "dr":"deferReference",
  1054.                                 "f":"force",
  1055.                                 "fr":"flushReference",
  1056.                                 "gl":"groupLocator",
  1057.                                 "gn":"groupName",
  1058.                                 "gr":"groupReference",
  1059.                                 "ir":"importReference",
  1060.                                 "lck":"lockReference",
  1061.                                 "lf":"lockFile",
  1062.                                 "lrd":"loadReferenceDepth",
  1063.                                 "lad":"loadAllDeferred",
  1064.                                 "lar":"loadAllReferences",
  1065.                                 "lnr":"loadNoReferences",
  1066.                                 "lr":"loadReference",
  1067.                                 "ls":"loadSettings",
  1068.                                 "pr":"preserveReferences",
  1069.                                 "new":"newFile",
  1070.                                 "o":"open",
  1071.                                 "op":"options",
  1072.                                 "pmt":"prompt",
  1073.                                 "r":"reference",
  1074.                                 "ra":"renameAll",
  1075.                                 "rdi":"referenceDepthInfo",
  1076.                                 "rfn":"referenceNode",
  1077.                                 "rpr":"renamingPrefix",
  1078.                                 "shd":"sharedNodes",
  1079.                                 "sns":"swapNamespace",
  1080.                                 "srf":"sharedReferenceFile",
  1081.                                 "str":"strict",
  1082.                                 "ns":"namespace",
  1083.                                 # The following flags are not documented in the Maya docs
  1084.                                 "pm":"proxyManager",
  1085.                                 "pt":"proxyTag",
  1086.                                 "ap":"activeProxy" }
  1087.         
  1088.         self.file_opt_def = { "buildLoadSettings" : (0, None), 
  1089.                               "command" : (1, None),
  1090.                               "defaultNamespace" : (0, None),
  1091.                               "deferReference" : (1, None),
  1092.                               "force" : (0, None),
  1093.                               "flushReference" : (1, None),
  1094.                               "groupLocator" : (0, None),
  1095.                               "groupName" : (1, None),
  1096.                               "groupReference" : (0, None),
  1097.                               "importReference" : (0, None),
  1098.                               "lockReference" : (0, None),
  1099.                               "lockFile" : (1, None),
  1100.                               "loadReferenceDepth" : (1, None),
  1101.                               "loadAllDeferred" : (1, None),
  1102.                               "loadAllReferences" : (0, None),
  1103.                               "loadNoReferences" : (0, None),
  1104.                               "loadReference" : (1, None),
  1105.                               "loadSettings" : (1, None),
  1106.                               "preserveReferences" : (0, None),
  1107.                               "newFile" : (0, None),
  1108.                               "open" : (0, None),
  1109.                               "options" : (1, None),
  1110.                               "prompt" : (1, None),
  1111.                               "reference" : (0, None),
  1112.                               "renameAll" : (1, None),
  1113.                               "referenceDepthInfo" : (1, None),
  1114.                               "referenceNode" : (1, None),
  1115.                               "renamingPrefix" : (1, None),
  1116.                               "sharedNodes" : (1, None),
  1117.                               "swapNamespace" : (2, None),
  1118.                               "sharedReferenceFile" : (0, None),
  1119.                               "strict" : (1, None),
  1120.                               "namespace" : (1, None),
  1121.                               "proxyManager" : (1, None),
  1122.                               "proxyTag" : (1, None),
  1123.                               "activeProxy" : (0, None) }
  1124.  
  1125.         # lockNode options
  1126.         self.lockNode_name_dict = { "l":"lock",
  1127.                                     "ic":"ignoreComponents" }
  1128.         self.lockNode_opt_def = { "lock" : (1, None),
  1129.                                   "ignoreComponents" : (0, None) }
  1130.         
  1131.     # Provide linenr as an alias for cmd_start_linenr
  1132.     @property
  1133.     def linenr(self):
  1134.         return self.cmd_start_linenr
  1135.     
  1136.     def read(self, f):
  1137.         """Read a MA file and invoke the callbacks.
  1138.  
  1139.         f is a file-like object or the name of a file.
  1140.         """
  1141.         self.begin()
  1142.         if isinstance(f, types.StringTypes):
  1143.             self.filename = f
  1144.         else:
  1145.             self.filename = getattr(f, "name", "?")
  1146.         # A flag that indicates if a new MEL command is about to begin
  1147.         self.new_cmd = True
  1148.         # The name of the current MEL command
  1149.         self.cmd = None
  1150.         # The arguments of the current MEL command
  1151.         self.args = None
  1152.         # This flag specifies whether reading the file should continue or not
  1153.         self.continue_flag = True
  1154.  
  1155.         # The line number where the current MEL command began
  1156.         self.cmd_start_linenr = None
  1157.         # The line number where the current MEL command ended
  1158.         self.cmd_end_linenr = None
  1159.         
  1160.         cpp = MAPreProcessor(self.lineHandler)
  1161.         self.cpp = cpp
  1162.         # Read the file and invoke the lineHandler for each line...
  1163.         cpp(f)
  1164.        
  1165.         # Execute the last command
  1166.         self.processCommands(";")
  1167.         self.end()
  1168.  
  1169.     def lineHandler(self, s):
  1170. #        self.linenr += 1
  1171.         z = s.strip()
  1172.         if z!="":
  1173.             self.processCommands(z)
  1174.         return self.continue_flag
  1175.     
  1176.     def abort(self):
  1177.         """Stop reading the MA file.
  1178.         
  1179.         This method can be called by a callback method to abort
  1180.         reading the file.
  1181.         """
  1182.         self.continue_flag = False
  1183.  
  1184.     def begin(self):
  1185.         """Callback that is invoked before the file is read."""
  1186.         pass
  1187.  
  1188.     def end(self):
  1189.         """Callback that is invoked after the file was read."""
  1190.         pass
  1191.  
  1192.     def onFile(self, filename, opts):
  1193.         """Callback for the 'file' MEL command."""
  1194.         pass
  1195. #        print "file", filename, opts
  1196.  
  1197.     def onRequires(self, product, version):
  1198.         """Callback for the 'requires' MEL command."""
  1199.         pass
  1200. #        print "requires",product, version
  1201.  
  1202.     def onFileInfo(self, keyword, value, opts):
  1203.         """Callback for the 'fileInfo' MEL command."""
  1204.         pass
  1205. #        print "fileInfo",keyword, value
  1206.  
  1207.     def onCurrentUnit(self, opts):
  1208.         """Callback for the 'currentUnit' MEL command."""
  1209.         pass
  1210. #        print "currentUnit",opts
  1211.  
  1212.     def onCreateNode(self, nodetype, opts):
  1213.         """Callback for the 'createNode' MEL command."""
  1214.         pass
  1215. #        print "createNode", nodetype, opts
  1216.  
  1217.     def onSetAttr(self, attr, vals, opts):
  1218.         """Callback for the 'setAttr' MEL command."""
  1219.         pass
  1220. #        print "setAttr %s = %s %s"%(attr, vals, opts)
  1221.  
  1222.     def onConnectAttr(self, srcattr, dstattr, opts):
  1223.         """Callback for the 'connectAttr' MEL command."""
  1224.         pass
  1225. #        print "connectAttr %s %s %s"%(srcattr, dstattr, opts)
  1226.  
  1227.     def onDisconnectAttr(self, srcattr, dstattr, opts):
  1228.         """Callback for the 'disconnectAttr' MEL command."""
  1229.         pass
  1230. #        print "disconnectAttr %s %s %s"%(srcattr, dstattr, opts)
  1231.  
  1232.     def onAddAttr(self, opts):
  1233.         """Callback for the 'addAttr' MEL command."""
  1234.         pass
  1235. #        print "addAttr", opts
  1236.  
  1237.     def onParent(self, objects, parent, opts):
  1238.         """Callback for the 'parent' MEL command."""
  1239.         pass
  1240. #        print "parent",objects,parent,opts
  1241.  
  1242.     def onSelect(self, objects, opts):
  1243.         """Callback for the 'select' MEL command."""
  1244.         pass
  1245. #        print "select",objects,opts
  1246.  
  1247.     def onLockNode(self, objects, opts):
  1248.         """Callback for the 'lockNode' MEL command.
  1249.         
  1250.         objects is a list of objects (which may be empty).
  1251.         """
  1252.         pass
  1253. #        print "lockNode",objects,opts
  1254.  
  1255.     # onCommand
  1256.     def onCommand(self, cmd, args):
  1257.         """Generic command callback.
  1258.  
  1259.         This callback invokes the "per command" callbacks.
  1260.         
  1261.         cmd is the MEL command name and args is a list of strings
  1262.         that are the arguments of the command. The arguments have
  1263.         been split into their individual tokens. Quotes around
  1264.         quoted tokens are still present.
  1265.         
  1266.         Example:
  1267.         
  1268.         The MEL command setAttr -k off ".v"; would be passed in
  1269.         as onCommand('setAttr', ['-k', 'off', '"v"'])
  1270.         """
  1271. #        print "**",cmd, args
  1272.         # setAttr
  1273.         if cmd=="setAttr":
  1274.             args, opts = self.getOpt(args,
  1275.                                      self.setAttr_opt_def,
  1276.                                      self.setAttr_name_dict)
  1277.             self.onSetAttr(args[0], args[1:], opts)
  1278.         # createNode
  1279.         elif cmd=="createNode":
  1280.             args, opts = self.getOpt(args,
  1281.                                      self.createNode_opt_def,
  1282.                                      self.createNode_name_dict)
  1283.             self.onCreateNode(args[0], opts)
  1284.         # connectAttr
  1285.         elif cmd=="connectAttr":
  1286.             args, opts = self.getOpt(args,
  1287.                                      self.connectAttr_opt_def,
  1288.                                      self.connectAttr_name_dict)
  1289.             self.onConnectAttr(args[0], args[1], opts)
  1290.         # disconnectAttr
  1291.         elif cmd=="disconnectAttr":
  1292.             args, opts = self.getOpt(args,
  1293.                                      self.disconnectAttr_opt_def,
  1294.                                      self.disconnectAttr_name_dict)
  1295.             self.onDisconnectAttr(args[0], args[1], opts)
  1296.         # addAttr
  1297.         elif cmd=="addAttr":
  1298.             args, opts = self.getOpt(args,
  1299.                                      self.addAttr_opt_def,
  1300.                                      self.addAttr_name_dict)
  1301.             self.onAddAttr(opts)
  1302.         # parent
  1303.         elif cmd=="parent":
  1304.             args, opts = self.getOpt(args,
  1305.                                      self.parent_opt_def,
  1306.                                      self.parent_name_dict)
  1307.             self.onParent(args[:-1], args[-1], opts)
  1308.         # select
  1309.         elif cmd=="select":
  1310.             args, opts = self.getOpt(args,
  1311.                                      self.select_opt_def,
  1312.                                      self.select_name_dict)
  1313.             self.onSelect(args, opts)
  1314.         # fileInfo
  1315.         elif cmd=="fileInfo":
  1316.             args, opts = self.getOpt(args,
  1317.                                      self.fileInfo_opt_def,
  1318.                                      self.fileInfo_name_dict)
  1319.             self.onFileInfo(args[0], args[1], opts)
  1320.         # currentUnit
  1321.         elif cmd=="currentUnit":
  1322.             args, opts = self.getOpt(args,
  1323.                                      self.currentUnit_opt_def,
  1324.                                      self.currentUnit_name_dict)
  1325.             self.onCurrentUnit(opts)
  1326.         # requires
  1327.         elif cmd=="requires":
  1328.             args, opts = self.getOpt(args, {}, {})
  1329.             self.onRequires(args[0], args[1])
  1330.         # file
  1331.         elif cmd=="file":
  1332.             args, opts = self.getOpt(args,
  1333.                                      self.file_opt_def,
  1334.                                      self.file_name_dict)
  1335.             self.onFile(args[0], opts)
  1336.         # lockNode
  1337.         elif cmd=="lockNode":
  1338.             args, opts = self.getOpt(args,
  1339.                                      self.lockNode_opt_def,
  1340.                                      self.lockNode_name_dict)
  1341.             self.onLockNode(args, opts)
  1342.         # unknown
  1343.         else:
  1344.             print >>sys.stderr, "WARNING: %s, line %d: Unknown MEL command: '%s'"%(self.filename, self.cmd_start_linenr, cmd)
  1345.  
  1346.  
  1347.     # getOpt
  1348.     def getOpt(self, arglist, opt_def, name_dict):
  1349.         """Separate arguments from options and preprocess options.
  1350.  
  1351.         arglist is a list of arguments (i.e. the 'command line').
  1352.         opt_def specifies the available options and their respective
  1353.         number of arguments. name_dict is a dictionary that is used
  1354.         to convert short names into long names.
  1355.  
  1356.         The return value is a 2-tuple (args, opts) where args is a list
  1357.         of arguments and opts is a dictionary containing the options.
  1358.         The key is the long name of the option (without leading dash)
  1359.         and the value is a list of values. Any existing quotes around
  1360.         a value is removed (only around the option values, not around
  1361.         the args!).
  1362.         """
  1363.  
  1364.         args = []
  1365.         opts = {}
  1366.         
  1367.         i=0
  1368.         while i<len(arglist):
  1369.             arg = arglist[i]
  1370.             i += 1
  1371.             try:
  1372.                 float(arg)
  1373.                 is_number = True
  1374.             except:
  1375.                 is_number = False
  1376.             # Option?
  1377.             a = stripQuotes(arg)
  1378.             if a[0:1]=="-" and not is_number:
  1379.                 # Convert short names into long names...
  1380.                 optname = name_dict.get(a[1:], a[1:])
  1381.                 # Check if the option is known
  1382.                 if optname not in opt_def:
  1383.                     raise SyntaxError, "Unknown option in line %d: %s"%(self.cmd_start_linenr, optname)
  1384.                 # Get the number of arguments
  1385.                 numargs, filter = opt_def[optname]
  1386.                 optvals = [stripQuotes(x) for x in arglist[i:i+numargs]]
  1387.                 # Did the same option already appear? So this is a multi-use flag.
  1388.                 # Then extend the current list with the new values
  1389.                 if optname in opts:
  1390.                     opts[optname].extend(optvals)
  1391.                 else:
  1392.                     opts[optname] = optvals
  1393.                 i += numargs
  1394.             else:
  1395.                 args.append(arg)
  1396.  
  1397.         return args, opts
  1398.  
  1399.     # processCommands
  1400.     def processCommands(self, s):
  1401.         """Process one or more commands.
  1402.  
  1403.         s is a string that contains one line of MEL code (may be several
  1404.         commands or only a partial command that is continued in the next
  1405.         line). This method splits the arguments and calls onCommand()
  1406.         for every command found.
  1407.         """
  1408.         # Split the command into tokens...
  1409.         a,n = self.splitCommand(s)
  1410.         if a!=[]:
  1411.             # Does a new command begin? then set the command name
  1412.             # and the args, otherwise just append to the existing args
  1413.             if self.new_cmd:
  1414.                 self.cmd = a[0]
  1415.                 self.args = a[1:]
  1416.                 # Store the line number where the command began
  1417.                 self.cmd_start_linenr = self.cpp.context.start_linenr
  1418.             else:
  1419.                 self.args += a
  1420.  
  1421.         if n==-1:
  1422.             # The command isn't finished yet
  1423.             if self.cmd!=None:
  1424.                 self.new_cmd = False
  1425.         else:
  1426.             # The command is finished, so execute it
  1427.             if self.cmd!=None:
  1428.                 # Store the line number where the command ended
  1429.                 self.cmd_end_linenr = self.cpp.context.linenr
  1430.                 self.onCommand(self.cmd, self.args)
  1431.             self.new_cmd = True
  1432.             self.cmd = None
  1433.             self.args = []
  1434.             self.processCommands(s[n+1:])
  1435.  
  1436.     # splitCommand
  1437.     def splitCommand(self, s):
  1438.         """Split a command into its arguments.
  1439.  
  1440.         This is an extended version of the string split() method. It
  1441.         splits (using whitespace as separator) but takes quoted strings
  1442.         and ';' into account.
  1443.         Returns a list of strings and the position of the ';'
  1444.         that terminated the first command (or -1).
  1445.         The quotes around strings are kept.
  1446.  
  1447.         'setAttr -k off ".v";' -> (['setAttr', '-k', 'off', '".v"'], 19)
  1448.         """
  1449.         # Search for a quoted string
  1450.         b,e = self.findString(s)
  1451.         # Search for the first semicolon (which might be the true command
  1452.         # separator or not)
  1453.         n = s.find(";")
  1454.         # Was a semicolon before the first string? Then the string belongs
  1455.         # to a subsequent command, so ignore it for now
  1456.         if n!=-1 and n<b:
  1457.             b = e = None
  1458.         # No string found?
  1459.         if b==None:
  1460.             if n==-1:
  1461.                 return s.split(), -1
  1462.             else:
  1463.                 return s[:n].split(), n
  1464.         else:
  1465.             if e==None:
  1466.                 return s[:b].split() + [s[b:]+'"'], -1
  1467.             else:
  1468.                 s2,n = self.splitCommand(s[e+1:])
  1469.                 if n!=-1:
  1470.                     n += e+1
  1471.                 return s[:b].split() + [s[b:e+1]] + s2, n
  1472.  
  1473.     # findString
  1474.     def findString(self, s):
  1475.         """Find the first string occurence.
  1476.  
  1477.         The return value is a 2-tuple (begin, end) with the indices
  1478.         of the opening and closing apostrophes (can also be None).
  1479.  
  1480.         'foo'             -> (None, None)
  1481.         'a="foo"'         -> (2, 6)
  1482.         'a="foo'          -> (2, None)
  1483.         'a="foo \" spam"' -> (2,14)
  1484.         """
  1485.         #'
  1486.         offset = 0
  1487.         while 1:
  1488.             # Search the beginning of a string
  1489.             n1 = s.find('"', offset)
  1490.             if n1==-1:
  1491.                 return None,None
  1492.             # Search the end of the string (ignore quoted apostrophes)...
  1493.             start = n1+1
  1494.             n2 = None
  1495.             while 1:
  1496.                 n2 = s.find('"', start)
  1497.                 if n2==-1:
  1498.                     return n1,None
  1499.                 elif s[n2-1]!='\\':
  1500.                     return n1,n2
  1501.                 else:
  1502.                     start = n2+1
  1503.  
  1504. # DefaultMAReader
  1505. class DefaultMAReader(MAReader):
  1506.     """Default MA reader implementation.
  1507.  
  1508.     This class creates Node objects, sets attributes and does the
  1509.     connections so that after the file is read the entire dependency
  1510.     graph is available.
  1511.  
  1512.     A derived class only has to implement the end() callback and
  1513.     process the graph as desired. All created Node objects are available
  1514.     in the attribute self.nodelist.
  1515.     """
  1516.  
  1517.     def read(self, f):
  1518.         # A dict with imported Node objects
  1519.         # Key: Node name (without path) / Value: Node object
  1520.         # If the node name is not unique anymore, the value contains None.
  1521.         self.nodes = {}
  1522.         
  1523.         # A list with all Node objects (in the same order as they were
  1524.         # encountered in the file)
  1525.         self.nodelist = []
  1526.         
  1527.         # The currently active Node object
  1528.         # (changes with every createNode or select command)
  1529.         self.currentnode = None
  1530.  
  1531.         MAReader.read(self, f)
  1532.  
  1533.     # onCreateNode
  1534.     def onCreateNode(self, nodetype, opts):
  1535.         """Create a new node and make it current.
  1536.         """
  1537.         # Remove all quotes...
  1538.         nodetype = stripQuotes(nodetype)
  1539. #        for name in opts:
  1540. #            opts[name] = map(lambda x: stripQuotes(x), opts[name])
  1541.             
  1542.         node = self.createNode(nodetype, opts)
  1543.         self.currentnode = node
  1544.  
  1545.     # onSelect
  1546.     def onSelect(self, objects, opts):
  1547.         """Make another node current."""
  1548.         # Remove all quotes...
  1549.         objects = map(lambda x: stripQuotes(x), objects)
  1550.         
  1551.         if opts!={"noExpand":[]}:
  1552.             raise ValueError, "%s, %d: The select command contains unsupported options."%(self.filename, self.linenr)
  1553.  
  1554.         if len(objects)==0:
  1555.             raise ValueError, "%s, %d: The select command contains no object name."%(self.filename, self.linenr)
  1556.         if len(objects)!=1:
  1557.             raise ValueError, "%s, %d: The select command contains more than one object."%(self.filename, self.linenr)
  1558.  
  1559.         self.currentnode = self.findNode(objects[0], create=True)
  1560.  
  1561.     # onSetAttr
  1562.     def onSetAttr(self, attr, vals, opts):
  1563.         """Set an attribute."""
  1564.         if self.currentnode==None:
  1565.             return
  1566.  
  1567.         # Remove the quotes...
  1568.         attr = stripQuotes(attr)
  1569. #        for name in opts:
  1570. #            opts[name] = map(lambda x: stripQuotes(x), opts[name])
  1571.  
  1572.         if attr[0]!=".":
  1573.             print >>sys.stderr, "mayaascii: Warning: DefaultMAReader.onSetAttr(): The attribute refers to a different object than the current object. This is not yet supported."
  1574.  
  1575.         self.currentnode.setAttr(attr, vals, opts)
  1576.  
  1577.     # onAddAttr
  1578.     def onAddAttr(self, opts):
  1579.         """Add a dynamic attribute."""
  1580.         if self.currentnode==None:
  1581.             return
  1582.  
  1583.         # Remove the quotes...
  1584. #        for name in opts:
  1585. #            opts[name] = map(lambda x: stripQuotes(x), opts[name])
  1586.  
  1587.         self.currentnode.addAttr(opts)
  1588.  
  1589.     # onConnectAttr
  1590.     def onConnectAttr(self, srcattr, dstattr, opts):
  1591.         """Make a connection.
  1592.         """
  1593.  
  1594.         # Remove the quotes...
  1595.         srcattr = stripQuotes(srcattr)
  1596.         dstattr = stripQuotes(dstattr)
  1597. #        for name in opts:
  1598. #            opts[name] = map(lambda x: stripQuotes(x), opts[name])
  1599.  
  1600.         # Split into object name and attribute name
  1601.         a = srcattr.split(".")
  1602.         snode = a[0]
  1603.         sattr = a[1]
  1604.         b = dstattr.split(".")
  1605.         dnode = b[0]
  1606.         dattr = b[1]
  1607.         
  1608.         sn = self.findNode(snode, create=True)
  1609.         dn = self.findNode(dnode, create=True)
  1610.         if sn!=None:
  1611.             if dn==None:
  1612.                 print >>sys.stderr, 'WARNING: %s, %d: connectAttr "%s" "%s"'%(self.filename, self.linenr, srcattr, dstattr)
  1613.                 print >>sys.stderr, ' Node "%s" not found. The connection is ignored.'%dnode
  1614.             else:
  1615.                 sn.addOutConnection(sattr, dn, dnode, dattr)
  1616.         if dn!=None:
  1617.             dn.addInConnection(dattr, snode, sattr)
  1618.  
  1619.  
  1620.     # findNode
  1621.     def findNode(self, path, create=False):
  1622.         """Return the Node object corresponding to a particular path.
  1623.  
  1624.         path may also be None in which case None is returned.
  1625.         If create is True, any missing nodes are automatically created.
  1626.  
  1627.         (this method doesn't handle namespaces yet)
  1628.         """
  1629.         if path==None:
  1630.             return None
  1631.         
  1632.         namespace,names = splitDAGPath(path)
  1633.         # The current node (and eventually the result)
  1634.         node = None
  1635.         # Iterate over all names from 'top' to 'bottom'...
  1636.         for name in names:
  1637.             # An empty name? Then start from the beginning
  1638.             if name=="":
  1639.                 node = None
  1640.             else:
  1641.                 if node==None:
  1642.                     node = self.nodes.get(name)
  1643.                     if node==None:
  1644.                         if create:
  1645.                             node = self.createNode("<unknown>", {"name":[name]})
  1646.                         else:
  1647.                             raise KeyError, "Node %s not found (%s is missing)"%(path, name)
  1648.                 else:
  1649.                     for cn in node.iterChildren():
  1650.                         if name==cn.getName():
  1651.                             node = cn
  1652.                             break
  1653.                     else:
  1654.                         if create:
  1655.                             node = self.createNode("<unknown>", {"name":[name], "parent":[node.getFullName()]})
  1656.                         else:
  1657.                             raise KeyError, "Node %s not found (%s is missing)"%(path, name)
  1658.         return node
  1659.  
  1660.     # createNode
  1661.     def createNode(self, nodetype, opts):
  1662.         """Create a new node and return it.
  1663.         """
  1664.         parentname = opts.get("parent", [None])[0]
  1665.         parent = self.findNode(parentname)
  1666.         node = Node(nodetype, opts, parent=parent)
  1667.         # The constant default name (if there was no name set in the file)
  1668.         # will override a previous node without name. But this doesn't
  1669.         # matter as the node cannot be addressed by name in the file anyway.
  1670.         nodename = node.getName()
  1671.         self.nodes[nodename] = node
  1672.         self.nodelist.append(node)
  1673.         return node
  1674.         
  1675.  
  1676.  
  1677. ######################################################################
  1678.  
  1679. if __name__=="__main__":
  1680.  
  1681.     class TestReader(DefaultMAReader):
  1682.  
  1683.         def end(self):
  1684.             for node in self.nodelist:
  1685.                 print '%-30s %-20s parent:%s'%('"'+node.getFullName()+'"', node.nodetype, node.getParentName())
  1686.                 for attrname in node._setattr:
  1687.                     try:
  1688.                         val = node.getAttrValue(attrname, attrname, None, None)
  1689.                     except:
  1690.                         val = "<error retrieving value, need more type information>"
  1691.                     val = str(val)
  1692.                     if len(val)>60:
  1693.                         val = val[:60]+"..."
  1694.                     print "  %s = %s"%(attrname, val)
  1695.                 
  1696.  
  1697.  
  1698.     maname = sys.argv[1]
  1699.     rd = TestReader()
  1700.     rd.read(maname)
  1701.