scripted/slopeShader.py

scripted/slopeShader.py
1 #-
2 # ==========================================================================
3 # Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All
4 # rights reserved.
5 #
6 # The coded instructions, statements, computer programs, and/or related
7 # material (collectively the "Data") in these files contain unpublished
8 # information proprietary to Autodesk, Inc. ("Autodesk") and/or its
9 # licensors, which is protected by U.S. and Canadian federal copyright
10 # law and by international treaties.
11 #
12 # The Data is provided for use exclusively by You. You have the right
13 # to use, modify, and incorporate this Data into other products for
14 # purposes authorized by the Autodesk software license agreement,
15 # without fee.
16 #
17 # The copyright notices in the Software and this entire statement,
18 # including the above license grant, this restriction and the
19 # following disclaimer, must be included in all copies of the
20 # Software, in whole or in part, and all derivative works of
21 # the Software, unless such copies or derivative works are solely
22 # in the form of machine-executable object code generated by a
23 # source language processor.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
26 # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
27 # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
28 # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
29 # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
30 # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
31 # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
32 # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
33 # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
34 # OR PROBABILITY OF SUCH DAMAGES.
35 #
36 # ==========================================================================
37 #+
38 
39 ########################################################################
40 # DESCRIPTION:
41 #
42 # Produces the dependency graph node "spSlopeShader".
43 #
44 # This plug-in implements a Maya software shader in Python. An angle attribute
45 # is used to define the space on the object that is being shaded, which is both
46 # walkable and non-walkable. The walkable and non-walkable colors are set as
47 # attributes on the node. The shader will render both the walkable and non-walkable
48 # areas based on the color that has been set.
49 #
50 # To use:
51 #
52 # (1) Load the slopeShader.py plug-in into Maya.
53 #
54 # import maya
55 # maya.cmds.loadPlugin("slopeShader.py")
56 #
57 # (2) Create a polygon sphere.
58 # (3) Assign a shader to the sphere.
59 # (4) Open the Attribute Editor > Color Channel > Utilities > spSlopeShader to apply
60 # the texture on the sphere.
61 # (5) Render the scene to view the results.
62 #
63 # The angle attribute can be adjusted to see different results.
64 #
65 ########################################################################
66 
67 # imports
68 import maya.OpenMaya as OpenMaya
69 import maya.OpenMayaUI as OpenMayaUI
70 import maya.OpenMayaMPx as OpenMayaMPx
71 import math, sys
72 
73 # consts
74 kSlopeShaderBehaviourName = "slopeShaderBehaviour"
75 kSlopeNodeName = "spSlopeShader"
76 kSlopeNodeClassify = "utility/color"
77 kSlopeNodeId = OpenMaya.MTypeId(0x87001)
78 
79 # Node definition
80 class slopeShader(OpenMayaMPx.MPxNode):
81  # class variables
82  aAngle = OpenMaya.MObject()
83  aColor1 = OpenMaya.MObject()
84  aColor2 = OpenMaya.MObject()
85  aTriangleNormalCamera = OpenMaya.MObject()
86  aOutColor = OpenMaya.MObject()
87  aMatrixEyeToWorld = OpenMaya.MObject()
88  aDirtyShaderAttr = OpenMaya.MObject()
89 
90  def __init__(self):
91  OpenMayaMPx.MPxNode.__init__(self)
92 
93 
94  def compute(self, plug, dataBlock):
95  if plug == slopeShader.aOutColor or plug.parent() == slopeShader.aOutColor:
96 
97  resultColor = OpenMaya.MFloatVector(0.0,0.0,0.0)
98 
99  try:
100  dataHandle = dataBlock.inputValue( slopeShader.aColor1 )
101  except:
102  sys.stderr.write( "Failed to get inputValue aColor1" )
103  raise
104  walkable = dataHandle.asFloatVector()
105 
106  try:
107  dataHandle = dataBlock.inputValue( slopeShader.aColor2 )
108  except:
109  sys.stderr.write( "Failed to get inputValue aColor2" )
110  raise
111  nonWalkable = dataHandle.asFloatVector()
112 
113  try:
114  dataHandle = dataBlock.inputValue( slopeShader.aTriangleNormalCamera )
115  except:
116  sys.stderr.write( "Failed to get inputValue aTriangleNormalCamera" )
117  raise
118  surfaceNormal = dataHandle.asFloatVector()
119 
120  try:
121  dataHandle = dataBlock.inputValue( slopeShader.aMatrixEyeToWorld )
122  except:
123  sys.stderr.write( "Failed to get inputValue aMatrixEyeToWorld" )
124  raise
125  viewMatrix = dataHandle.asFloatMatrix()
126 
127  # Get the angle
128  try:
129  dataHandle = dataBlock.inputValue( slopeShader.aAngle )
130  except:
131  sys.stderr.write( "Failed to get inputValue aAngle" )
132  raise
133  angle = dataHandle.asFloat()
134 
135  yVector = OpenMaya.MFloatVector(0, 1, 0)
136 
137  # Normalize the view vector
138  #
139  surfaceNormal.normalize()
140  WSVector = surfaceNormal * viewMatrix
141 
142  # find dot product
143  #
144  scalarNormal = WSVector * yVector
145 
146  # take the absolute value
147  #
148  if scalarNormal < 0.0:
149  scalarNormal *= -1.0
150 
151  radianAngle = math.radians(angle)
152  cosOfAngle = math.cos(radianAngle)
153  if cosOfAngle < scalarNormal:
154  resultColor = walkable
155  else:
156  resultColor = nonWalkable
157 
158  # set output color attribute
159  #
160  try:
161  outColorHandle = dataBlock.outputValue( slopeShader.aOutColor )
162  except:
163  sys.stderr.write( "Failed to get outputValue aOutColor" )
164  raise
165 
166  outColorHandle.setMFloatVector(resultColor)
167  outColorHandle.setClean()
168 
169  else:
170  return OpenMaya.kUnknownParameter
171 
172 
173  def postConstructor(self):
174  print "In slopeShader.postConstructor"
175  # OpenMayaMPx.MPxNode.setMPSafe(self, 1)
176 
177 
178 class slopeShaderBehavior(OpenMayaMPx.MPxDragAndDropBehavior):
179  def __init__(self):
180  OpenMayaMPx.MPxDragAndDropBehavior.__init__(self)
181 
182 
183  def shouldBeUsedFor(self, sourceNode, destinationNode, sourcePlug, destinationPlug):
184  """
185  Overloaded function from MPxDragAndDropBehavior
186  this method will return True if it is going to handle the connection
187  between the two nodes given.
188  """
189  result = False
190 
191  if sourceNode.hasFn(OpenMaya.MFn.kLambert):
192  #if the source node was a lambert
193  #than we will check the downstream connections to see
194  #if a slope shader is assigned to it.
195  #
196  shaderNode = None
197  src = OpenMaya.MFnDependencyNode(sourceNode)
198  connections = OpenMaya.MPlugArray()
199  src.getConnections(connections)
200 
201  for i in range(connections.length()):
202  #check the incoming connections to this plug
203  #
204  connectedPlugs = OpenMaya.MPlugArray()
205  connections[i].connectedTo(connectedPlugs, True, False)
206  for j in range(connectedPlugs.length()):
207  #if the incoming node is a slope shader than
208  #set shaderNode equal to it and break out of the inner
209  #loop
210  #
211  testNode = OpenMaya.MFnDependencyNode(connectedPlugs[j].node())
212  if testNode.typeName() == kSlopeNodeName:
213  shaderNode = connectedPlugs[j].node()
214  break
215 
216  #if the shaderNode is not None
217  #than we have found a slopeShader
218  #
219  if shaderNode is not None:
220  #if the destination node is a mesh than we will take
221  #care of this connection so set the result to True
222  #and break out of the outer loop
223  #
224  if destinationNode.hasFn(OpenMaya.MFn.kMesh):
225  result = True
226  break
227 
228  node = OpenMaya.MFnDependencyNode(sourceNode)
229  if node.typeName() == kSlopeNodeName:
230  #if the sourceNode is a slope shader than check what we
231  #are dropping on to
232  #
233  if destinationNode.hasFn(OpenMaya.MFn.kLambert):
234  result = True
235  elif destinationNode.hasFn(OpenMaya.MFn.kMesh):
236  result = True
237 
238  return result
239 
240 
241  def connectNodeToNode(self, sourceNode, destinationNode, force):
242  """
243  Overloaded function from MPxDragAndDropBehavior
244  this method will handle the connection between the slopeShader and the shader it is
245  assigned to as well as any meshes that it is assigned to.
246  """
247  src = OpenMaya.MFnDependencyNode(sourceNode)
248 
249  #if we are dragging from a lambert
250  #we want to check what we are dragging
251  #onto.
252  if sourceNode.hasFn(OpenMaya.MFn.kLambert):
253  shaderNode = OpenMaya.MObject()
254  connections = OpenMaya.MPlugArray()
255  shaderNodes = OpenMaya.MObjectArray()
256  shaderNodes.clear()
257 
258  #if the source node was a lambert
259  #than we will check the downstream connections to see
260  #if a slope shader is assigned to it.
261  #
262  src.getConnections(connections)
263  for i in range(connections.length()):
264  #check the incoming connections to this plug
265  #
266  connectedPlugs = OpenMaya.MPlugArray()
267  connections[i].connectedTo(connectedPlugs, True, False)
268  for j in range(connectedPlugs.length()):
269  #if the incoming node is a slope shader than
270  #append the node to the shaderNodes array
271  #
272  currentnode = connectedPlugs[j].node()
273  if OpenMaya.MFnDependencyNode(currentnode).typeName() == kSlopeNodeName:
274  shaderNodes.append(currentnode)
275 
276  #if we found a shading node
277  #than check the destination node
278  #type to see if it is a mesh
279  #
280  if shaderNodes.length() > 0:
281  dest = OpenMaya.MFnDependencyNode(destinationNode)
282  if destinationNode.hasFn(OpenMaya.MFn.kMesh):
283  #if the node is a mesh than for each slopeShader
284  #connect the worldMesh attribute to the dirtyShaderPlug
285  #attribute to force an evaluation of the node when the mesh
286  #changes
287  #
288  for i in range(shaderNodes.length()):
289  srcPlug = dest.findPlug("worldMesh")
290  destPlug = OpenMaya.MFnDependencyNode(shaderNodes[i]).findPlug("dirtyShaderPlug")
291 
292  if (not srcPlug.isNull()) and (not destPlug.isNull()):
293  cmd = "connectAttr -na %s %s" % (srcPlug.name(), destPlug.name())
294  OpenMaya.MGlobal.executeCommand(cmd)
295 
296  #get the shading engine so we can assign the shader
297  #to the mesh after doing the connection
298  #
299  shadingEngine = self._findShadingEngine(sourceNode)
300 
301  #if there is a valid shading engine than make
302  #the connection
303  #
304  if not shadingEngine.isNull():
305  cmd = "sets -edit -forceElement %s %s" % (
306  OpenMaya.MFnDependencyNode(shadingEngine).name(),
307  OpenMaya.MFnDagNode(destinationNode).partialPathName()
308  )
309  OpenMaya.MGlobal.executeCommand(cmd)
310 
311  elif src.typeName() == kSlopeNodeName:
312  #if we are dragging from a slope shader
313  #than we want to see what we are dragging onto
314  #
315  if destinationNode.hasFn(OpenMaya.MFn.kMesh):
316  #if the user is dragging onto a mesh
317  #than make the connection from the worldMesh
318  #to the dirtyShader plug on the slopeShader
319  #
320  dest = OpenMaya.MFnDependencyNode(destinationNode)
321  srcPlug = dest.findPlug("worldMesh")
322  destPlug = src.findPlug("dirtyShaderPlug")
323  if (not srcPlug.isNull()) and (not destPlug.isNull()):
324  cmd = "connectAttr -na %s %s" % (srcPlug.name(), destPlug.name())
325  OpenMaya.MGlobal.executeCommand(cmd)
326 
327  elif destinationNode.hasFn(OpenMaya.MFn.kLambert):
328  dest = OpenMaya.MFnDependencyNode(destinationNode)
329  srcPlug = src.findPlug("outColor")
330  destPlug = dest.findPlug("color")
331  if (not srcPlug.isNull()) and (not destPlug.isNull()):
332  if force:
333  cmd = "connectAttr -f %s %s" % (srcPlug.name(), destPlug.name())
334  else:
335  cmd = "connectAttr %s %s" % (srcPlug.name(), destPlug.name())
336  OpenMaya.MGlobal.executeCommand(cmd)
337 
338 
339  def connectNodeToAttr(self, sourceNode, destinationPlug, force):
340  """
341  Overloaded function from MPxDragAndDropBehavior
342  this method will assign the correct output from the slope shader
343  onto the given attribute.
344  """
345  src = OpenMaya.MFnDependencyNode(sourceNode)
346 
347  #if we are dragging from a slopeShader
348  #to a shader than connect the outColor
349  #plug to the plug being passed in
350  #
351  if destinationPlug.node().hasFn(OpenMaya.MFn.kLambert):
352  if src.typeName() == kSlopeNodeName:
353  srcPlug = src.findPlug("outColor")
354  if (not srcPlug.isNull()) and (not destinationPlug.isNull()):
355  cmd = "connectAttr %s %s" % (srcPlug.name(), destinationPlug.name())
356  OpenMaya.MGlobal.executeCommand(cmd)
357  else:
358  #in all of the other cases we do not need the plug just the node
359  #that it is on
360  #
361  destinationNode = destinationPlug.node()
362  self.connectNodeToNode(sourceNode, destinationNode, force)
363 
364 
365  def _findShadingEngine(self, node):
366  """
367  Given the material MObject this method will
368  return the shading group that it is assigned to.
369  if there is no shading group associated with
370  the material than a null MObject is passed back.
371  """
372  nodeFn = OpenMaya.MFnDependencyNode (node)
373  srcPlug = nodeFn.findPlug("outColor")
374  nodeConnections = OpenMaya.MPlugArray()
375  srcPlug.connectedTo(nodeConnections, False, True)
376  #loop through the connections
377  #and find the shading engine node that
378  #it is connected to
379  #
380  for i in range(nodeConnections.length()):
381  theNode = nodeConnections[i].node()
382  if theNode.hasFn(OpenMaya.MFn.kShadingEngine):
383  return theNode
384 
385  #no shading engine associated so return a
386  #null MObject
387  #
388  return OpenMaya.MObject()
389 
390 
391 ##################################################################
392 
393 
394 def nodeCreator():
395  return OpenMayaMPx.asMPxPtr(slopeShader())
396 
397 
398 def behaviourCreator():
399  return OpenMayaMPx.asMPxPtr(slopeShaderBehavior())
400 
401 
402 def nodeInitializer():
404  nMAttr = OpenMaya.MFnMatrixAttribute()
405  nTAttr = OpenMaya.MFnTypedAttribute()
407  # input
408  slopeShader.aAngle = nAttr.create( "angle", "ang", OpenMaya.MFnNumericData.kFloat )
409  nAttr.setDefault(30.0)
410  nAttr.setMin(0.0)
411  nAttr.setMax(100.0)
412  nAttr.setKeyable(1)
413  nAttr.setStorable(1)
414  nAttr.setReadable(1)
415  nAttr.setWritable(1)
416 
417  slopeShader.aColor1 = nAttr.createColor("walkableColor", "w")
418  nAttr.setDefault(0.0, 1.0, 0.0)
419  nAttr.setKeyable(1)
420  nAttr.setStorable(1)
421  nAttr.setUsedAsColor(1)
422  nAttr.setReadable(1)
423  nAttr.setWritable(1)
424 
425  slopeShader.aColor2 = nAttr.createColor( "nonWalkableColor", "nw" )
426  nAttr.setDefault(1.0, 0.0, 0.0)
427  nAttr.setKeyable(1)
428  nAttr.setStorable(1)
429  nAttr.setUsedAsColor(1)
430  nAttr.setReadable(1)
431  nAttr.setWritable(1)
432 
433  # Surface Normal supplied by the render sampler
434  #
435  slopeShader.aTriangleNormalCamera = nAttr.createPoint( "triangleNormalCamera", "n" )
436  nAttr.setStorable(0)
437  nAttr.setHidden(1)
438  nAttr.setReadable(1)
439  nAttr.setWritable(1)
440 
441  # View matrix from the camera into world space
442  #
443  slopeShader.aMatrixEyeToWorld = nMAttr.create( "matrixEyeToWorld", "mew", OpenMaya.MFnMatrixAttribute.kFloat )
444  nAttr.setHidden(1)
445  nMAttr.setWritable(1)
446 
447  # Output Attributes
448  #
449  slopeShader.aOutColor = nAttr.createColor( "outColor", "oc" )
450  nAttr.setStorable(0)
451  nAttr.setHidden(0)
452  nAttr.setReadable(1)
453  nAttr.setWritable(0)
454 
455  meshTypeId = OpenMaya.MTypeId(OpenMaya.MFnData.kMesh)
456 
457  # dummy plug for forcing evaluation
458  #
459  slopeShader.aDirtyShaderAttr = nGAttr.create( "dirtyShaderPlug", "dsp")
460  nGAttr.setArray(1)
461  nGAttr.setHidden(0)
462  nGAttr.setUsesArrayDataBuilder(1)
463  nGAttr.setReadable(0)
464  nGAttr.setStorable(1)
465  nGAttr.setIndexMatters(0)
466  # nGAttr.addAccept(meshTypeId)
467 
468  # Add attribues
469  #
470  slopeShader.addAttribute(slopeShader.aAngle)
471  slopeShader.addAttribute(slopeShader.aColor1)
472  slopeShader.addAttribute(slopeShader.aColor2)
473  slopeShader.addAttribute(slopeShader.aTriangleNormalCamera)
474  slopeShader.addAttribute(slopeShader.aOutColor)
475  slopeShader.addAttribute(slopeShader.aMatrixEyeToWorld)
476  slopeShader.addAttribute(slopeShader.aDirtyShaderAttr)
477 
478  slopeShader.attributeAffects (slopeShader.aAngle, slopeShader.aOutColor)
479  slopeShader.attributeAffects (slopeShader.aColor1, slopeShader.aOutColor)
480  slopeShader.attributeAffects (slopeShader.aColor2, slopeShader.aOutColor)
481  slopeShader.attributeAffects (slopeShader.aTriangleNormalCamera, slopeShader.aOutColor)
482  slopeShader.attributeAffects (slopeShader.aDirtyShaderAttr, slopeShader.aOutColor)
483 
484 
485 # initialize the script plug-in
486 def initializePlugin(mobject):
487  mplugin = OpenMayaMPx.MFnPlugin(mobject)
488 
489  # register node
490  try:
491  mplugin.registerNode(kSlopeNodeName, kSlopeNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kSlopeNodeClassify)
492  except:
493  sys.stderr.write("Failed to register node: %s" % kSlopeNodeName )
494  raise
495 
496  # register behaviors
497  try:
498  mplugin.registerDragAndDropBehavior(kSlopeShaderBehaviourName, behaviourCreator)
499  except:
500  sys.stderr.write("Failed to register behaviour: %s" % kSlopeShaderBehaviourName)
501 
502  postCmd = "if( `window -exists createRenderNodeWindow` ) {refreshCreateRenderNodeWindow(\"%s\");}\n" % kSlopeNodeClassify
503  OpenMaya.MGlobal.executeCommand(postCmd)
504 
505 
506 # uninitialize the script plug-in
507 def uninitializePlugin(mobject):
508  mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
509  try:
510  mplugin.deregisterNode(kSlopeNodeId)
511  except:
512  sys.stderr.write( "Failed to unregister node: %s" % kSlopeNodeName )
513  raise
514 
515  try:
516  plugin.deregisterDragAndDropBehavior(kSlopeShaderBehaviourName)
517  except:
518  sys.stderr.write("Failed to deregister behaviour: %s" % kSlopeShaderBehaviourName)
519 
520  postCmd = "if( `window -exists createRenderNodeWindow` ) {refreshCreateRenderNodeWindow(\"%s\");}\n" % kSlopeNodeClassify
521  OpenMaya.MGlobal.executeCommand(postCmd)