Python API 2.0 Reference
python/api1/py1PolyModifierCmd.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 #####################################################################
41 ## COMMAND ##########################################################
42 #####################################################################
43 
44 #
45 # Overview:
46 #
47 # polyModifierCmd is a generic base class designed to aid in modifying
48 # polygonal meshes. All polys in Maya possess two features: construction
49 # history and tweaks. Both of these have a large impact on the structure
50 # of the object as well as how it can be further manipulated. However,
51 # they cannot be easily implemented, which is the why we need this abstracted
52 # class. polyModifierCmd will automatically handle the DG maintenance of
53 # construction history and tweaks on a polygonal mesh.
54 #
55 # To understand what effect both construction history and tweaks have on
56 # a mesh, we need to understand the states of a node. There are three things
57 # which will affect the state of a node regarding construction history and
58 # tweaks. That is:
59 #
60 # (1) Does construction history exist?
61 # (2) Do tweaks exist?
62 # (3) Is construction history turned on?
63 #
64 # The answer to each of these questions changes how the mesh is interpreted,
65 # which in turn affects how the mesh can be accessed/modified. Under each
66 # circumstance, new modifications on the mesh go through a standard series
67 # of events. To further illustrate how these affect the interpretation of
68 # a mesh, we'll delve into each case separately looking at the problems
69 # that we face in each case.
70 #
71 # In the case where construction history exists, the existence of construction
72 # history informs us that there is a single linear DG chain of nodes upstream
73 # from the mesh node. That chain is the history chain. At the top of the chain
74 # we have the "original" mesh and at the bottom we have the "final" mesh,
75 # where "original" and "final" represent the respective state of the node with
76 # regards to mesh's history. Each of these nodes are adjoined via the
77 # inMesh/outMesh attributes, where in and out designate the dataflow into
78 # and out of the node. Now, with that aside, attempting to modify a node
79 # via mutator methods will always write the information onto the inMesh
80 # attribute (except in the case of tweaks, where it writes to the cachedInMesh).
81 # This presents a problem if history exists since a DG evaluation will overwrite
82 # the inMesh of the mesh node, due to the connection from the outMesh of the
83 # node directly upstream from the mesh. This will discard any modifications
84 # made to the mesh.
85 #
86 # So obviously modifying a mesh directly isn't possible when history exists.
87 # To properly modify a mesh with history, we introduce the concept of a modifier
88 # node. This polyModifierNode will encapsulate the operations on the mesh
89 # and behave similarly to the other nodes in the history chain. The node will
90 # then be inserted into the history chain so that on each DG evaluation, it
91 # is always accounted for. The diagram below shows the before and after
92 # of modifying a mesh with history.
93 #
94 #
95 # Before modification:
96 #
97 # ____ ____
98 # / \ / \
99 # | Hist | O --------> O | mesh | O
100 # \____/ | | \____/ |
101 # outMesh inMesh outMesh
102 #
103 #
104 # After modification:
105 #
106 # ____ ________ ____
107 # / \ / \ / \
108 # | Hist | O --------> O | modifier | O --------> O | mesh | O
109 # \____/ | | \________/ | | \____/ |
110 # outMesh inMesh outMesh inMesh outMesh
111 #
112 #
113 # (Figure 1. Nodes with History)
114 #
115 #
116 # In the case of tweaks: Tweaks are stored on a hidden attribute on the
117 # mesh. Tweaks are manual component modifications on a mesh (eg. repositioning
118 # a vertex). During a DG evaluation, the DG takes the inMesh attribute of
119 # the node and adds the tweak values onto it to get the final value. From this
120 # knowledge we can see that inserting a modifier node ahead of the mesh node
121 # reverses the order of operations which can be crucial to the structure of the
122 # resulting mesh if the modification is a topological change. To avoid this
123 # problem, we retrieve the tweaks off of the mesh, remove it from the mesh and
124 # place the tweaks into a polyTweak node. The tweak node is then inserted ahead
125 # of the modifier node to preserve the order of operations:
126 #
127 #
128 # Before modification:
129 #
130 # Tweak
131 # ____ __O__
132 # / \ / \
133 # | Hist | O --------> O | mesh | O
134 # \____/ | | \_____/ |
135 # outMesh inMesh outMesh
136 #
137 #
138 # After modification:
139 #
140 # Empty Tweak
141 # ____ _____ ________ __O__
142 # / \ / \ / \ / \
143 # | Hist | O -----> O | Tweak | O -----> O | modifier | O -----> O | mesh | O
144 # \____/ | | \_____/ | | \________/ | | \_____/ |
145 # outMesh inMesh outMesh inMesh outMesh inMesh outMesh
146 #
147 #
148 # (Figure 2. Node with Tweaks)
149 #
150 #
151 # The last of the questions deals with whether or not the user has construction
152 # history turned on or off. This will change how the node should be modified
153 # as well as what the node will look like in the DG following the operation. With
154 # history turned on, the user has selected that they would like to keep a
155 # history chain. So in that case, the resulting mesh would look like the above
156 # diagrams following the operation. On the other hand, with history turned off,
157 # the user has selected that they would not like to see a history chain. From here
158 # there are two possible choices to modify the mesh:
159 #
160 # (1) Operate on the mesh directly
161 # (2) Use the DG, like in the above diagrams, then collapse the nodes down into the mesh.
162 #
163 # The only exception to note out of this case is that if the node already possesses
164 # history (as would be answered by the first question), this preference is ignored.
165 # If a node has history, we continue to use history. The user is imposed with the
166 # task of deleting the history on the object first if they would not like to continue
167 # using history.
168 #
169 #
170 # With History:
171 #
172 # ____ ____
173 # / \ / \
174 # | Hist | O --------> O | mesh | O
175 # \____/ | | \____/ |
176 # outMesh inMesh outMesh
177 #
178 #
179 # Without History:
180 #
181 # ____
182 # / \
183 # O | mesh | O (All history compressed onto the inMesh attribute)
184 # | \____/ |
185 # inMesh outMesh
186 #
187 #
188 # (Figure 3. Node with History preference)
189 #
190 #
191 # This section has described the "why" part of the question regarding this command.
192 # Following sections will provide a more in depth look at "how" this command
193 # treats each of these situations and what it really does behind the scenes
194 # to handle the above cases.
195 #
196 #
197 # How it works:
198 #
199 # This command approaches the various node state cases similarly to the way
200 # Maya works with construction history and tweaks in polygons. It is important
201 # to note that history and tweaks are independent states having no effect on
202 # each other (in terms of their state). Thus this section will describe each
203 # case independently:
204 #
205 # 1) History
206 #
207 # For history, there are 4 cases that need to be considered:
208 #
209 # (a) History (yes) - RecordHistory (yes)
210 # (b) History (yes) - RecordHistory (no)
211 # (c) History (no) - RecordHistory (yes)
212 # (d) History (no) - RecordHistory (no)
213 #
214 # For (a) and (b), this command treats the node identically. Regardless of
215 # whether recording history is turned on or off, if history already exists
216 # on the node, we treat the node as though recording history is on. As such
217 # the command performs the following steps:
218 #
219 # (i) Create a modifier node.
220 # (ii) Find the node directly upstream to the mesh node.
221 # (iii) Disconnect the upstream node and the mesh node.
222 # (iv) Connect the upstream node to the modifier node.
223 # (v) Connect the modifier node to the mesh node.
224 # (vi) Done!
225 #
226 # For (c), polyModifierCmd needs to generate an input mesh to drive the
227 # modifier node. To do this, the mesh node is duplicated and connected
228 # like the upstream node in the previous two cases:
229 #
230 # (i) Create a modifier node.
231 # (ii) Duplicate the mesh node.
232 # (iii) Connect the duplicate mesh node to the modifier node
233 # (iv) Connect the modifier node to the mesh node
234 # (v) Done!
235 #
236 # For (d), this command is a bit more complicated. There are two approaches
237 # that can be done to respect the fact that no history is desired. The first
238 # involves using the approach in case (c) and simply "baking" or "flattening"
239 # the nodes down into the mesh node. Unfortunately, this presents some
240 # serious problems with undo, as the Maya API in its current state does not
241 # support construction history manipulation. Resorting to the command:
242 # "delete -ch" would be possible, however undoing the operation would not be
243 # trivial as calling an undo from within an undo could destabilize the undo
244 # queue.
245 #
246 # The second alternative and one that is currently implemented by this class
247 # is to respect the "No Construction History" preference strictly by
248 # not modifying the history chain at all and simply operating directly on the
249 # mesh. In order to do this and maintain generality, a hook is provided for
250 # derived classes to override and place in the code used to directly modify the
251 # mesh. polyModifierCmd will only call this method under the circumstances
252 # of case (d). To prevent code duplication between the operations done in the
253 # modifierNode and the command's directModifier implementation, the concept of
254 # a factory is used. It is recommended that an instance of such a factory is
255 # stored locally on the command much like it will be on the node. See
256 # polyModifierNode.h and polyModifierFty.h for more details.
257 #
258 #
259 # 2) Tweaks
260 #
261 # Tweaks are handled as noted above in the description section. However, how
262 # they are treated is dependent on the state of history. Using the four cases
263 # above:
264 #
265 # For (a), (b) and (c), it is as described in the description section:
266 #
267 # (i) Create a tweak node.
268 # (ii) Extract the tweaks from the mesh node.
269 # (iii) Copy the tweaks onto the tweak node.
270 # (iv) Clear the tweaks from the mesh node.
271 # (v) Clear the tweaks from the duplicate mesh node (for case (c) only!)
272 #
273 # For (d), we have yet another limitation. Tweaks are not handled in this case
274 # because of the same circumstances which give rise to the limitation in the
275 # history section. As such, topological changes may result in some odd behaviour
276 # unless the workaround provided in the limitations section is used.
277 #
278 #
279 # How to use:
280 #
281 # To use this command there are several things that are required based on the needs
282 # of the command:
283 #
284 # Step 1: polyModifierFty
285 #
286 # 1) Create a factory derived from polyModifierFty
287 # 2) Find and assign any inputs that your modifier will need onto the factory.
288 # 3) Override the polyModifierFty.doIt() method of the factory
289 # 4) Place your modifier code into the doIt() method of the factory
290 #
291 # Step 2: polyModifierNode
292 #
293 # 1) Create a node derived from polyModifierNode
294 # 2) Add any additional input attributes onto the node
295 # 3) Associate the attributes (ie. inMesh --> affects --> outMesh)
296 # 4) Add an instance of your polyModifierFty to the node
297 # 5) Override the MPxNode.compute() method
298 # 6) Retrieve inputs from attributes, setup the factory and call its doIt() in compute()
299 #
300 # Step 3: polyModifierCmd
301 #
302 # 1) Create a command derived from polyModifierCmd
303 #
304 # ---
305 #
306 # 2) Override the polyModifierCmd.initModifierNode() method
307 # 3) Place your node setup code inside the initModifierNode()
308 #
309 # ---
310 #
311 # 4) Add an instance of your polyModifierFty to the command
312 # 5) Cache any input parameters for the factory on the command
313 # 6) Override the polyModifierCmd.directModifier() method
314 # 7) Place your factory setup code and call its doIt() in directModifier()
315 #
316 # ---
317 #
318 # 8) Override the MPxCommand.doIt() method
319 # 9) Place your setup code inside the doIt()
320 # 10) Place the polyModifierCmd setup code inside the doIt()
321 # (ie. setMeshNode(), setModifierNodeType())
322 # 11) Call polyModifierCmd._doModifyPoly() inside the doIt()
323 #
324 # ---
325 #
326 # 12) Override the MPxCommand.redoIt() method
327 # 13) Call polyModifierCmd.redoModifyPoly() in redoIt()
328 #
329 # ---
330 #
331 # 14) Override the MPxCommand.undoIt() method
332 # 15) Call polyModifierCmd.undoModifyPoly() in undoIt()
333 #
334 # For more details on each of these steps, please visit the associated method/class
335 # headers.
336 #
337 #
338 # Limitations:
339 #
340 # There is one limitation in polyModifierCmd:
341 #
342 # (1) Duplicate mesh created under the "No History / History turned on" case not undoable
343 #
344 # Case (1):
345 #
346 # Under the "No History / History turned on" case, history is allowed so the DG
347 # is used to perform the operation. However, every polyModifierNode requires
348 # an input mesh and without any prior history, a mesh input needs to be created.
349 # polyModifierCmd compensates for this by duplicating the meshNode and marking
350 # it as an intermediate object.
351 #
352 # The problem with this duplication is that the only duplicate method in the
353 # Maya API resides in MFnDagNode, which does not have an associated undo/redo
354 # mechanism. Attempting to manually delete the node by use of a DGmodifier or
355 # the delete command will break the undo/redo mechanism for the entire
356 # command. As a result, this duplicate mesh is a remnant of each instance of the
357 # command excluding undo/redo.
358 #
359 # To work past this limitation, a manual delete from the command line is
360 # required.
361 #
362 
363 from builtins import object
364 from builtins import range
365 import maya.OpenMaya as OpenMaya
366 import maya.OpenMayaMPx as OpenMayaMPx
367 import sys
368 
369 def statusError(message):
370  fullMsg = "Status failed: %s\n" % message
371  sys.stderr.write(fullMsg)
373  raise # called from exception handlers only, reraise exception
374 
375 def statusAssert(condition, message):
376  if (not condition):
377  fullMsg = "Assertion failed: %s\n" % message
378  sys.stderr.write(fullMsg)
380  assert(False)
381 
382 
383 class polyModifierCmd(OpenMayaMPx.MPxCommand):
384  def __init__(self):
385  OpenMayaMPx.MPxCommand.__init__(self)
386 
387  ##########################
388  ## polyModifierCmd Data ##
389  ##########################
390 
391  # polyMesh
392  self.__fDagPathInitialized = False
393  self.__fDagPath = OpenMaya.MDagPath()
394  self.__fDuplicateDagPath = OpenMaya.MDagPath()
395 
396  # Modifier Node Type
397  self.__fModifierNodeTypeInitialized = False
398  self.__fModifierNodeNameInitialized = False
399  self.__fModifierNodeType = OpenMaya.MTypeId()
400  self.__fModifierNodeName = ""
401 
402  # Node State Information
403  self.__fHasHistory = False
404  self.__fHasTweaks = False
405  self.__fHasRecordHistory = False
406 
407  # Cached Tweak Data (for undo)
408  self.__fTweakIndexArray = OpenMaya.MIntArray()
409  self.__fTweakVectorArray = OpenMaya.MFloatVectorArray()
410 
411  # Cached Mesh Data (for undo in the 'No History'/'History turned off' case)
412  self.__fMeshData = OpenMaya.MObject()
413 
414  # DG and DAG Modifier
415  #
416  # - We need both DAG and DG modifiers since the MDagModifier::createNode()
417  # method is overridden and specific to DAG nodes. So to keep
418  # the operations consistent we will only use the fDagModifier
419  # when dealing with the DAG.
420  #
421  # - There is a limitation between the reparentNode() and deleteNode()
422  # methods on the MDagModifier. The deleteNode() method does some
423  # preparation work before it enqueues itself in the MDagModifier list
424  # of operations, namely, it looks at it's parents and children and
425  # deletes them as well if they are the only parent/child of the node
426  # scheduled to be deleted.
427  #
428  # This conflicts with our call to MDagModifier::reparentNode(),
429  # since we want to reparent the shape of a duplicated node under
430  # another node and then delete the transform of that node. Now you
431  # can see that since the reparentNode() doesn't execute until after
432  # the MDagModifier::doIt() call, the scheduled deleteNode() call
433  # still sees the child and marks it for delete. The subsequent
434  # doIt() call reparents the shape and then deletes both it and the
435  # transform.
436  #
437  # To avoid this conflict, we separate the calls individually and
438  # perform the reparenting (by calling a doIt()) before the deleteNode()
439  # method is enqueued on the modifier.
440  #
441  self.__fDGModifier = OpenMaya.MDGModifier()
442  self.__fDagModifier = OpenMaya.MDagModifier()
443 
444 
445  ####################### PROTECTED #######################
446 
447  ####################################
448  ## polyModifierCmd Initialization ##
449  ####################################
450  def _setMeshNode(self, mesh):
451  """
452  Target polyMesh to modify
453  """
454  self.__fDagPath = mesh
455  self.__fDagPathInitialized = True
456 
457 
458  def _getMeshNode(self):
459  return self.__fDagPath
460 
461 
462  def _setModifierNodeType(self, nodeType):
463  """
464  Modifier Node Type
465  """
466  self.__fModifierNodeType = nodeType
467  self.__fModifierNodeTypeInitialized = True
468 
469 
470  def _setModifierNodeName(self, nodeName):
471  self.__fModifierNodeName = nodeName
472  self.__fModifierNodeNameInitialized = True
473 
474 
475  def _getModifierNodeType(self):
476  return self.__fModifierNodeType
477 
478 
479  def _getModifierNodeName(self):
480  return self.__fModifierNodeName
481 
482 
483  ###############################
484  ## polyModifierCmd Execution ##
485  ###############################
486  def _initModifierNode(self, modifierNode):
487  """
488  Derived classes should override this method if they wish to initialize
489  input attributes on the modifierNode
490  """
491  pass
492 
493 
494  def _directModifier(self, mesh):
495  """
496  Derived classes should override this method to provide direct
497  modifications on the meshNode in the case where no history exists and
498  construction history is turned off. (ie. no DG operations desired)
499 
500  This method is called only if history does not exist and history is turned
501  off. At this point, a handle to the meshNode is passed in so a derived
502  class may directly modify the mesh.
503  """
504  pass
505 
506 
507  def _doModifyPoly(self):
508  if self.__isCommandDataValid():
509  # Get the state of the polyMesh
510  #
511  self.__collectNodeState()
512 
513  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
514  meshNode = self.__fDagPath.node()
515 
516  # Pre-process the mesh - Cache old mesh (including tweaks, if applicable)
517  #
518  self.__cacheMeshData()
519  self.__cacheMeshTweaks()
520 
521  # Call the directModifier
522  #
523  self._directModifier(meshNode)
524  else:
525  modifierNode = self.__createModifierNode()
526  self._initModifierNode(modifierNode)
527  self.__connectNodes(modifierNode)
528 
529 
530  def _redoModifyPoly(self):
531  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
532  meshNode = self.__fDagPath.node()
533  # Call the directModifier - No need to pre-process the mesh data again
534  # since we already have it.
535  #
536  self._directModifier(meshNode)
537  else:
538  # Call the redo on the DG and DAG modifiers
539  #
540  if self.__fHasHistory:
541  self.__fDagModifier.doIt()
542  self.__fDGModifier.doIt()
543 
544 
545  def _undoModifyPoly(self):
546  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
547  self.__undoDirectModifier()
548  else:
549  self.__fDGModifier.undoIt()
550 
551  # undoCachedMesh must be called before undoTweakProcessing because
552  # undoCachedMesh copies the original mesh *without* tweaks back onto
553  # the existing mesh. Any changes done before the copy will be lost.
554  #
555  if not self.__fHasHistory:
556  try:
557  self.__undoCachedMesh()
558  except:
559  statusError("undoCachedMesh")
560  self.__fDagModifier.undoIt()
561 
562  try:
563  self.__undoTweakProcessing()
564  except:
565  statusError("undoTweakProcessing")
566 
567 
568  ####################### PRIVATE #######################
569 
570  ##############################################
571  ## polyModifierCmd Internal Processing Data ##
572  ##############################################
573 
574  # This structure is used to maintain the data vital to the modifyPoly method.
575  # It is necessary to simplify parameter passing between the methods used inside
576  # modifyPoly (specifically inside connectNodes()). The diagram below dictates
577  # the naming style used:
578  #
579  # NOTE: modifierNode is intentionally left out of this structure since it
580  # is given protected access to derived classes.
581  #
582  # Before:
583  #
584  # (upstreamNode) *src -> dest* (meshNode)
585  #
586  # After:
587  #
588  # (upstreamNode) *src -> dest* (modifierNode) *src -> dest* (meshNode)
589  #
590  class __modifyPolyData(object):
591  def __init__(self):
592  self.meshNodeTransform = OpenMaya.MObject()
593  self.meshNodeShape = OpenMaya.MObject()
594  self.meshNodeDestPlug = OpenMaya.MPlug()
595  self.meshNodeDestAttr = OpenMaya.MObject()
596 
597  self.upstreamNodeTransform = OpenMaya.MObject()
598  self.upstreamNodeShape = OpenMaya.MObject()
599  self.upstreamNodeSrcPlug = OpenMaya.MPlug()
600  self.upstreamNodeSrcAttr = OpenMaya.MObject()
601 
602  self.modifierNodeSrcAttr = OpenMaya.MObject()
603  self.modifierNodeDestAttr = OpenMaya.MObject()
604 
605  self.tweakNode = OpenMaya.MObject()
606  self.tweakNodeSrcAttr = OpenMaya.MObject()
607  self.tweakNodeDestAttr = OpenMaya.MObject()
608 
609 
610  ######################################
611  ## polyModifierCmd Internal Methods ##
612  ######################################
613 
614  def __isCommandDataValid(self):
615  valid = True
616 
617  # Check the validity of the DAG path
618  #
619  if self.__fDagPathInitialized:
620  self.__fDagPath.extendToShape()
621  if (not self.__fDagPath.isValid()) or (self.__fDagPath.apiType() != OpenMaya.MFn.kMesh):
622  valid = False
623  else:
624  valid = False
625 
626  # Check the validity of the Modifier node type/name
627  #
628  if (not self.__fModifierNodeTypeInitialized) and (not self.__fModifierNodeNameInitialized):
629  valid = False
630 
631  return valid
632 
633 
634  def __collectNodeState(self):
635  """
636  Collect node state information on the given polyMeshShape
637  - HasHistory (Construction History exists)
638  - HasTweaks
639  - HasRecordHistory (Construction History is turned on)
640  """
641  self.__fDagPath.extendToShape()
642  meshNodeShape = self.__fDagPath.node()
643 
644  depNodeFn = OpenMaya.MFnDependencyNode()
645  depNodeFn.setObject(meshNodeShape)
646 
647  inMeshPlug = depNodeFn.findPlug("inMesh")
648  self.__fHasHistory = inMeshPlug.isConnected()
649 
650  # Tweaks exist only if the multi "pnts" attribute contains plugs
651  # which contain non-zero tweak values. Use false, until proven true
652  # search algorithm.
653  #
654  self.__fHasTweaks = False
655  tweakPlug = depNodeFn.findPlug("pnts")
656  if not tweakPlug.isNull():
657  # ASSERT: tweakPlug should be an array plug!
658  #
659  statusAssert(tweakPlug.isArray(),
660  "tweakPlug.isArray() -- tweakPlug is not an array plug")
661 
662  numElements = tweakPlug.numElements()
663  for i in range(numElements):
664  tweak = tweakPlug.elementByPhysicalIndex(i)
665  if not tweak.isNull():
666  tweakData = self.__getFloat3PlugValue(tweak)
667  if 0 != tweakData.x or 0 != tweakData.y or 0 != tweakData.z:
668  self.__fHasTweaks = True
669  break
670 
671  result = 0
672  OpenMaya.MGlobal.executeCommand("constructionHistory -q -tgl", result)
673  self.__fHasRecordHistory = (0 != result)
674 
675 
676  # Modifier node methods
677  #
678  def __createModifierNode(self):
679  modifierNode = OpenMaya.MObject()
680  if self.__fModifierNodeTypeInitialized or self.__fModifierNodeNameInitialized:
681  if self.__fModifierNodeTypeInitialized:
682  modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeType)
683  elif self.__fModifierNodeNameInitialized:
684  modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeName)
685 
686  # Check to make sure that we have a modifier node of the appropriate type.
687  # Requires an inMesh and outMesh attribute.
688  #
689  depNodeFn = OpenMaya.MFnDependencyNode(modifierNode)
690  inMeshAttr = depNodeFn.attribute("inMesh")
691  outMeshAttr = depNodeFn.attribute("outMesh")
692 
693  statusAssert(not (inMeshAttr.isNull() or outMeshAttr.isNull()),
694  "Invalid Modifier Node: inMesh and outMesh attributes are required.")
695  return modifierNode
696 
697 
698  # Node processing methods (need to be executed in this order)
699  #
700  def __processMeshNode(self, data):
701  # Declare our function sets. Use MFnDagNode here so
702  # we can retrieve the parent transform.
703  #
704  dagNodeFn = OpenMaya.MFnDagNode()
705 
706  # Use the DAG path to retrieve our mesh shape node.
707  #
708  data.meshNodeShape = self.__fDagPath.node()
709  dagNodeFn.setObject(data.meshNodeShape)
710 
711  # ASSERT: meshNodeShape node should have a parent transform!
712  #
713  statusAssert(0 < dagNodeFn.parentCount(),
714  "0 < dagNodeFn.parentCount() -- meshNodeshape has no parent transform")
715  data.meshNodeTransform = dagNodeFn.parent(0)
716 
717  data.meshNodeDestPlug = dagNodeFn.findPlug("inMesh")
718  data.meshNodeDestAttr = data.meshNodeDestPlug.attribute()
719 
720 
721  def __processUpstreamNode(self, data):
722  # Declare our function sets - Although dagNodeFn derives from depNodeFn, we need
723  # both since dagNodeFn can only refer to DAG objects.
724  # We will use depNodeFn for all times other when dealing
725  # with the DAG.
726  #
727  depNodeFn = OpenMaya.MFnDependencyNode()
728  dagNodeFn = OpenMaya.MFnDagNode()
729 
730  # Use the selected node's plug connections to find the upstream plug.
731  # Since we are looking at the selected node's inMesh attribute, it will
732  # always have only one connection coming in if it has history, and none
733  # otherwise.
734  #
735  # If there is no history, copy the selected node and place it ahead of the
736  # modifierNode as the new upstream node so that the modifierNode has an
737  # input mesh to operate on.
738  #
739  tempPlugArray = OpenMaya.MPlugArray()
740 
741  if self.__fHasHistory:
742  # Since we have history, look for what connections exist on the
743  # meshNode "inMesh" plug. "inMesh" plugs should only ever have one
744  # connection.
745  #
746  data.meshNodeDestPlug.connectedTo(tempPlugArray, True, False)
747 
748  # ASSERT: Only one connection should exist on meshNodeShape.inMesh!
749  #
750  statusAssert(tempPlugArray.length() == 1,
751  "tempPlugArray.length() == 1 -- 0 or >1 connections on meshNodeShape.inMesh")
752  data.upstreamNodeSrcPlug = tempPlugArray[0]
753 
754  # Construction history only deals with shapes, so we can grab the
755  # upstreamNodeShape off of the source plug.
756  #
757  data.upstreamNodeShape = data.upstreamNodeSrcPlug.node()
758  depNodeFn.setObject(data.upstreamNodeShape)
759  data.upstreamNodeSrcAttr = data.upstreamNodeSrcPlug.attribute()
760 
761  # Disconnect the upstream node and the selected node, so we can
762  # replace them with our own connections below.
763  #
764  self.__fDGModifier.disconnect(data.upstreamNodeSrcPlug, data.meshNodeDestPlug)
765 
766  else: # No History (!fHasHistory)
767  # Use the DAG node function set to duplicate the shape of the meshNode.
768  # The duplicate method will return an MObject handle to the transform
769  # of the duplicated shape, so traverse the dag to locate the shape. Store
770  # this duplicate shape as our "upstream" node to drive the input for the
771  # modifierNode.
772  #
773  dagNodeFn.setObject(data.meshNodeShape)
774  data.upstreamNodeTransform = dagNodeFn.duplicate(False, False)
775  dagNodeFn.setObject(data.upstreamNodeTransform)
776 
777  # Ensure that our upstreamNode is pointing to a shape.
778  #
779  statusAssert(0 < dagNodeFn.childCount(),
780  "0 < dagNodeFn.childCount() -- Duplicate meshNode transform has no shape.")
781  data.upstreamNodeShape = dagNodeFn.child(0)
782 
783  # Re-parent the upstreamNodeShape under our original transform
784  #
785  try:
786  self.__fDagModifier.reparentNode(data.upstreamNodeShape, data.meshNodeTransform)
787  except:
788  statusError("reparentNode")
789 
790  # Perform the DAG re-parenting
791  #
792  # Note: This reparent must be performed before the deleteNode() is called.
793  # See polyModifierCmd.h (see definition of fDagModifier) for more details.
794  #
795  try:
796  self.__fDagModifier.doIt()
797  except:
798  statusError("fDagModifier.doIt()")
799 
800  # Mark the upstreamNodeShape (the original shape) as an intermediate object
801  # (making it invisible to the user)
802  #
803  dagNodeFn.setObject(data.upstreamNodeShape)
804  dagNodeFn.setIntermediateObject(True)
805 
806  # Get the upstream node source attribute
807  #
808  data.upstreamNodeSrcAttr = dagNodeFn.attribute("outMesh")
809  data.upstreamNodeSrcPlug = dagNodeFn.findPlug("outMesh")
810 
811  # Remove the duplicated transform node (clean up)
812  #
813  try:
814  self.__fDagModifier.deleteNode(data.upstreamNodeTransform)
815  except:
816  statusError("deleteNode")
817 
818  # Perform the DAG delete node
819  #
820  # Note: This deleteNode must be performed after the reparentNode() method is
821  # completed. See polyModifierCmd.h (see definition of fDagModifier) for
822  # details.
823  #
824  try:
825  self.__fDagModifier.doIt()
826  except:
827  statusError("fDagModifier.doIt()")
828 
829  # Cache the DAG path to the duplicate shape
830  #
831  dagNodeFn.getPath(self.__fDuplicateDagPath)
832 
833 
834  def __processModifierNode(self, modifierNode, data):
835  depNodeFn = OpenMaya.MFnDependencyNode(modifierNode)
836  data.modifierNodeSrcAttr = depNodeFn.attribute("outMesh")
837  data.modifierNodeDestAttr = depNodeFn.attribute("inMesh")
838 
839 
840  def __processTweaks(self, data):
841  # Clear tweak undo information (to be rebuilt)
842  #
843  self.__fTweakIndexArray.clear()
844  self.__fTweakVectorArray.clear()
845 
846  # Extract the tweaks and place them into a polyTweak node. This polyTweak node
847  # will be placed ahead of the modifier node to maintain the order of operations.
848  # Special care must be taken into recreating the tweaks:
849  #
850  # 1) Copy tweak info (including connections!)
851  # 2) Remove tweak info from both meshNode and a duplicate meshNode (if applicable)
852  # 3) Cache tweak info for undo operations
853  #
854  if self.__fHasTweaks:
855  # Declare our function sets
856  #
857  depNodeFn = OpenMaya.MFnDependencyNode()
858 
859  # Declare our tweak processing variables
860  #
861  tweakDataArray = OpenMaya.MObjectArray()
862  tweakSrcConnectionCountArray = OpenMaya.MIntArray()
863  tweakSrcConnectionPlugArray = OpenMaya.MPlugArray()
864  tweakDstConnectionCountArray = OpenMaya.MIntArray()
865  tweakDstConnectionPlugArray = OpenMaya.MPlugArray()
866  tempPlugArray = OpenMaya.MPlugArray()
867 
868  # Create the tweak node and get its attributes
869  #
870  data.tweakNode = self.__fDGModifier.createNode("polyTweak")
871  depNodeFn.setObject(data.tweakNode)
872  data.tweakNodeSrcAttr = depNodeFn.attribute("output")
873  data.tweakNodeDestAttr = depNodeFn.attribute("inputPolymesh")
874  tweakNodeTweakAttr = depNodeFn.attribute("tweak")
875 
876  depNodeFn.setObject(data.meshNodeShape)
877  meshTweakPlug = depNodeFn.findPlug("pnts")
878 
879  # ASSERT: meshTweakPlug should be an array plug!
880  #
881  statusAssert(meshTweakPlug.isArray(),
882  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug")
883 
884  # Gather meshTweakPlug data
885  #
886  numElements = meshTweakPlug.numElements()
887  for i in range(numElements):
888  # MPlug.numElements() only returns the number of physical elements
889  # in the array plug. Thus we must use elementByPhysical index when using
890  # the index i.
891  #
892  tweak = meshTweakPlug.elementByPhysicalIndex(i)
893 
894  # If the method fails, the element is NULL. Only append the index
895  # if it is a valid plug.
896  #
897  if not tweak.isNull():
898  # Cache the logical index of this element plug
899  #
900  logicalIndex = tweak.logicalIndex()
901 
902  # Collect tweak data and cache the indices and float vectors
903  #
904  tweakData = tweak.asMObject()
905  tweakDataArray.append(tweakData)
906  tweakVector = self.__getFloat3PlugValue(tweak)
907  self.__fTweakIndexArray.append(logicalIndex)
908  self.__fTweakVectorArray.append(tweakVector)
909 
910  # Collect tweak connection data
911  #
912  # Parse down to the deepest level of the plug tree and check
913  # for connections - look at the child nodes of the element plugs.
914  # If any connections are found, record the connection and disconnect
915  # it.
916  #
917 
918  # ASSERT: The element plug should be compound!
919  #
920  statusAssert(tweak.isCompound(),
921  "tweak.isCompound() -- Element tweak plug is not compound")
922 
923  numChildren = tweak.numChildren()
924  for j in range(numChildren):
925  tweakChild = tweak.child(j)
926  if tweakChild.isConnected():
927  # Get all connections with this plug as source, if they exist
928  #
929  tempPlugArray.clear()
930  if tweakChild.connectedTo(tempPlugArray, False, True):
931  numSrcConnections = tempPlugArray.length()
932  tweakSrcConnectionCountArray.append(numSrcConnections)
933 
934  for k in range(numSrcConnections):
935  tweakSrcConnectionPlugArray.append(tempPlugArray[k])
936  self.__fDGModifier.disconnect(tweakChild, tempPlugArray[k])
937  else:
938  tweakSrcConnectionCountArray.append(0)
939 
940  # Get the connection with this plug as destination, if it exists
941  #
942  tempPlugArray.clear()
943  if tweakChild.connectedTo(tempPlugArray, True, False):
944  # ASSERT: tweakChild should only have one connection as destination!
945  #
946  statusAssert(tempPlugArray.length() == 1,
947  "tempPlugArray.length() == 1 -- 0 or >1 connections on tweakChild")
948 
949  tweakDstConnectionCountArray.append(1)
950  tweakDstConnectionPlugArray.append(tempPlugArray[0])
951  self.__fDGModifier.disconnect(tempPlugArray[0], tweakChild)
952  else:
953  tweakDstConnectionCountArray.append(0)
954  else:
955  tweakSrcConnectionCountArray.append(0)
956  tweakDstConnectionCountArray.append(0)
957 
958  # Apply meshTweakPlug data to our polyTweak node
959  #
960  polyTweakPlug = OpenMaya.MPlug(data.tweakNode, tweakNodeTweakAttr)
961  numTweaks = self.__fTweakIndexArray.length()
962  srcOffset = 0
963  dstOffset = 0
964  for i in range(numTweaks):
965  # Apply tweak data
966  #
967  tweak = polyTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i])
968  tweak.setMObject(tweakDataArray[i])
969 
970  # ASSERT: Element plug should be compound!
971  #
972  statusAssert(tweak.isCompound(),
973  "tweak.isCompound() -- Element plug, 'tweak', is not compound")
974 
975  numChildren = tweak.numChildren()
976  for j in range(numChildren):
977  tweakChild = tweak.child(j)
978 
979  # Apply tweak source connection data
980  #
981  if 0 < tweakSrcConnectionCountArray[i*numChildren + j]:
982  k = 0
983  while (k < tweakSrcConnectionCountArray[i*numChildren + j]):
984  self.__fDGModifier.connect(tweakChild, tweakSrcConnectionPlugArray[srcOffset])
985  srcOffset += 1
986  k += 1
987 
988  # Apply tweak destination connection data
989  #
990  if 0 < tweakDstConnectionCountArray[i*numChildren + j]:
991  self.__fDGModifier.connect(tweakDstConnectionPlugArray[dstOffset], tweakChild)
992  dstOffset += 1
993 
994 
995  # Node connection method
996  #
997  def __connectNodes(self, modifierNode):
998  """
999  This method connects up the modifier nodes, while accounting for DG factors
1000  such as construction history and tweaks. The method has a series of steps which
1001  it runs through to process nodes under varying circumstances:
1002 
1003  1) Gather meshNode connection data (ie. attributes and plugs)
1004 
1005  2) Gather upstreamNode data - This is history-dependent. If the node has history,
1006  an actual upstreamNode exists and that is used to
1007  drive the input of our modifierNode.
1008 
1009  Otherwise, if the node does not have history, the
1010  meshNode is duplicated, set as an intermediate object
1011  and regarded as our new upstreamNode which will drive
1012  the input of our modifierNode. The case with history
1013  already has this duplicate meshNode at the top, driving
1014  all other history nodes and serving as a reference
1015  to the "original state" of the node before any
1016  modifications.
1017 
1018  3) Gather modifierNode connection data
1019 
1020  4) Process tweak data (if it exists) - This is history-dependent. If there is
1021  history, the tweak data is extracted and deleted
1022  from the meshNode and encapsulated inside a
1023  polyTweak node. The polyTweak node is then
1024  inserted ahead of the modifier node.
1025 
1026  If there is no history, the same is done as
1027  in the history case, except the tweaks are
1028  deleted from the duplicate meshNode in addition
1029  to the actual meshNode.
1030 
1031  5) Connect the nodes
1032 
1033  6) Collapse/Bake nodes into the actual meshNode if the meshNode had no previous
1034  construction history and construction history recording is turned off.
1035  (ie. (!fHasHistory && !fHasRecordHistory) == true )
1036 
1037  """
1038  # Declare our internal processing data structure (see polyModifierCmd.h for definition)
1039  #
1040  data = polyModifierCmd.__modifyPolyData()
1041 
1042  # Get the mesh node, plugs and attributes
1043  #
1044  try:
1045  self.__processMeshNode(data)
1046  except:
1047  statusError("processMeshNode")
1048 
1049  # Get upstream node, plugs and attributes
1050  #
1051  try:
1052  self.__processUpstreamNode(data)
1053  except:
1054  statusError("processUpstreamNode")
1055 
1056  # Get the modifierNode attributes
1057  #
1058  try:
1059  self.__processModifierNode(modifierNode, data)
1060  except:
1061  statusError("processModifierNode")
1062 
1063  # Process tweaks on the meshNode
1064  #
1065  try:
1066  self.__processTweaks(data)
1067  except:
1068  statusError("processTweaks")
1069 
1070  # Connect the nodes
1071  #
1072  if self.__fHasTweaks:
1073  tweakDestPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeDestAttr)
1074  self.__fDGModifier.connect(data.upstreamNodeSrcPlug, tweakDestPlug)
1075 
1076  tweakSrcPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeSrcAttr)
1077  modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr)
1078  self.__fDGModifier.connect(tweakSrcPlug, modifierDestPlug)
1079  else:
1080  modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr)
1081  self.__fDGModifier.connect(data.upstreamNodeSrcPlug, modifierDestPlug)
1082 
1083  modifierSrcPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeSrcAttr)
1084  meshDestAttr = OpenMaya.MPlug(data.meshNodeShape, data.meshNodeDestAttr)
1085  self.__fDGModifier.connect(modifierSrcPlug, meshDestAttr)
1086 
1087  self.__fDGModifier.doIt()
1088 
1089 
1090  # Mesh caching methods - Only used in the directModifier case
1091  #
1092  def __cacheMeshData(self):
1093  depNodeFn = OpenMaya.MFnDependencyNode()
1094  dagNodeFn = OpenMaya.MFnDagNode()
1095 
1096  meshNode = self.__fDagPath.node()
1097 
1098  # Duplicate the mesh
1099  #
1100  dagNodeFn.setObject(meshNode)
1101  dupMeshNode = dagNodeFn.duplicate()
1102 
1103  dupMeshDagPath = OpenMaya.MDagPath()
1104  OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath)
1105  dupMeshDagPath.extendToShape()
1106 
1107  depNodeFn.setObject(dupMeshDagPath.node())
1108  try:
1109  dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1110  except:
1111  statusError("Could not retrieve outMesh")
1112 
1113  # Retrieve the meshData
1114  #
1115  try:
1116  self.__fMeshData = dupMeshNodeOutMeshPlug.asMObject()
1117  except:
1118  statusError("Could not retrieve meshData")
1119 
1120  # Delete the duplicated node
1121  #
1122  OpenMaya.MGlobal.deleteNode(dupMeshNode)
1123 
1124 
1125  def __cacheMeshTweaks(self):
1126  # Clear tweak undo information (to be rebuilt)
1127  #
1128  self.__fTweakIndexArray.clear()
1129  self.__fTweakVectorArray.clear()
1130 
1131  # Extract the tweaks and store them in our local tweak cache members
1132  #
1133  if self.__fHasTweaks:
1134  # Declare our function sets
1135  #
1136  depNodeFn = OpenMaya.MFnDependencyNode()
1137 
1138  meshNode = self.__fDagPath.node()
1139 
1140  depNodeFn.setObject(meshNode)
1141  meshTweakPlug = depNodeFn.findPlug("pnts")
1142 
1143  # ASSERT: meshTweakPlug should be an array plug!
1144  #
1145  statusAssert(meshTweakPlug.isArray(),
1146  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" )
1147 
1148  # Gather meshTweakPlug data
1149  #
1150  numElements = meshTweakPlug.numElements()
1151  for i in range(numElements):
1152  # MPlug.numElements() only returns the number of physical elements
1153  # in the array plug. Thus we must use elementByPhysical index when using
1154  # the index i.
1155  #
1156  tweak = meshTweakPlug.elementByPhysicalIndex(i)
1157 
1158  # If the method fails, the element is NULL. Only append the index
1159  # if it is a valid plug.
1160  #
1161  if not tweak.isNull():
1162  # Cache the logical index of this element plug
1163  #
1164  logicalIndex = tweak.logicalIndex()
1165 
1166  # Collect tweak data and cache the indices and float vectors
1167  #
1168  tweakVector = self.__getFloat3PlugValue(tweak)
1169  self.__fTweakIndexArray.append(logicalIndex)
1170  self.__fTweakVectorArray.append(tweakVector)
1171 
1172 
1173  # Undo methods
1174  #
1175  def __undoCachedMesh(self):
1176  # Only need to restore the cached mesh if there was no history. Also
1177  # check to make sure that we are in the record history state.
1178  #
1179  statusAssert(self.__fHasRecordHistory, "fHasRecordHistory == true")
1180 
1181  if not self.__fHasHistory:
1182  depNodeFn = OpenMaya.MFnDependencyNode()
1183 
1184  meshNodeShape = self.__fDagPath.node()
1185  dupMeshNodeShape = self.__fDuplicateDagPath.node()
1186 
1187  depNodeFn.setObject(meshNodeShape)
1188  meshNodeName = depNodeFn.name()
1189  try:
1190  meshNodeDestPlug = depNodeFn.findPlug("inMesh")
1191  except:
1192  statusError("Could not retrieve inMesh")
1193  try:
1194  meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1195  except:
1196  statusError("Could not retrieve outMesh")
1197 
1198  depNodeFn.setObject(dupMeshNodeShape)
1199  try:
1200  dupMeshNodeSrcPlug = depNodeFn.findPlug("outMesh")
1201  except:
1202  statusError("Could not retrieve outMesh")
1203 
1204  # For the case with tweaks, we cannot write the mesh directly back onto
1205  # the cachedInMesh, since the shape can have out of date information from the
1206  # cachedInMesh, thus we temporarily connect the duplicate mesh shape to the
1207  # mesh shape and force a DG evaluation.
1208  #
1209  # For the case without tweaks, we can simply write onto the outMesh, since
1210  # the shape relies solely on an outMesh when there is no history nor tweaks.
1211  #
1212  if self.__fHasTweaks:
1213  dgModifier = OpenMaya.MDGModifier()
1214  dgModifier.connect(dupMeshNodeSrcPlug, meshNodeDestPlug)
1215  try:
1216  dgModifier.doIt()
1217  except:
1218  statusError("Could not connect dupMeshNode -> meshNode")
1219 
1220  # Need to force a DG evaluation now that the input has been changed.
1221  #
1222  cmd = "dgeval -src %s.inMesh" % meshNodeName
1223  try:
1224  OpenMaya.MGlobal.executeCommand(cmd, False, False)
1225  except:
1226  statusError("Could not force DG eval")
1227 
1228  # Disconnect the duplicate meshNode now
1229  #
1230  dgModifier.undoIt()
1231  else:
1232  try:
1233  meshData = dupMeshNodeSrcPlug.asMObject()
1234  try:
1235  meshNodeOutMeshPlug.setMObject(meshData)
1236  except:
1237  statusError("Could not set outMesh")
1238  except:
1239  statusError("Could not retrieve meshData")
1240 
1241 
1242  def __undoTweakProcessing(self):
1243  if self.__fHasTweaks:
1244  meshNodeShape = self.__fDagPath.node()
1245  depNodeFn = OpenMaya.MFnDependencyNode()
1246  depNodeFn.setObject(meshNodeShape)
1247  meshTweakPlug = depNodeFn.findPlug("pnts")
1248 
1249  statusAssert(meshTweakPlug.isArray(),
1250  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug")
1251 
1252  numElements = self.__fTweakIndexArray.length()
1253 
1254  for i in range(numElements):
1255  tweak = meshTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i])
1256  tweakData = self.__getFloat3asMObject(self.__fTweakVectorArray[i])
1257  tweak.setMObject(tweakData)
1258 
1259  # In the case of no history, the duplicate node shape will be disconnected on undo
1260  # so, there is no need to undo the tweak processing on it.
1261  #
1262 
1263 
1264  def __undoDirectModifier(self):
1265  depNodeFn = OpenMaya.MFnDependencyNode()
1266  dagNodeFn = OpenMaya.MFnDagNode()
1267 
1268  meshNode = self.__fDagPath.node()
1269  depNodeFn.setObject( meshNode )
1270 
1271  # For the case with tweaks, we cannot write the mesh directly back onto
1272  # the cachedInMesh, since the shape can have out of date information from the
1273  # cachedInMesh. Thus we temporarily create an duplicate mesh, place our
1274  # old mesh on the outMesh attribute of our duplicate mesh, connect the
1275  # duplicate mesh shape to the mesh shape, and force a DG evaluation.
1276  #
1277  # For the case without tweaks, we can simply write onto the outMesh, since
1278  # the shape relies solely on an outMesh when there is no history nor tweaks.
1279  #
1280  if self.__fHasTweaks:
1281  # Retrieve the inMesh and name of our mesh node (for the DG eval)
1282  #
1283  depNodeFn.setObject(meshNode)
1284  try:
1285  meshNodeInMeshPlug = depNodeFn.findPlug("inMesh")
1286  except:
1287  statusError("Could not retrieve inMesh")
1288 
1289  meshNodeName = depNodeFn.name()
1290 
1291  # Duplicate our current mesh
1292  #
1293  dagNodeFn.setObject(meshNode)
1294  dupMeshNode = dagNodeFn.duplicate()
1295 
1296  # The dagNodeFn.duplicate() returns a transform, but we need a shape
1297  # so retrieve the DAG path and extend it to the shape.
1298  #
1299  dupMeshDagPath = OpenMaya.MDagPath()
1300  OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath)
1301  dupMeshDagPath.extendToShape()
1302 
1303  # Retrieve the outMesh of the duplicate mesh and set our mesh data back
1304  # on it.
1305  #
1306  depNodeFn.setObject(dupMeshDagPath.node())
1307  try:
1308  dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1309  except:
1310  statusError("Could not retrieve outMesh")
1311  dupMeshNodeOutMeshPlug.setMObject(self.__fMeshData)
1312 
1313  # Temporarily connect the duplicate mesh node to our mesh node
1314  #
1315  dgModifier = OpenMaya.MDGModifier()
1316  dgModifier.connect(dupMeshNodeOutMeshPlug, meshNodeInMeshPlug)
1317  try:
1318  dgModifier.doIt()
1319  except:
1320  statusError("Could not connect dupMeshNode -> meshNode")
1321 
1322  # Need to force a DG evaluation now that the input has been changed.
1323  #
1324  cmd = "dgeval -src %s.inMesh" % meshNodeName
1325  try:
1326  OpenMaya.MGlobal.executeCommand(cmd, False, False)
1327  except:
1328  statusError("Could not force DG eval")
1329 
1330  # Disconnect and delete the duplicate mesh node now
1331  #
1332  dgModifier.undoIt()
1333  OpenMaya.MGlobal.deleteNode(dupMeshNode)
1334 
1335  # Restore the tweaks on the mesh
1336  #
1337  self.__undoTweakProcessing()
1338  else:
1339  # Restore the original mesh by writing the old mesh data (fMeshData) back
1340  # onto the outMesh of our meshNode
1341  #
1342  depNodeFn.setObject(meshNode)
1343  try:
1344  meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1345  except:
1346  statusError("Could not retrieve outMesh")
1347  try:
1348  meshNodeOutMeshPlug.setMObject(self.__fMeshData)
1349  except:
1350  statusError("Could not set meshData")
1351 
1352 
1353  #####################################
1354  ## polyModifierCmd Utility Methods ##
1355  #####################################
1356 
1357  def __getFloat3PlugValue(self, plug):
1358  # Retrieve the value as an MObject
1359  object = plug.asMObject()
1360 
1361  # Convert the MObject to a float3
1362  numDataFn = OpenMaya.MFnNumericData(object)
1363  xParam = OpenMaya.MScriptUtil(0.0)
1364  xPtr = xParam.asFloatPtr()
1365  yParam = OpenMaya.MScriptUtil(0.0)
1366  yPtr = yParam.asFloatPtr()
1367  zParam = OpenMaya.MScriptUtil(0.0)
1368  zPtr = zParam.asFloatPtr()
1369  numDataFn.getData3Float(xPtr, yPtr, zPtr)
1370  return OpenMaya.MFloatVector(
1371  xParam.getFloat(xPtr),
1372  yParam.getFloat(yPtr),
1373  zParam.getFloat(zPtr))
1374 
1375 
1376  def __getFloat3asMObject(self, value):
1377  # Convert the float value into an MObject
1378  numDataFn = OpenMaya.MFnNumericData()
1379  numDataFn.create(OpenMaya.MFnNumericData.k3Float)
1380  numDataFn.setData3Float(value[0], value[1], value[2])
1381  return numDataFn.object()
1382 
1383 
1384 #####################################################################
1385 ## FACTORY ##########################################################
1386 #####################################################################
1387 
1388 # Overview:
1389 #
1390 # The polyModifierFty class is the main workhorse of the polyModifierCmd operation.
1391 # It is here that the actual operation is implemented. The idea of the factory is
1392 # to create a single implementation of the modifier that can be reused in more than
1393 # one place.
1394 #
1395 # As such, the details of the factory are quite simple. Each factory contains a doIt()
1396 # method which should be overridden. This is the method which will be called by the
1397 # node and the command when a modifier is requested.
1398 #
1399 # How to use:
1400 #
1401 # 1) Create a factory derived from polyModifierFty
1402 # 2) Add any input methods and members to the factory
1403 # 3) Override the polyModifierFty.doIt() method
1404 #
1405 # (a) Retrieve the inputs from the class
1406 # (b) Process the inputs
1407 # (c) Perform the modifier
1408 #
1409 #
1410 
1411 class polyModifierFty(object):
1412  def __init__(self):
1413  pass
1414 
1415  def doIt(self):
1416  pass
1417 
1418 
1419 #####################################################################
1420 ## NODE #############################################################
1421 #####################################################################
1422 
1423 # Overview:
1424 #
1425 # The polyModifierNode class is a intermediate class used by polyModifierCmd to
1426 # modify a polygonal object in Maya. The purpose of the polyModifierNode is to
1427 # generalize a node that can receive an input mesh object, modify the mesh and
1428 # return the modified mesh.
1429 #
1430 # polyModifierNode is an abstraction which does not need to know about the DG
1431 # and simply needs to know about the process outlined above. polyModifierCmd
1432 # manages when and how this node will be used.
1433 #
1434 # Each polyModifierNode is recommended to contain an instance of a polyModifierFty
1435 # which will do the actual work, leaving only the node setup and attribute
1436 # associations to the node. The recommended structure of a polyModifierNode is
1437 # as follows:
1438 #
1439 # _____________
1440 # / ___ \
1441 # / / \ \
1442 # O | Node | Fty | | O
1443 # | \ \___/ / |
1444 # | \_____________/ |
1445 # inMesh outMesh
1446 #
1447 #
1448 # The purpose of the node is to simply define the attributes (inputs and outputs) of
1449 # the node and associate which attribute affects each other. This is basic node setup
1450 # for a DG node. Using the above structure, the node's inherited "compute()" method
1451 # (from MPxNode) should retrieve the inputs and pass the appropriate data down to the
1452 # polyModifierFty for processing.
1453 #
1454 #
1455 # How to use:
1456 #
1457 # (1) Create a class derived from polyModifierNode
1458 # (2) Define and associate inMesh and outMesh attributes (inMesh --> affects --> outMesh)
1459 # (3) Add any additional attributes specific to the derived node and setup associations
1460 # (4) Define an instance of your specific polyModifierFty to perform the operation on the node
1461 # (5) Override the MPxNode::compute() method
1462 # (6) Inside compute():
1463 #
1464 # (a) Retrieve input attributes
1465 # (b) Use inputs to setup your factory to operate on the given mesh
1466 # (c) Call the factory's inherited doIt() method
1467 #
1468 #
1469 
1470 class polyModifierNode(OpenMayaMPx.MPxNode):
1471  # There needs to be a MObject handle declared for each attribute that
1472  # the node will have. These handles are needed for getting and setting
1473  # the values later.
1474  #
1475  inMesh = OpenMaya.MObject()
1476  outMesh = OpenMaya.MObject()
1477 
1478  def __init__(self):
1479  OpenMayaMPx.MPxNode.__init__(self)