#- # ========================================================================== # Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All # rights reserved. # # The coded instructions, statements, computer programs, and/or related # material (collectively the "Data") in these files contain unpublished # information proprietary to Autodesk, Inc. ("Autodesk") and/or its # licensors, which is protected by U.S. and Canadian federal copyright # law and by international treaties. # # The Data is provided for use exclusively by You. You have the right # to use, modify, and incorporate this Data into other products for # purposes authorized by the Autodesk software license agreement, # without fee. # # The copyright notices in the Software and this entire statement, # including the above license grant, this restriction and the # following disclaimer, must be included in all copies of the # Software, in whole or in part, and all derivative works of # the Software, unless such copies or derivative works are solely # in the form of machine-executable object code generated by a # source language processor. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY # OR PROBABILITY OF SUCH DAMAGES. # # ========================================================================== #+ ##################################################################### ## COMMAND ########################################################## ##################################################################### # # Overview: # # polyModifierCmd is a generic base class designed to aid in modifying # polygonal meshes. All polys in Maya possess two features: construction # history and tweaks. Both of these have a large impact on the structure # of the object as well as how it can be further manipulated. However, # they cannot be easily implemented, which is the why we need this abstracted # class. polyModifierCmd will automatically handle the DG maintenance of # construction history and tweaks on a polygonal mesh. # # To understand what effect both construction history and tweaks have on # a mesh, we need to understand the states of a node. There are three things # which will affect the state of a node regarding construction history and # tweaks. That is: # # (1) Does construction history exist? # (2) Do tweaks exist? # (3) Is construction history turned on? # # The answer to each of these questions changes how the mesh is interpreted, # which in turn affects how the mesh can be accessed/modified. Under each # circumstance, new modifications on the mesh go through a standard series # of events. To further illustrate how these affect the interpretation of # a mesh, we'll delve into each case separately looking at the problems # that we face in each case. # # In the case where construction history exists, the existence of construction # history informs us that there is a single linear DG chain of nodes upstream # from the mesh node. That chain is the history chain. At the top of the chain # we have the "original" mesh and at the bottom we have the "final" mesh, # where "original" and "final" represent the respective state of the node with # regards to mesh's history. Each of these nodes are adjoined via the # inMesh/outMesh attributes, where in and out designate the dataflow into # and out of the node. Now, with that aside, attempting to modify a node # via mutator methods will always write the information onto the inMesh # attribute (except in the case of tweaks, where it writes to the cachedInMesh). # This presents a problem if history exists since a DG evaluation will overwrite # the inMesh of the mesh node, due to the connection from the outMesh of the # node directly upstream from the mesh. This will discard any modifications # made to the mesh. # # So obviously modifying a mesh directly isn't possible when history exists. # To properly modify a mesh with history, we introduce the concept of a modifier # node. This polyModifierNode will encapsulate the operations on the mesh # and behave similarly to the other nodes in the history chain. The node will # then be inserted into the history chain so that on each DG evaluation, it # is always accounted for. The diagram below shows the before and after # of modifying a mesh with history. # # # Before modification: # # ____ ____ # / \ / \ # | Hist | O --------> O | mesh | O # \____/ | | \____/ | # outMesh inMesh outMesh # # # After modification: # # ____ ________ ____ # / \ / \ / \ # | Hist | O --------> O | modifier | O --------> O | mesh | O # \____/ | | \________/ | | \____/ | # outMesh inMesh outMesh inMesh outMesh # # # (Figure 1. Nodes with History) # # # In the case of tweaks: Tweaks are stored on a hidden attribute on the # mesh. Tweaks are manual component modifications on a mesh (eg. repositioning # a vertex). During a DG evaluation, the DG takes the inMesh attribute of # the node and adds the tweak values onto it to get the final value. From this # knowledge we can see that inserting a modifier node ahead of the mesh node # reverses the order of operations which can be crucial to the structure of the # resulting mesh if the modification is a topological change. To avoid this # problem, we retrieve the tweaks off of the mesh, remove it from the mesh and # place the tweaks into a polyTweak node. The tweak node is then inserted ahead # of the modifier node to preserve the order of operations: # # # Before modification: # # Tweak # ____ __O__ # / \ / \ # | Hist | O --------> O | mesh | O # \____/ | | \_____/ | # outMesh inMesh outMesh # # # After modification: # # Empty Tweak # ____ _____ ________ __O__ # / \ / \ / \ / \ # | Hist | O -----> O | Tweak | O -----> O | modifier | O -----> O | mesh | O # \____/ | | \_____/ | | \________/ | | \_____/ | # outMesh inMesh outMesh inMesh outMesh inMesh outMesh # # # (Figure 2. Node with Tweaks) # # # The last of the questions deals with whether or not the user has construction # history turned on or off. This will change how the node should be modified # as well as what the node will look like in the DG following the operation. With # history turned on, the user has selected that they would like to keep a # history chain. So in that case, the resulting mesh would look like the above # diagrams following the operation. On the other hand, with history turned off, # the user has selected that they would not like to see a history chain. From here # there are two possible choices to modify the mesh: # # (1) Operate on the mesh directly # (2) Use the DG, like in the above diagrams, then collapse the nodes down into the mesh. # # The only exception to note out of this case is that if the node already possesses # history (as would be answered by the first question), this preference is ignored. # If a node has history, we continue to use history. The user is imposed with the # task of deleting the history on the object first if they would not like to continue # using history. # # # With History: # # ____ ____ # / \ / \ # | Hist | O --------> O | mesh | O # \____/ | | \____/ | # outMesh inMesh outMesh # # # Without History: # # ____ # / \ # O | mesh | O (All history compressed onto the inMesh attribute) # | \____/ | # inMesh outMesh # # # (Figure 3. Node with History preference) # # # This section has described the "why" part of the question regarding this command. # Following sections will provide a more in depth look at "how" this command # treats each of these situations and what it really does behind the scenes # to handle the above cases. # # # How it works: # # This command approaches the various node state cases similarly to the way # Maya works with construction history and tweaks in polygons. It is important # to note that history and tweaks are independent states having no effect on # each other (in terms of their state). Thus this section will describe each # case independently: # # 1) History # # For history, there are 4 cases that need to be considered: # # (a) History (yes) - RecordHistory (yes) # (b) History (yes) - RecordHistory (no) # (c) History (no) - RecordHistory (yes) # (d) History (no) - RecordHistory (no) # # For (a) and (b), this command treats the node identically. Regardless of # whether recording history is turned on or off, if history already exists # on the node, we treat the node as though recording history is on. As such # the command performs the following steps: # # (i) Create a modifier node. # (ii) Find the node directly upstream to the mesh node. # (iii) Disconnect the upstream node and the mesh node. # (iv) Connect the upstream node to the modifier node. # (v) Connect the modifier node to the mesh node. # (vi) Done! # # For (c), polyModifierCmd needs to generate an input mesh to drive the # modifier node. To do this, the mesh node is duplicated and connected # like the upstream node in the previous two cases: # # (i) Create a modifier node. # (ii) Duplicate the mesh node. # (iii) Connect the duplicate mesh node to the modifier node # (iv) Connect the modifier node to the mesh node # (v) Done! # # For (d), this command is a bit more complicated. There are two approaches # that can be done to respect the fact that no history is desired. The first # involves using the approach in case (c) and simply "baking" or "flattening" # the nodes down into the mesh node. Unfortunately, this presents some # serious problems with undo, as the Maya API in its current state does not # support construction history manipulation. Resorting to the command: # "delete -ch" would be possible, however undoing the operation would not be # trivial as calling an undo from within an undo could destabilize the undo # queue. # # The second alternative and one that is currently implemented by this class # is to respect the "No Construction History" preference strictly by # not modifying the history chain at all and simply operating directly on the # mesh. In order to do this and maintain generality, a hook is provided for # derived classes to override and place in the code used to directly modify the # mesh. polyModifierCmd will only call this method under the circumstances # of case (d). To prevent code duplication between the operations done in the # modifierNode and the command's directModifier implementation, the concept of # a factory is used. It is recommended that an instance of such a factory is # stored locally on the command much like it will be on the node. See # polyModifierNode.h and polyModifierFty.h for more details. # # # 2) Tweaks # # Tweaks are handled as noted above in the description section. However, how # they are treated is dependent on the state of history. Using the four cases # above: # # For (a), (b) and (c), it is as described in the description section: # # (i) Create a tweak node. # (ii) Extract the tweaks from the mesh node. # (iii) Copy the tweaks onto the tweak node. # (iv) Clear the tweaks from the mesh node. # (v) Clear the tweaks from the duplicate mesh node (for case (c) only!) # # For (d), we have yet another limitation. Tweaks are not handled in this case # because of the same circumstances which give rise to the limitation in the # history section. As such, topological changes may result in some odd behaviour # unless the workaround provided in the limitations section is used. # # # How to use: # # To use this command there are several things that are required based on the needs # of the command: # # Step 1: polyModifierFty # # 1) Create a factory derived from polyModifierFty # 2) Find and assign any inputs that your modifier will need onto the factory. # 3) Override the polyModifierFty.doIt() method of the factory # 4) Place your modifier code into the doIt() method of the factory # # Step 2: polyModifierNode # # 1) Create a node derived from polyModifierNode # 2) Add any additional input attributes onto the node # 3) Associate the attributes (ie. inMesh --> affects --> outMesh) # 4) Add an instance of your polyModifierFty to the node # 5) Override the MPxNode.compute() method # 6) Retrieve inputs from attributes, setup the factory and call its doIt() in compute() # # Step 3: polyModifierCmd # # 1) Create a command derived from polyModifierCmd # # --- # # 2) Override the polyModifierCmd.initModifierNode() method # 3) Place your node setup code inside the initModifierNode() # # --- # # 4) Add an instance of your polyModifierFty to the command # 5) Cache any input parameters for the factory on the command # 6) Override the polyModifierCmd.directModifier() method # 7) Place your factory setup code and call its doIt() in directModifier() # # --- # # 8) Override the MPxCommand.doIt() method # 9) Place your setup code inside the doIt() # 10) Place the polyModifierCmd setup code inside the doIt() # (ie. setMeshNode(), setModifierNodeType()) # 11) Call polyModifierCmd._doModifyPoly() inside the doIt() # # --- # # 12) Override the MPxCommand.redoIt() method # 13) Call polyModifierCmd.redoModifyPoly() in redoIt() # # --- # # 14) Override the MPxCommand.undoIt() method # 15) Call polyModifierCmd.undoModifyPoly() in undoIt() # # For more details on each of these steps, please visit the associated method/class # headers. # # # Limitations: # # There is one limitation in polyModifierCmd: # # (1) Duplicate mesh created under the "No History / History turned on" case not undoable # # Case (1): # # Under the "No History / History turned on" case, history is allowed so the DG # is used to perform the operation. However, every polyModifierNode requires # an input mesh and without any prior history, a mesh input needs to be created. # polyModifierCmd compensates for this by duplicating the meshNode and marking # it as an intermediate object. # # The problem with this duplication is that the only duplicate method in the # Maya API resides in MFnDagNode, which does not have an associated undo/redo # mechanism. Attempting to manually delete the node by use of a DGmodifier or # the delete command will break the undo/redo mechanism for the entire # command. As a result, this duplicate mesh is a remnant of each instance of the # command excluding undo/redo. # # To work past this limitation, a manual delete from the command line is # required. # import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx import sys def statusError(message): fullMsg = "Status failed: %s\n" % message sys.stderr.write(fullMsg) OpenMaya.MGlobal.displayError(fullMsg) raise # called from exception handlers only, reraise exception def statusAssert(condition, message): if (not condition): fullMsg = "Assertion failed: %s\n" % message sys.stderr.write(fullMsg) OpenMaya.MGlobal.displayError(fullMsg) assert(False) class polyModifierCmd(OpenMayaMPx.MPxCommand): def __init__(self): OpenMayaMPx.MPxCommand.__init__(self) ########################## ## polyModifierCmd Data ## ########################## # polyMesh self.__fDagPathInitialized = False self.__fDagPath = OpenMaya.MDagPath() self.__fDuplicateDagPath = OpenMaya.MDagPath() # Modifier Node Type self.__fModifierNodeTypeInitialized = False self.__fModifierNodeNameInitialized = False self.__fModifierNodeType = OpenMaya.MTypeId() self.__fModifierNodeName = "" # Node State Information self.__fHasHistory = False self.__fHasTweaks = False self.__fHasRecordHistory = False # Cached Tweak Data (for undo) self.__fTweakIndexArray = OpenMaya.MIntArray() self.__fTweakVectorArray = OpenMaya.MFloatVectorArray() # Cached Mesh Data (for undo in the 'No History'/'History turned off' case) self.__fMeshData = OpenMaya.MObject() # DG and DAG Modifier # # - We need both DAG and DG modifiers since the MDagModifier::createNode() # method is overridden and specific to DAG nodes. So to keep # the operations consistent we will only use the fDagModifier # when dealing with the DAG. # # - There is a limitation between the reparentNode() and deleteNode() # methods on the MDagModifier. The deleteNode() method does some # preparation work before it enqueues itself in the MDagModifier list # of operations, namely, it looks at it's parents and children and # deletes them as well if they are the only parent/child of the node # scheduled to be deleted. # # This conflicts with our call to MDagModifier::reparentNode(), # since we want to reparent the shape of a duplicated node under # another node and then delete the transform of that node. Now you # can see that since the reparentNode() doesn't execute until after # the MDagModifier::doIt() call, the scheduled deleteNode() call # still sees the child and marks it for delete. The subsequent # doIt() call reparents the shape and then deletes both it and the # transform. # # To avoid this conflict, we separate the calls individually and # perform the reparenting (by calling a doIt()) before the deleteNode() # method is enqueued on the modifier. # self.__fDGModifier = OpenMaya.MDGModifier() self.__fDagModifier = OpenMaya.MDagModifier() ####################### PROTECTED ####################### #################################### ## polyModifierCmd Initialization ## #################################### def _setMeshNode(self, mesh): """ Target polyMesh to modify """ self.__fDagPath = mesh self.__fDagPathInitialized = True def _getMeshNode(self): return self.__fDagPath def _setModifierNodeType(self, nodeType): """ Modifier Node Type """ self.__fModifierNodeType = nodeType self.__fModifierNodeTypeInitialized = True def _setModifierNodeName(self, nodeName): self.__fModifierNodeName = nodeName self.__fModifierNodeNameInitialized = True def _getModifierNodeType(self): return self.__fModifierNodeType def _getModifierNodeName(self): return self.__fModifierNodeName ############################### ## polyModifierCmd Execution ## ############################### def _initModifierNode(self, modifierNode): """ Derived classes should override this method if they wish to initialize input attributes on the modifierNode """ pass def _directModifier(self, mesh): """ Derived classes should override this method to provide direct modifications on the meshNode in the case where no history exists and construction history is turned off. (ie. no DG operations desired) This method is called only if history does not exist and history is turned off. At this point, a handle to the meshNode is passed in so a derived class may directly modify the mesh. """ pass def _doModifyPoly(self): if self.__isCommandDataValid(): # Get the state of the polyMesh # self.__collectNodeState() if (not self.__fHasHistory) and (not self.__fHasRecordHistory): meshNode = self.__fDagPath.node() # Pre-process the mesh - Cache old mesh (including tweaks, if applicable) # self.__cacheMeshData() self.__cacheMeshTweaks() # Call the directModifier # self._directModifier(meshNode) else: modifierNode = self.__createModifierNode() self._initModifierNode(modifierNode) self.__connectNodes(modifierNode) def _redoModifyPoly(self): if (not self.__fHasHistory) and (not self.__fHasRecordHistory): meshNode = self.__fDagPath.node() # Call the directModifier - No need to pre-process the mesh data again # since we already have it. # self._directModifier(meshNode) else: # Call the redo on the DG and DAG modifiers # if self.__fHasHistory: self.__fDagModifier.doIt() self.__fDGModifier.doIt() def _undoModifyPoly(self): if (not self.__fHasHistory) and (not self.__fHasRecordHistory): self.__undoDirectModifier() else: self.__fDGModifier.undoIt() # undoCachedMesh must be called before undoTweakProcessing because # undoCachedMesh copies the original mesh *without* tweaks back onto # the existing mesh. Any changes done before the copy will be lost. # if not self.__fHasHistory: try: self.__undoCachedMesh() except: statusError("undoCachedMesh") self.__fDagModifier.undoIt() try: self.__undoTweakProcessing() except: statusError("undoTweakProcessing") ####################### PRIVATE ####################### ############################################## ## polyModifierCmd Internal Processing Data ## ############################################## # This structure is used to maintain the data vital to the modifyPoly method. # It is necessary to simplify parameter passing between the methods used inside # modifyPoly (specifically inside connectNodes()). The diagram below dictates # the naming style used: # # NOTE: modifierNode is intentionally left out of this structure since it # is given protected access to derived classes. # # Before: # # (upstreamNode) *src -> dest* (meshNode) # # After: # # (upstreamNode) *src -> dest* (modifierNode) *src -> dest* (meshNode) # class __modifyPolyData: def __init__(self): self.meshNodeTransform = OpenMaya.MObject() self.meshNodeShape = OpenMaya.MObject() self.meshNodeDestPlug = OpenMaya.MPlug() self.meshNodeDestAttr = OpenMaya.MObject() self.upstreamNodeTransform = OpenMaya.MObject() self.upstreamNodeShape = OpenMaya.MObject() self.upstreamNodeSrcPlug = OpenMaya.MPlug() self.upstreamNodeSrcAttr = OpenMaya.MObject() self.modifierNodeSrcAttr = OpenMaya.MObject() self.modifierNodeDestAttr = OpenMaya.MObject() self.tweakNode = OpenMaya.MObject() self.tweakNodeSrcAttr = OpenMaya.MObject() self.tweakNodeDestAttr = OpenMaya.MObject() ###################################### ## polyModifierCmd Internal Methods ## ###################################### def __isCommandDataValid(self): valid = True # Check the validity of the DAG path # if self.__fDagPathInitialized: self.__fDagPath.extendToShape() if (not self.__fDagPath.isValid()) or (self.__fDagPath.apiType() != OpenMaya.MFn.kMesh): valid = False else: valid = False # Check the validity of the Modifier node type/name # if (not self.__fModifierNodeTypeInitialized) and (not self.__fModifierNodeNameInitialized): valid = False return valid def __collectNodeState(self): """ Collect node state information on the given polyMeshShape - HasHistory (Construction History exists) - HasTweaks - HasRecordHistory (Construction History is turned on) """ self.__fDagPath.extendToShape() meshNodeShape = self.__fDagPath.node() depNodeFn = OpenMaya.MFnDependencyNode() depNodeFn.setObject(meshNodeShape) inMeshPlug = depNodeFn.findPlug("inMesh") self.__fHasHistory = inMeshPlug.isConnected() # Tweaks exist only if the multi "pnts" attribute contains plugs # which contain non-zero tweak values. Use false, until proven true # search algorithm. # self.__fHasTweaks = False tweakPlug = depNodeFn.findPlug("pnts") if not tweakPlug.isNull(): # ASSERT: tweakPlug should be an array plug! # statusAssert(tweakPlug.isArray(), "tweakPlug.isArray() -- tweakPlug is not an array plug") numElements = tweakPlug.numElements() for i in range(numElements): tweak = tweakPlug.elementByPhysicalIndex(i) if not tweak.isNull(): tweakData = self.__getFloat3PlugValue(tweak) if 0 != tweakData.x or 0 != tweakData.y or 0 != tweakData.z: self.__fHasTweaks = True break result = 0 OpenMaya.MGlobal.executeCommand("constructionHistory -q -tgl", result) self.__fHasRecordHistory = (0 != result) # Modifier node methods # def __createModifierNode(self): modifierNode = OpenMaya.MObject() if self.__fModifierNodeTypeInitialized or self.__fModifierNodeNameInitialized: if self.__fModifierNodeTypeInitialized: modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeType) elif self.__fModifierNodeNameInitialized: modifierNode = self.__fDGModifier.createNode(self.__fModifierNodeName) # Check to make sure that we have a modifier node of the appropriate type. # Requires an inMesh and outMesh attribute. # depNodeFn = OpenMaya.MFnDependencyNode(modifierNode) inMeshAttr = depNodeFn.attribute("inMesh") outMeshAttr = depNodeFn.attribute("outMesh") statusAssert(not (inMeshAttr.isNull() or outMeshAttr.isNull()), "Invalid Modifier Node: inMesh and outMesh attributes are required.") return modifierNode # Node processing methods (need to be executed in this order) # def __processMeshNode(self, data): # Declare our function sets. Use MFnDagNode here so # we can retrieve the parent transform. # dagNodeFn = OpenMaya.MFnDagNode() # Use the DAG path to retrieve our mesh shape node. # data.meshNodeShape = self.__fDagPath.node() dagNodeFn.setObject(data.meshNodeShape) # ASSERT: meshNodeShape node should have a parent transform! # statusAssert(0 < dagNodeFn.parentCount(), "0 < dagNodeFn.parentCount() -- meshNodeshape has no parent transform") data.meshNodeTransform = dagNodeFn.parent(0) data.meshNodeDestPlug = dagNodeFn.findPlug("inMesh") data.meshNodeDestAttr = data.meshNodeDestPlug.attribute() def __processUpstreamNode(self, data): # Declare our function sets - Although dagNodeFn derives from depNodeFn, we need # both since dagNodeFn can only refer to DAG objects. # We will use depNodeFn for all times other when dealing # with the DAG. # depNodeFn = OpenMaya.MFnDependencyNode() dagNodeFn = OpenMaya.MFnDagNode() # Use the selected node's plug connections to find the upstream plug. # Since we are looking at the selected node's inMesh attribute, it will # always have only one connection coming in if it has history, and none # otherwise. # # If there is no history, copy the selected node and place it ahead of the # modifierNode as the new upstream node so that the modifierNode has an # input mesh to operate on. # tempPlugArray = OpenMaya.MPlugArray() if self.__fHasHistory: # Since we have history, look for what connections exist on the # meshNode "inMesh" plug. "inMesh" plugs should only ever have one # connection. # data.meshNodeDestPlug.connectedTo(tempPlugArray, True, False) # ASSERT: Only one connection should exist on meshNodeShape.inMesh! # statusAssert(tempPlugArray.length() == 1, "tempPlugArray.length() == 1 -- 0 or >1 connections on meshNodeShape.inMesh") data.upstreamNodeSrcPlug = tempPlugArray[0] # Construction history only deals with shapes, so we can grab the # upstreamNodeShape off of the source plug. # data.upstreamNodeShape = data.upstreamNodeSrcPlug.node() depNodeFn.setObject(data.upstreamNodeShape) data.upstreamNodeSrcAttr = data.upstreamNodeSrcPlug.attribute() # Disconnect the upstream node and the selected node, so we can # replace them with our own connections below. # self.__fDGModifier.disconnect(data.upstreamNodeSrcPlug, data.meshNodeDestPlug) else: # No History (!fHasHistory) # Use the DAG node function set to duplicate the shape of the meshNode. # The duplicate method will return an MObject handle to the transform # of the duplicated shape, so traverse the dag to locate the shape. Store # this duplicate shape as our "upstream" node to drive the input for the # modifierNode. # dagNodeFn.setObject(data.meshNodeShape) data.upstreamNodeTransform = dagNodeFn.duplicate(False, False) dagNodeFn.setObject(data.upstreamNodeTransform) # Ensure that our upstreamNode is pointing to a shape. # statusAssert(0 < dagNodeFn.childCount(), "0 < dagNodeFn.childCount() -- Duplicate meshNode transform has no shape.") data.upstreamNodeShape = dagNodeFn.child(0) # Re-parent the upstreamNodeShape under our original transform # try: self.__fDagModifier.reparentNode(data.upstreamNodeShape, data.meshNodeTransform) except: statusError("reparentNode") # Perform the DAG re-parenting # # Note: This reparent must be performed before the deleteNode() is called. # See polyModifierCmd.h (see definition of fDagModifier) for more details. # try: self.__fDagModifier.doIt() except: statusError("fDagModifier.doIt()") # Mark the upstreamNodeShape (the original shape) as an intermediate object # (making it invisible to the user) # dagNodeFn.setObject(data.upstreamNodeShape) dagNodeFn.setIntermediateObject(True) # Get the upstream node source attribute # data.upstreamNodeSrcAttr = dagNodeFn.attribute("outMesh") data.upstreamNodeSrcPlug = dagNodeFn.findPlug("outMesh") # Remove the duplicated transform node (clean up) # try: self.__fDagModifier.deleteNode(data.upstreamNodeTransform) except: statusError("deleteNode") # Perform the DAG delete node # # Note: This deleteNode must be performed after the reparentNode() method is # completed. See polyModifierCmd.h (see definition of fDagModifier) for # details. # try: self.__fDagModifier.doIt() except: statusError("fDagModifier.doIt()") # Cache the DAG path to the duplicate shape # dagNodeFn.getPath(self.__fDuplicateDagPath) def __processModifierNode(self, modifierNode, data): depNodeFn = OpenMaya.MFnDependencyNode(modifierNode) data.modifierNodeSrcAttr = depNodeFn.attribute("outMesh") data.modifierNodeDestAttr = depNodeFn.attribute("inMesh") def __processTweaks(self, data): # Clear tweak undo information (to be rebuilt) # self.__fTweakIndexArray.clear() self.__fTweakVectorArray.clear() # Extract the tweaks and place them into a polyTweak node. This polyTweak node # will be placed ahead of the modifier node to maintain the order of operations. # Special care must be taken into recreating the tweaks: # # 1) Copy tweak info (including connections!) # 2) Remove tweak info from both meshNode and a duplicate meshNode (if applicable) # 3) Cache tweak info for undo operations # if self.__fHasTweaks: # Declare our function sets # depNodeFn = OpenMaya.MFnDependencyNode() # Declare our tweak processing variables # tweakDataArray = OpenMaya.MObjectArray() tweakSrcConnectionCountArray = OpenMaya.MIntArray() tweakSrcConnectionPlugArray = OpenMaya.MPlugArray() tweakDstConnectionCountArray = OpenMaya.MIntArray() tweakDstConnectionPlugArray = OpenMaya.MPlugArray() tempPlugArray = OpenMaya.MPlugArray() # Create the tweak node and get its attributes # data.tweakNode = self.__fDGModifier.createNode("polyTweak") depNodeFn.setObject(data.tweakNode) data.tweakNodeSrcAttr = depNodeFn.attribute("output") data.tweakNodeDestAttr = depNodeFn.attribute("inputPolymesh") tweakNodeTweakAttr = depNodeFn.attribute("tweak") depNodeFn.setObject(data.meshNodeShape) meshTweakPlug = depNodeFn.findPlug("pnts") # ASSERT: meshTweakPlug should be an array plug! # statusAssert(meshTweakPlug.isArray(), "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug") # Gather meshTweakPlug data # numElements = meshTweakPlug.numElements() for i in range(numElements): # MPlug.numElements() only returns the number of physical elements # in the array plug. Thus we must use elementByPhysical index when using # the index i. # tweak = meshTweakPlug.elementByPhysicalIndex(i) # If the method fails, the element is NULL. Only append the index # if it is a valid plug. # if not tweak.isNull(): # Cache the logical index of this element plug # logicalIndex = tweak.logicalIndex() # Collect tweak data and cache the indices and float vectors # tweakData = tweak.asMObject() tweakDataArray.append(tweakData) tweakVector = self.__getFloat3PlugValue(tweak) self.__fTweakIndexArray.append(logicalIndex) self.__fTweakVectorArray.append(tweakVector) # Collect tweak connection data # # Parse down to the deepest level of the plug tree and check # for connections - look at the child nodes of the element plugs. # If any connections are found, record the connection and disconnect # it. # # ASSERT: The element plug should be compound! # statusAssert(tweak.isCompound(), "tweak.isCompound() -- Element tweak plug is not compound") numChildren = tweak.numChildren() for j in range(numChildren): tweakChild = tweak.child(j) if tweakChild.isConnected(): # Get all connections with this plug as source, if they exist # tempPlugArray.clear() if tweakChild.connectedTo(tempPlugArray, False, True): numSrcConnections = tempPlugArray.length() tweakSrcConnectionCountArray.append(numSrcConnections) for k in range(numSrcConnections): tweakSrcConnectionPlugArray.append(tempPlugArray[k]) self.__fDGModifier.disconnect(tweakChild, tempPlugArray[k]) else: tweakSrcConnectionCountArray.append(0) # Get the connection with this plug as destination, if it exists # tempPlugArray.clear() if tweakChild.connectedTo(tempPlugArray, True, False): # ASSERT: tweakChild should only have one connection as destination! # statusAssert(tempPlugArray.length() == 1, "tempPlugArray.length() == 1 -- 0 or >1 connections on tweakChild") tweakDstConnectionCountArray.append(1) tweakDstConnectionPlugArray.append(tempPlugArray[0]) self.__fDGModifier.disconnect(tempPlugArray[0], tweakChild) else: tweakDstConnectionCountArray.append(0) else: tweakSrcConnectionCountArray.append(0) tweakDstConnectionCountArray.append(0) # Apply meshTweakPlug data to our polyTweak node # polyTweakPlug = OpenMaya.MPlug(data.tweakNode, tweakNodeTweakAttr) numTweaks = self.__fTweakIndexArray.length() srcOffset = 0 dstOffset = 0 for i in range(numTweaks): # Apply tweak data # tweak = polyTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i]) tweak.setMObject(tweakDataArray[i]) # ASSERT: Element plug should be compound! # statusAssert(tweak.isCompound(), "tweak.isCompound() -- Element plug, 'tweak', is not compound") numChildren = tweak.numChildren() for j in range(numChildren): tweakChild = tweak.child(j) # Apply tweak source connection data # if 0 < tweakSrcConnectionCountArray[i*numChildren + j]: k = 0 while (k < tweakSrcConnectionCountArray[i*numChildren + j]): self.__fDGModifier.connect(tweakChild, tweakSrcConnectionPlugArray[srcOffset]) srcOffset += 1 k += 1 # Apply tweak destination connection data # if 0 < tweakDstConnectionCountArray[i*numChildren + j]: self.__fDGModifier.connect(tweakDstConnectionPlugArray[dstOffset], tweakChild) dstOffset += 1 # Node connection method # def __connectNodes(self, modifierNode): """ This method connects up the modifier nodes, while accounting for DG factors such as construction history and tweaks. The method has a series of steps which it runs through to process nodes under varying circumstances: 1) Gather meshNode connection data (ie. attributes and plugs) 2) Gather upstreamNode data - This is history-dependent. If the node has history, an actual upstreamNode exists and that is used to drive the input of our modifierNode. Otherwise, if the node does not have history, the meshNode is duplicated, set as an intermediate object and regarded as our new upstreamNode which will drive the input of our modifierNode. The case with history already has this duplicate meshNode at the top, driving all other history nodes and serving as a reference to the "original state" of the node before any modifications. 3) Gather modifierNode connection data 4) Process tweak data (if it exists) - This is history-dependent. If there is history, the tweak data is extracted and deleted from the meshNode and encapsulated inside a polyTweak node. The polyTweak node is then inserted ahead of the modifier node. If there is no history, the same is done as in the history case, except the tweaks are deleted from the duplicate meshNode in addition to the actual meshNode. 5) Connect the nodes 6) Collapse/Bake nodes into the actual meshNode if the meshNode had no previous construction history and construction history recording is turned off. (ie. (!fHasHistory && !fHasRecordHistory) == true ) """ # Declare our internal processing data structure (see polyModifierCmd.h for definition) # data = polyModifierCmd.__modifyPolyData() # Get the mesh node, plugs and attributes # try: self.__processMeshNode(data) except: statusError("processMeshNode") # Get upstream node, plugs and attributes # try: self.__processUpstreamNode(data) except: statusError("processUpstreamNode") # Get the modifierNode attributes # try: self.__processModifierNode(modifierNode, data) except: statusError("processModifierNode") # Process tweaks on the meshNode # try: self.__processTweaks(data) except: statusError("processTweaks") # Connect the nodes # if self.__fHasTweaks: tweakDestPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeDestAttr) self.__fDGModifier.connect(data.upstreamNodeSrcPlug, tweakDestPlug) tweakSrcPlug = OpenMaya.MPlug(data.tweakNode, data.tweakNodeSrcAttr) modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr) self.__fDGModifier.connect(tweakSrcPlug, modifierDestPlug) else: modifierDestPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeDestAttr) self.__fDGModifier.connect(data.upstreamNodeSrcPlug, modifierDestPlug) modifierSrcPlug = OpenMaya.MPlug(modifierNode, data.modifierNodeSrcAttr) meshDestAttr = OpenMaya.MPlug(data.meshNodeShape, data.meshNodeDestAttr) self.__fDGModifier.connect(modifierSrcPlug, meshDestAttr) self.__fDGModifier.doIt() # Mesh caching methods - Only used in the directModifier case # def __cacheMeshData(self): depNodeFn = OpenMaya.MFnDependencyNode() dagNodeFn = OpenMaya.MFnDagNode() meshNode = self.__fDagPath.node() # Duplicate the mesh # dagNodeFn.setObject(meshNode) dupMeshNode = dagNodeFn.duplicate() dupMeshDagPath = OpenMaya.MDagPath() OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath) dupMeshDagPath.extendToShape() depNodeFn.setObject(dupMeshDagPath.node()) try: dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh") except: statusError("Could not retrieve outMesh") # Retrieve the meshData # try: self.__fMeshData = dupMeshNodeOutMeshPlug.asMObject() except: statusError("Could not retrieve meshData") # Delete the duplicated node # OpenMaya.MGlobal.deleteNode(dupMeshNode) def __cacheMeshTweaks(self): # Clear tweak undo information (to be rebuilt) # self.__fTweakIndexArray.clear() self.__fTweakVectorArray.clear() # Extract the tweaks and store them in our local tweak cache members # if self.__fHasTweaks: # Declare our function sets # depNodeFn = OpenMaya.MFnDependencyNode() meshNode = self.__fDagPath.node() depNodeFn.setObject(meshNode) meshTweakPlug = depNodeFn.findPlug("pnts") # ASSERT: meshTweakPlug should be an array plug! # statusAssert(meshTweakPlug.isArray(), "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" ) # Gather meshTweakPlug data # numElements = meshTweakPlug.numElements() for i in range(numElements): # MPlug.numElements() only returns the number of physical elements # in the array plug. Thus we must use elementByPhysical index when using # the index i. # tweak = meshTweakPlug.elementByPhysicalIndex(i) # If the method fails, the element is NULL. Only append the index # if it is a valid plug. # if not tweak.isNull(): # Cache the logical index of this element plug # logicalIndex = tweak.logicalIndex() # Collect tweak data and cache the indices and float vectors # tweakVector = self.__getFloat3PlugValue(tweak) self.__fTweakIndexArray.append(logicalIndex) self.__fTweakVectorArray.append(tweakVector) # Undo methods # def __undoCachedMesh(self): # Only need to restore the cached mesh if there was no history. Also # check to make sure that we are in the record history state. # statusAssert(self.__fHasRecordHistory, "fHasRecordHistory == true") if not self.__fHasHistory: depNodeFn = OpenMaya.MFnDependencyNode() meshNodeShape = self.__fDagPath.node() dupMeshNodeShape = self.__fDuplicateDagPath.node() depNodeFn.setObject(meshNodeShape) meshNodeName = depNodeFn.name() try: meshNodeDestPlug = depNodeFn.findPlug("inMesh") except: statusError("Could not retrieve inMesh") try: meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh") except: statusError("Could not retrieve outMesh") depNodeFn.setObject(dupMeshNodeShape) try: dupMeshNodeSrcPlug = depNodeFn.findPlug("outMesh") except: statusError("Could not retrieve outMesh") # For the case with tweaks, we cannot write the mesh directly back onto # the cachedInMesh, since the shape can have out of date information from the # cachedInMesh, thus we temporarily connect the duplicate mesh shape to the # mesh shape and force a DG evaluation. # # For the case without tweaks, we can simply write onto the outMesh, since # the shape relies solely on an outMesh when there is no history nor tweaks. # if self.__fHasTweaks: dgModifier = OpenMaya.MDGModifier() dgModifier.connect(dupMeshNodeSrcPlug, meshNodeDestPlug) try: dgModifier.doIt() except: statusError("Could not connect dupMeshNode -> meshNode") # Need to force a DG evaluation now that the input has been changed. # cmd = "dgeval -src %s.inMesh" % meshNodeName try: OpenMaya.MGlobal.executeCommand(cmd, False, False) except: statusError("Could not force DG eval") # Disconnect the duplicate meshNode now # dgModifier.undoIt() else: try: meshData = dupMeshNodeSrcPlug.asMObject() try: meshNodeOutMeshPlug.setMObject(meshData) except: statusError("Could not set outMesh") except: statusError("Could not retrieve meshData") def __undoTweakProcessing(self): if self.__fHasTweaks: meshNodeShape = self.__fDagPath.node() depNodeFn = OpenMaya.MFnDependencyNode() depNodeFn.setObject(meshNodeShape) meshTweakPlug = depNodeFn.findPlug("pnts") statusAssert(meshTweakPlug.isArray(), "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug") numElements = self.__fTweakIndexArray.length() for i in range(numElements): tweak = meshTweakPlug.elementByLogicalIndex(self.__fTweakIndexArray[i]) tweakData = self.__getFloat3asMObject(self.__fTweakVectorArray[i]) tweak.setMObject(tweakData) # In the case of no history, the duplicate node shape will be disconnected on undo # so, there is no need to undo the tweak processing on it. # def __undoDirectModifier(self): depNodeFn = OpenMaya.MFnDependencyNode() dagNodeFn = OpenMaya.MFnDagNode() meshNode = self.__fDagPath.node() depNodeFn.setObject( meshNode ) # For the case with tweaks, we cannot write the mesh directly back onto # the cachedInMesh, since the shape can have out of date information from the # cachedInMesh. Thus we temporarily create an duplicate mesh, place our # old mesh on the outMesh attribute of our duplicate mesh, connect the # duplicate mesh shape to the mesh shape, and force a DG evaluation. # # For the case without tweaks, we can simply write onto the outMesh, since # the shape relies solely on an outMesh when there is no history nor tweaks. # if self.__fHasTweaks: # Retrieve the inMesh and name of our mesh node (for the DG eval) # depNodeFn.setObject(meshNode) try: meshNodeInMeshPlug = depNodeFn.findPlug("inMesh") except: statusError("Could not retrieve inMesh") meshNodeName = depNodeFn.name() # Duplicate our current mesh # dagNodeFn.setObject(meshNode) dupMeshNode = dagNodeFn.duplicate() # The dagNodeFn.duplicate() returns a transform, but we need a shape # so retrieve the DAG path and extend it to the shape. # dupMeshDagPath = OpenMaya.MDagPath() OpenMaya.MDagPath.getAPathTo(dupMeshNode, dupMeshDagPath) dupMeshDagPath.extendToShape() # Retrieve the outMesh of the duplicate mesh and set our mesh data back # on it. # depNodeFn.setObject(dupMeshDagPath.node()) try: dupMeshNodeOutMeshPlug = depNodeFn.findPlug("outMesh") except: statusError("Could not retrieve outMesh") dupMeshNodeOutMeshPlug.setMObject(self.__fMeshData) # Temporarily connect the duplicate mesh node to our mesh node # dgModifier = OpenMaya.MDGModifier() dgModifier.connect(dupMeshNodeOutMeshPlug, meshNodeInMeshPlug) try: dgModifier.doIt() except: statusError("Could not connect dupMeshNode -> meshNode") # Need to force a DG evaluation now that the input has been changed. # cmd = "dgeval -src %s.inMesh" % meshNodeName try: OpenMaya.MGlobal.executeCommand(cmd, False, False) except: statusError("Could not force DG eval") # Disconnect and delete the duplicate mesh node now # dgModifier.undoIt() OpenMaya.MGlobal.deleteNode(dupMeshNode) # Restore the tweaks on the mesh # self.__undoTweakProcessing() else: # Restore the original mesh by writing the old mesh data (fMeshData) back # onto the outMesh of our meshNode # depNodeFn.setObject(meshNode) try: meshNodeOutMeshPlug = depNodeFn.findPlug("outMesh") except: statusError("Could not retrieve outMesh") try: meshNodeOutMeshPlug.setMObject(self.__fMeshData) except: statusError("Could not set meshData") ##################################### ## polyModifierCmd Utility Methods ## ##################################### def __getFloat3PlugValue(self, plug): # Retrieve the value as an MObject object = plug.asMObject() # Convert the MObject to a float3 numDataFn = OpenMaya.MFnNumericData(object) xParam = OpenMaya.MScriptUtil() xParam.createFromDouble(0.0) xPtr = xParam.asFloatPtr() yParam = OpenMaya.MScriptUtil() yParam.createFromDouble(0.0) yPtr = yParam.asFloatPtr() zParam = OpenMaya.MScriptUtil() zParam.createFromDouble(0.0) zPtr = zParam.asFloatPtr() numDataFn.getData3Float(xPtr, yPtr, zPtr) return OpenMaya.MFloatVector( OpenMaya.MScriptUtil(xPtr).asFloat(), OpenMaya.MScriptUtil(yPtr).asFloat(), OpenMaya.MScriptUtil(zPtr).asFloat()) def __getFloat3asMObject(self, value): # Convert the float value into an MObject numDataFn = OpenMaya.MFnNumericData() numDataFn.create(OpenMaya.MFnNumericData.k3Float) numDataFn.setData3Float(value[0], value[1], value[2]) return numDataFn.object() ##################################################################### ## FACTORY ########################################################## ##################################################################### # Overview: # # The polyModifierFty class is the main workhorse of the polyModifierCmd operation. # It is here that the actual operation is implemented. The idea of the factory is # to create a single implementation of the modifier that can be reused in more than # one place. # # As such, the details of the factory are quite simple. Each factory contains a doIt() # method which should be overridden. This is the method which will be called by the # node and the command when a modifier is requested. # # How to use: # # 1) Create a factory derived from polyModifierFty # 2) Add any input methods and members to the factory # 3) Override the polyModifierFty.doIt() method # # (a) Retrieve the inputs from the class # (b) Process the inputs # (c) Perform the modifier # # class polyModifierFty: def __init__(self): pass def doIt(self): pass ##################################################################### ## NODE ############################################################# ##################################################################### # Overview: # # The polyModifierNode class is a intermediate class used by polyModifierCmd to # modify a polygonal object in Maya. The purpose of the polyModifierNode is to # generalize a node that can receive an input mesh object, modify the mesh and # return the modified mesh. # # polyModifierNode is an abstraction which does not need to know about the DG # and simply needs to know about the process outlined above. polyModifierCmd # manages when and how this node will be used. # # Each polyModifierNode is recommended to contain an instance of a polyModifierFty # which will do the actual work, leaving only the node setup and attribute # associations to the node. The recommended structure of a polyModifierNode is # as follows: # # _____________ # / ___ \ # / / \ \ # O | Node | Fty | | O # | \ \___/ / | # | \_____________/ | # inMesh outMesh # # # The purpose of the node is to simply define the attributes (inputs and outputs) of # the node and associate which attribute affects each other. This is basic node setup # for a DG node. Using the above structure, the node's inherited "compute()" method # (from MPxNode) should retrieve the inputs and pass the appropriate data down to the # polyModifierFty for processing. # # # How to use: # # (1) Create a class derived from polyModifierNode # (2) Define and associate inMesh and outMesh attributes (inMesh --> affects --> outMesh) # (3) Add any additional attributes specific to the derived node and setup associations # (4) Define an instance of your specific polyModifierFty to perform the operation on the node # (5) Override the MPxNode::compute() method # (6) Inside compute(): # # (a) Retrieve input attributes # (b) Use inputs to setup your factory to operate on the given mesh # (c) Call the factory's inherited doIt() method # # class polyModifierNode(OpenMayaMPx.MPxNode): # There needs to be a MObject handle declared for each attribute that # the node will have. These handles are needed for getting and setting # the values later. # inMesh = OpenMaya.MObject() outMesh = OpenMaya.MObject() def __init__(self): OpenMayaMPx.MPxNode.__init__(self)
Autodesk® Maya® 2009 © 1997-2008 Autodesk, Inc. All rights reserved. | Generated with 1.5.6 |