// ================================================================== // Copyright 2012 Autodesk, Inc. All rights reserved. // // This computer source code and related instructions and comments are // the unpublished confidential and proprietary information of Autodesk, // Inc. and are protected under applicable copyright and trade secret // law. They may not be disclosed to, copied or used by any third party // without the prior written consent of Autodesk, Inc. // ================================================================== /////////////////////////////////////////////////////////////////////////////// // // apiMesh // // Implements a new type of shape node in Maya called apiMesh. // // INPUTS // inputSurface - input apiMeshData // outputSurface - output apiMeshData // worldSurface - array of world space apiMeshData, each element // represents an instance of the shape // OUTPUTS // mControlPoints - inherited control vertices for the mesh. These values // are tweaks (offsets) that will be applied to the // vertices of the input shape. // bboxCorner1 - bounding box upper left corner // bboxCorner2 - bounding box lower right corner // //////////////////////////////////////////////////////////////////////////////// using System; using Autodesk.Maya.OpenMaya; using Autodesk.Maya.OpenMayaRender; using Autodesk.Maya.OpenMayaRender.MHWRender; [assembly: MPxShapeClass(typeof(MayaNetPlugin.apiMesh), typeof(MayaNetPlugin.apiMeshUI), "apiMeshCSharp", 0x8106f, Classification = "drawdb/geometry/apiMesh")] namespace MayaNetPlugin { class apiMesh : MPxSurfaceShape { ////////////////////////////////////////////////////////// // // Attributes // ////////////////////////////////////////////////////////// public static MObject inputSurface = null; public static MObject outputSurface = null; public static MObject worldSurface = null; // used to support tweaking of points, the inputSurface attribute data is // transferred into the cached surface when it is dirty. The control points // tweaks are added into it there. // public static MObject cachedSurface = null; public static MObject bboxCorner1 = null; public static MObject bboxCorner2 = null; public bool fHasHistoryOnCreate; [MPxNodeInitializer()] public static void initialize() { MFnNumericAttribute numAttr = new MFnNumericAttribute(); MFnTypedAttribute typedAttr = new MFnTypedAttribute(); // ----------------------- INPUTS -------------------------- inputSurface = typedAttr.create("inputSurface", "is", new MTypeId(apiMeshData.id)); typedAttr.isStorable = false; addAttribute( inputSurface ); // ----------------------- OUTPUTS ------------------------- // bbox attributes // bboxCorner1 = numAttr.create( "bboxCorner1", "bb1", MFnNumericData.Type.k3Double, 0 ); numAttr.isArray = false; numAttr.usesArrayDataBuilder = false; numAttr.isHidden = false; numAttr.isKeyable = false; addAttribute( bboxCorner1 ); bboxCorner2 = numAttr.create( "bboxCorner2", "bb2", MFnNumericData.Type.k3Double, 0 ); numAttr.isArray = false; numAttr.usesArrayDataBuilder = false; numAttr.isHidden = false; numAttr.isKeyable = false; addAttribute( bboxCorner2 ); // local/world output surface attributes // outputSurface = typedAttr.create("outputSurface", "os", new MTypeId(apiMeshData.id)); addAttribute( outputSurface ); typedAttr.isWritable = false; worldSurface = typedAttr.create("worldSurface", "ws", new MTypeId(apiMeshData.id)); typedAttr.isCached = false; typedAttr.isWritable = false; typedAttr.isArray = true; typedAttr.usesArrayDataBuilder = true; typedAttr.disconnectBehavior = MFnAttribute.DisconnectBehavior.kDelete; typedAttr.isWorldSpace = true; addAttribute( worldSurface ); // Cached surface used for file IO // cachedSurface = typedAttr.create("cachedSurface", "cs", new MTypeId(apiMeshData.id)); typedAttr.isReadable = true; typedAttr.isWritable = true; typedAttr.isStorable = true; addAttribute( cachedSurface ); // ---------- Specify what inputs affect the outputs ---------- // attributeAffects( inputSurface, outputSurface ); attributeAffects( inputSurface, worldSurface ); attributeAffects( outputSurface, worldSurface ); attributeAffects( inputSurface, bboxCorner1 ); attributeAffects( inputSurface, bboxCorner2 ); attributeAffects( cachedSurface, outputSurface ); attributeAffects( cachedSurface, worldSurface ); attributeAffects( mControlPoints, outputSurface ); attributeAffects( mControlValueX, outputSurface ); attributeAffects( mControlValueY, outputSurface ); attributeAffects( mControlValueZ, outputSurface ); attributeAffects( mControlPoints, cachedSurface ); attributeAffects( mControlValueX, cachedSurface ); attributeAffects( mControlValueY, cachedSurface ); attributeAffects( mControlValueZ, cachedSurface ); attributeAffects( mControlPoints, worldSurface ); attributeAffects( mControlValueX, worldSurface ); attributeAffects( mControlValueY, worldSurface ); attributeAffects( mControlValueZ, worldSurface ); } public apiMesh() { } ////////////////////////////////////////////////////////////////// // // Overrides from MPxNode // ////////////////////////////////////////////////////////////////// public override void postConstructor() // // Description // // When instances of this node are created internally, the MObject associated // with the instance is not created until after the constructor of this class // is called. This means that no member functions of MPxSurfaceShape can // be called in the constructor. // The postConstructor solves this problem. Maya will call this function // after the internal object has been created. // As a general rule do all of your initialization in the postConstructor. // { // This call allows the shape to have shading groups assigned // isRenderable = true; // Is there input history to this node // fHasHistoryOnCreate = false; } public override bool compute(MPlug plug, MDataBlock datablock) // // Description // // When input attributes are dirty this method will be called to // recompute the output attributes. // // Arguments // // plug - the attribute that triggered the compute // datablock - the nodes data // // Returns // // kSuccess - this method could compute the dirty attribute, // kUnknownParameter - the dirty attribute can not be handled at this level // { if ( plug.attribute.equalEqual(outputSurface) ) { computeOutputSurface( plug, datablock ); return true; } else if ( plug.attribute.equalEqual(cachedSurface) ) { computeOutputSurface( plug, datablock ); return true; } else if ( plug.attribute.equalEqual(worldSurface) ) { computeWorldSurface( plug, datablock ); return true; } else { return false; } } public override void setDependentsDirty(MPlug plug, MPlugArray plugArray) // // Description // // Horribly abuse the purpose of this method to notify the Viewport 2.0 // renderer that something about this shape has changed and that it should // be retranslated. // { // if the dirty attribute is the output mesh then we need to signal the // the renderer that it needs to update the object if ( plug.attribute.equalEqual(inputSurface) ) { MRenderer.setGeometryDrawDirty(thisMObject()); } return; } public override bool getInternalValue(MPlug plug, MDataHandle result) // // Description // // Handle internal attributes. // // Attributes that require special storage, bounds checking, // or other non-standard behavior can be marked as "Internal" by // using the "MFnAttribute.setInternal" method. // // The get/setInternalValue methods will get called for internal // attributes whenever the attribute values are stored or retrieved // using getAttr/setAttr or MPlug getValue/setValue. // // The inherited attribute mControlPoints is internal and we want // its values to get stored only if there is input history. Otherwise // any changes to the vertices are stored in the cachedMesh and outputMesh // directly. // // If values are retrieved then we want the controlPoints value // returned if there is history, this will be the offset or tweak. // In the case of no history, the vertex position of the cached mesh // is returned. // { bool isOk = true; if( plug.attribute.equalEqual(mControlPoints) || plug.attribute.equalEqual(mControlValueX) || plug.attribute.equalEqual(mControlValueY) || plug.attribute.equalEqual(mControlValueZ) ) { // If there is input history then the control point value is // directly returned. This is the tweak or offset that // was applied to the vertex. // // If there is no input history then return the actual vertex // position and ignore the controlPoints attribute. // if ( hasHistory() ) { return base.getInternalValue( plug, result ); } else { double val = 0.0; if ( plug.attribute.equalEqual(mControlPoints) && !plug.isArray ) { MPoint pnt = new MPoint(); int index = (int)plug.logicalIndex; value( index, ref pnt ); result.set( pnt[0], pnt[1], pnt[2] ); } else if ( plug.attribute.equalEqual(mControlValueX) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; value( index, 0, ref val ); result.set( val ); } else if ( plug.attribute.equalEqual(mControlValueY) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; value( index, 1, ref val ); result.set( val ); } else if ( plug.attribute.equalEqual(mControlValueZ) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; value( index, 2, ref val ); result.set( val ); } } } // This inherited attribute is used to specify whether or // not this shape has history. During a file read, the shape // is created before any input history can get connected. // This attribute, also called "tweaks", provides a way to // for the shape to determine if there is input history // during file reads. // else if ( plug.attribute.equalEqual(mHasHistoryOnCreate) ) { result.set( fHasHistoryOnCreate ); } else { isOk = base.getInternalValue( plug, result ); } return isOk; } public override bool setInternalValue(MPlug plug, MDataHandle handle) { bool isOk = true; if( plug.attribute.equalEqual(mControlPoints) || plug.attribute.equalEqual(mControlValueX) || plug.attribute.equalEqual(mControlValueY) || plug.attribute.equalEqual(mControlValueZ) ) { // If there is input history then set the control points value // using the normal mechanism. In this case we are setting // the tweak or offset that will get applied to the input // history. // // If there is no input history then ignore the controlPoints // attribute and set the vertex position directly in the // cachedMesh. // if ( hasHistory() ) { verticesUpdated(); return base.setInternalValue( plug, handle ); } else { if( plug.attribute.equalEqual(mControlPoints) && !plug.isArray) { int index = (int)plug.logicalIndex; MPoint point = new MPoint(); double[] ptData = handle.Double3; point.x = ptData[0]; point.y = ptData[1]; point.z = ptData[2]; setValue( index, point ); } else if( plug.attribute.equalEqual(mControlValueX) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; setValue( index, 0, handle.asDouble ); } else if( plug.attribute.equalEqual(mControlValueY) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; setValue( index, 1, handle.asDouble ); } else if( plug.attribute.equalEqual(mControlValueZ) ) { MPlug parentPlug = plug.parent; int index = (int)parentPlug.logicalIndex; setValue( index, 2, handle.asDouble ); } } } // This inherited attribute is used to specify whether or // not this shape has history. During a file read, the shape // is created before any input history can get connected. // This attribute, also called "tweaks", provides a way to // for the shape to determine if there is input history // during file reads. // else if ( plug.attribute.equalEqual(mHasHistoryOnCreate) ) { fHasHistoryOnCreate = handle.asBool; } else { isOk = base.setInternalValue( plug, handle ); } return isOk; } public override bool connectionMade(MPlug plug, MPlug otherPlug, bool asSrc) // // Description // // Whenever a connection is made to this node, this method // will get called. // { if ( plug.attribute.equalEqual(inputSurface) ) { MObject thisObj = thisMObject(); MPlug historyPlug = new MPlug( thisObj, mHasHistoryOnCreate ); historyPlug.setValue( true ); } return base.connectionMade( plug, otherPlug, asSrc ); } public override bool connectionBroken(MPlug plug, MPlug otherPlug, bool asSrc) // // Description // // Whenever a connection to this node is broken, this method // will get called. // { if ( plug.attribute.equalEqual(inputSurface) ) { MObject thisObj = thisMObject(); MPlug historyPlug = new MPlug( thisObj, mHasHistoryOnCreate ); historyPlug.setValue( false ); } return base.connectionBroken( plug, otherPlug, asSrc ); } public override bool shouldSave(MPlug plug, ref bool result) // // Description // // During file save this method is called to determine which // attributes of this node should get written. The default behavior // is to only save attributes whose values differ from the default. // // // { if( plug.attribute.equalEqual(mControlPoints) || plug.attribute.equalEqual(mControlValueX) || plug.attribute.equalEqual(mControlValueY) || plug.attribute.equalEqual(mControlValueZ) ) { if( hasHistory() ) { // Calling this will only write tweaks if they are // different than the default value. // return base.shouldSave( plug, ref result ); } else { result = false; } } else if ( plug.attribute.equalEqual(cachedSurface) ) { if ( hasHistory() ) { result = false; } else { MObject data = new MObject(); plug.getValue( data ); result = ( ! data.isNull ); } } else { return base.shouldSave( plug, ref result ); } return true; } public override void componentToPlugs(MObject component, MSelectionList list) // // Description // // Converts the given component values into a selection list of plugs. // This method is used to map components to attributes. // // Arguments // // component - the component to be translated to a plug/attribute // list - a list of plugs representing the passed in component // { if ( component.hasFn(MFn.Type.kSingleIndexedComponent) ) { MFnSingleIndexedComponent fnVtxComp = new MFnSingleIndexedComponent( component ); MObject thisNode = thisMObject(); MPlug plug = new MPlug( thisNode, mControlPoints ); // If this node is connected to a tweak node, reset the // plug to point at the tweak node. // convertToTweakNodePlug(plug); int len = fnVtxComp.elementCount; for ( int i = 0; i < len; i++ ) { plug.selectAncestorLogicalIndex((uint)fnVtxComp.element(i), plug.attribute); list.add(plug); } } } public override MatchResult matchComponent(MSelectionList item, MAttributeSpecArray spec, MSelectionList list) // // Description: // // Component/attribute matching method. // This method validates component names and indices which are // specified as a string and adds the corresponding component // to the passed in selection list. // // For instance, select commands such as "select shape1.vtx[0:7]" // are validated with this method and the corresponding component // is added to the selection list. // // Arguments // // item - DAG selection item for the object being matched // spec - attribute specification object // list - list to add components to // // Returns // // the result of the match // { MatchResult result = MatchResult.kMatchOk; MAttributeSpec attrSpec = spec[0]; int dim = attrSpec.dimensions; // Look for attributes specifications of the form : // vtx[ index ] // vtx[ lower:upper ] // if ( (1 == spec.length) && (dim > 0) && (attrSpec.name == "vtx") ) { int numVertices = (int)meshGeom().vertices.length; MAttributeIndex attrIndex = attrSpec[0]; int upper = 0; int lower = 0; if ( attrIndex.hasLowerBound ) { attrIndex.getLower( out lower ); } if ( attrIndex.hasUpperBound ) { attrIndex.getUpper( out upper ); } // Check the attribute index range is valid // if ( (lower > upper) || (upper >= numVertices) ) { result = MatchResult.kMatchInvalidAttributeRange; } else { MDagPath path = new MDagPath(); item.getDagPath( 0, path ); MFnSingleIndexedComponent fnVtxComp = new MFnSingleIndexedComponent(); MObject vtxComp = fnVtxComp.create( MFn.Type.kMeshVertComponent ); for ( int i=lower; i<=upper; i++ ) { fnVtxComp.addElement( i ); } list.add( path, vtxComp ); } } else { // Pass this to the parent class return base.matchComponent( item, spec, list ); } return result; } public override bool match(MSelectionMask mask, MObjectArray componentList) // // Description: // // Check for matches between selection type / component list, and // the type of this shape / or it's components // // This is used by sets and deformers to make sure that the selected // components fall into the "vertex only" category. // // Arguments // // mask - selection type mask // componentList - possible component list // // Returns // true if matched any // { bool result = false; if( componentList.length == 0 ) { result = mask.intersects( MSelectionMask.SelectionType.kSelectMeshes ); } else { for ( int i=0; i 0) { // traverse the component list // for ( i = 0; i < len && j < cacheLen; i++ ) { MObject comp = componentList[i]; MFnSingleIndexedComponent fnComp = new MFnSingleIndexedComponent( comp ); int elemCount = fnComp.elementCount; for ( int idx=0; idx 0) { // Traverse the componentList // for ( i=0; i 0) { // traverse the component list // for ( i=0; i 0) { for ( i=0; i lower[2] ) lower[2] = pnt[2]; if ( pnt[0] > upper[0] ) upper[0] = pnt[0]; if ( pnt[1] > upper[1] ) upper[1] = pnt[1]; if ( pnt[2] < upper[2] ) upper[2] = pnt[2]; } lowerHandle.Double3 = lower; lowerHandle.setClean(); upperHandle.Double3 = upper; upperHandle.setClean(); // Signal that the bounding box has changed. // childChanged(MPxSurfaceShape.MChildChanged.kBoundingBoxChanged); } public void applyTweaks(MDataBlock datablock, apiMeshGeom meshGeom) // // Description // // If the shape has history, apply any tweaks (offsets) made // to the control points. // { MArrayDataHandle cpHandle = datablock.inputArrayValue( mControlPoints ); // Loop through the component list and transform each vertex. // uint elemCount = cpHandle.elementCount(); for ( uint idx=0; idx