sceneAssembly/assemblyReference.cpp

sceneAssembly/assemblyReference.cpp
#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 <maya/MExternalContentInfoTable.h>
#include <maya/MExternalContentLocationTable.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";
const MString DEFINITION_FILE_ATTR_UINAME( "definition" );
const MString DEFINITION_FILE_ATTR_SHORTNAME( "def" );
//==============================================================================
// 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.
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);
MFnTypedAttribute defnFileAttrFn;
aDefnFile = defnFileAttrFn.create(
DEFINITION_FILE_ATTR_UINAME, DEFINITION_FILE_ATTR_SHORTNAME,
// 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);
// Namespace for assembly representations.
MFnTypedAttribute repNamespaceAttrFn;
aRepNamespace = repNamespaceAttrFn.create("repNamespace", "rns", MFnData::kString);
CHECK_MSTATUS(repNamespaceAttrFn.setInternal(true));
stat = MPxNode::addAttribute(aRepNamespace);
// Initial configuration (may be empty, only used for top level assemblies)
MFnTypedAttribute initialRepAttrFn;
aInitialRep = initialRepAttrFn.create("initialRep", "irp", MFnData::kString);
stat = MPxNode::addAttribute(aInitialRep);
return stat;
}
//------------------------------------------------------------------------------
//
MStatus AssemblyReference::uninitialize()
{
}
//------------------------------------------------------------------------------
//
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();
}
//------------------------------------------------------------------------------
//
void AssemblyReference::getExternalContent(
) const
{
// Not invoking the base class version of the method: AssemblyReference
// exposes representations from the definition it points at, but these are
// not direct dependencies of the reference.
table.addUnresolvedEntry( DEFINITION_FILE_ATTR_UINAME, getDefinitionFile(), name() );
}
//------------------------------------------------------------------------------
//
void AssemblyReference::setExternalContent(
)
{
// Could call the default implementation in MPxNode, but this will end up
// looking up the attribute by name in order to set its value. Here, since
// we already have the aDefnFile attribute, it will be slightly more
// efficient to use it, thus bypassing the search altogether.
MString location;
if ( !table.getLocation( DEFINITION_FILE_ATTR_UINAME, location ) ) {
return;
}
MPlug defnPlug(thisMObject(), aDefnFile);
defnPlug.setValue(location);
}
//------------------------------------------------------------------------------
//
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();
}
//------------------------------------------------------------------------------
//
void AssemblyReference::copyInternalData(MPxNode* srcNode)
{
assert(dynamic_cast<AssemblyReference*>(srcNode) != 0);
AssemblyReference* srcAssembly = static_cast<AssemblyReference*>(srcNode);
fDefnFile = srcAssembly->fDefnFile;
fUseDefnFileAttrib = srcAssembly->fUseDefnFileAttrib;
fDefnFileCacheEntry = srcAssembly->fDefnFileCacheEntry;
}
//------------------------------------------------------------------------------
//
bool AssemblyReference::setInternalValueInContext(
const MPlug& plug, const MDataHandle& dataHandle, MDGContext& /* ctx */
)
{
if (plug == aDefnFile ) {
// 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
return false;
}
// 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) {
// Rename the Maya namespace associated to the assembly with the new repNamespace.
// Correct the repNamespace if needed.
// To rename the Maya namespace, there are 2 cases to get the oldNS to rename:
// 1- If the assembly NS attribute is changed directly (i.e. someone
// did a setAttr directly, or modified it via the AE), we get the
// oldNS (namespace to be renamed) using the plug value, which has
// not been set 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.
//
// 2- If we are in IO, the plug value has already been set, but the
// namespace still has the default value given by MPxAssembly::getRepNamespace().
MString oldNS;
plug.getValue(oldNS);
// Early-out if the plug value is empty: the namespace has not been created yet.
if (0 == oldNS.numChars())
{
return false;
}
// Get the default namespace to rename.
}
MString newNS = dataHandle.asString();
// Validate the name and only use it if valid (not "").
// If the name is not valid, or if the user entered "" as repNamespace,
// use the default name (assembly name + "_NS").
MStatus status;
MString validNewNS = MNamespace::validateName(newNS, &status);
if (status != MStatus::kSuccess) {
return false;
}
if( validNewNS.numChars() == 0 ) {
// defaults to the MPxAssembly implementation
}
if (validNewNS != newNS)
{
// update the value of newNS and of the data-handle
newNS = validNewNS;
MDataHandle* nonConstHandle = (MDataHandle*) &dataHandle;
nonConstHandle->set(newNS);
}
// Finally, tell Maya to rename namespaces.
if( oldNS.numChars() > 0 && newNS.numChars() > 0 && oldNS != newNS) {
status = MNamespace::renameNamespace(oldNS, newNS);
if (status != MStatus::kSuccess) {
// Rename failed. Set back old value.
// Note: if rename failed, it is probably because the namespace
// newNS already existed. But it is the responsibility of
// the user to provide a name that does not exist.
MDataHandle* nonConstHandle = (MDataHandle*) &dataHandle;
nonConstHandle->set(oldNS);
}
}
}
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;
}
// Have we previously read that assembly definition file ? Can we
// simply reuse its content ?
//
// Note that we have to take a non-const reference since the get()
// member function is non-const!
AssemblyDefinitionFileCache& cache =
AssemblyDefinitionFileCache::getInstance();
fDefnFileCacheEntry = cache.get(defnFile);
if (!fDefnFileCacheEntry) {
const 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);
AssemblyDefinitionFileCache::RepCreationArgsList repCreationArgsList;
for (int i=0; i<nbReps; ++i) {
const MString repName = defnRepresentations[i];
repCreationArgsList.push_back(
AssemblyDefinitionFileCache::RepresentationCreationArgs(
repName,
defn->getRepType(repName),
defn->getRepLabel(repName),
defn->getRepData(repName)));
}
fDefnFileCacheEntry = cache.insert(defnFile, repCreationArgsList);
}
// Get rid of imported asset definition.
status = contFn.clear();
if (status != MStatus::kSuccess) {
return;
}
}
// Found an assembly definition. Copy over its attributes, which
// at time of writing (3-May-2012) is its list of representations.
const AssemblyDefinitionFileCache::RepCreationArgsList& repCreationArgslist =
fDefnFileCacheEntry->getRepCreationArgsList();
{
AssemblyDefinitionFileCache::RepCreationArgsList::const_iterator it =
repCreationArgslist.begin();
AssemblyDefinitionFileCache::RepCreationArgsList::const_iterator end =
repCreationArgslist.end();
for (;it != end; ++it) {
performCreateRepresentation(
it->getName(), it->getType(), it->getLabel(), it->getData());
}
}
// 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 (!repCreationArgslist.empty()) {
// 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 the first one).
initialRep = repCreationArgslist.front().getName();
}
// 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.
}
//------------------------------------------------------------------------------
//
MStatus AssemblyReference::deleteAllRepresentations()
{
// can't delete representations through an assembly reference.
}
//------------------------------------------------------------------------------
//
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;
}
//-
// Copyright 2015 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.
//+