// =========================================================================== // Copyright 2018 Autodesk, Inc. All rights reserved. // // Use of this software is subject to the terms of the Autodesk license // agreement provided at the time of installation or download, or which // otherwise accompanies this software in either electronic or hard copy form. // =========================================================================== //=========================================================================== // // // Creation Date: november 22, 2002 // // Procedure Names: // // Description Name: // Utilities for creating and managing dropdown menus for selecting uv // sets on a polymesh node to be used by a dynamics node. Currently, // the code is used to implement uv set support in 3 places: // // 1) textured polymesh surface emitters // 2) polymesh particle goal objects // 3) collisionU/collisionV values for polymesh collision objects // // Usage: // // Call uvControlUtils() to bring all the functions into scope. // // Call uvsCreateControl() to create an option menu group that lists // the uv sets for a particular polymesh, and updates an attribute // accordingly // // Call uvsReplaceControl() to take an existing control (created by // uvsCreateControl()) and make it refer to another polymesh. // // Currently, this code is used in two places: // // 1) AEpointEmitterTemplate.mel: presents a dropdown menu for // selecting which uv set to use for textured particle emission // from polymesh surfaces. // // 2) AEdynObjectGoalWeightNew.mel: presents dropdown menus for // selecting which uv set to use on each poly goal object to which // a particle system is being attracted. // // 3) AEparticleCollisionNew.mel: similar to the goal weight code, // presents a dropdown menu for selecting which uv set will be // used to fill in per-particle collisionU/collisionV values // for polymesh collision objects. // //=========================================================================== global proc uvControlUtils() // // Description: Empty procedure that can be called to bring in all the // global procedures defined in this file. // { } global proc string uvsControl( string $action, string $dynNode, string $dynReason, int $dynIndex, string $controlName ) // // Description: Creates or updates a dropdown menu that keeps track of a // selected uv set on a polymesh node that is associated with // a dynamics node. Currently, there are 2 types of dynamics // nodes that use these menus: // // 1) particle systems: each polymesh goal object will have a // dropdown menu in the particle AE for // selecting which uv set to use on that // mesh (for all uv-based goal behaviour). // Also, each collision object will have // a dropdown menu for selecting which uv // set will be used to supply per-particle // collisionU/collisionV values. // // 2) emitters: each emitter that emits particles from a polymesh // surface will have a dropdown menu in the emitter // AE for selecting which uv set to use for textured // emission effects. // // Arguments: // // $action: "create" or "replace", to indicate whether we are // creating a new uv control, or updating an existing one // to point to a new object. // // $dynNode: the name of the dynamics node whose AE the control will // be added to. Should be an emitter or particle shape // // $dynReason: indicates what aspect of the node's function will // be affected by the uv set choice. Currently, there // are 3 options: // // 1) "emit" - for polymesh emitters // 2) "goal" - for particles with polymesh goals // 3) "collision" - for particles with polymesh // collision objects. // // $dynIndex: when the dynamics node is keeping track of multiple // polymesh objects (multiple goal shapes for a particle // system, for example), this indicates which polymesh // the control refers to. When only one polymesh can be // used (as with polymesh surface emitters), then the // specified index should always be zero. // // $aeControlName: optional string to specify the name of the // optionMenuGrp control that will be created. // This same string must always be used for a // given control when it is being created or // replaced. // // Returns: the name of the control that was created. // { string $unknownAction = (uiRes("m_uvControlUtils.kUnknownAction")); string $uvSet = (uiRes("m_uvControlUtils.kUVSet")); string $nodeType = `nodeType $dynNode`; string $nodeApiType = `nodeType -api $dynNode`; if( ($nodeType == "pointEmitter") || ($nodeApiType == "kPluginEmitterNode") ) { // only one polymesh object to keep track of. // string $dynAttr = "uvSetName"; // callback command is uvsChangeUvSet. Needs no index in this case, since only // one poly object is associated with an emitter. // string $cbCommand = ( "uvsChangeUvSet " + $dynNode + " " + $dynReason + " " + $dynAttr + " 0 0 #1" ); if( $action == "create" ) { $controlName = `optionMenuGrp -label $uvSet -cc $cbCommand $controlName`; } else if( $action == "replace" ) { $controlName = ( `setParent -q` + "|" + $controlName ); optionMenuGrp -e -cc $cbCommand $controlName; } else { error( $unknownAction ); } // refresh the menu to reflect the object's uv sets, and the currently selected one. // uvsRefreshMenu( $dynNode, $dynReason, $dynAttr, 0, 0, $controlName ); return $controlName; } else if( $nodeType == "particle" || $nodeType == "nParticle" ) { string $dynAttr; int $isMulti; if( $dynReason == "goal" ) { // for polymesh goal objects, the goalUvSetName attribute // on the particle shape is used to keep track of which uvsets // are selected. // $dynAttr = "goalUvSetName"; $isMulti = 1; } else if( $dynReason == "collision" ) { // for polymesh collision objects, the uvSetName attribute on // the geoConnector is used to keep track of which uvset // is selected. // $dynAttr = "uvSetName"; $isMulti = 0; } // callback command is uvsChangeUvSet, but it needs to know the index of the goal object // to which the control is referring. // string $cbCommand = ( "uvsChangeUvSet " + $dynNode + " " + $dynReason + " " + $dynAttr + " " + $isMulti + " " + $dynIndex + " #1" ); if( $action == "create" ) { $controlName = `optionMenuGrp -label $uvSet -cc $cbCommand $controlName`; } else if( $action == "replace" ) { $controlName = ( `setParent -q` + "|" + $controlName ); optionMenuGrp -e -cc $cbCommand $controlName; } else { error( $unknownAction ); } // refresh the menu to reflect the object's uv sets, and the currently selected one. // uvsRefreshMenu( $dynNode, $dynReason, $dynAttr, $isMulti, $dynIndex, $controlName ); return $controlName; } return (""); } global proc uvsDimControl( string $dynNode, string $dynPurpose, int $val, string $controlName ) // // Description: Dims or undims a uv set menu that was created by uvsControl("create"). // The value of $aeControlName must be the same one that was specified // when the control was created. // { string $nodeType = `nodeType $dynNode`; int $enable; if( $val ) { $enable = 0; } else { $enable = 1; } optionMenuGrp -e -en $enable $controlName; } global proc int uvsRefreshMenu( string $dynNode, string $dynReason, string $dynAttr, int $dynMulti, int $dynIndex, string $aeOptionMenuGrp ) // // Description: Updates the menu given by $aeOptionMenuGrp to reflect the // uv set status of a particular dynamics node associated // with $dynNode. // { // find the option menu UI // string $aeUvMenu = ($aeOptionMenuGrp + "|OptionMenu"); string $aeOptionMenuItems[] = `optionMenu -query -itemListLong $aeUvMenu`; string $polyNode; string $relatedNodes[]; int $foundPoly = uvsFindDynPoly( $dynNode, $dynReason, $dynIndex, $relatedNodes ); $polyNode = $relatedNodes[0]; if( size($relatedNodes) > 1 ) { $dynNode = $relatedNodes[1]; } // First we build a list of what the menu will look like in the end. // The first element should read "UseCurrentUVSet", and the remaining // items should correspond to the uv sets on the object. If the incoming // node is not a poly surface, then the menu should be empty. // string $aeNewMenuItems[]; if( $foundPoly ) { string $uvSets[]; uvsListPolyUvSets( $polyNode, $uvSets ); $aeNewMenuItems = { (uiRes("m_uvControlUtils.kUseCurrUVSet")) }; appendStringArray( $aeNewMenuItems, $uvSets, size($uvSets) ); } else { $aeNewMenuItems = {}; } // We now update the option menu to look like the $newMenuItems array. // This means adding menu items if the menu is currently too short, or // deleting them if the menu is currently too long. // // The algorithm is like lining the current menu and the new menu up // side by side and making a decision on each spot depending on // whether or not that spot is filled in the new and/or old menus. // // We could just delete the array entirely and rebuild it from scratch // every time, but this should be more efficient. // int $num = max( size($aeOptionMenuItems), size($aeNewMenuItems) ); int $i; for( $i = 0; $i < $num; $i++ ) { if( $i < size($aeNewMenuItems) ) { // new menu has this spot filled // if( $i < size($aeOptionMenuItems) ) { // old menu also has this spot filled, so copy the new // menu's entry for this spot into the old menu item. // menuItem -e -l $aeNewMenuItems[$i] $aeOptionMenuItems[$i]; } else { // old menu doesn't have this spot filled, so we need // to create a new menu item // menuItem -parent $aeUvMenu -l $aeNewMenuItems[$i]; } } else { // new menu doesn't have this spot filled, but the old one // does, so delete the item from the old menu. // deleteUI $aeOptionMenuItems[$i]; } } // if the incoming node is not a polymesh, then dim the uv set chooser // menu. Otherwise, enable it and ensure that the current item in the // menu reflects the uv set currently in use. // if( $foundPoly ) { // figure out which uv set is currently in use // string $curUvSet = uvsFindCurUvSet( $dynNode, $dynAttr, $dynMulti, $dynIndex ); // set the current menu item to that uv set // if( $curUvSet != "" ) { // select the specified uv set // optionMenuGrp -e -v $curUvSet $aeOptionMenuGrp; } else { // empty string means that the object's current uv set is // being used, so select the first menu item (which just // says "UseCurrentUVSet". // optionMenuGrp -e -sl 1 $aeOptionMenuGrp; } } return $foundPoly; } global proc uvsChangeUvSet( string $dynNode, string $dynReason, string $dynAttr, int $dynMulti, int $dynIndex, string $newUvSet ) // // Description: This is the function that gets called when an item is selected // in an option menu that is displaying uv sets for a particular poly // node. Its job is to establish a connection between the uvSetName // attribute on the poly node and the appropriate attribute on the // related dynamics node. // { // find which node the uv set attribute resides on (could be the // dynamics node, or it could be on a geoConnector feeding into the // dynamics node, depending on what the uvset is being used for). // string $relatedNodes[]; int $foundPoly = uvsFindDynPoly( $dynNode, $dynReason, $dynIndex, $relatedNodes ); string $polyNode = $relatedNodes[0]; if( size($relatedNodes) > 1 ) { $dynNode = $relatedNodes[1]; } int $clearUvSetAttribute = 0; if( $newUvSet == `uiRes( "m_uvControlUtils.kUseCurrUVSet" )` ) { // user has selected the first item in the menu, which is // always "UseCurrentUVSet". To implement this, we // make sure that the uvSetName attribute on the dynamics node // is unconnected and set to the empty string. // // set $clearUvSetAttribute to 1, which will cause the code // after this if statement to clear the uvSetName attribute on the // dynamics node // $clearUvSetAttribute = 1; } else { // user has selected one of the object's uv sets to use. Find the index // of that uv set in the mesh shape's uvSet multi, and connect from the // uvSetName of that index to the uv set name attribute on the dynamics // node. // // Figure out which element of the "uvSet" multi-attribute on the // polymesh shape contains the data for the specified uv set. // string $allUvSets[] = `polyUVSet -q -auv $polyNode`; int $uvSetIndices[] = `polyUVSet -q -uvn $polyNode`; int $uvIndex; int $found = 0; for( $uvIndex = 0; $uvIndex < size($allUvSets); $uvIndex++ ) { if( $allUvSets[$uvIndex] == $newUvSet ) { $found = true; break; } } // Make sure that a valid uv set was specified. An invalid set could // have been specified if the menu got out of sync with the list of // uv sets on the shape. // if( $found ) { string $srcAttr = ( $polyNode + ".uvSet[" + $uvSetIndices[$uvIndex] + "].uvSetName" ); string $dstAttr = uvsFindDestAttr( $dynNode, $dynAttr, $dynMulti, $dynIndex ); //print( "Connecting " + $srcAttr + " -> " + $dstAttr + "\n" ); connectAttr -f $srcAttr $dstAttr; $clearUvSetAttribute = 0; } else { string $fmt = (uiRes("m_uvControlUtils.kNoUVSet")); print( `format -s $newUvSet -s $polyNode $fmt` ); $clearUvSetAttribute = 1; } } // clear out the uvSetName attribute on the dynamics node if necessary. // if( $clearUvSetAttribute ) { // get any incoming connection to the uvSetName attribute // string $dstAttr = uvsFindDestAttr( $dynNode, $dynAttr, $dynMulti, $dynIndex ); string $incoming[] = `listConnections -d 1 -p 1 $dstAttr`; if( size($incoming) > 0 ) { string $in = $incoming[0]; disconnectAttr $in $dstAttr; } // make sure the string is empty // setAttr -type "string" $dstAttr ""; } } global proc string uvsFindDestAttr( string $node, string $attrName, int $multi, int $index ) // // Description: // // Finds the attribute on the dynamics node to which the uv set name will be // connected. The attribute is created if it does not already exist. // $node and $attrName specify the name of the dynamics node and the name of the // attribute to be added. $multi specifies whether the attribute is supposed to be // an array, and if it is, then $index indicates which element we are interested in. // { if( !attributeExists( $attrName, $node ) ) { if( $multi ) { addAttr -m -im true -ln $attrName -dt "string" $node; } else { addAttr -ln $attrName -dt "string" $node; } } string $fullAttr = ( $node + "." + $attrName ); if( $multi ) { $fullAttr += ( "[" + $index + "]" ); } return $fullAttr; } global proc string uvsFindCurUvSet( string $node, string $attr, int $multi, int $index ) // // Description: // // Finds the uv set currently selected on a particular dynamics node. You must // specify the name of the dynamics node, its uv set name attribute, and optionally // the index that you are interested in if it is an array attribute. // { string $fullAttr = uvsFindDestAttr( $node, $attr, $multi, $index ); string $curVal = `getAttr $fullAttr`; return $curVal; } global proc int uvsListPolyUvSets( string $node, string $uvSets[] ) // // Description: // // Given a polymesh node, finds a list of the mesh's uv sets, which // are returned into the $uvSets array. Return value is 1 if the object // is a polymesh, 0 otherwise. // { if( `nodeType $node` == "mesh" ) { $uvSets = `polyUVSet -q -auv $node`; return 1; } else { return 0; } } global proc int uvsIsDynPoly( string $dynNode, string $dynReason, int $dynIndex ) // // Description: Determines whether a particular node associated with a // dynamics node (either an emitter surface or a goal // object) is a polymesh. // { string $bogus[]; return uvsFindDynPoly( $dynNode, $dynReason, $dynIndex, $bogus ); } global proc int uvsFindDynPoly( string $dynNode, string $dynReason, int $index, string $nodes[] ) { string $nodeType = `nodeType $dynNode`; if( $nodeType == "pointEmitter" ) { // geoConnectors hook up to the "sweptGeometry" attribute of the emitter // string $inputConnectors[] = `listConnections ($dynNode + ".sweptGeometry")`; if( size($inputConnectors) == 1 ) { // there is a geoConnector hooked up to the emitter. See if there's a poly // mesh feeding into it. // string $inputGeometry[] = `listConnections -sh on ($inputConnectors[0] + ".localGeometry")`; if( size($inputGeometry) == 1 ) { string $shape = $inputGeometry[0]; if( `nodeType $shape` == "mesh" ) { // this emitter is a polymesh surface emitter, so return the geoConnector // and shape node names. // $nodes[0] = $shape; $nodes[1] = $inputConnectors[0]; return 1; } } } return 0; } else if( $nodeType == "particle"|| $nodeType == "nParticle" ) { if( $dynReason == "goal" ) { // for goal objects, look for something feeding into the // goalGeometry array. If it is a polymesh, return its name. // string $inputGoals[] = `listConnections -sh on ($dynNode + ".goalGeometry[" + $index + "]" )`; if( size($inputGoals) == 1 ) { $nodes[0] = $inputGoals[0]; if( `nodeType $inputGoals[0]` == "mesh" ) { return 1; } else { return 0; } } } else if( $dynReason == "collision" ) { // for collision objects, look for geoConnectors feeding into // the collisionGeometry array, then look for meshes feeding // into the geoConnector localGeometry attribute. // string $inputCollisions[] = `listConnections -sh on ($dynNode + ".collisionGeometry[" + $index + "]" )`; if( (size($inputCollisions) == 1) && (`nodeType $inputCollisions[0]` == "geoConnector") ) { string $inputPolys[] = `listConnections -sh on ($inputCollisions[0]+".localGeometry")`; if( `nodeType $inputPolys[0]` == "mesh" ) { $nodes[0] = $inputPolys[0]; $nodes[1] = $inputCollisions[0]; return 1; } else { $nodes = {}; return 0; } } } } $nodes = {}; return 0; }