scripted/pyJsonAttrPatternFactory.py

scripted/pyJsonAttrPatternFactory.py
1 """
2 To use, make sure that pyJsonAttrPatternFactory.py is in your MAYA_PLUG_IN_PATH
3 then do the following:
4 
5 import maya
6 maya.cmds.loadPlugin("pyJsonAttrPatternFactory.py")
7 maya.cmds.listAttrPatterns(patternType=True)
8 // Return: ["json"]
9 """
10 import os
11 import sys
12 import json
13 import traceback
14 import maya.api.OpenMaya as omAPI
15 JsonKeys = None
16 jsonDebug = None
17 
18 #======================================================================
19 def import_helpers():
20  """
21  Equivalent to these import statements but presuming installation into
22  a utils/ subdirectory of the current directory that's not in the current
23  PYTHONPATH.
24 
25  from pyJsonAttrPatternInfo import PyJsonAttrPatternInfo as JsonKeys
26  from pyJsonAttrPatternInfo import jsonDebug as jsonDebug
27 
28  The method is set up to still work even if the module path was already
29  visible in the PYTHONPATH so there's no harm in setting it.
30 
31  Calling this twice will force re-import of the modules; that's a good
32  thing for testing since you can unload the plug-in, load it back in and
33  it will pick up the modified modules.
34  """
35  global JsonKeys
36  global jsonDebug
37 
38  # Find the subdirectory of the current directory, that's where modules
39  # live. Had to do it this way to prevent modules from being misinterpreted
40  # to be plug-ins themselves.
41  # The executable is in ..../runTime/bin
42  # The modules are in ..../runTime/devkit/plug-ins/scripted/modules
43  #
44  location = os.environ['MAYA_LOCATION']
45  moduleDir = os.path.join( location, 'devkit', 'plug-ins', 'scripted', 'modules' )
46  sys.path.append(moduleDir)
47 
48  # Load the module information, now visible using the updated path.
49  module = __import__('pyJsonAttrPatternInfo', globals(), locals(), ["PyJsonAttrPatternInfo", "jsonDebug"], -1)
50  reload(module) # Might be out of date
51 
52  # Name the interesting module elements
53  JsonKeys = module.PyJsonAttrPatternInfo
54  jsonDebug = module.jsonDebug
55 
56  # Remove the temporary path entry
57  del sys.path[-1]
58 
59 #======================================================================
60 def maya_useNewAPI():
61  """
62  The presence of this function tells Maya that the plugin produces, and
63  expects to be passed, objects created using the Maya Python API 2.0.
64  """
65  pass
66 
67 #======================================================================
68 class PyJsonAttrPatternFactory(omAPI.MPxAttributePatternFactory):
69  #----------------------------------------------------------------------
70  def __init__(self):
71  omAPI.MPxAttributePatternFactory.__init__(self)
72  self.currentPattern = None
73  self.currentAttribute = None
74 
75  #----------------------------------------------------------------------
76  @staticmethod
77  def patternFactoryCreator():
78  return PyJsonAttrPatternFactory()
79 
80  #----------------------------------------------------------------------
81  def reportWarning(self, warningStr):
82  """
83  Report a pattern parsing problem but do not raise a failure exception.
84  This is for harmless errors that may or may not indicate a problem
85  (e.g. a misspelled flag name)
86  """
87  print 'WARN: Pattern %s, attribute %s: %s' % (self.currentPattern, self.currentAttribute, warningStr)
88 
89  #----------------------------------------------------------------------
90  def reportError(self, errorStr):
91  """
92  Report a pattern parsing error and raise a failure exception.
93  Unfortunately there doesn't seem to be any way to report the actual
94  line of the definition that failed (when it could be known) so
95  the pattern name and attribute name will have to suffice to narrow
96  down the source of the error.
97  """
98  traceback.print_stack()
99  raise ValueError( 'ERR: Pattern %s, attribute %s: %s' % (self.currentPattern, self.currentAttribute, errorStr) )
100 
101  #----------------------------------------------------------------------
102  def parseStandardFlags(self, attr, flags):
103  """
104  Apply the JSON-described flags to the given attributes. Only check the
105  flags understood by all attribute types; let specific types handle
106  their own particular flags.
107  attr = Created attribute object
108  flags = Array of flags to set
109  """
110  jsonDebug( 'Parsing %d standard flags for "%s"' % (len(flags), str(attr)) )
111  for flag in flags:
112  valueToSet = True
113  strippedFlag = flag.strip().rstrip()
114  # If the flag starts with "!" then unset it rather than setting it
115  if strippedFlag.startswith('!'):
116  strippedFlag = strippedFlag[1:]
117  valueToSet = False
118  # Unrecognized flags will be silently ignored. They might be
119  # relevant to derived attribute types.
120  if strippedFlag.lower() in [x.lower() for x in JsonKeys.kFlagFunctions]:
121  try:
122  jsonDebug( '--- Set flag %s' % strippedFlag )
123  jsonDebug( '--- Flag function = %s, value = %s' % ( JsonKeys.kFlagFunctions[strippedFlag.lower()], valueToSet ) )
124  setattr( attr, JsonKeys.kFlagFunctions[strippedFlag.lower()], valueToSet )
125  except Exception, e:
126  self.reportError( 'Failed setting flag %s on attribute %s : %s"' % (strippedFlag, attr.name, str(e)) )
127  else:
128  self.reportWarning( 'Unrecognized attribute flag "%s" is ignored' % strippedFlag )
129 
130  #----------------------------------------------------------------------
131  def parseCompoundAttribute(self, name, shortName, attrInfo):
132  """
133  Given a JSON subsection describing a compound attribute create the
134  attribute and all children, and set all of the provided flags/members
135  for it.
136  name = Attribute long name
137  shortName = Attribute short name
138  attrInfo = JSON object containing the main attribute information
139  """
140  jsonDebug( 'parseCompoundAttribute(%s : %s)' % (name, attrInfo) )
141  attr = None
142  try:
143  cAttr = omAPI.MFnCompoundAttribute()
144  attr = cAttr.create( name, shortName )
145 
146  # Recursively create all children, and children of children, etc.
147  if JsonKeys.kKeyChildren in attrInfo:
148  childInfo = attrInfo[JsonKeys.kKeyChildren]
149  tmpAttr = self.currentAttribute
150  for child in childInfo:
151  jsonDebug( 'Add compound child %s' % child )
152  childAttr = self.parseAttribute( child )
153  cAttr.addChild( childAttr )
154  self.currentAttribute = tmpAttr
155  except Exception, e:
156  self.reportError( 'Error creating compound: %s' % str(e) )
157  return attr
158 
159  #----------------------------------------------------------------------
160  def parseEnumAttribute(self, name, shortName, attrInfo):
161  """
162  Given a JSON subsection describing an enum attribute create the
163  attribute and set all of the provided flags/members for it.
164  name = Attribute long name
165  shortName = Attribute short name
166  attrInfo = JSON object containing the main attribute information
167  """
168  jsonDebug( 'parseEnumAttribute(%s)' % name )
169  eAttr = omAPI.MFnEnumAttribute()
170  attr = eAttr.create( name, shortName )
171 
172  # Look for any fields being specified. (If no field names then the
173  # attribute can only accept integer values.)
174  if JsonKeys.kKeyEnumNames in attrInfo:
175  enumIndex = 0
176  try:
177  for enumName in attrInfo[JsonKeys.kKeyEnumNames]:
178  equalSign = enumName.find('=')
179  if equalSign >= 0:
180  enumIndex = int(enumName[equalSign+1:])
181  enumName = enumName[:equalSign]
182  eAttr.addField( enumName, enumIndex )
183  enumIndex += 1
184  except Exception, e:
185  self.reportError( 'Bad enum specification: "%s"' % str(e) )
186 
187  # Set default after creation so that we can handle both enum names and
188  # index values as a default
189  if JsonKeys.kKeyDefault in attrInfo:
190  defaultValue = attrInfo[JsonKeys.kKeyDefault]
191  jsonDebug( 'Setting the enum default to "%s" of type %s' % (defaultValue, type(defaultValue)) )
192  if type(defaultValue) == int or str(defaultValue).isdigit():
193  eAttr.default = defaultValue
194  else:
195  eAttr.setDefaultByName( defaultValue )
196 
197  return attr
198 
199  #----------------------------------------------------------------------
200  def parseTypedAttribute(self, name, shortName, attrInfo):
201  """
202  Given a JSON subsection describing a typed attribute create the
203  attribute and set all of the provided flags/members for it.
204  name = Attribute long name
205  shortName = Attribute short name
206  attrInfo = JSON object containing the main attribute information
207  """
208  jsonDebug( 'parseTypedAttribute(%s)' % name )
209 
210  # First convert the list of accepted types into the equivalent Maya
211  # enum type values
212  acceptedTypeEnums = []
213  acceptedNumericEnums = []
214  acceptedPluginTypes = []
215  hasNumeric = False
216 
217  # Plugin data types are identified by name at runtime, they take
218  # precedence.
219  if JsonKeys.kKeyAcceptedPluginTypes in attrInfo:
220  jsonDebug( '...getting accepted plugin types %s' % attrInfo[JsonKeys.kKeyAcceptedPluginTypes] )
221  for pluginId in attrInfo[JsonKeys.kKeyAcceptedPluginTypes]:
222  pId = omAPI.MTypeId()
223  acceptedPluginTypes.append( pId.create( int(pluginId) ) )
224  if JsonKeys.kKeyAcceptedTypes in attrInfo:
225  if 'any' in attrInfo[JsonKeys.kKeyAcceptedTypes]:
226  acceptedTypeEnums.append( omAPI.MFnData.kAny )
227  else:
228  for typeName in attrInfo[JsonKeys.kKeyAcceptedTypes]:
229  if typeName == 'numeric':
230  hasNumeric = True
231  acceptedTypeEnums.append( JsonKeys.kGenericTypes[typeName] )
232  elif typeName in JsonKeys.kGenericTypes:
233  jsonDebug( '...getting accepted generic %s' % JsonKeys.kGenericTypes[typeName] )
234  acceptedTypeEnums.append( JsonKeys.kGenericTypes[typeName] )
235  else:
236  self.reportError( 'Bad type name specification: "%s"' % str(typeName) )
237  if JsonKeys.kKeyAcceptedNumericTypes in attrInfo:
238  for typeName in attrInfo[JsonKeys.kKeyAcceptedNumericTypes]:
239  if typeName in JsonKeys.kNumericTypes:
240  jsonDebug( '...getting accepted numeric %s' % JsonKeys.kNumericTypes[typeName] )
241  acceptedNumericEnums.append( JsonKeys.kNumericTypes[typeName] )
242  else:
243  self.reportError( 'Bad numeric type name specification: "%s"' % str(typeName) )
244 
245  # Numeric types have to be generic, it's just how the attributes are
246  if len(acceptedTypeEnums) == 0 and len(acceptedNumericEnums) == 0 and len(acceptedPluginTypes) == 0:
247  self.reportError( 'Need at least one accepted type' )
248  # Only one data type means it can be an MFnTypedAttribute, except for
249  # numeric type which for some reason is not supported in the API
250  elif len(acceptedTypeEnums) == 1 and len(acceptedNumericEnums) == 0 and len(acceptedPluginTypes) == 0 and not hasNumeric:
251  jsonDebug( '--- Accepts only one type : %s' % acceptedTypeEnums[0] )
252  tAttr = omAPI.MFnTypedAttribute()
253  attr = tAttr.create( name, shortName, acceptedTypeEnums[0] )
254  jsonDebug( '--- created' )
255  # One plugin type has a special MFnTypedAttribute constructor
256  elif len(acceptedTypeEnums) == 0 and len(acceptedNumericEnums) == 0 and len(acceptedPluginTypes) == 1:
257  jsonDebug( '--- Accepts only one plugin : %s' % acceptedPluginTypes[0] )
258  tAttr = omAPI.MFnTypedAttribute()
259  attr = tAttr.create( name, shortName, acceptedPluginTypes[0] )
260  jsonDebug( '--- created' )
261  # Every other combination forces a generic attribute
262  else:
263  jsonDebug( '--- Accepts multiple or base numeric types' )
264  tAttr = omAPI.MFnGenericAttribute()
265  attr = tAttr.create( name, shortName )
266  for typeEnum in acceptedTypeEnums:
267  jsonDebug( '--> add data type %s' % typeEnum )
268  tAttr.addDataType( typeEnum )
269  for numericEnum in acceptedNumericEnums:
270  jsonDebug( '--> add numeric type %s' % numericEnum )
271  tAttr.addNumericType( numericEnum )
272  for pluginId in acceptedPluginTypes:
273  jsonDebug( '--> add plugin type %s' % pluginId )
274  tAttr.addTypeId( pluginId )
275  jsonDebug( '--- created' )
276 
277  return attr
278 
279  #----------------------------------------------------------------------
280  def parseLightDataAttribute(self, name, shortName, attrInfo):
281  """
282  Given a JSON subsection describing a light data attribute create the
283  attribute and set all of the provided flags/members for it.
284  name = Attribute long name
285  shortName = Attribute short name
286  attrInfo = JSON object containing the main attribute information
287  """
288  # List of all child attributes with their numeric type and default values
289  cNames = [ 'direction', 'intensity', 'ambient', 'diffuse', 'specular',
290  'shadowFraction', 'preShadowIntensity', 'blindData' ]
291  lightChildren = { cNames[0] : (omAPI.MFnNumericData.k3Float, [0.0,0.0,0.0]),
292  cNames[1] : (omAPI.MFnNumericData.k3Float, [0.0,0.0,0.0]),
293  cNames[2] : (omAPI.MFnNumericData.kBoolean, 0),
294  cNames[3] : (omAPI.MFnNumericData.kBoolean, 0),
295  cNames[4] : (omAPI.MFnNumericData.kBoolean, 0),
296  cNames[5] : (omAPI.MFnNumericData.kFloat, 0.0),
297  cNames[6] : (omAPI.MFnNumericData.kFloat, 0.0),
298  cNames[7] : (omAPI.MFnNumericData.kAddr, 0) }
299 
300  jsonDebug( 'parseLightDataAttribute(%s)' % name )
301  ldAttr = omAPI.MFnLightDataAttribute()
302 
303  missingNames = []
304  ldChildren = []
305  defaultValues = []
306  for child in cNames:
307  try:
308  jsonDebug( 'Creating light data child %s' % child )
309  childInfo = attrInfo[child]
310  cName = childInfo[JsonKeys.kKeyName]
311  jsonDebug( '--- child name %s' % cName )
312  if JsonKeys.kKeyShortName in childInfo:
313  cShortName = childInfo[JsonKeys.kKeyShortName]
314  else:
315  cShortName = cName
316  jsonDebug( '--- child short name %s' % cShortName )
317  if JsonKeys.kKeyDefault in childInfo and child != 'blindData':
318  jsonDebug( '--- Defining a default' )
319  defaultValues.append( childInfo[JsonKeys.kKeyDefault] )
320  else:
321  jsonDebug( '--- Accepting default 0' )
322  defaultValues.append( lightChildren[child][1] )
323  jsonDebug( '--- child default %s' % defaultValues[-1] )
324 
325  nAttr = omAPI.MFnNumericAttribute()
326  jsonDebug( '--- created numeric type %s' % lightChildren[child][0] )
327  ldChildren.append( nAttr.create( cName, cShortName, lightChildren[child][0] ) )
328  except Exception, e:
329  jsonDebug( 'Missing data for sub-attribute %s : %s' % (child, str(e)) )
330  missingNames.append( child )
331 
332  if len(missingNames) > 0:
333  self.reportError( 'Not all required subattribute names are present. Add %s' % str(missingNames) )
334 
335  # Now make the master attribute
336  jsonDebug( 'Creating master light data attribute' )
337  attr = ldAttr.create( name, shortName,
338  ldChildren[0], ldChildren[1], ldChildren[2],
339  ldChildren[3], ldChildren[4], ldChildren[5],
340  ldChildren[6], ldChildren[7] )
341  jsonDebug( 'Setting master light data defaults' )
342  ldAttr.default = defaultValues
343 
344  return attr
345 
346  #----------------------------------------------------------------------
347  def parseMessageAttribute(self, name, shortName, attrInfo):
348  """
349  Given a JSON subsection describing a message attribute create the
350  attribute and set all of the provided flags/members for it.
351  name = Attribute long name
352  shortName = Attribute short name
353  attrInfo = JSON object containing the main attribute information
354  """
355  jsonDebug( 'parseMessageAttribute(%s)' % name )
356  mAttr = omAPI.MFnMessageAttribute()
357 
358  jsonDebug( 'Creating message attribute' )
359  attr = mAttr.create( name, shortName )
360 
361  return attr
362 
363  #----------------------------------------------------------------------
364  def parseStringAttribute(self, name, shortName, attrInfo):
365  """
366  Given a JSON subsection describing a string attribute create the
367  attribute and set all of the provided flags/members for it.
368  name = Attribute long name
369  shortName = Attribute short name
370  attrInfo = JSON object containing the main attribute information
371  """
372  jsonDebug( 'parseStringAttribute(%s)' % name )
373  sAttr = omAPI.MFnTypedAttribute()
374 
375  if JsonKeys.kKeyDefault in attrInfo:
376  jsonDebug( 'Setting the string default to "%s"' % attrInfo[JsonKeys.kKeyDefault] )
377  sDefault = omAPI.MFnStringData()
378  defaultValue = sDefault.create( attrInfo[JsonKeys.kKeyDefault] )
379  attr = sAttr.create( name, shortName, omAPI.MFnData.kString, defaultValue )
380  else:
381  jsonDebug( 'Creating string attribute with no default' )
382  attr = sAttr.create( name, shortName, omAPI.MFnData.kString )
383 
384  return attr
385 
386  #----------------------------------------------------------------------
387  def parseMatrixAttribute(self, name, shortName, attrInfo):
388  """
389  Given a JSON subsection describing a matrix attribute create the
390  attribute and set all of the provided flags/members for it.
391  name = Attribute long name
392  shortName = Attribute short name
393  attrInfo = JSON object containing the main attribute information
394  """
395  jsonDebug( 'parseMatrixAttribute(%s)' % name )
396 
397  matrixType = JsonKeys.kTypeMatrixTypes[attrInfo[JsonKeys.kKeyAttrType]]
398  if JsonKeys.kKeyDefault in attrInfo:
399  jsonDebug( 'Setting the matrix default to "%s"' % attrInfo[JsonKeys.kKeyDefault] )
400  mDefault = omAPI.MFnMatrixData()
401  defaultValue = mDefault.create( omAPI.MMatrix(attrInfo[JsonKeys.kKeyDefault]) )
402  mAttr = omAPI.MFnMatrixAttribute( defaultValue )
403  attr = mAttr.create( name, shortName, matrixType )
404  else:
405  jsonDebug( 'Creating matrix attribute with no default' )
406  mAttr = omAPI.MFnMatrixAttribute()
407  attr = mAttr.create( name, shortName, matrixType )
408 
409  return attr
410 
411  #----------------------------------------------------------------------
412  def parseNumericAttribute(self, name, shortName, numericType, attrInfo):
413  """
414  Given a JSON subsection describing a numeric attribute create the
415  attribute and set all of the provided flags/members for it.
416  name = Attribute long name
417  shortName = Attribute short name
418  type = Numeric type
419  attrInfo = JSON object containing the main attribute information
420  """
421  jsonDebug( 'parseNumericAttribute(%s, type=%s)' % (name, type) )
422 
423  if numericType in [ 'angle', 'distance', 'time' ]:
424  jsonDebug( '... unit attribute type being set up' )
425  nAttr = omAPI.MFnUnitAttribute()
426  else:
427  jsonDebug( '... regular numeric attribute type being set up' )
428  nAttr = omAPI.MFnNumericAttribute()
429 
430  jsonDebug( 'Creating numeric attribute' )
431  attr = nAttr.create( name, shortName, JsonKeys.kNumericTypes[numericType] )
432  jsonDebug( '...creation succeeded' )
433  if JsonKeys.kKeyDefault in attrInfo:
434  defaultValue = attrInfo[JsonKeys.kKeyDefault]
435  jsonDebug( '...setting numeric default to %s - it is a %s' % (str(defaultValue), type(defaultValue)) )
436  if type(defaultValue) == list:
437  # Internally the array numerics insist on tuples for defaults
438  jsonDebug( '...converting to tuple %s' % str(tuple(defaultValue)) )
439  nAttr.default = tuple(defaultValue)
440  else:
441  nAttr.default = defaultValue
442 
443  jsonDebug( 'Setting range information on attribute' )
444 
445  # Parse the numeric-specific attributes
446  if JsonKeys.kKeyMin in attrInfo:
447  jsonDebug( '...setting minimum' )
448  if type(attrInfo[JsonKeys.kKeyMin]) == list:
449  # Internally the array numerics insist on tuples for values
450  # but in the JSON it makes more sense to have them as a list
451  jsonDebug( '...converting list %s to tuple' % attrInfo[JsonKeys.kKeyMin] )
452  nAttr.setMin( tuple(attrInfo[JsonKeys.kKeyMin]) )
453  else:
454  jsonDebug( '...using %s as-is' % attrInfo[JsonKeys.kKeyMin] )
455  nAttr.setMin( attrInfo[JsonKeys.kKeyMin] )
456  #
457  if JsonKeys.kKeyMax in attrInfo:
458  jsonDebug( '...setting maximum' )
459  if type(attrInfo[JsonKeys.kKeyMax]) == list:
460  # Internally the array numerics insist on tuples for values
461  # but in the JSON it makes more sense to have them as a list
462  jsonDebug( '...converting list %s to tuple' % attrInfo[JsonKeys.kKeyMax] )
463  nAttr.setMax( tuple(attrInfo[JsonKeys.kKeyMax]) )
464  else:
465  jsonDebug( '...using %s as-is' % attrInfo[JsonKeys.kKeyMax] )
466  nAttr.setMax( attrInfo[JsonKeys.kKeyMax] )
467  #
468  if JsonKeys.kKeySoftMin in attrInfo:
469  jsonDebug( '...setting soft minimum to %s' % attrInfo[JsonKeys.kKeySoftMin] )
470  nAttr.setSoftMin( attrInfo[JsonKeys.kKeySoftMin] )
471  #
472  if JsonKeys.kKeySoftMax in attrInfo:
473  jsonDebug( '...setting soft maximum to %s' % attrInfo[JsonKeys.kKeySoftMax] )
474  nAttr.setSoftMax( attrInfo[JsonKeys.kKeySoftMax] )
475 
476  jsonDebug( 'Numeric attribute creation of "%s" complete' % attr )
477  return attr
478 
479  #----------------------------------------------------------------------
480  def parseAttribute(self, jsonInfo):
481  """
482  Create an attribute using the JSON parameters to decode the structure
483  and values for the attribute. If the attribute is a compound then this
484  method will be recursively called so as to create the entire attribute
485  tree below it.
486  jsonInfo = JSON object containing the attribute's information
487  Returns the newly created attribute.
488  """
489  if JsonKeys.kKeyName not in jsonInfo:
490  self.reportError( 'Missing attribute name' )
491  self.currentAttribute = jsonInfo[JsonKeys.kKeyName]
492  jsonDebug( 'parseAttribute(%s)' % str(jsonInfo) )
493  attr = None
494 
495  # Short name must always be present so find or generate one now
496  if JsonKeys.kKeyShortName in jsonInfo:
497  shortName = jsonInfo[JsonKeys.kKeyShortName]
498  else:
499  shortName = self.currentAttribute
500  jsonDebug( '...got short name %s' % shortName )
501 
502  #----------------------------------------
503  # Create the specific type of attribute requested and handle the
504  # type-specific parameters.
505  #
506  if JsonKeys.kKeyAttrType not in jsonInfo:
507  self.reportError('Required keyword "%s" missing' % JsonKeys.kKeyAttrType)
508  elif jsonInfo[JsonKeys.kKeyAttrType] in JsonKeys.kNumericTypes:
509  attr = self.parseNumericAttribute( self.currentAttribute, shortName, jsonInfo[JsonKeys.kKeyAttrType], jsonInfo )
510  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeCompound:
511  attr = self.parseCompoundAttribute( self.currentAttribute, shortName, jsonInfo )
512  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeEnum:
513  attr = self.parseEnumAttribute( self.currentAttribute, shortName, jsonInfo )
514  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeString:
515  attr = self.parseStringAttribute( self.currentAttribute, shortName, jsonInfo )
516  elif jsonInfo[JsonKeys.kKeyAttrType] in JsonKeys.kTypeMatrix:
517  attr = self.parseMatrixAttribute( self.currentAttribute, shortName, jsonInfo )
518  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeTyped:
519  attr = self.parseTypedAttribute( self.currentAttribute, shortName, jsonInfo )
520  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeLightData:
521  attr = self.parseLightDataAttribute( self.currentAttribute, shortName, jsonInfo )
522  elif jsonInfo[JsonKeys.kKeyAttrType] == JsonKeys.kTypeMessage:
523  attr = self.parseMessageAttribute( self.currentAttribute, shortName, jsonInfo )
524  else:
525  self.reportError( 'Unknown attribute type "%s"' % jsonInfo[JsonKeys.kKeyAttrType] )
526  return None
527 
528  jsonDebug( 'Done creating attribute "%s", now setting shared parameters' % str(attr) )
529 
530  #----------------------------------------
531  # Handle the parameters common to all attribute types
532  #
533  aBase = omAPI.MFnAttribute( attr )
534  jsonDebug( '...handling common attribute flags for "%s"' % str(aBase) )
535 
536  # Handle the standard flags
537  if JsonKeys.kKeyFlags in jsonInfo:
538  self.parseStandardFlags( aBase, jsonInfo[JsonKeys.kKeyFlags] )
539 
540  # Look for a nice name override
541  if JsonKeys.kKeyNiceName in jsonInfo:
542  jsonDebug( '...Overriding nice name with "%s"' % jsonInfo[JsonKeys.kKeyNiceName] )
543  aBase.setNiceNameOverride( jsonInfo[JsonKeys.kKeyNiceName] )
544 
545  # See if the attribute has been added to any categories
546  if JsonKeys.kKeyCategories in jsonInfo:
547  for category in jsonInfo[JsonKeys.kKeyCategories]:
548  jsonDebug( '...Adding category "%s"' % category )
549  aBase.addToCategory( category )
550  jsonDebug( '...Done on category "%s"' % category )
551  jsonDebug( '...Done the categories' )
552 
553  # See if there is any special disconnection behaviour
554  if JsonKeys.kKeyDisconnect in jsonInfo:
555  behavior = jsonInfo[JsonKeys.kKeyDisconnect]
556  jsonDebug( '...Setting disconnect behaviour to "%s"' % behavior )
557  if behavior in JsonKeys.kDisconnectBehaviors:
558  aBase.disconnectBehavior = JsonKeys.kDisconnectBehaviors[behavior]
559  else:
560  self.reportError( 'Unknown behavior type "%s"' % behavior )
561 
562  return attr
563 
564  #----------------------------------------------------------------------
565  def parseJsonPatterns(self, jsonObj):
566  """
567  The workhorse method. Takes the JSON Python object and deconstructs it
568  into one or more pattern descriptions and returns them.
569 
570  If any of the patterns fail to create an exception is raised.
571  ValueError : When the JSON text is not valid
572  ValueError : When the pattern name already exists
573  """
574  patternList = []
575  try:
576  jsonDebug( 'parseJsonPatterns(%d patterns)' % len(jsonObj) )
577  for thisPattern in jsonObj:
578  jsonDebug( '...Pattern is %s' % thisPattern )
579  if JsonKeys.kKeyName not in thisPattern:
580  self.reportError( 'Missing pattern name' )
581  continue
582  self.currentPattern = thisPattern[JsonKeys.kKeyName]
583  newPattern = omAPI.MAttributePattern( self.currentPattern )
584  jsonDebug( '...Pattern %s has %d attributes' % (self.currentPattern, len(thisPattern["attributes"])) )
585  if "attributes" not in thisPattern:
586  self.reportError( 'Empty attribute list' )
587  continue
588  for thisAttribute in thisPattern["attributes"]:
589  jsonDebug('Started parsing attribute "%s"' % str(thisAttribute))
590  attr = self.parseAttribute( thisAttribute )
591  jsonDebug('Completed parsing of attribute "%s"' % str(attr))
592  # If the attribute creation succeeded add it to the pattern.
593  # If it failed the creation code will have already reported
594  # the problem with the attribute's description.
595  newPattern.addRootAttr(attr)
596  jsonDebug( 'Done adding the attribute to the pattern' )
597  patternList.append( newPattern )
598  except Exception, e:
599  self.reportError( e )
600  return patternList
601 
602  #----------------------------------------------------------------------
603  def createPatternsFromString(self, definition):
604  """
605  Decode the input string from JSON format and create a set of
606  patterns each containing the set of root attributes described in
607  that pattern's data.
608  """
609  jsonDebug( 'createPatternsFromString(%d chars, %d lines)' % (len(definition), definition.count('\n')) )
610  parsedPattern = None
611  try:
612  jsonPattern = json.loads( definition )
613  jsonDebug( 'Created pattern %s' % str(jsonPattern) )
614  parsedPattern = self.parseJsonPatterns( jsonPattern )
615  except Exception, e:
616  self.reportError( e )
617  return parsedPattern
618 
619  #----------------------------------------------------------------------
620  def createPatternsFromFile(self, fileName):
621  """
622  Decode the input file contents from JSON format and create a set of
623  patterns each containing the set of root attributes described in
624  that pattern's data.
625 
626  """
627  jsonDebug( 'createPatternsFromFile(%s)' % fileName )
628  parsedPattern = None
629  try:
630  fd = open(fileName, 'r')
631  definition = ""
632  for line in fd:
633  definition += line
634  fd.close()
635  parsedPattern = self.createPatternsFromString( definition )
636  except Exception, e:
637  self.reportError( e )
638  return parsedPattern
639 
640  #----------------------------------------------------------------------
641  def name(self):
642  """
643  Get the name of this pattern type.
644  """
645  return JsonKeys.kPluginPatternFactoryName
646 
647 
648 #======================================================================
649 # Initialize the plug-in
650 def initializePlugin(plugin):
651  import_helpers()
652  pluginFn = omAPI.MFnPlugin(plugin)
653  try:
654  pluginFn.registerAttributePatternFactory(
655  JsonKeys.kPluginPatternFactoryName, PyJsonAttrPatternFactory.patternFactoryCreator
656  )
657  except:
658  sys.stderr.write(
659  "Failed to register attribute pattern factory: %s\n" % JsonKeys.kPluginPatternFactoryName
660  )
661  raise
662 
663 #======================================================================
664 # Uninitialize the plug-in
665 def uninitializePlugin(plugin):
666  pluginFn = omAPI.MFnPlugin(plugin)
667  try:
668  pluginFn.deregisterAttributePatternFactory(JsonKeys.kPluginPatternFactoryName)
669  except:
670  sys.stderr.write(
671  "Failed to unregister command: %s\n" % JsonKeys.kPluginPatternFactoryName
672  )
673  raise
674 
675 #-
676 # ==========================================================================
677 # Copyright (C) 2011 Autodesk, Inc. and/or its licensors. All
678 # rights reserved.
679 #
680 # The coded instructions, statements, computer programs, and/or related
681 # material (collectively the "Data") in these files contain unpublished
682 # information proprietary to Autodesk, Inc. ("Autodesk") and/or its
683 # licensors, which is protected by U.S. and Canadian federal copyright
684 # law and by international treaties.
685 #
686 # The Data is provided for use exclusively by You. You have the right
687 # to use, modify, and incorporate this Data into other products for
688 # purposes authorized by the Autodesk software license agreement,
689 # without fee.
690 #
691 # The copyright notices in the Software and this entire statement,
692 # including the above license grant, this restriction and the
693 # following disclaimer, must be included in all copies of the
694 # Software, in whole or in part, and all derivative works of
695 # the Software, unless such copies or derivative works are solely
696 # in the form of machine-executable object code generated by a
697 # source language processor.
698 #
699 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
700 # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
701 # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
702 # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
703 # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
704 # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
705 # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
706 # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
707 # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
708 # OR PROBABILITY OF SUCH DAMAGES.
709 #
710 # ==========================================================================
711 #+
712