scripted/polyModifier.py

scripted/polyModifier.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 import maya.OpenMaya as OpenMaya
364 import maya.OpenMayaMPx as OpenMayaMPx
365 import sys
366 
367 def statusError(message):
368  fullMsg = "Status failed: %s\n" % message
369  sys.stderr.write(fullMsg)
371  raise # called from exception handlers only, reraise exception
372 
373 def statusAssert(condition, message):
374  if (not condition):
375  fullMsg = "Assertion failed: %s\n" % message
376  sys.stderr.write(fullMsg)
378  assert(False)
379 
380 
381 class polyModifierCmd(OpenMayaMPx.MPxCommand):
382  def __init__(self):
383  OpenMayaMPx.MPxCommand.__init__(self)
384 
385  ##########################
386  ## polyModifierCmd Data ##
387  ##########################
388 
389  # polyMesh
390  self.__fDagPathInitialized = False
391  self.__fDagPath = OpenMaya.MDagPath()
392  self.__fDuplicateDagPath = OpenMaya.MDagPath()
393 
394  # Modifier Node Type
395  self.__fModifierNodeTypeInitialized = False
396  self.__fModifierNodeNameInitialized = False
397  self.__fModifierNodeType = OpenMaya.MTypeId()
398  self.__fModifierNodeName = ""
399 
400  # Node State Information
401  self.__fHasHistory = False
402  self.__fHasTweaks = False
403  self.__fHasRecordHistory = False
404 
405  # Cached Tweak Data (for undo)
406  self.__fTweakIndexArray = OpenMaya.MIntArray()
407  self.__fTweakVectorArray = OpenMaya.MFloatVectorArray()
408 
409  # Cached Mesh Data (for undo in the 'No History'/'History turned off' case)
410  self.__fMeshData = OpenMaya.MObject()
411 
412  # DG and DAG Modifier
413  #
414  # - We need both DAG and DG modifiers since the MDagModifier::createNode()
415  # method is overridden and specific to DAG nodes. So to keep
416  # the operations consistent we will only use the fDagModifier
417  # when dealing with the DAG.
418  #
419  # - There is a limitation between the reparentNode() and deleteNode()
420  # methods on the MDagModifier. The deleteNode() method does some
421  # preparation work before it enqueues itself in the MDagModifier list
422  # of operations, namely, it looks at it's parents and children and
423  # deletes them as well if they are the only parent/child of the node
424  # scheduled to be deleted.
425  #
426  # This conflicts with our call to MDagModifier::reparentNode(),
427  # since we want to reparent the shape of a duplicated node under
428  # another node and then delete the transform of that node. Now you
429  # can see that since the reparentNode() doesn't execute until after
430  # the MDagModifier::doIt() call, the scheduled deleteNode() call
431  # still sees the child and marks it for delete. The subsequent
432  # doIt() call reparents the shape and then deletes both it and the
433  # transform.
434  #
435  # To avoid this conflict, we separate the calls individually and
436  # perform the reparenting (by calling a doIt()) before the deleteNode()
437  # method is enqueued on the modifier.
438  #
439  self.__fDGModifier = OpenMaya.MDGModifier()
440  self.__fDagModifier = OpenMaya.MDagModifier()
441 
442 
443  ####################### PROTECTED #######################
444 
445  ####################################
446  ## polyModifierCmd Initialization ##
447  ####################################
448  def _setMeshNode(self, mesh):
449  """
450  Target polyMesh to modify
451  """
452  self.__fDagPath = mesh
453  self.__fDagPathInitialized = True
454 
455 
456  def _getMeshNode(self):
457  return self.__fDagPath
458 
459 
460  def _setModifierNodeType(self, nodeType):
461  """
462  Modifier Node Type
463  """
464  self.__fModifierNodeType = nodeType
465  self.__fModifierNodeTypeInitialized = True
466 
467 
468  def _setModifierNodeName(self, nodeName):
469  self.__fModifierNodeName = nodeName
470  self.__fModifierNodeNameInitialized = True
471 
472 
473  def _getModifierNodeType(self):
474  return self.__fModifierNodeType
475 
476 
477  def _getModifierNodeName(self):
478  return self.__fModifierNodeName
479 
480 
481  ###############################
482  ## polyModifierCmd Execution ##
483  ###############################
484  def _initModifierNode(self, modifierNode):
485  """
486  Derived classes should override this method if they wish to initialize
487  input attributes on the modifierNode
488  """
489  pass
490 
491 
492  def _directModifier(self, mesh):
493  """
494  Derived classes should override this method to provide direct
495  modifications on the meshNode in the case where no history exists and
496  construction history is turned off. (ie. no DG operations desired)
497 
498  This method is called only if history does not exist and history is turned
499  off. At this point, a handle to the meshNode is passed in so a derived
500  class may directly modify the mesh.
501  """
502  pass
503 
504 
505  def _doModifyPoly(self):
506  if self.__isCommandDataValid():
507  # Get the state of the polyMesh
508  #
509  self.__collectNodeState()
510 
511  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
512  meshNode = self.__fDagPath.node()
513 
514  # Pre-process the mesh - Cache old mesh (including tweaks, if applicable)
515  #
516  self.__cacheMeshData()
517  self.__cacheMeshTweaks()
518 
519  # Call the directModifier
520  #
521  self._directModifier(meshNode)
522  else:
523  modifierNode = self.__createModifierNode()
524  self._initModifierNode(modifierNode)
525  self.__connectNodes(modifierNode)
526 
527 
528  def _redoModifyPoly(self):
529  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
530  meshNode = self.__fDagPath.node()
531  # Call the directModifier - No need to pre-process the mesh data again
532  # since we already have it.
533  #
534  self._directModifier(meshNode)
535  else:
536  # Call the redo on the DG and DAG modifiers
537  #
538  if self.__fHasHistory:
539  self.__fDagModifier.doIt()
540  self.__fDGModifier.doIt()
541 
542 
543  def _undoModifyPoly(self):
544  if (not self.__fHasHistory) and (not self.__fHasRecordHistory):
545  self.__undoDirectModifier()
546  else:
547  self.__fDGModifier.undoIt()
548 
549  # undoCachedMesh must be called before undoTweakProcessing because
550  # undoCachedMesh copies the original mesh *without* tweaks back onto
551  # the existing mesh. Any changes done before the copy will be lost.
552  #
553  if not self.__fHasHistory:
554  try:
555  self.__undoCachedMesh()
556  except:
557  statusError("undoCachedMesh")
558  self.__fDagModifier.undoIt()
559 
560  try:
561  self.__undoTweakProcessing()
562  except:
563  statusError("undoTweakProcessing")
564 
565 
566  ####################### PRIVATE #######################
567 
568  ##############################################
569  ## polyModifierCmd Internal Processing Data ##
570  ##############################################
571 
572  # This structure is used to maintain the data vital to the modifyPoly method.
573  # It is necessary to simplify parameter passing between the methods used inside
574  # modifyPoly (specifically inside connectNodes()). The diagram below dictates
575  # the naming style used:
576  #
577  # NOTE: modifierNode is intentionally left out of this structure since it
578  # is given protected access to derived classes.
579  #
580  # Before:
581  #
582  # (upstreamNode) *src -> dest* (meshNode)
583  #
584  # After:
585  #
586  # (upstreamNode) *src -> dest* (modifierNode) *src -> dest* (meshNode)
587  #
588  class __modifyPolyData:
589  def __init__(self):
590  self.meshNodeTransform = OpenMaya.MObject()
591  self.meshNodeShape = OpenMaya.MObject()
592  self.meshNodeDestPlug = OpenMaya.MPlug()
593  self.meshNodeDestAttr = OpenMaya.MObject()
594 
595  self.upstreamNodeTransform = OpenMaya.MObject()
596  self.upstreamNodeShape = OpenMaya.MObject()
597  self.upstreamNodeSrcPlug = OpenMaya.MPlug()
598  self.upstreamNodeSrcAttr = OpenMaya.MObject()
599 
600  self.modifierNodeSrcAttr = OpenMaya.MObject()
601  self.modifierNodeDestAttr = OpenMaya.MObject()
602 
603  self.tweakNode = OpenMaya.MObject()
604  self.tweakNodeSrcAttr = OpenMaya.MObject()
605  self.tweakNodeDestAttr = OpenMaya.MObject()
606 
607 
608  ######################################
609  ## polyModifierCmd Internal Methods ##
610  ######################################
611 
612  def __isCommandDataValid(self):
613  valid = True
614 
615  # Check the validity of the DAG path
616  #
617  if self.__fDagPathInitialized:
618  self.__fDagPath.extendToShape()
619  if (not self.__fDagPath.isValid()) or (self.__fDagPath.apiType() != OpenMaya.MFn.kMesh):
620  valid = False
621  else:
622  valid = False
623 
624  # Check the validity of the Modifier node type/name
625  #
626  if (not self.__fModifierNodeTypeInitialized) and (not self.__fModifierNodeNameInitialized):
627  valid = False
628 
629  return valid
630 
631 
632  def __collectNodeState(self):
633  """
634  Collect node state information on the given polyMeshShape
635  - HasHistory (Construction History exists)
636  - HasTweaks
637  - HasRecordHistory (Construction History is turned on)
638  """
639  self.__fDagPath.extendToShape()
640  meshNodeShape = self.__fDagPath.node()
641 
642  depNodeFn = OpenMaya.MFnDependencyNode()
643  depNodeFn.setObject(meshNodeShape)
644 
645  inMeshPlug = depNodeFn.findPlug("inMesh")
646  self.__fHasHistory = inMeshPlug.isConnected()
647 
648  # Tweaks exist only if the multi "pnts" attribute contains plugs
649  # which contain non-zero tweak values. Use false, until proven true
650  # search algorithm.
651  #
652  self.__fHasTweaks = False
653  tweakPlug = depNodeFn.findPlug("pnts")
654  if not tweakPlug.isNull():
655  # ASSERT: tweakPlug should be an array plug!
656  #
657  statusAssert(tweakPlug.isArray(),
658  "tweakPlug.isArray() -- tweakPlug is not an array plug")
659 
660  numElements = tweakPlug.numElements()
661  for i in range(numElements):
662  tweak = tweakPlug.elementByPhysicalIndex(i)
663  if not tweak.isNull():
664  tweakData = self.__getFloat3PlugValue(tweak)
665  if 0 != tweakData.x or 0 != tweakData.y or 0 != tweakData.z:
666  self.__fHasTweaks = True
667  break
668 
669  result = 0
670  OpenMaya.MGlobal.executeCommand("constructionHistory -q -tgl", result)
671  self.__fHasRecordHistory = (0 != result)
672 
673 
674  # Modifier node methods
675  #
676  def __createModifierNode(self):
677  modifierNode = OpenMaya.MObject()
678  if self.__fModifierNodeTypeInitialized or self.__fModifierNodeNameInitialized:
679  if self.__fModifierNodeTypeInitialized:
680  modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeType)
681  elif self.__fModifierNodeNameInitialized:
682  modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeName)
683 
684  # Check to make sure that we have a modifier node of the appropriate type.
685  # Requires an inMesh and outMesh attribute.
686  #
687  depNodeFn = OpenMaya.MFnDependencyNode(modifierNode)
688  inMeshAttr = depNodeFn.attribute("inMesh")
689  outMeshAttr = depNodeFn.attribute("outMesh")
690 
691  statusAssert(not (inMeshAttr.isNull() or outMeshAttr.isNull()),
692  "Invalid Modifier Node: inMesh and outMesh attributes are required.")
693  return modifierNode
694 
695 
696  # Node processing methods (need to be executed in this order)
697  #
698  def __processMeshNode(self, data):
699  # Declare our function sets. Use MFnDagNode here so
700  # we can retrieve the parent transform.
701  #
702  dagNodeFn = OpenMaya.MFnDagNode()
703 
704  # Use the DAG path to retrieve our mesh shape node.
705  #
706  data.meshNodeShape = self.__fDagPath.node()
707  dagNodeFn.setObject(data.meshNodeShape)
708 
709  # ASSERT: meshNodeShape node should have a parent transform!
710  #
711  statusAssert(0 < dagNodeFn.parentCount(),
712  "0 < dagNodeFn.parentCount() -- meshNodeshape has no parent transform")
713  data.meshNodeTransform = dagNodeFn.parent(0)
714 
715  data.meshNodeDestPlug = dagNodeFn.findPlug("inMesh")
716  data.meshNodeDestAttr = data.meshNodeDestPlug.attribute()
717 
718 
719  def __processUpstreamNode(self, data):
720  # Declare our function sets - Although dagNodeFn derives from depNodeFn, we need
721  # both since dagNodeFn can only refer to DAG objects.
722  # We will use depNodeFn for all times other when dealing
723  # with the DAG.
724  #
725  depNodeFn = OpenMaya.MFnDependencyNode()
726  dagNodeFn = OpenMaya.MFnDagNode()
727 
728  # Use the selected node's plug connections to find the upstream plug.
729  # Since we are looking at the selected node's inMesh attribute, it will
730  # always have only one connection coming in if it has history, and none
731  # otherwise.
732  #
733  # If there is no history, copy the selected node and place it ahead of the
734  # modifierNode as the new upstream node so that the modifierNode has an
735  # input mesh to operate on.
736  #
737  tempPlugArray = OpenMaya.MPlugArray()
738 
739  if self.__fHasHistory:
740  # Since we have history, look for what connections exist on the
741  # meshNode "inMesh" plug. "inMesh" plugs should only ever have one
742  # connection.
743  #
744  data.meshNodeDestPlug.connectedTo(tempPlugArray, True, False)
745 
746  # ASSERT: Only one connection should exist on meshNodeShape.inMesh!
747  #
748  statusAssert(tempPlugArray.length() == 1,
749  "tempPlugArray.length() == 1 -- 0 or >1 connections on meshNodeShape.inMesh")
750  data.upstreamNodeSrcPlug = tempPlugArray[0]
751 
752  # Construction history only deals with shapes, so we can grab the
753  # upstreamNodeShape off of the source plug.
754  #
755  data.upstreamNodeShape = data.upstreamNodeSrcPlug.node()
756  depNodeFn.setObject(data.upstreamNodeShape)
757  data.upstreamNodeSrcAttr = data.upstreamNodeSrcPlug.attribute()
758 
759  # Disconnect the upstream node and the selected node, so we can
760  # replace them with our own connections below.
761  #
762  self.__fDGModifier.disconnect(data.upstreamNodeSrcPlug, data.meshNodeDestPlug)
763 
764  else: # No History (!fHasHistory)
765  # Use the DAG node function set to duplicate the shape of the meshNode.
766  # The duplicate method will return an MObject handle to the transform
767  # of the duplicated shape, so traverse the dag to locate the shape. Store
768  # this duplicate shape as our "upstream" node to drive the input for the
769  # modifierNode.
770  #
771  dagNodeFn.setObject(data.meshNodeShape)
772  data.upstreamNodeTransform = dagNodeFn.duplicate(False, False)
773  dagNodeFn.setObject(data.upstreamNodeTransform)
774 
775  # Ensure that our upstreamNode is pointing to a shape.
776  #
777  statusAssert(0 < dagNodeFn.childCount(),
778  "0 < dagNodeFn.childCount() -- Duplicate meshNode transform has no shape.")
779  data.upstreamNodeShape = dagNodeFn.child(0)
780 
781  # Re-parent the upstreamNodeShape under our original transform
782  #
783  try:
784  self.__fDagModifier.reparentNode(data.upstreamNodeShape, data.meshNodeTransform)
785  except:
786  statusError("reparentNode")
787 
788  # Perform the DAG re-parenting
789  #
790  # Note: This reparent must be performed before the deleteNode() is called.
791  # See polyModifierCmd.h (see definition of fDagModifier) for more details.
792  #
793  try:
794  self.__fDagModifier.doIt()
795  except:
796  statusError("fDagModifier.doIt()")
797 
798  # Mark the upstreamNodeShape (the original shape) as an intermediate object
799  # (making it invisible to the user)
800  #
801  dagNodeFn.setObject(data.upstreamNodeShape)
802  dagNodeFn.setIntermediateObject(True)
803 
804  # Get the upstream node source attribute
805  #
806  data.upstreamNodeSrcAttr = dagNodeFn.attribute("outMesh")
807  data.upstreamNodeSrcPlug = dagNodeFn.findPlug("outMesh")
808 
809  # Remove the duplicated transform node (clean up)
810  #
811  try:
812  self.__fDagModifier.deleteNode(data.upstreamNodeTransform)
813  except:
814  statusError("deleteNode")
815 
816  # Perform the DAG delete node
817  #
818  # Note: This deleteNode must be performed after the reparentNode() method is
819  # completed. See polyModifierCmd.h (see definition of fDagModifier) for
820  # details.
821  #
822  try:
823  self.__fDagModifier.doIt()
824  except:
825  statusError("fDagModifier.doIt()")
826 
827  # Cache the DAG path to the duplicate shape
828  #
829  dagNodeFn.getPath(self.__fDuplicateDagPath)
830 
831 
832  def __processModifierNode(self, modifierNode, data):
833  depNodeFn = OpenMaya.MFnDependencyNode(modifierNode)
834  data.modifierNodeSrcAttr = depNodeFn.attribute("outMesh")
835  data.modifierNodeDestAttr = depNodeFn.attribute("inMesh")
836 
837 
838  def __processTweaks(self, data):
839  # Clear tweak undo information (to be rebuilt)
840  #
841  self.__fTweakIndexArray.clear()
842  self.__fTweakVectorArray.clear()
843 
844  # Extract the tweaks and place them into a polyTweak node. This polyTweak node
845  # will be placed ahead of the modifier node to maintain the order of operations.
846  # Special care must be taken into recreating the tweaks:
847  #
848  # 1) Copy tweak info (including connections!)
849  # 2) Remove tweak info from both meshNode and a duplicate meshNode (if applicable)
850  # 3) Cache tweak info for undo operations
851  #
852  if self.__fHasTweaks:
853  # Declare our function sets
854  #
855  depNodeFn = OpenMaya.MFnDependencyNode()
856 
857  # Declare our tweak processing variables
858  #
859  tweakDataArray = OpenMaya.MObjectArray()
860  tweakSrcConnectionCountArray = OpenMaya.MIntArray()
861  tweakSrcConnectionPlugArray = OpenMaya.MPlugArray()
862  tweakDstConnectionCountArray = OpenMaya.MIntArray()
863  tweakDstConnectionPlugArray = OpenMaya.MPlugArray()
864  tempPlugArray = OpenMaya.MPlugArray()
865 
866  # Create the tweak node and get its attributes
867  #
868  data.tweakNode = self.__fDGModifier.createNode("polyTweak")
869  depNodeFn.setObject(data.tweakNode)
870  data.tweakNodeSrcAttr = depNodeFn.attribute("output")
871  data.tweakNodeDestAttr = depNodeFn.attribute("inputPolymesh")
872  tweakNodeTweakAttr = depNodeFn.attribute("tweak")
873 
874  depNodeFn.setObject(data.meshNodeShape)
875  meshTweakPlug = depNodeFn.findPlug("pnts")
876 
877  # ASSERT: meshTweakPlug should be an array plug!
878  #
879  statusAssert(meshTweakPlug.isArray(),
880  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug")
881 
882  # Gather meshTweakPlug data
883  #
884  numElements = meshTweakPlug.numElements()
885  for i in range(numElements):
886  # MPlug.numElements() only returns the number of physical elements
887  # in the array plug. Thus we must use elementByPhysical index when using
888  # the index i.
889  #
890  tweak = meshTweakPlug.elementByPhysicalIndex(i)
891 
892  # If the method fails, the element is NULL. Only append the index
893  # if it is a valid plug.
894  #
895  if not tweak.isNull():
896  # Cache the logical index of this element plug
897  #
898  logicalIndex = tweak.logicalIndex()
899 
900  # Collect tweak data and cache the indices and float vectors
901  #
902  tweakData = tweak.asMObject()
903  tweakDataArray.append(tweakData)
904  tweakVector = self.__getFloat3PlugValue(tweak)
905  self.__fTweakIndexArray.append(logicalIndex)
906  self.__fTweakVectorArray.append(tweakVector)
907 
908  # Collect tweak connection data
909  #
910  # Parse down to the deepest level of the plug tree and check
911  # for connections - look at the child nodes of the element plugs.
912  # If any connections are found, record the connection and disconnect
913  # it.
914  #
915 
916  # ASSERT: The element plug should be compound!
917  #
918  statusAssert(tweak.isCompound(),
919  "tweak.isCompound() -- Element tweak plug is not compound")
920 
921  numChildren = tweak.numChildren()
922  for j in range(numChildren):
923  tweakChild = tweak.child(j)
924  if tweakChild.isConnected():
925  # Get all connections with this plug as source, if they exist
926  #
927  tempPlugArray.clear()
928  if tweakChild.connectedTo(tempPlugArray, False, True):
929  numSrcConnections = tempPlugArray.length()
930  tweakSrcConnectionCountArray.append(numSrcConnections)
931 
932  for k in range(numSrcConnections):
933  tweakSrcConnectionPlugArray.append(tempPlugArray[k])
934  self.__fDGModifier.disconnect(tweakChild, tempPlugArray[k])
935  else:
936  tweakSrcConnectionCountArray.append(0)
937 
938  # Get the connection with this plug as destination, if it exists
939  #
940  tempPlugArray.clear()
941  if tweakChild.connectedTo(tempPlugArray, True, False):
942  # ASSERT: tweakChild should only have one connection as destination!
943  #
944  statusAssert(tempPlugArray.length() == 1,
945  "tempPlugArray.length() == 1 -- 0 or >1 connections on tweakChild")
946 
947  tweakDstConnectionCountArray.append(1)
948  tweakDstConnectionPlugArray.append(tempPlugArray[0])
949  self.__fDGModifier.disconnect(tempPlugArray[0], tweakChild)
950  else:
951  tweakDstConnectionCountArray.append(0)
952  else:
953  tweakSrcConnectionCountArray.append(0)
954  tweakDstConnectionCountArray.append(0)
955 
956  # Apply meshTweakPlug data to our polyTweak node
957  #
958  polyTweakPlug = OpenMaya.MPlug(data.tweakNode, tweakNodeTweakAttr)
959  numTweaks = self.__fTweakIndexArray.length()
960  srcOffset = 0
961  dstOffset = 0
962  for i in range(numTweaks):
963  # Apply tweak data
964  #
965  tweak = polyTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i])
966  tweak.setMObject(tweakDataArray[i])
967 
968  # ASSERT: Element plug should be compound!
969  #
970  statusAssert(tweak.isCompound(),
971  "tweak.isCompound() -- Element plug, 'tweak', is not compound")
972 
973  numChildren = tweak.numChildren()
974  for j in range(numChildren):
975  tweakChild = tweak.child(j)
976 
977  # Apply tweak source connection data
978  #
979  if 0 < tweakSrcConnectionCountArray[i*numChildren + j]:
980  k = 0
981  while (k < tweakSrcConnectionCountArray[i*numChildren + j]):
982  self.__fDGModifier.connect(tweakChild, tweakSrcConnectionPlugArray[srcOffset])
983  srcOffset += 1
984  k += 1
985 
986  # Apply tweak destination connection data
987  #
988  if 0 < tweakDstConnectionCountArray[i*numChildren + j]:
989  self.__fDGModifier.connect(tweakDstConnectionPlugArray[dstOffset], tweakChild)
990  dstOffset += 1
991 
992 
993  # Node connection method
994  #
995  def __connectNodes(self, modifierNode):
996  """
997  This method connects up the modifier nodes, while accounting for DG factors
998  such as construction history and tweaks. The method has a series of steps which
999  it runs through to process nodes under varying circumstances:
1000 
1001  1) Gather meshNode connection data (ie. attributes and plugs)
1002 
1003  2) Gather upstreamNode data - This is history-dependent. If the node has history,
1004  an actual upstreamNode exists and that is used to
1005  drive the input of our modifierNode.
1006 
1007  Otherwise, if the node does not have history, the
1008  meshNode is duplicated, set as an intermediate object
1009  and regarded as our new upstreamNode which will drive
1010  the input of our modifierNode. The case with history
1011  already has this duplicate meshNode at the top, driving
1012  all other history nodes and serving as a reference
1013  to the "original state" of the node before any
1014  modifications.
1015 
1016  3) Gather modifierNode connection data
1017 
1018  4) Process tweak data (if it exists) - This is history-dependent. If there is
1019  history, the tweak data is extracted and deleted
1020  from the meshNode and encapsulated inside a
1021  polyTweak node. The polyTweak node is then
1022  inserted ahead of the modifier node.
1023 
1024  If there is no history, the same is done as
1025  in the history case, except the tweaks are
1026  deleted from the duplicate meshNode in addition
1027  to the actual meshNode.
1028 
1029  5) Connect the nodes
1030 
1031  6) Collapse/Bake nodes into the actual meshNode if the meshNode had no previous
1032  construction history and construction history recording is turned off.
1033  (ie. (!fHasHistory && !fHasRecordHistory) == true )
1034 
1035  """
1036  # Declare our internal processing data structure (see polyModifierCmd.h for definition)
1037  #
1038  data = polyModifierCmd.__modifyPolyData()
1039 
1040  # Get the mesh node, plugs and attributes
1041  #
1042  try:
1043  self.__processMeshNode(data)
1044  except:
1045  statusError("processMeshNode")
1046 
1047  # Get upstream node, plugs and attributes
1048  #
1049  try:
1050  self.__processUpstreamNode(data)
1051  except:
1052  statusError("processUpstreamNode")
1053 
1054  # Get the modifierNode attributes
1055  #
1056  try:
1057  self.__processModifierNode(modifierNode, data)
1058  except:
1059  statusError("processModifierNode")
1060 
1061  # Process tweaks on the meshNode
1062  #
1063  try:
1064  self.__processTweaks(data)
1065  except:
1066  statusError("processTweaks")
1067 
1068  # Connect the nodes
1069  #
1070  if self.__fHasTweaks:
1071  tweakDestPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeDestAttr)
1072  self.__fDGModifier.connect(data.upstreamNodeSrcPlug, tweakDestPlug)
1073 
1074  tweakSrcPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeSrcAttr)
1075  modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr)
1076  self.__fDGModifier.connect(tweakSrcPlug, modifierDestPlug)
1077  else:
1078  modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr)
1079  self.__fDGModifier.connect(data.upstreamNodeSrcPlug, modifierDestPlug)
1080 
1081  modifierSrcPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeSrcAttr)
1082  meshDestAttr = OpenMaya.MPlug(data.meshNodeShape, data.meshNodeDestAttr)
1083  self.__fDGModifier.connect(modifierSrcPlug, meshDestAttr)
1084 
1085  self.__fDGModifier.doIt()
1086 
1087 
1088  # Mesh caching methods - Only used in the directModifier case
1089  #
1090  def __cacheMeshData(self):
1091  depNodeFn = OpenMaya.MFnDependencyNode()
1092  dagNodeFn = OpenMaya.MFnDagNode()
1093 
1094  meshNode = self.__fDagPath.node()
1095 
1096  # Duplicate the mesh
1097  #
1098  dagNodeFn.setObject(meshNode)
1099  dupMeshNode = dagNodeFn.duplicate()
1100 
1101  dupMeshDagPath = OpenMaya.MDagPath()
1102  OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath)
1103  dupMeshDagPath.extendToShape()
1104 
1105  depNodeFn.setObject(dupMeshDagPath.node())
1106  try:
1107  dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1108  except:
1109  statusError("Could not retrieve outMesh")
1110 
1111  # Retrieve the meshData
1112  #
1113  try:
1114  self.__fMeshData = dupMeshNodeOutMeshPlug.asMObject()
1115  except:
1116  statusError("Could not retrieve meshData")
1117 
1118  # Delete the duplicated node
1119  #
1120  OpenMaya.MGlobal.deleteNode(dupMeshNode)
1121 
1122 
1123  def __cacheMeshTweaks(self):
1124  # Clear tweak undo information (to be rebuilt)
1125  #
1126  self.__fTweakIndexArray.clear()
1127  self.__fTweakVectorArray.clear()
1128 
1129  # Extract the tweaks and store them in our local tweak cache members
1130  #
1131  if self.__fHasTweaks:
1132  # Declare our function sets
1133  #
1134  depNodeFn = OpenMaya.MFnDependencyNode()
1135 
1136  meshNode = self.__fDagPath.node()
1137 
1138  depNodeFn.setObject(meshNode)
1139  meshTweakPlug = depNodeFn.findPlug("pnts")
1140 
1141  # ASSERT: meshTweakPlug should be an array plug!
1142  #
1143  statusAssert(meshTweakPlug.isArray(),
1144  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" )
1145 
1146  # Gather meshTweakPlug data
1147  #
1148  numElements = meshTweakPlug.numElements()
1149  for i in range(numElements):
1150  # MPlug.numElements() only returns the number of physical elements
1151  # in the array plug. Thus we must use elementByPhysical index when using
1152  # the index i.
1153  #
1154  tweak = meshTweakPlug.elementByPhysicalIndex(i)
1155 
1156  # If the method fails, the element is NULL. Only append the index
1157  # if it is a valid plug.
1158  #
1159  if not tweak.isNull():
1160  # Cache the logical index of this element plug
1161  #
1162  logicalIndex = tweak.logicalIndex()
1163 
1164  # Collect tweak data and cache the indices and float vectors
1165  #
1166  tweakVector = self.__getFloat3PlugValue(tweak)
1167  self.__fTweakIndexArray.append(logicalIndex)
1168  self.__fTweakVectorArray.append(tweakVector)
1169 
1170 
1171  # Undo methods
1172  #
1173  def __undoCachedMesh(self):
1174  # Only need to restore the cached mesh if there was no history. Also
1175  # check to make sure that we are in the record history state.
1176  #
1177  statusAssert(self.__fHasRecordHistory, "fHasRecordHistory == true")
1178 
1179  if not self.__fHasHistory:
1180  depNodeFn = OpenMaya.MFnDependencyNode()
1181 
1182  meshNodeShape = self.__fDagPath.node()
1183  dupMeshNodeShape = self.__fDuplicateDagPath.node()
1184 
1185  depNodeFn.setObject(meshNodeShape)
1186  meshNodeName = depNodeFn.name()
1187  try:
1188  meshNodeDestPlug = depNodeFn.findPlug("inMesh")
1189  except:
1190  statusError("Could not retrieve inMesh")
1191  try:
1192  meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1193  except:
1194  statusError("Could not retrieve outMesh")
1195 
1196  depNodeFn.setObject(dupMeshNodeShape)
1197  try:
1198  dupMeshNodeSrcPlug = depNodeFn.findPlug("outMesh")
1199  except:
1200  statusError("Could not retrieve outMesh")
1201 
1202  # For the case with tweaks, we cannot write the mesh directly back onto
1203  # the cachedInMesh, since the shape can have out of date information from the
1204  # cachedInMesh, thus we temporarily connect the duplicate mesh shape to the
1205  # mesh shape and force a DG evaluation.
1206  #
1207  # For the case without tweaks, we can simply write onto the outMesh, since
1208  # the shape relies solely on an outMesh when there is no history nor tweaks.
1209  #
1210  if self.__fHasTweaks:
1211  dgModifier = OpenMaya.MDGModifier()
1212  dgModifier.connect(dupMeshNodeSrcPlug, meshNodeDestPlug)
1213  try:
1214  dgModifier.doIt()
1215  except:
1216  statusError("Could not connect dupMeshNode -> meshNode")
1217 
1218  # Need to force a DG evaluation now that the input has been changed.
1219  #
1220  cmd = "dgeval -src %s.inMesh" % meshNodeName
1221  try:
1222  OpenMaya.MGlobal.executeCommand(cmd, False, False)
1223  except:
1224  statusError("Could not force DG eval")
1225 
1226  # Disconnect the duplicate meshNode now
1227  #
1228  dgModifier.undoIt()
1229  else:
1230  try:
1231  meshData = dupMeshNodeSrcPlug.asMObject()
1232  try:
1233  meshNodeOutMeshPlug.setMObject(meshData)
1234  except:
1235  statusError("Could not set outMesh")
1236  except:
1237  statusError("Could not retrieve meshData")
1238 
1239 
1240  def __undoTweakProcessing(self):
1241  if self.__fHasTweaks:
1242  meshNodeShape = self.__fDagPath.node()
1243  depNodeFn = OpenMaya.MFnDependencyNode()
1244  depNodeFn.setObject(meshNodeShape)
1245  meshTweakPlug = depNodeFn.findPlug("pnts")
1246 
1247  statusAssert(meshTweakPlug.isArray(),
1248  "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug")
1249 
1250  numElements = self.__fTweakIndexArray.length()
1251 
1252  for i in range(numElements):
1253  tweak = meshTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i])
1254  tweakData = self.__getFloat3asMObject(self.__fTweakVectorArray[i])
1255  tweak.setMObject(tweakData)
1256 
1257  # In the case of no history, the duplicate node shape will be disconnected on undo
1258  # so, there is no need to undo the tweak processing on it.
1259  #
1260 
1261 
1262  def __undoDirectModifier(self):
1263  depNodeFn = OpenMaya.MFnDependencyNode()
1264  dagNodeFn = OpenMaya.MFnDagNode()
1265 
1266  meshNode = self.__fDagPath.node()
1267  depNodeFn.setObject( meshNode )
1268 
1269  # For the case with tweaks, we cannot write the mesh directly back onto
1270  # the cachedInMesh, since the shape can have out of date information from the
1271  # cachedInMesh. Thus we temporarily create an duplicate mesh, place our
1272  # old mesh on the outMesh attribute of our duplicate mesh, connect the
1273  # duplicate mesh shape to the mesh shape, and force a DG evaluation.
1274  #
1275  # For the case without tweaks, we can simply write onto the outMesh, since
1276  # the shape relies solely on an outMesh when there is no history nor tweaks.
1277  #
1278  if self.__fHasTweaks:
1279  # Retrieve the inMesh and name of our mesh node (for the DG eval)
1280  #
1281  depNodeFn.setObject(meshNode)
1282  try:
1283  meshNodeInMeshPlug = depNodeFn.findPlug("inMesh")
1284  except:
1285  statusError("Could not retrieve inMesh")
1286 
1287  meshNodeName = depNodeFn.name()
1288 
1289  # Duplicate our current mesh
1290  #
1291  dagNodeFn.setObject(meshNode)
1292  dupMeshNode = dagNodeFn.duplicate()
1293 
1294  # The dagNodeFn.duplicate() returns a transform, but we need a shape
1295  # so retrieve the DAG path and extend it to the shape.
1296  #
1297  dupMeshDagPath = OpenMaya.MDagPath()
1298  OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath)
1299  dupMeshDagPath.extendToShape()
1300 
1301  # Retrieve the outMesh of the duplicate mesh and set our mesh data back
1302  # on it.
1303  #
1304  depNodeFn.setObject(dupMeshDagPath.node())
1305  try:
1306  dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1307  except:
1308  statusError("Could not retrieve outMesh")
1309  dupMeshNodeOutMeshPlug.setMObject(self.__fMeshData)
1310 
1311  # Temporarily connect the duplicate mesh node to our mesh node
1312  #
1313  dgModifier = OpenMaya.MDGModifier()
1314  dgModifier.connect(dupMeshNodeOutMeshPlug, meshNodeInMeshPlug)
1315  try:
1316  dgModifier.doIt()
1317  except:
1318  statusError("Could not connect dupMeshNode -> meshNode")
1319 
1320  # Need to force a DG evaluation now that the input has been changed.
1321  #
1322  cmd = "dgeval -src %s.inMesh" % meshNodeName
1323  try:
1324  OpenMaya.MGlobal.executeCommand(cmd, False, False)
1325  except:
1326  statusError("Could not force DG eval")
1327 
1328  # Disconnect and delete the duplicate mesh node now
1329  #
1330  dgModifier.undoIt()
1331  OpenMaya.MGlobal.deleteNode(dupMeshNode)
1332 
1333  # Restore the tweaks on the mesh
1334  #
1335  self.__undoTweakProcessing()
1336  else:
1337  # Restore the original mesh by writing the old mesh data (fMeshData) back
1338  # onto the outMesh of our meshNode
1339  #
1340  depNodeFn.setObject(meshNode)
1341  try:
1342  meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh")
1343  except:
1344  statusError("Could not retrieve outMesh")
1345  try:
1346  meshNodeOutMeshPlug.setMObject(self.__fMeshData)
1347  except:
1348  statusError("Could not set meshData")
1349 
1350 
1351  #####################################
1352  ## polyModifierCmd Utility Methods ##
1353  #####################################
1354 
1355  def __getFloat3PlugValue(self, plug):
1356  # Retrieve the value as an MObject
1357  object = plug.asMObject()
1358 
1359  # Convert the MObject to a float3
1360  numDataFn = OpenMaya.MFnNumericData(object)
1361  xParam = OpenMaya.MScriptUtil(0.0)
1362  xPtr = xParam.asFloatPtr()
1363  yParam = OpenMaya.MScriptUtil(0.0)
1364  yPtr = yParam.asFloatPtr()
1365  zParam = OpenMaya.MScriptUtil(0.0)
1366  zPtr = zParam.asFloatPtr()
1367  numDataFn.getData3Float(xPtr, yPtr, zPtr)
1368  return OpenMaya.MFloatVector(
1369  xParam.getFloat(xPtr),
1370  yParam.getFloat(yPtr),
1371  zParam.getFloat(zPtr))
1372 
1373 
1374  def __getFloat3asMObject(self, value):
1375  # Convert the float value into an MObject
1376  numDataFn = OpenMaya.MFnNumericData()
1377  numDataFn.create(OpenMaya.MFnNumericData.k3Float)
1378  numDataFn.setData3Float(value[0], value[1], value[2])
1379  return numDataFn.object()
1380 
1381 
1382 #####################################################################
1383 ## FACTORY ##########################################################
1384 #####################################################################
1385 
1386 # Overview:
1387 #
1388 # The polyModifierFty class is the main workhorse of the polyModifierCmd operation.
1389 # It is here that the actual operation is implemented. The idea of the factory is
1390 # to create a single implementation of the modifier that can be reused in more than
1391 # one place.
1392 #
1393 # As such, the details of the factory are quite simple. Each factory contains a doIt()
1394 # method which should be overridden. This is the method which will be called by the
1395 # node and the command when a modifier is requested.
1396 #
1397 # How to use:
1398 #
1399 # 1) Create a factory derived from polyModifierFty
1400 # 2) Add any input methods and members to the factory
1401 # 3) Override the polyModifierFty.doIt() method
1402 #
1403 # (a) Retrieve the inputs from the class
1404 # (b) Process the inputs
1405 # (c) Perform the modifier
1406 #
1407 #
1408 
1409 class polyModifierFty:
1410  def __init__(self):
1411  pass
1412 
1413  def doIt(self):
1414  pass
1415 
1416 
1417 #####################################################################
1418 ## NODE #############################################################
1419 #####################################################################
1420 
1421 # Overview:
1422 #
1423 # The polyModifierNode class is a intermediate class used by polyModifierCmd to
1424 # modify a polygonal object in Maya. The purpose of the polyModifierNode is to
1425 # generalize a node that can receive an input mesh object, modify the mesh and
1426 # return the modified mesh.
1427 #
1428 # polyModifierNode is an abstraction which does not need to know about the DG
1429 # and simply needs to know about the process outlined above. polyModifierCmd
1430 # manages when and how this node will be used.
1431 #
1432 # Each polyModifierNode is recommended to contain an instance of a polyModifierFty
1433 # which will do the actual work, leaving only the node setup and attribute
1434 # associations to the node. The recommended structure of a polyModifierNode is
1435 # as follows:
1436 #
1437 # _____________
1438 # / ___ \
1439 # / / \ \
1440 # O | Node | Fty | | O
1441 # | \ \___/ / |
1442 # | \_____________/ |
1443 # inMesh outMesh
1444 #
1445 #
1446 # The purpose of the node is to simply define the attributes (inputs and outputs) of
1447 # the node and associate which attribute affects each other. This is basic node setup
1448 # for a DG node. Using the above structure, the node's inherited "compute()" method
1449 # (from MPxNode) should retrieve the inputs and pass the appropriate data down to the
1450 # polyModifierFty for processing.
1451 #
1452 #
1453 # How to use:
1454 #
1455 # (1) Create a class derived from polyModifierNode
1456 # (2) Define and associate inMesh and outMesh attributes (inMesh --> affects --> outMesh)
1457 # (3) Add any additional attributes specific to the derived node and setup associations
1458 # (4) Define an instance of your specific polyModifierFty to perform the operation on the node
1459 # (5) Override the MPxNode::compute() method
1460 # (6) Inside compute():
1461 #
1462 # (a) Retrieve input attributes
1463 # (b) Use inputs to setup your factory to operate on the given mesh
1464 # (c) Call the factory's inherited doIt() method
1465 #
1466 #
1467 
1468 class polyModifierNode(OpenMayaMPx.MPxNode):
1469  # There needs to be a MObject handle declared for each attribute that
1470  # the node will have. These handles are needed for getting and setting
1471  # the values later.
1472  #
1473  inMesh = OpenMaya.MObject()
1474  outMesh = OpenMaya.MObject()
1475 
1476  def __init__(self):
1477  OpenMayaMPx.MPxNode.__init__(self)