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