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