Package slumMaya :: Module shaderBase
[hide private]
[frames] | no frames]

Source Code for Module slumMaya.shaderBase

  1  # 
  2  # shaderBase -  defines a base class for slum maya nodes. Also defines a class 
  3  #                               AETemplate responsable for the node UI. 
  4  # 
  5  #    Copyright (C) 2008 - Roberto Hradec 
  6  # 
  7  # --------------------------------------------------------------------------- 
  8  #        This file is part of SLUM. 
  9  # 
 10  #    SLUM is free software: you can redistribute it and/or modify 
 11  #    it under the terms of the GNU General Public License as published by 
 12  #    the Free Software Foundation, either version 3 of the License, or 
 13  #    (at your option) any later version. 
 14  # 
 15  #    SLUM is distributed in the hope that it will be useful, 
 16  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 17  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 18  #    GNU General Public License for more details. 
 19  # 
 20  #    You should have received a copy of the GNU General Public License 
 21  #    along with SLUM.  If not, see <http://www.gnu.org/licenses/>. 
 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={} 
35 36 -class 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 '''
46 - def __init__(self, nodeType):
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
57 - def _layoutExists(layoutName, type):
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
73 - def _deleteLayoutIfExists(layoutName, type):
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
83 - def template(nodeName):
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 # supress everything 96 for each in node.keys(): 97 m.editorTemplate( suppress=each ) 98 99 # we need to register another global proc, as editorTemplate python command only calls 100 # mel procs (if theres a way to make it call a python function, we need to find out!) 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 # add extra controls 124 m.editorTemplate( addExtraControls=True ) 125 m.editorTemplate( endScrollLayout=True )
126 127 @staticmethod
128 - def customUI(nodeName):
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
142 - def customUIHeader(nodeName):
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 # begin first line 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 #m.image( w=800,h=40, enable=True, 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 #m.text( label = ' ' ) 168 169 # if node is updated compared to template source 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 # update button 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 # edit button 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 # add popmenu for the renderer 207 menu = m.optionMenuGrp(label='preview', cw2 = (60,80)) 208 209 # loop trough slum template supported shaders. 210 previewRenderers = [] 211 for each in slumNode.slum._renderers(): 212 # check if the slum template have support for the renderer. 213 # if so, add it to the listbox 214 if each in dir(slumNode.slum): 215 m.menuItem( label = each ) 216 previewRenderers.append(each) 217 218 219 # we should check the selected renderer here. for now, default to the first one in the list 220 m.optionMenuGrp( menu, e=True, value=previewRenderers[0] ) 221 menuOption = m.optionMenuGrp( menu, q=True, value=True ) 222 223 m.setParent('..') 224 # ---------------------------------------------------------------------------- 225 # end first line 226 227 228 # run swatchUI method of the current selected renderer 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
238 - def parameters(slumNode):
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 # overrides template clientRefresh method to allow callbacks in the template to 246 # trigger a UI refresh in Maya 247 def refresh(): 248 AETemplate.customUIHeader(slumNode.node)
249 slumNode.slum.clientRefresh = refresh 250 251 # this garantees the layout will be rebuild properly everytime 252 # the attribute editor is open/selected for a node. 253 layoutName = "layoutID_%s" % m.nodeType(slumNode.node) 254 #layoutName = "layoutID_%s" % slumNode.node 255 AETemplate._deleteLayoutIfExists( layoutName, 'columnLayout' ) 256 257 if not AETemplate._layoutExists(layoutName, 'columnLayout'): 258 259 m.columnLayout( layoutName, visible=True, adjustableColumn=True ) 260 261 # a recursive function to create our UI. perfect for hierarquical parameters 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 # custom UI 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 # normal parameters 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: # vectors/normals 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
331 332 -class shaderBase(OpenMayaMPx.MPxNode):
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
342 - def __init__(self):
343 ''' placeholder. not used at the moment ''' 344 #OpenMayaMPx.MPxNode.__init__(self) 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 #sys.stderr.write( "\ncompute %s\n" % plug.name() ) 356 pass
357 358 @staticmethod
359 - def nodeCreator():
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
367 - def nodeInitializer():
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 # dinamically source AETemplate for this node 389 AETemplate( self.typeName() ) 390 391 # add the code to the slum attribute. After this is done, every call to slumNode will automatically 392 # evaluate the slumClass 393 classCache = slum.collectSlumClasses( refresh=forceRefresh ) 394 395 nodeTypeName = self.typeName().replace('slum_','') 396 if not refreshNodeOnly: 397 # find slumclass name, get the data from classCache and store in the slum key of the node (string parameter) 398 node['slum'] = classCache.allClasses[nodeTypeName] 399 else: 400 path = classCache.allClasses[nodeTypeName]['path'] 401 node['slum'] = classCache.readSlumFile( path )[nodeTypeName] 402 403 # re-initialize now that the slum key is in place 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 #print type(save), type(node[parameter.name]), save, node[parameter.name] 419 save = node[parameter.name] 420 node[parameter.name] = save 421 node.setInternal( parameter.name, True ) # add set/get callback 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 # use pars to set attributeAffects !!!! 433 pars = recursiveAddAttr( node.slum.parameters() ) 434 435 436 # loop trough registered renderers and call slumInitializer method 437 # if the renderer object have it 438 for each in slumMaya.renderers: 439 if hasattr(each,'slumInitializer'): 440 each.slumInitializer(node)
441
442 - def setDependentsDirty ( self, plugBeingDirtied, affectedPlugs ):
443 sys.stderr.write('...%s...\n' % plugBeingDirtied.name() ) 444 #node = slumMaya.slumNode( self.name() ) 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 # loop trough registered renderers and call setInternalValueInContext 455 # method if the renderer object have it 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 # loop trough registered renderers and call getInternalValueInContext 479 # method if the renderer object have it 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
486 - def renderSwatchImage ( self, image ):
487 pass
488