1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 21   
 22   
 23   
 24  import maya.OpenMaya as OpenMaya 
 25  import maya.OpenMayaMPx as OpenMayaMPx 
 26  import maya.cmds as m 
 27  from maya.mel import eval as meval 
 28  from nodeFactory import * 
 29  import slumMaya 
 30  import slum 
 31  import os, md5, textwrap, sys 
 32   
 33  global AETemplateCache 
 34  AETemplate={} 
 37          ''' 
 38                  This class initialize an AETemplate for a given nodetype dinamycally. 
 39                  Basically this avoid the need of having an AEnodetypTemplate.mel file. 
 40                  When the class is created, it sources a piece of mel code that register a 
 41                  global proc AEnodetypeTemplate for the given nodetype. 
 42                  This global proc just calls the static method template of this class, where 
 43                  the code for the template really is, in python. 
 44                  Hopefully, in future versions of maya we will be able to use python directly for AETemplates. 
 45          ''' 
 47                  mel =  ''' 
 48                          global proc AE%sTemplate(string $nodeName) 
 49                          { 
 50                                  python ("import slumMaya\\nslumMaya.AETemplate.template('"+$nodeName+"')"); 
 51                          } 
 52                  ''' % nodeType 
 53                  meval( mel ) 
 54                  self.nodeType = nodeType 
  55   
 56          @staticmethod 
 58                  ''' 
 59                          checks for the existence of a layout with the given layoutName, of type "type" 
 60                          and deletes it, if it exists. 
 61                          Used to delete the framelayout of our template. 
 62                  ''' 
 63                  ret = False 
 64                  parent = m.setParent(query=True) 
 65                  children = m.layout( parent, q=True, ca=True, ) 
 66                  if children!=None: 
 67                          for child in children: 
 68                                  if m.objectTypeUI( child, isType=type ) and child==layoutName: 
 69                                          ret=True 
 70                  return ret 
  71   
 72          @staticmethod 
 74                  ''' 
 75                          checks for the existence of a layout with the given layoutName, of type "type" 
 76                          and deletes it, if it exists. 
 77                          Used to delete the framelayout of our template. 
 78                  ''' 
 79                  if AETemplate._layoutExists(layoutName, type): 
 80                          m.deleteUI( layoutName, layout=True ); 
  81   
 82          @staticmethod 
 84                  ''' 
 85                          This is the method called by the dinamic AETemplate mel. 
 86                          Basically, it sets up a scroll layout, adds an extra attributes layout and 
 87                          suppress all attributes found in the node. 
 88                          The real important piece of code is the callCustom template that calls the 
 89                          static method customUI of this class, at runtime. 
 90                          Unfortunately, AGAIN we need to rely on a small portion of mel code to trigger the call for 
 91                          our customUI method, since a callCustom only calls mel global procs. 
 92                  ''' 
 93                  node = slumMaya.slumNode(nodeName) 
 94   
 95                   
 96                  for each in node.keys(): 
 97                          m.editorTemplate( suppress=each ) 
 98   
 99                   
100                   
101                  attrUICustom = 'attrUICustom_%s' % m.nodeType(nodeName) 
102                  mel =  ''' 
103                          global proc %s(string $node) 
104                          { 
105                                  python ("import slumMaya\\nslumMaya.AETemplate.customUI('"+$node+"')"); 
106                          } 
107                          ''' % (attrUICustom) 
108                  meval( mel ) 
109   
110                  attrUICustomHeader = 'attrUICustomHeader_%s' % m.nodeType(nodeName) 
111                  mel =  ''' 
112                          global proc %s(string $node) 
113                          { 
114                                  python ("import slumMaya\\nslumMaya.AETemplate.customUIHeader('"+$node+"')"); 
115                          } 
116                          ''' % (attrUICustomHeader) 
117                  meval( mel ) 
118   
119                  m.editorTemplate( attrUICustomHeader, attrUICustomHeader, '', callCustom=True ) 
120                  m.editorTemplate( beginScrollLayout=True ) 
121                  m.editorTemplate( attrUICustom, attrUICustom, '', callCustom=True ) 
122   
123                   
124                  m.editorTemplate( addExtraControls=True ) 
125                  m.editorTemplate( endScrollLayout=True ) 
 126   
127          @staticmethod 
129                  ''' 
130                          This method is called at runtime, and its responsible to create the 
131                          ui for every type of node we have. It calls genericHeader and parameters 
132                          methods to initialize the generic header all slum shaders have, and the 
133                          parameters for each node type, based on the returned data from the parameter 
134                          of a slum class shader. 
135                  ''' 
136                  nodeName = nodeName.strip('.') 
137                  slumNode = slumMaya.slumNode(nodeName) 
138   
139                  AETemplate.parameters(slumNode) 
 140   
141          @staticmethod 
143                  ''' 
144                          this method creates generic UI that is the same for every slum shader node. 
145                  ''' 
146                  nodeName = nodeName.strip('.') 
147                  slumNode = slumMaya.slumNode(nodeName) 
148   
149                  layoutName = "customUIHeaderID_%s" % m.nodeType(slumNode.node) 
150                  updateButtonName = "genericHeaderUpdateButtonID_%s" % m.nodeType(slumNode.node) 
151   
152                  AETemplate._deleteLayoutIfExists( layoutName, 'columnLayout' ) 
153                  m.columnLayout( layoutName, manage=True, adjustableColumn=True ) 
154   
155                   
156                   
157                  m.rowLayout( numberOfColumns=1, columnWidth1=800, 
158                                          columnAttach1 = 'right',  columnOffset1=0) 
159                  m.iconTextButton( style='iconOnly', w=800, h=40, align='left', 
160                   
161                                   image = os.path.join(slumMaya.__path__[0],'images','slum4maya.xpm') ) 
162                  m.setParent('..') 
163   
164                  m.rowLayout( numberOfColumns=4, adj=4, columnWidth4=(20, 20, 100,1), 
165                                          columnAttach4 = ('left','left','left','left'),  columnOffset4=(0,0,0,0)) 
166   
167                   
168   
169                   
170                  light = 'green' 
171                  help = "Green light - node is up-to-date with original slum template. No need to update." 
172                  if slumNode['slum']['edited']: 
173                          light = 'yellow' 
174                          help = "Yellow light - node has being edited inside maya. If you update, you'll lose all edited code. Don't update!" 
175                  elif not slumNode.updated(): 
176                          light = 'red' 
177                          help = "Red light - node is NOT up-to-date with original slum template. Click the red light to bring it to the latest version!" 
178                  xpm = [ 
179                          os.path.join(slumMaya.__path__[0],'images','%s.xpm' % light), 
180                          os.path.join(slumMaya.__path__[0],'images','%sSelected.xpm' % light), 
181                  ] 
182                   
183                  def updateCode(*args): 
184                          if slumNode['slum']['edited']: 
185                                  raise "If you update, you'll loose all the edit in this node." 
186                          m.iconTextButton( updateButtonName, e=True, image=xpm[1] ) 
187                          shaderBase.slumInitializer( slumNode.MObject(), refreshNodeOnly=True ) 
188                          m.iconTextButton( updateButtonName, e=True, image=xpm[0] ) 
189                          m.select(cl=True) 
190                          m.select(slumNode.node) 
191                          slumNode['slum']['edited'] = False 
 192                  m.iconTextButton( updateButtonName, style='iconOnly', w=20, h=20, image=xpm[0],  selectionImage=xpm[1], command=updateCode, annotation = help ) 
193   
194                   
195                  xpmEdit = [ 
196                                  os.path.join(slumMaya.__path__[0],'images','edit.xpm'), 
197                                  os.path.join(slumMaya.__path__[0],'images','editSelected.xpm'), 
198                          ] 
199                  def editCode(*args): 
200                          slumNode['slum']['edited'] = True 
201                          m.select(cl=True) 
202                          m.select(slumNode.node) 
 203                  m.iconTextButton( style='iconOnly', w=20, h=20, image=xpmEdit[0],  selectionImage=xpmEdit[1], command=editCode, annotation = "Edit slum template code stored in this node. After editing, Update light will become yellow, signing it has being edit inside maya." ) 
204   
205   
206                   
207                  menu = m.optionMenuGrp(label='preview', cw2 = (60,80)) 
208   
209                   
210                  previewRenderers = [] 
211                  for each in slumNode.slum._renderers(): 
212                           
213                           
214                          if each in dir(slumNode.slum): 
215                                  m.menuItem( label = each ) 
216                                  previewRenderers.append(each) 
217   
218   
219                   
220                  m.optionMenuGrp( menu, e=True, value=previewRenderers[0] ) 
221                  menuOption = m.optionMenuGrp( menu, q=True, value=True ) 
222   
223                  m.setParent('..') 
224                   
225                   
226   
227   
228                   
229                  rendererObject = slumMaya.renderers[slumMaya.renderers.index(eval('slumMaya.%s' % menuOption))] 
230                  if hasattr(rendererObject,'swatchUI'): 
231                          rendererObject.swatchUI(slumNode) 
232   
233                  m.setParent('..') 
234   
235   
236   
237          @staticmethod 
239                  ''' 
240                          This is the method that creates the UI for our attributes. 
241                          The UI is based in the data returned from the parameters method of 
242                          the slum class associated with the node. 
243                  ''' 
244   
245                   
246                   
247                  def refresh(): 
248                          AETemplate.customUIHeader(slumNode.node) 
 249                  slumNode.slum.clientRefresh = refresh 
250   
251                   
252                   
253                  layoutName = "layoutID_%s" % m.nodeType(slumNode.node) 
254                   
255                  AETemplate._deleteLayoutIfExists( layoutName, 'columnLayout' ) 
256   
257                  if not AETemplate._layoutExists(layoutName, 'columnLayout'): 
258   
259                          m.columnLayout( layoutName, visible=True, adjustableColumn=True ) 
260   
261                           
262                          def recursiveAddAttrUI( parameter ): 
263                                  if not parameter.hidden: 
264                                          if   parameter.__class__.__name__ == 'group' : 
265                                                  m.frameLayout( label = parameter.name, collapse = not parameter.opened ) 
266                                                  m.columnLayout( visible=True, adjustableColumn=True ) 
267   
268                                                  for each in parameter.value: 
269                                                          recursiveAddAttrUI( each ) 
270   
271                                                  m.setParent('..') 
272                                                  m.setParent('..') 
273   
274                                          elif parameter.__class__.__name__ == 'button' or parameter.ui.__class__.__name__ == 'button': 
275                                                  m.button( l = parameter.name, c = parameter.callback ) 
276                                          elif parameter.__class__.__name__ == 'parameter': 
277                                                  if not parameter.output: 
278                                                          attribute = "%s.%s" % (slumNode.node, parameter.name) 
279                                                          help = textwrap.fill(parameter.help,100) 
280                                                          type = parameter.value.__class__.__name__ 
281                                                          value = parameter.value 
282   
283                                                           
284                                                          if parameter.ui.__class__.__name__ == 'popup': 
285                                                                  popup=parameter.ui.values 
286                                                                  menu = m.optionMenuGrp( label=parameter.name ) 
287                                                                  keyz = popup.keys() 
288                                                                  keyz.sort() 
289                                                                  defaulValue=keyz[0] 
290                                                                  for each in keyz: 
291                                                                          m.menuItem( label = each, data = popup[each] ) 
292                                                                          if popup[each]==value: 
293                                                                                  defaulValue = each 
294                                                                  m.optionMenuGrp( menu, edit=True, value=defaulValue, annotation=help ) 
295                                                                  m.connectControl( menu, attribute, index=2) 
296   
297                                                          elif parameter.ui.__class__.__name__ == 'checkbox': 
298                                                                  checkbox = m.checkBoxGrp( numberOfCheckBoxes=1, label=parameter.name, annotation=help  ) 
299                                                                  m.connectControl( checkbox, attribute, index=2) 
300   
301                                                           
302                                                          elif  type in ['float','int']: 
303                                                                  value=value 
304                                                                  min=parameter.min 
305                                                                  max=parameter.max 
306                                                                  m.attrFieldSliderGrp( 
307                                                                          sliderMinValue=parameter.min, 
308                                                                          sliderMaxValue=parameter.max, 
309                                                                          fmn=-100000000, fmx=10000000, 
310                                                                          attribute=attribute, 
311                                                                          label=parameter.name, 
312                                                                          annotation=help 
313                                                                  ) 
314   
315                                                          elif type == 'str': 
316                                                                  ui=m.textFieldGrp( label=parameter.name, annotation=help ) 
317                                                                  m.connectControl( ui, attribute, index=2) 
318   
319                                                          elif type == 'color': 
320                                                                  m.attrColorSliderGrp( attribute=attribute, label=parameter.name, annotation=help ) 
321   
322                                                          else:  
323                                                                  nameUI = '__%sUI' % parameter.name 
324                                                                  m.floatFieldGrp( nameUI, l=parameter.name, numberOfFields=3, annotation=help ) 
325                                                                  m.connectControl( nameUI, '%sX' % attribute, index=2 ) 
326                                                                  m.connectControl( nameUI, '%sY' % attribute, index=3 ) 
327                                                                  m.connectControl( nameUI, '%sZ' % attribute, index=4 ) 
328   
329                          recursiveAddAttrUI( slumNode.slum.parameters( ) ) 
330   
333          ''' 
334                  This is the base class used by slum shader nodes. Every node is initialized 
335                  based on a class that derivates from this one. 
336                  For surface shaders for example, we use the shaderSurface class, which is derivate from 
337                  this class and MPxHardwareShader. 
338                  For light shaders, we use shaderLight class, which is derivate from this one and MPxLocatorNode 
339          ''' 
340          slum = None 
341   
343                  ''' placeholder. not used at the moment ''' 
344                   
345                  pass 
 346   
347 -        def compute(self, plug, dataBlock): 
 348                  ''' 
349                          this method is called by maya when rendering in software mode or viewport texture mode. 
350                          we need to come up with a way to have a maya software code inside a slum template that can be 
351                          executed here. The way maya software shaders are written is pretty complex, compared to 
352                          rsl and others, so my plan is to create some python classes that would simplify the maya 
353                          software shader development, bringing it more close to rsl... 
354                  ''' 
355                   
356                  pass 
 357   
358          @staticmethod 
360                  ''' 
361                          plugin creation method (returns the class object). this is needed to register a new node in maya. 
362                  ''' 
363                  return OpenMayaMPx.asMPxPtr( shaderBase() ) 
 364   
365   
366          @staticmethod 
368                  ''' 
369                          plugin initializaton method. this is needed to register a new node in maya. 
370                          usually, this method would handle all the initialization of parameters for the node. 
371                          Instead, we initialize the node in another method that is called AFTER the node 
372                          already exists in maya. 
373                          This make the whole process much simple, and if we need to update a node from a new version 
374                          of a slum template, we just call the same method again. 
375                  ''' 
376                  pass 
 377   
378          @staticmethod 
379 -        def slumInitializer(object, data=None, forceRefresh=False, refreshNodeOnly=False): 
 380                  ''' 
381                          this is the real initialization function... this is called after the node exists in maya. 
382                          so we can use this to dinamicaly add the shader attributes. 
383                          Also, this same method can be called to update the node if the class code changes. 
384                  ''' 
385                  self = OpenMaya.MFnDependencyNode(object) 
386                  node = slumMaya.slumNode(self.name(), forceSlumEval = refreshNodeOnly) 
387   
388                   
389                  AETemplate( self.typeName() ) 
390   
391                   
392                   
393                  classCache = slum.collectSlumClasses( refresh=forceRefresh ) 
394   
395                  nodeTypeName = self.typeName().replace('slum_','') 
396                  if not refreshNodeOnly: 
397                           
398                          node['slum'] = classCache.allClasses[nodeTypeName] 
399                  else: 
400                          path = classCache.allClasses[nodeTypeName]['path'] 
401                          node['slum'] = classCache.readSlumFile( path )[nodeTypeName] 
402   
403                   
404                  node = slumMaya.slumNode(self.name(), forceSlumEval = refreshNodeOnly) 
405   
406   
407                  def recursiveAddAttr( parameter ): 
408                          pars={'input':[], 'output':[]} 
409                          if parameter.__class__.__name__ == 'group': 
410                                  for each in parameter.value: 
411                                          tmp = recursiveAddAttr( each ) 
412                                          pars['input'].extend( tmp['input'] ) 
413                                          pars['output'].extend( tmp['output'] ) 
414                          elif parameter.__class__.__name__ == 'parameter': 
415                                  save = parameter.value 
416                                  if node.has_key(parameter.name): 
417                                          if type(save)==type(node[parameter.name]): 
418                                                   
419                                                  save = node[parameter.name] 
420                                  node[parameter.name] = save 
421                                  node.setInternal( parameter.name, True )  
422                                  node.setReadable( parameter.name, True ) 
423                                  node.setStorable( parameter.name, True ) 
424                                  if not parameter.output: 
425                                          node.setWritable( parameter.name, True ) 
426                                          pars['input'].append(parameter.name) 
427                                  else: 
428                                          node.setWritable( parameter.name, False ) 
429                                          pars['output'].append(parameter.name) 
430                          return pars 
 431   
432                   
433                  pars = recursiveAddAttr( node.slum.parameters() ) 
434   
435   
436                   
437                   
438                  for each in slumMaya.renderers: 
439                          if hasattr(each,'slumInitializer'): 
440                                  each.slumInitializer(node) 
 441   
443                  sys.stderr.write('...%s...\n' %  plugBeingDirtied.name() ) 
444                   
445                  return True 
 446   
447 -        def setInternalValueInContext ( self, plug, dataHandle,  ctx ): 
 448                  ''' 
449                          callback when user changes parameters in the node. 
450                          Returning false forces maya to set the value of the attribute as it would whitout a callback. 
451                          returning true means that this function set the value and maya dont need to do a thing. 
452                          False is default! 
453                  ''' 
454                   
455                   
456                  for each in slumMaya.renderers: 
457                          if hasattr(each,'setInternalValueInContext'): 
458                                  each.setInternalValueInContext( 
459                                          plug.name().split('.')[1], 
460                                          slumMaya.slumNode( self.name() ), 
461                                          dataHandle 
462                                  ) 
463   
464                  return False 
 465   
466 -        def getInternalValueInContext ( self, plug, dataHandle,  ctx ): 
 467                  ''' 
468                          callback when reading parameters from the node. 
469                          returning false forces maya to get teh value of the attribute as it would whitout a callback. 
470                          returning true forces maya to avoid getting the value itself, and will return whatever 
471                          this method put inside dataHandle. 
472                          False is default! 
473                  ''' 
474                  ret = False 
475                  node = slumMaya.slumNode( self.name() ) 
476                  plugName = plug.name().split('.')[1] 
477   
478                   
479                   
480                  for each in slumMaya.renderers: 
481                          if hasattr(each,'getInternalValueInContext'): 
482                                  ret = ret or each.getInternalValueInContext(plugName, node, dataHandle) 
483   
484                  return ret 
 485   
488