sceneAssembly/assemblyReference.cpp
 
 
 
sceneAssembly/assemblyReference.cpp
//-
//**************************************************************************/
// Copyright (c) 2011 Autodesk, Inc.
// All rights reserved.
//
// These coded instructions, statements, and computer programs contain
// unpublished proprietary information written by Autodesk, Inc., and are
// protected by Federal copyright law. They may not be disclosed to third
// parties or copied or duplicated in any form, in whole or in part, without
// the prior written consent of Autodesk, Inc.
//**************************************************************************/
//+

#include <assemblyReference.h>
#include <sceneAssemblyStrings.h>
#include <assemblyReferenceInitialRep.h>

#include <maya/MPxRepresentation.h>
#include <maya/MFnAssembly.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnContainerNode.h>
#include <maya/MObjectArray.h>
#include <maya/MFileIO.h>
#include <maya/MGlobal.h>
#include <maya/MNamespace.h>

#include <cassert>
#include <iostream>             // For CHECK_MSTATUS_AND_RETURN_IT.
using namespace std;            // For CHECK_MSTATUS macros.

namespace {

//==============================================================================
// LOCAL DECLARATIONS
//==============================================================================

/*----- constants -----*/

const char* const ICON_NAME = "out_assemblyReference.png";

//==============================================================================
// LOCAL FUNCTIONS
//==============================================================================

bool isAssemblyDefinition(MObject& obj)
{
   // Require exact type match for assembly definition.  Precludes
   // having a class derived from AssemblyDefinition to be used as an
   // assembly definition, but this is an acceptable restriction.
   MFnDependencyNode fn(obj);
   return fn.typeId() == AssemblyDefinition::id;
}

}

//==============================================================================
// CLASS AssemblyReference
//==============================================================================

const MTypeId AssemblyReference::id(0x580000b1);

const MString AssemblyReference::typeName("assemblyReference");

MObject AssemblyReference::aDefnFile;
MObject AssemblyReference::aRepNamespace;
MObject AssemblyReference::aRepresentations;
MObject AssemblyReference::aRepName;
MObject AssemblyReference::aRepLabel;
MObject AssemblyReference::aRepType;
MObject AssemblyReference::aRepData;
MObject AssemblyReference::aInitialRep;

//------------------------------------------------------------------------------
//
void* AssemblyReference::creator()
{
   return new AssemblyReference;
}

//------------------------------------------------------------------------------
//
MStatus AssemblyReference::initialize()
{
   // We are not using the inheritAttributesFrom(BaseNode::typeName)
   // because we need to set them "not storable".  Representation
   // attributes are not stored because they are obtained from the
   // assembly definition.
   MStatus stat = initRepresentations(
      kNotStorable, aRepresentations, aRepName, aRepLabel, aRepType, aRepData);
   CHECK_MSTATUS_AND_RETURN_IT(stat);

   MFnTypedAttribute defnFileAttrFn;
   aDefnFile = defnFileAttrFn.create("definition", "def", MFnData::kString);
   // Set the attribute as "internal" not to store it ourselves, but
   // to get setInternalValueInContext() to be called.
   CHECK_MSTATUS(defnFileAttrFn.setInternal(true));
   CHECK_MSTATUS(defnFileAttrFn.setUsedAsFilename(true));
   stat = MPxNode::addAttribute(aDefnFile);
   CHECK_MSTATUS_AND_RETURN_IT(stat);

   // Namespace for assembly representations.
   MFnTypedAttribute repNamespaceAttrFn;
   aRepNamespace = repNamespaceAttrFn.create("repNamespace", "rns", MFnData::kString);
   CHECK_MSTATUS(repNamespaceAttrFn.setInternal(true));
   stat = MPxNode::addAttribute(aRepNamespace);
   CHECK_MSTATUS_AND_RETURN_IT(stat);

   // Initial configuration (may be empty, only used for top level assemblies) 
   MFnTypedAttribute initialRepAttrFn;
   aInitialRep = initialRepAttrFn.create("initialRep", "irp", MFnData::kString);
   stat = MPxNode::addAttribute(aInitialRep);
   CHECK_MSTATUS_AND_RETURN_IT(stat);

   return stat;
}

//------------------------------------------------------------------------------
//
MStatus AssemblyReference::uninitialize()
{
   return MStatus::kSuccess;
}

//------------------------------------------------------------------------------
//
AssemblyReference::AssemblyReference() 
        : BaseNode()
        , fDefnFile()
        , fUseDefnFileAttrib(true)
        , fInitialRep(NULL)
        , fUpdatingRepNamespace(false)
{}

//------------------------------------------------------------------------------
//
AssemblyReference::~AssemblyReference()
{
        // TODO: MAYA-15544 - either restructure to remove the need for this member 
        // variable, or make it an auto_ptr. 
        assert(NULL == fInitialRep);
}

//------------------------------------------------------------------------------
//
MString AssemblyReference::createRepresentation(
   const MString& /* input */,
   const MString& /* type */,
   const MString& /* representation */,
   MDagModifier*  /* undoRedo */,
   MStatus*       status
)
{
    // Can't create a representation through an assembly reference.
    if (status != NULL) {
        *status = MStatus::kFailure;
    }

    return MString();
}

//------------------------------------------------------------------------------
//
MStringArray AssemblyReference::repTypes() const
{
   // This is the representation creation query for existing assembly
   // references.  
   // An assembly reference cannot create new representations, only use
   // those provided by its assembly definition, so return an empty
   // array.
   return MStringArray();
}

//------------------------------------------------------------------------------
//
bool AssemblyReference::setInternalValueInContext(
   const MPlug& plug, const MDataHandle& dataHandle, MDGContext& /* ctx */
)
{
        // Skip any setAttrs done as part of file IO. If we call
        // postLoad() when we import the assembly definition 
        // container into our own container while it's not fully-defined
        // yet, it can cause havoc
        if( MFileIO::isOpeningFile() || MFileIO::isReadingFile() ) {
                return false;
        }

   if (plug == aDefnFile ) {
      // At this point the plug value has not been set yet, and we
      // can't set it here, so save a transient copy from the data handle.
      fDefnFile = dataHandle.asString();
          fUseDefnFileAttrib = false;

          // Call the MFn version of postLoad so that it goes through
          // Maya's virtual function calls rather than calling
          // AssemblyReference::postLoad directly
      MFnAssembly aFn(thisMObject());
          aFn.postLoad();    

          fUseDefnFileAttrib = true;      
   }
   else if( plug == aRepNamespace && !fUpdatingRepNamespace) {
                // If the assembly NS attribute is changed directly (i.e. someone
                // did a setAttr directly, or modified it via the AE), need to
                // initiate a rename of the associated NS.
                // 
                // At this point, the new value has not been set in the data block yet.
                // So query the old NS name from current state of the datablock, and 
                // the new one from the the data handle that is passed into this method.
                //
                MString oldNS;
                plug.getValue(oldNS);
                
                // Assembly nodes must be loaded into a NS. There are problems
                // with name-clashes (and thus, robustness of edits) without them.
                // So use the default NS if someone tries to set the NS to "".
                MString newNS = dataHandle.asString();
                if( newNS.numChars() == 0 ) {
                        // defaults to the MPxAssembly implementation
                        newNS = MPxAssembly::getRepNamespace();

                        // set the value in the data-handle
                        MDataHandle* nonConstHandle  = (MDataHandle*) &dataHandle;
                        nonConstHandle->set(newNS);
                }

                // If both the old and new NSes are non-empty, just need to rename
                if( oldNS.numChars() > 0 && newNS.numChars() > 0 ) {
                        MNamespace::renameNamespace(oldNS, dataHandle.asString());
                }
   }

   // Tell Maya to handle things.
   return false;
}


//------------------------------------------------------------------------------
//
// Handle required processing before file save 
// Maya will call this on top level assemblies only. 
//
void AssemblyReference::beforeSave()
{

   // Invoke assemblyReferenceInitialRep to store the initialRep configuration
   // onto the top level assembly. 
   // TODO: MAYA-15544 reorganize assemblyReferenceInitialRep wrapper class
   // into more a more polished interface, e.g. given its current use, 
   // writer() could be a static method. 
   assemblyReferenceInitialRep saveRep;
   saveRep.writer(thisMObject()); 
}

//------------------------------------------------------------------------------
//
// Determine the intial representation to use for the given assembly
// Maya guarantees this will be called on top level references only. 
// If an initial configuration cannot be determined, an empty string
// is returned.

MString AssemblyReference::getInitialRep(const MObject &assembly, bool& hasInitialRep, MStatus* status) const
{

        // If we have an initial representation object to query, use it to 
        // try and get the initial value.     
        if (fInitialRep)
        {
                return fInitialRep->getInitialRep(assembly, hasInitialRep);
        }  
        // Otherwise, we simply return an empty string.
        return MString();
}


//------------------------------------------------------------------------------
//
void AssemblyReference::postLoad()
{
   // Clear out our representations.
   clearRepresentationList();
   
   MFnAssembly aFn(thisMObject());

   // If no definition file is provided, we can stop here.
   MString defnFile = getDefinitionFile();
   if (defnFile.numChars() == 0) {
          // Activate to "none" or else, on reload, the new active representation 
          // will be the same as the old one and we won't load it.
          aFn.activate("");
      return;
   }

   bool fileIgnoreVersion = (MGlobal::optionVarIntValue("fileIgnoreVersion") == 1);

   // Next, import the file into the container.  
   MStatus status = aFn.importFile(defnFile, NULL /*type*/, false /*preserveReferences*/, NULL /*nameSpace*/, fileIgnoreVersion);
   if (status != MStatus::kSuccess) {
      definitionError(rAssemblyDefnImportError);
      return;
   }

   // Clear out our representations some more.  This should be
   // completely unnecessary, since we've already cleared out the
   // representation list, which covers the case where we set the
   // definition file to the empty string and therefore exit before
   // performing the import.  Inexplicably, the representation array
   // is somehow resized back to its initial size by the call to
   // import, though it is left empty.  Entered as JIRA-10452.
   // PPT, 3-Feb-2012.
   clearRepresentationList();

   // Loop through the imported nodes and try to find an assembly
   // definition node.  The supported workflow is to have a single
   // assembly definition node in the file.
   MFnContainerNode contFn(thisMObject());
   MObjectArray members;
   status = contFn.getMembers(members);
   if (status != MStatus::kSuccess) {
      definitionError(rAssemblyDefnNotFoundError);
      return;
   }

   // Prefer safety over performance and keep looking even if we've
   // found an assembly definition node, to make sure there isn't
   // another one in the file.  Otherwise, can stop at found!=nbMembers.
   const int nbMembers = static_cast<int>(members.length());
   int found = nbMembers;
   int nbFound = 0;
   for (int i=0; i<nbMembers; ++i) {
      if (isAssemblyDefinition(members[i])) {
         found = i;
         ++nbFound;
      }
   }

   if (found==nbMembers) {
      definitionError(rAssemblyDefnNotFoundError);
      return;
   }
   else if (nbFound > 1) {
      definitionError(rMultAssemblyDefnFoundError);
      return;
   }

   // Found an assembly definition.  Copy over its attributes, which
   // at time of writing (3-May-2012) is its list of representations.
   MFnAssembly defnFn(members[found]);
   MStringArray defnRepresentations = defnFn.getRepresentations(&status);
   const int nbReps = defnRepresentations.length();
   if (status == MS::kSuccess) {
      const AssemblyDefinition* defn =
         dynamic_cast<AssemblyDefinition*>(defnFn.userNode());
      // Will succeed because of isAssemblyDefinition().
      assert(defn != 0);
      for (int i=0; i<nbReps; ++i) {
         performCreateRepresentation(
            defnRepresentations[i],
            defn->getRepType(defnRepresentations[i]),
            defn->getRepLabel(defnRepresentations[i]),
            defn->getRepData(defnRepresentations[i])
         );
      }
   }
   
   // Get rid of imported asset definition.
   status = contFn.clear();
   if (status != MStatus::kSuccess) {
      return;
   }

   // If this is not a top-level assembly, lock the repNamespace attrib.
   // User should not be able to change this attribute on nested assembly
   // because otherwise it won't match the info stored in nested file.
   if(!aFn.isTopLevel())
   {
       MPlug repNamespacePlug(thisMObject(), aRepNamespace);
       repNamespacePlug.setLocked(true);
   }

   // If we can't activate a representation (because our parent assembly is
   // being called with activateNonRecursive()), stop now.
   if (!aFn.canActivate()) {
      return;
   }

   // If this is a top level assembly, initialize the 
   // initialRep configuration. This object is destroyed
   // on exit from this postLoad routine.
   // The initialRep configuration from this topLevel assembly
   // will be accessed by this assembly, and each nested subAssembly 
   // as they are activated and call getInitialRep() from within their 
   // own postLoad. 
   // TODO: MAYA-15544 reorganize assemblyReferenceInitialRep wrapper class
   // to have a clearer interface (see other comments in this file and 
   // in earlier code reviews on MAYA-15544). 
   assert(fInitialRep == NULL);
   if (aFn.isTopLevel())
   {
           fInitialRep = new assemblyReferenceInitialRep();
           fInitialRep->reader(thisMObject());
   }

   // Activate the initial representation
   if (defnRepresentations.length() > 0) {
           
           // Check if initial representation is specified
           // Use hasInitialRep to know if it has initial representation
       // since we can have an empty string representation. 
           bool hasInitialRep = false;
           MString initialRep = aFn.getInitialRep(hasInitialRep);       
           if (!hasInitialRep)
           {
                   // No initial representation has been found, 
                   // use the default (which is representation 0).
                   initialRep = defnRepresentations[0];
           }
           // MFnAssembly::activate() must be called to benefit from scene
           // assembly infrastructure activation services.
           aFn.activate(initialRep);
   }

   // We no longer need the initial representation information. 
   if (fInitialRep)
   {
           fInitialRep->clear(thisMObject());
           delete fInitialRep;
           fInitialRep = NULL;
   }
}

//------------------------------------------------------------------------------
//
MString AssemblyReference::getDefaultIcon() const
{
   return MString(ICON_NAME);
}

//------------------------------------------------------------------------------
//
MString AssemblyReference::getDefinitionFile() const
{
   MString defnFile = fDefnFile;
   if (fUseDefnFileAttrib) {
      MPlug defnPlug(thisMObject(), aDefnFile);
      defnPlug.getValue(defnFile);
   }
   return defnFile;
}

//------------------------------------------------------------------------------
//
void AssemblyReference::definitionError(const MStringResourceId& id)
{
   // The following relies on the error message being formatted with
   // URI first, assembly reference name second.
   MStatus status;
   const MString defnFile = getDefinitionFile();
   MFnContainerNode contFn(thisMObject());
   MString errorString = MStringResource::getString(id, status);
   errorString.format(errorString, defnFile, contFn.name());
   MGlobal::displayError(errorString);
   contFn.clear();
}

//------------------------------------------------------------------------------
//
MStatus AssemblyReference::deleteRepresentation(const MString& repName)
{    
   // can't delete representations through an assembly reference.
   return MStatus::kFailure;
}

//------------------------------------------------------------------------------
//
MStatus AssemblyReference::deleteAllRepresentations()
{  
   // can't delete representations through an assembly reference.
   return MStatus::kFailure;
}

//------------------------------------------------------------------------------
//
MString AssemblyReference::getRepNamespace() const
{

        MString repNamespaceStr;

        MPlug repNamespacePlug(thisMObject(), aRepNamespace);
        repNamespacePlug.getValue(repNamespaceStr);
           
        if ( !repNamespaceStr.length() ) {
                // defaults to the MPxAssembly implementation
                repNamespaceStr = MPxAssembly::getRepNamespace();
                        
                // update attribute if we're reading from it
                repNamespacePlug.setValue(repNamespaceStr);
        }

   // This assembly does not support nodes in the 
   // rootNS. So we should never set the repNamespace
   // attribute to an empty string
   assert ( repNamespaceStr.length() > 0 );

   return repNamespaceStr;
}

//------------------------------------------------------------------------------
//
void AssemblyReference::updateRepNamespace( const MString& repNamespace )
{
   MPlug repNamespacePlug(thisMObject(), aRepNamespace);

   MString repCurrentNamespaceStr;
   repNamespacePlug.getValue(repCurrentNamespaceStr);

   bool prevVal = fUpdatingRepNamespace;
   fUpdatingRepNamespace = true;

   // update attribute 
   repNamespacePlug.setValue(repNamespace);
   
   fUpdatingRepNamespace = prevVal;
}

bool AssemblyReference::supportsEdits() const
{
        // Opt into Maya's edit tracking system
        return true;
}