C++ API Reference
AbcImport/CreateSceneHelper.cpp
//-*****************************************************************************
//
// Copyright (c) 2009-2014,
// Sony Pictures Imageworks, Inc. and
// Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Sony Pictures Imageworks, nor
// Industrial Light & Magic nor the names of their contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//-*****************************************************************************
#include "util.h"
#include "CameraHelper.h"
#include "LocatorHelper.h"
#include "MeshHelper.h"
#include "NurbsCurveHelper.h"
#include "NurbsSurfaceHelper.h"
#include "PointHelper.h"
#include "XformHelper.h"
#include "CreateSceneHelper.h"
#include <Alembic/AbcGeom/Visibility.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>
#include <maya/MIntArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MPlug.h>
#include <maya/MDGModifier.h>
#include <maya/MFnCamera.h>
#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnIntArrayData.h>
#include <maya/MFnStringData.h>
#include <maya/MFnTransform.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MFnNurbsSurface.h>
#include <maya/MFnSet.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnSingleIndexedComponent.h>
#include <maya/MItSelectionList.h>
#include <maya/MItDependencyGraph.h>
#include <map>
#include <set>
#include <string>
#include <vector>
namespace
{
void copyIndicesToNode(MIntArray& iIndices, const MObject& iNode,
MObject& iSet)
{
MStatus status;
MFnDagNode mFnNode(iNode);
MDagPath dpShape;
status = mFnNode.getPath(dpShape);
// The mesh may be an intermediate object if it's deformed. We should
// assign shading groups to the faces of the deformed mesh instead.
// Because the deformed mesh is the mesh for shading.
if (mFnNode.isIntermediateObject())
{
// Only non-intermediate shapes are used for shading. We call
// extendToShape() to find the non-intermediate shape.
MDagPath shadedShape = dpShape;
shadedShape.pop();
if (shadedShape.extendToShape())
dpShape = shadedShape;
}
// Empty the set
MFnSet fnSet( iSet );
MSelectionList selList;
fnSet.getMembers ( selList, false );
MItSelectionList iterSelList( selList );
for( ; iterSelList.isDone()!=true; iterSelList.next() )
{
MDagPath curDag;
MObject curCompObj;
iterSelList.getDagPath(curDag, curCompObj);
if ((curDag==dpShape) && curCompObj.isNull())
fnSet.removeMember(curDag, curCompObj);
}
// Feed the indexed component
fnSComp.addElements( iIndices );
// Remove the elements from all other sets (exclusivity constraint)
MObjectArray connSGObjs( getOutConnectedSG(dpShape) );
MObject connSGObj;
MFnSet fnOtherSet;
MSelectionList setList, otherSetList, xorList;
setList.add( dpShape, comp );
int nSG = connSGObjs.length();
for( int i=0; i<nSG; i++ )
{
// Get the current SG Object
connSGObj = connSGObjs[i];
if( connSGObj != iSet )
{
// Build the intersection list and remove it from the set
otherSetList.clear();
fnOtherSet.setObject( connSGObj );
fnOtherSet.getMembers( otherSetList, false );
// test if it's a full partition
if (otherSetList.length()>=1)
{
MItSelectionList itSelList( otherSetList );
for( ; itSelList.isDone()!=true; itSelList.next() )
{
MObject compObj;
itSelList.getDagPath(dp, compObj);
if (!(dp==dpShape) || !compObj.isNull())
continue;
fnOtherSet.removeMember( dp, compObj );
// create a component with the full list
MFnMesh fnMesh(dpShape);
compObj = fnFullSComp.create(
fnFullSComp.setCompleteData(fnMesh.numPolygons());
// fill the sel list
otherSetList.clear();
otherSetList.add( dpShape, compObj );
break;
}
}
xorList = otherSetList;
xorList.merge( setList, MSelectionList::kXORWithList );
otherSetList.merge( xorList, MSelectionList::kRemoveFromList);
if( !otherSetList.isEmpty() )
fnOtherSet.removeMembers( otherSetList );
}
}
// Feed the set
fnSet.addMember(dpShape, comp);
}
void addFaceSets(MObject & iNode, Alembic::Abc::IObject & iObj)
{
MStatus status;
MFnDependencyNode mesh(iNode, &status);
if (status != MS::kSuccess)
return;
std::size_t numChildren = iObj.getNumChildren();
for ( std::size_t i = 0 ; i < numChildren; ++i )
{
Alembic::Abc::IObject child = iObj.getChild(i);
if (Alembic::AbcGeom::IFaceSet::matches(child.getHeader()))
{
Alembic::AbcGeom::IFaceSet faceSet(child,
Alembic::Abc::kWrapExisting);
// create a shading group for this faceset.
MObject shadingGroup;
const MString& faceSetName(faceSet.getName().c_str());
// check if this SG already exists.
status = getObjectByName(faceSetName, shadingGroup);
if (shadingGroup.isNull())
{
shadingGroup = createShadingGroup(faceSetName);
}
// set (and create if needed) 'AbcFacesetName' string
// attribute on the shadingGroup
MFnDependencyNode fnDepNode(shadingGroup);
MPlug abcFacesetNamePlug = fnDepNode.findPlug("AbcFacesetName",
true);
if (abcFacesetNamePlug.isNull())
{
MFnStringData fnStringData;
MString attrName("AbcFacesetName");
MObject strAttrObject = fnStringData.create("");
MObject attrObj = attr.create(attrName, attrName,
MFnData::kString, strAttrObject);
fnDepNode.addAttribute(attrObj);
abcFacesetNamePlug = fnDepNode.findPlug(attrObj, true);
}
abcFacesetNamePlug.setValue(faceSetName);
// retrive face indices.
Alembic::AbcGeom::IFaceSetSchema::Sample samp;
faceSet.getSchema().get(samp);
const int* faceArray((int *)samp.getFaces()->getData());
const size_t size = samp.getFaces()->size();
MIntArray arr(faceArray, static_cast<unsigned int>(size));
// copy indices to the set
copyIndicesToNode(arr, iNode, shadingGroup);
Alembic::Abc::ICompoundProperty arbProp =
faceSet.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
faceSet.getSchema().getUserProperties();
addProps(arbProp, shadingGroup, false);
addProps(userProp, shadingGroup, false);
}
}
}
void removeDagNode(MDagPath & dagPath)
{
MStatus status = deleteDagNode(dagPath);
if ( status != MS::kSuccess )
{
MString theError = dagPath.partialPathName();
theError += MString(" removal not successful");
printError(theError);
}
}
Alembic::Abc::IScalarProperty getVisible(Alembic::Abc::IObject & iNode,
bool isObjConstant,
std::vector<Prop> & oPropList,
std::vector<Alembic::AbcGeom::IObject> & oAnimVisStaticObj)
{
Alembic::AbcGeom::IVisibilityProperty visProp =
Alembic::AbcGeom::GetVisibilityProperty(iNode);
if (visProp && !visProp.isConstant())
{
Prop prop;
prop.mScalar = visProp;
oPropList.push_back(prop);
if (isObjConstant)
{
oAnimVisStaticObj.push_back(iNode);
}
}
return visProp;
}
void setConstantVisibility(Alembic::Abc::IScalarProperty iVisProp,
MObject & iParent)
{
if (iVisProp.valid() && iVisProp.isConstant())
{
Alembic::Util::int8_t visVal;
iVisProp.get(&visVal);
MFnDependencyNode dep(iParent);
MPlug plug = dep.findPlug("visibility", true);
if (!plug.isNull())
{
plug.setBool(visVal != 0);
}
}
}
bool matchesNameWithRegex (const MString& iName,
const MStringArray & iPatterns)
{
unsigned int length = iPatterns.length();
if (length == 0)
return true;
for (unsigned int i=0; i<length; ++i)
{
// use 'match' function provided in the maya mel script.
MString scriptStr, result;
scriptStr.format("match \"^1s\" \"^2s\";", iPatterns[i], iName);
MGlobal::executeCommand(scriptStr, result);
// found a match!
if (result.length() > 0)
{
return true;
}
}
return false;
}
void connectIntermediateMesh(MFnMesh& ioFn, MFnMesh& fn)
{
// Maya doesn't allow to delete history on a referenced mesh. We
// can't disconnect the history and change it via MFnMesh directly.
// Instead, we could create an intermediate mesh and connect it to
// the inMesh plug of the referenced mesh.
// To avoid leaving orphan intermediate mesh in the scene, we
// tag the intermediate mesh with a dynamic attribute so that
// it can be deleted properly when imported again.
MDGModifier modifier;
// Create the dynamic attribute to indicate that the mesh is an
// intermediate mesh.
MFnNumericAttribute numericAttr;
MObject aioAttr = numericAttr.create(
"AlembicIntermediateObject", "aio", MFnNumericData::kBoolean);
modifier.addAttribute(ioFn.object(), aioAttr);
modifier.doIt();
// Set the intermediate mesh as Maya intermediate object and
// connect it to the inMesh plug
modifier.renameNode(ioFn.object(), fn.name() + "Orig");
modifier.newPlugValueBool(ioFn.findPlug("intermediateObject", true), true);
modifier.newPlugValueBool(ioFn.findPlug(aioAttr, true), true);
modifier.connect(ioFn.findPlug("outMesh", true), fn.findPlug("inMesh", true));
modifier.doIt();
}
void deleteIntermediateMesh(MFnMesh& fn)
{
// When merge with a referenced node with history, delete the
// previous intermediate mesh that is created by Alembic plug-in.
MPlugArray sources;
fn.findPlug("inMesh", true).connectedTo(sources, true, false);
if (sources.length() > 0)
{
MObject io = sources[0].node();
if (MFnMesh(io).hasAttribute("AlembicIntermediateObject"))
{
MDGModifier modifier;
modifier.deleteNode(io);
modifier.doIt();
}
}
}
}
CreateSceneVisitor::CreateSceneVisitor(double iFrame,
bool iUnmarkedFaceVaryingColors, const MObject & iParent,
Action iAction, MString iRootNodes,
MString iIncludeFilterString, MString iExcludeFilterString) :
mFrame(iFrame), mParent(iParent),
mUnmarkedFaceVaryingColors(iUnmarkedFaceVaryingColors), mAction(iAction)
{
mAnyRoots = false;
// parse the input string to extract the nodes that need (re)connection
if (iRootNodes != MString() && iRootNodes != MString("/"))
{
MStringArray theArray;
if (iRootNodes.split(' ', theArray) == MS::kSuccess)
{
unsigned int len = theArray.length();
for (unsigned int i = 0; i < len; i++)
{
MString name = theArray[i];
// the name could be either a partial path or a full path
MDagPath dagPath;
if ( getDagPathByName( name, dagPath ) == MS::kSuccess )
{
name = dagPath.partialPathName();
mRootNodes.insert(name.asChar());
mAnyRoots = true;
}
else
{
MString theWarning("Could not find root: ");
theWarning += name;
theWarning += " in the scene.";
printWarning(theWarning);
}
}
}
}
else if (iRootNodes == MString("/"))
{
mAnyRoots = true;
}
mOnlyPatterns.clear();
if (iIncludeFilterString != MString() &&
iIncludeFilterString != MString("*"))
{
iIncludeFilterString.split(' ', mOnlyPatterns);
}
mExceptPatterns.clear();
if (iExcludeFilterString != MString() &&
iExcludeFilterString != MString("*"))
{
iExcludeFilterString.split(' ', mExceptPatterns);
}
}
CreateSceneVisitor::~CreateSceneVisitor()
{
}
void CreateSceneVisitor::getData(WriterData & oData)
{
oData = mData;
}
bool CreateSceneVisitor::hasSampledData()
{
// Currently there's no support for bringing in particle system simulation
return (mData.mPropList.size() > 0
|| mData.mXformList.size() > 0
|| mData.mSubDList.size() > 0
|| mData.mPolyMeshList.size() > 0
|| mData.mCameraList.size() > 0
|| mData.mNurbsList.size() > 0
|| mData.mCurvesList.size() > 0
|| mData.mLocList.size() > 0);
}
// re-add the selection back to the sets
void CreateSceneVisitor::applyShaderSelection()
{
std::map < MObject, MSelectionList, ltMObj >::iterator i =
mShaderMeshMap.begin();
std::map < MObject, MSelectionList, ltMObj >::iterator end =
mShaderMeshMap.end();
for (; i != end; ++i)
{
MFnSet curSet(i->first);
curSet.addMembers(i->second);
}
mShaderMeshMap.clear();
}
// add face sets after connections are made
void CreateSceneVisitor::addFaceSetsAfterConnection()
{
std::map < MObject, Alembic::Abc::IObject, ltMObj >::iterator i =
mAddFaceSetsMap.begin();
std::map < MObject, Alembic::Abc::IObject, ltMObj >::iterator end =
mAddFaceSetsMap.end();
for (; i != end; ++i)
{
MObject dagNode = i->first;
addFaceSets(dagNode, i->second);
}
}
void CreateSceneVisitor::addToPropList(std::size_t iFirst, MObject & iObject)
{
std::size_t last = mData.mPropList.size();
std::vector<std::string> attrList;
for (std::size_t i = iFirst; i < last; ++i)
{
if (mData.mPropList[i].mArray.valid())
{
attrList.push_back(mData.mPropList[i].mArray.getName());
}
else
{
attrList.push_back(mData.mPropList[i].mScalar.getName());
}
}
mData.mPropObjList.push_back(SampledPair(iObject, attrList));
}
// remembers what sets a mesh was part of, gets those sets as a selection
// and then clears the sets for reassignment later this is only used when
// hooking up an Alembic node to a previous hierarchy (swapping)
void CreateSceneVisitor::checkShaderSelection(MFnMesh & iMesh,
unsigned int iInst)
{
MObjectArray comps;
iMesh.getConnectedSetsAndMembers(iInst, sets, comps, false);
unsigned int setsLength = sets.length();
for (unsigned int i = 0; i < setsLength; ++i)
{
MObject & curSetObj = sets[i];
if (mShaderMeshMap.find(curSetObj) == mShaderMeshMap.end())
{
MFnSet curSet(curSetObj);
MSelectionList & curSel = mShaderMeshMap[curSetObj];
curSet.getMembers(curSel, true);
// clear before hand so that when we add the selection to the
// set later, it will take. This is to get around a problem where
// shaders are assigned per face dont shade correctly
curSet.clear();
}
}
}
void CreateSceneVisitor::visit(AlembicObjectPtr iObject)
{
Alembic::Abc::IObject iObj = iObject->object();
if ( Alembic::AbcGeom::IXform::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::IXform xform(iObj, Alembic::Abc::kWrapExisting);
(*this)(xform, iObject);
}
else if ( Alembic::AbcGeom::ISubD::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::ISubD mesh(iObj, Alembic::Abc::kWrapExisting);
(*this)(mesh);
}
else if ( Alembic::AbcGeom::IPolyMesh::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::IPolyMesh mesh(iObj, Alembic::Abc::kWrapExisting);
(*this)(mesh);
}
else if ( Alembic::AbcGeom::ICamera::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::ICamera cam(iObj, Alembic::Abc::kWrapExisting);
(*this)(cam);
}
else if ( Alembic::AbcGeom::ICurves::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::ICurves curves(iObj, Alembic::Abc::kWrapExisting);
(*this)(curves);
}
else if ( Alembic::AbcGeom::INuPatch::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::INuPatch nurbs(iObj, Alembic::Abc::kWrapExisting);
(*this)(nurbs);
}
else if ( Alembic::AbcGeom::IPoints::matches(iObj.getHeader()) )
{
Alembic::AbcGeom::IPoints pts(iObj, Alembic::Abc::kWrapExisting);
(*this)(pts);
}
else if ( iObj.getHeader().getMetaData().get("schema") == "" )
{
createEmptyObject(iObject);
}
else
{
MString theWarning(iObj.getName().c_str());
theWarning += " is an unsupported schema, skipping: ";
theWarning += iObj.getMetaData().get("schema").c_str();
printWarning(theWarning);
}
}
AlembicObjectPtr CreateSceneVisitor::previsit(AlembicObjectPtr iParentObject)
{
Alembic::Abc::IObject parent = iParentObject->object();
const MString name = parent.getFullName().c_str();
const size_t numChildren = parent.getNumChildren();
// Apply exclude filters first as a preorder traversal.
if (mExceptPatterns.length() > 0 &&
matchesNameWithRegex(name, mExceptPatterns))
{
return AlembicObjectPtr();
}
for (size_t i = 0; i < numChildren; ++i)
{
Alembic::Abc::IObject child = parent.getChild(i);
AlembicObjectPtr childObject =
previsit(AlembicObjectPtr(new AlembicObject(child)));
if (childObject)
{
iParentObject->addChild(childObject);
}
}
// We traverse a tree in postorder. The invarient is that iParentObject
// will have no child unless any descendent of it has the matching name.
if (iParentObject->getNumChildren() == 0)
{
if (!matchesNameWithRegex(name, mOnlyPatterns))
{
return AlembicObjectPtr();
}
}
return iParentObject;
}
std::string CreateSceneVisitor::searchRootNames(const std::string & iName)
{
// we can bail early
if (mRootNodes.empty())
{
return iName;
}
// we can't do find because there could be a namespace mismatch
// if things match exactly however, bail early
std::string strippedName = stripPathAndNamespace(iName);
std::set<std::string>::iterator it = mRootNodes.begin();
std::set<std::string>::iterator itEnd = mRootNodes.end();
std::string closeMatch;
for (; it != itEnd; ++it)
{
if (*it == iName)
{
return iName;
}
if (*it == strippedName)
{
return strippedName;
}
std::string strippedRoot = stripPathAndNamespace(*it);
if (strippedName == strippedRoot)
{
closeMatch = *it;
}
}
return closeMatch;
}
// root of file, no creation of DG node
MStatus CreateSceneVisitor::walk(Alembic::Abc::IArchive & iRoot)
{
MStatus status = MS::kSuccess;
MObject saveParent = mParent;
if (!iRoot.valid()) return MS::kFailure;
// preload the cache hierarchy with an optional filtering.
AlembicObjectPtr topObject =
previsit(AlembicObjectPtr(new AlembicObject(iRoot.getTop())));
if (!topObject) return status;
size_t numChildren = topObject->getNumChildren();
if (numChildren == 0) return status;
if (mAction == NONE) // simple scene creation mode
{
for (size_t i = 0; i < numChildren; i++)
{
this->visit(topObject->getChild(i));
mParent = saveParent;
}
return status;
}
std::string rootName;
// one root node? see if it matches any of the children
if (mRootNodes.size() == 1)
{
rootName = *mRootNodes.begin();
std::string rootBase = stripPathAndNamespace(rootName);
bool foundRoot = false;
for (size_t i = 0; i < numChildren; ++i)
{
std::string childName =
topObject->object().getChildHeader(i).getName();
if (rootBase == stripPathAndNamespace(childName))
{
foundRoot = true;
break;
}
}
// none of the direct children of the archive matches, so we'll inject
// the children of the specified root
if (!foundRoot)
{
MDagPath dagPath;
if (getDagPathByName(MString(rootName.c_str()), dagPath)
== MS::kSuccess)
{
unsigned int numChildDags = dagPath.childCount();
if (numChildDags > 0)
{
mRootNodes.clear();
}
for (unsigned int j = 0; j < numChildDags; ++j)
{
MObject child = dagPath.child(j);
dagPath.push(child);
mRootNodes.insert(dagPath.partialPathName().asChar());
dagPath.pop();
}
}
}
}
// doing connections
std::set<std::string> connectUpdateNodes;
std::set<std::string> connectCurNodesInFile;
std::set<std::string>::iterator fileEnd =
connectCurNodesInFile.end();
for (size_t i = 0; i < numChildren; i++)
{
AlembicObjectPtr object = topObject->getChild(i);
std::string name = object->object().getName();
connectCurNodesInFile.insert(name);
std::string rootPath = searchRootNames(name);
// see if this name is part of the input to AlembicNode
if (!rootPath.empty())
{
// Find out if this node exists in the current scene
MDagPath dagPath;
if (mAnyRoots &&
getDagPathByName(MString(rootPath.c_str()), dagPath) ==
MS::kSuccess)
{
connectUpdateNodes.insert(name);
mConnectDagNode = dagPath;
mConnectDagNode.pop();
this->visit(object);
mParent = saveParent;
}
else if (mAction != CREATE && mAction != CREATE_REMOVE)
{
MString theWarning("Could not find: ");
theWarning += name.c_str();
theWarning += " in the scene.";
theWarning += " Skipping it and all descendants.";
printWarning(theWarning);
}
else
{
mConnectDagNode = MDagPath();
connectUpdateNodes.insert(name);
this->visit(object);
mParent = saveParent;
}
}
else
{
MString theWarning("Could not find a match for: ");
theWarning += name.c_str();
if (rootName.empty())
{
theWarning += " in the scene.";
}
else
{
theWarning += " beneath parent: ";
theWarning += rootName.c_str();
}
printWarning(theWarning);
}
} // for-loop
if (mRootNodes.size() > connectUpdateNodes.size() &&
(mAction == REMOVE || mAction == CREATE_REMOVE))
{
std::set<std::string>::iterator iter =
mRootNodes.begin();
const std::set<std::string>::iterator fileEndIter =
connectCurNodesInFile.end();
MDagPath dagPath;
for ( ; iter != mRootNodes.end(); iter++)
{
std::string name = *iter;
bool existInFile =
(connectCurNodesInFile.find(name) != fileEndIter);
bool existInScene =
(getDagPathByName(MString(name.c_str()), dagPath)
== MS::kSuccess);
if (existInScene && !existInFile)
{
removeDagNode(dagPath);
}
else if (!existInScene && !existInFile)
{
MString theWarning(name.c_str());
theWarning +=
" exists neither in file nor in the scene";
printWarning(theWarning);
}
}
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::ICamera & iNode)
{
MStatus status = MS::kSuccess;
bool isConstant = iNode.getSchema().isConstant();
// add animated camera to the list
if (!isConstant)
{
mData.mCameraList.push_back(iNode);
}
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, false);
getAnimatedProps(userProp, mData.mPropList, false);
Alembic::Abc::IScalarProperty visProp = getVisible(iNode, isConstant,
mData.mPropList, mData.mAnimVisStaticObjList);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
cameraObj = mConnectDagNode.node();
if (!isConstant)
{
mData.mCameraObjList.push_back(cameraObj);
}
}
}
if (mAction == CREATE || mAction == CREATE_REMOVE)
{
cameraObj = create(iNode, mParent);
if (!isConstant)
{
mData.mCameraObjList.push_back(cameraObj);
}
}
if (cameraObj != MObject::kNullObj)
{
setConstantVisibility(visProp, cameraObj);
addProps(arbProp, cameraObj, false);
addProps(userProp, cameraObj, false);
}
if ( mAction >= CONNECT )
{
MFnCamera fn(cameraObj, &status);
// check that the data types are compatible, they might not be
// if we have a weird hierarchy, where the node in the scene
// differs from the node on disk
if ( status != MS::kSuccess )
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return status;
}
addToPropList(firstProp, cameraObj);
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::ICurves & iNode)
{
MStatus status = MS::kSuccess;
bool isConstant = iNode.getSchema().isConstant();
// read sample 0 to determine and use it to set the number of total
// curves. We can't support changing the number of curves over time.
Alembic::AbcGeom::ICurvesSchema::Sample samp;
iNode.getSchema().get(samp);
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
Alembic::AbcGeom::IFloatGeomParam::Sample widthSamp;
if (iNode.getSchema().getWidthsParam())
{
iNode.getSchema().getWidthsParam().getExpanded(widthSamp);
}
std::size_t numCurves = samp.getNumCurves();
if (numCurves == 0)
{
MString theWarning(iNode.getName().c_str());
theWarning += " has no curves, skipping.";
printWarning(theWarning);
return MS::kFailure;
}
// add animated curves to the list
else if (!isConstant)
{
mData.mNumCurves.push_back(numCurves);
mData.mCurvesList.push_back(iNode);
}
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, false);
getAnimatedProps(userProp, mData.mPropList, false);
Alembic::Abc::IScalarProperty visProp = getVisible(iNode, isConstant,
mData.mPropList, mData.mAnimVisStaticObjList);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
curvesObj = mConnectDagNode.node();
if (!isConstant)
{
if (numCurves == 1)
{
mData.mNurbsCurveObjList.push_back(curvesObj);
}
else
{
unsigned int childCurves = mConnectDagNode.childCount();
for (unsigned int i = 0; i < numCurves; ++i)
{
if (i < childCurves)
{
mData.mNurbsCurveObjList.push_back(
mConnectDagNode.child(i));
}
else
{
// push a null object since we have more Alembic
// curves than we have Maya curves
MObject obj;
mData.mNurbsCurveObjList.push_back(obj);
}
}
} // else
}
} // if hasDag
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
curvesObj = createCurves(iNode.getName(), samp, widthSamp, mParent,
mData.mNurbsCurveObjList, !isConstant);
}
if (curvesObj != MObject::kNullObj)
{
setConstantVisibility(visProp, curvesObj);
addProps(arbProp, curvesObj, false);
addProps(userProp, curvesObj, false);
}
if (mAction >= CONNECT)
{
MFnNurbsCurve fncurve(curvesObj, &status);
// not a single curve, try the transform for a group of curves
if (status != MS::kSuccess)
{
MFnTransform fntrans(curvesObj, &status);
}
// check that the data types are compatible, they might not be
// if we have a weird hierarchy, where the node in the scene
// differs from the node on disk
if (status != MS::kSuccess)
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return status;
}
if (!fncurve.object().isNull())
{
MPlug dstPlug = fncurve.findPlug("create", true);
disconnectAllPlugsTo(dstPlug);
disconnectProps(fncurve, mData.mPropList, firstProp);
addToPropList(firstProp, curvesObj);
}
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::IPoints& iNode)
{
MStatus status = MS::kSuccess;
MObject particleObj = MObject::kNullObj;
bool isConstant = iNode.getSchema().isConstant();
if (!isConstant)
mData.mPointsList.push_back(iNode);
// since we don't really support animated points, don't bother
// with the animated properties on it
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
particleObj = mConnectDagNode.node();
}
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
status = create(mFrame, iNode, mParent, particleObj);
if (!isConstant)
{
mData.mPointsObjList.push_back(particleObj);
}
}
// don't currently care about anything animated on a particleObj
std::vector<Prop> fakePropList;
std::vector<Alembic::AbcGeom::IObject> fakeObjList;
if (particleObj != MObject::kNullObj)
{
Alembic::Abc::IScalarProperty visProp =
getVisible(iNode, false, fakePropList, fakeObjList);
setConstantVisibility(visProp, particleObj);
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
addProps(arbProp, particleObj, false);
addProps(userProp, particleObj, false);
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::ISubD& iNode)
{
MStatus status = MS::kSuccess;
SubDAndFriends subdAndFriends;
subdAndFriends.mMesh = iNode;
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
bool colorAnim = getUVandColorAttrs(arbProp, subdAndFriends.mV2s,
subdAndFriends.mC3s, subdAndFriends.mC4s,
mUnmarkedFaceVaryingColors);
bool isConstant = iNode.getSchema().isConstant();
// add animated SubDs to the list
if (!isConstant || colorAnim)
{
mData.mSubDList.push_back(subdAndFriends);
}
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, mUnmarkedFaceVaryingColors);
getAnimatedProps(userProp, mData.mPropList, mUnmarkedFaceVaryingColors);
Alembic::Abc::IScalarProperty visProp = getVisible(iNode, isConstant,
mData.mPropList, mData.mAnimVisStaticObjList);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
subDObj = mConnectDagNode.node();
if (!isConstant || colorAnim)
{
mData.mSubDObjList.push_back(subDObj);
}
}
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
subDObj = createSubD(mFrame, subdAndFriends, mParent);
if (!isConstant || colorAnim)
{
mData.mSubDObjList.push_back(subDObj);
mAddFaceSetsMap[subDObj] = iNode;
}
}
if (subDObj != MObject::kNullObj)
{
setConstantVisibility(visProp, subDObj);
addProps(arbProp, subDObj, mUnmarkedFaceVaryingColors);
addProps(userProp, subDObj, mUnmarkedFaceVaryingColors);
addFaceSets(subDObj, iNode);
}
if ( mAction >= CONNECT )
{
MFnMesh fn(subDObj, &status);
// check that the data types are compatible, they might not be
// if we have a weird hierarchy, where the node in the scene
// differs from the node on disk
if (!subDObj.hasFn(MFn::kMesh))
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return MS::kFailure;
}
// the mesh from Alembic is static but the Maya mesh is referenced.
// direct changes to the Maya mesh will lost after unloading/loading
// the reference file. we create an intermediate mesh and connect
// it to the Maya mesh.
MFnMesh ioFn;
if (isConstant && fn.isFromReferencedFile())
{
deleteIntermediateMesh(fn);
ioFn.setObject(createSubD(mFrame, subdAndFriends, mParent));
}
if (mConnectDagNode.isValid())
{
checkShaderSelection(fn, mConnectDagNode.instanceNumber());
}
disconnectMesh(subDObj, mData.mPropList, firstProp);
fn.setObject(subDObj);
if (isConstant && CONNECT == mAction)
{
if (ioFn.object().hasFn(MFn::kMesh))
connectIntermediateMesh(ioFn, fn);
else
readSubD(mFrame, fn, subDObj, subdAndFriends, false);
}
addToPropList(firstProp, subDObj);
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::IPolyMesh& iNode)
{
MStatus status = MS::kSuccess;
PolyMeshAndFriends meshAndFriends;
meshAndFriends.mMesh = iNode;
bool isConstant = iNode.getSchema().isConstant();
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
bool colorAnim = getUVandColorAttrs(arbProp, meshAndFriends.mV2s,
meshAndFriends.mC3s, meshAndFriends.mC4s,
mUnmarkedFaceVaryingColors);
// add animated poly mesh to the list
if (!isConstant || colorAnim)
mData.mPolyMeshList.push_back(meshAndFriends);
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, mUnmarkedFaceVaryingColors);
getAnimatedProps(userProp, mData.mPropList, mUnmarkedFaceVaryingColors);
Alembic::Abc::IScalarProperty visProp = getVisible(iNode, isConstant,
mData.mPropList, mData.mAnimVisStaticObjList);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
polyObj = mConnectDagNode.node();
if (!isConstant || colorAnim)
{
mData.mPolyMeshObjList.push_back(polyObj);
}
}
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
polyObj = createPoly(mFrame, meshAndFriends, mParent);
if (!isConstant || colorAnim)
{
mData.mPolyMeshObjList.push_back(polyObj);
mAddFaceSetsMap[polyObj] = iNode;
}
}
if (polyObj != MObject::kNullObj)
{
setConstantVisibility(visProp, polyObj);
addProps(arbProp, polyObj, mUnmarkedFaceVaryingColors);
addProps(userProp, polyObj, mUnmarkedFaceVaryingColors);
addFaceSets(polyObj, iNode);
}
if ( mAction >= CONNECT )
{
MFnMesh fn(polyObj, &status);
// check that the data types are compatible, they might not be
// if we have a weird hierarchy, where the node in the scene
// differs from the node on disk
if ( status != MS::kSuccess )
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return status;
}
// the mesh from Alembic is static but the Maya mesh is referenced.
// direct changes to the Maya mesh will lost after unloading/loading
// the reference file. we create an intermediate mesh and connect
// it to the Maya mesh.
MFnMesh ioFn;
if (isConstant && fn.isFromReferencedFile())
{
deleteIntermediateMesh(fn);
ioFn.setObject(createPoly(mFrame, meshAndFriends, mParent));
}
if (mConnectDagNode.isValid())
checkShaderSelection(fn, mConnectDagNode.instanceNumber());
disconnectMesh(polyObj, mData.mPropList, firstProp);
fn.setObject(polyObj);
if (isConstant && CONNECT == mAction)
{
if (ioFn.object().hasFn(MFn::kMesh))
connectIntermediateMesh(ioFn, fn);
else
readPoly(mFrame, fn, polyObj, meshAndFriends, false);
}
addToPropList(firstProp, polyObj);
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::INuPatch& iNode)
{
MStatus status = MS::kSuccess;
bool isConstant = iNode.getSchema().isConstant();
// add animated poly mesh to the list
if (!isConstant)
mData.mNurbsList.push_back(iNode);
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, false);
getAnimatedProps(userProp, mData.mPropList, false);
Alembic::Abc::IScalarProperty visProp = getVisible(iNode, isConstant,
mData.mPropList, mData.mAnimVisStaticObjList);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
nurbsObj = mConnectDagNode.node();
if (!isConstant)
{
mData.mNurbsObjList.push_back(nurbsObj);
}
}
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
nurbsObj = createNurbs(mFrame, iNode, mParent);
if (!isConstant)
{
mData.mNurbsObjList.push_back(nurbsObj);
}
}
if (nurbsObj != MObject::kNullObj)
{
addProps(arbProp, nurbsObj, false);
addProps(userProp, nurbsObj, false);
setConstantVisibility(visProp, nurbsObj);
}
if ( mAction >= CONNECT )
{
MFnNurbsSurface fn(nurbsObj, &status);
// check that the data types are compatible, they might not be
// if we have a weird hierarchy, where the node in the scene
// differs from the node on disk
if ( status != MS::kSuccess )
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return status;
}
MPlug dstPlug = fn.findPlug("create", true);
disconnectAllPlugsTo(dstPlug);
disconnectProps(fn, mData.mPropList, firstProp);
addToPropList(firstProp, nurbsObj);
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}
MStatus CreateSceneVisitor::operator()(Alembic::AbcGeom::IXform & iNode,
AlembicObjectPtr iNodeObject)
{
MStatus status = MS::kSuccess;
Alembic::Abc::ICompoundProperty arbProp =
iNode.getSchema().getArbGeomParams();
Alembic::Abc::ICompoundProperty userProp =
iNode.getSchema().getUserProperties();
std::size_t firstProp = mData.mPropList.size();
getAnimatedProps(arbProp, mData.mPropList, false);
getAnimatedProps(userProp, mData.mPropList, false);
if (iNode.getProperties().getPropertyHeader("locator") != NULL)
{
Alembic::Abc::ICompoundProperty props = iNode.getProperties();
const Alembic::AbcCoreAbstract::PropertyHeader * locHead =
props.getPropertyHeader("locator");
if (locHead != NULL && locHead->isScalar() &&
locHead->getDataType().getPod() == Alembic::Util::kFloat64POD &&
locHead->getDataType().getExtent() == 6)
{
Alembic::Abc::IScalarProperty locProp(props, "locator");
bool isConstant = locProp.isConstant();
Alembic::Abc::IScalarProperty visProp = getVisible(iNode,
isConstant, mData.mPropList, mData.mAnimVisStaticObjList);
// add animated locator to the list
if (!isConstant)
mData.mLocList.push_back(iNode);
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode,
iNode.getName());
if (hasDag)
{
xformObj = mConnectDagNode.node();
if (!isConstant)
{
mData.mLocObjList.push_back(xformObj);
}
}
}
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE))
{
xformObj = create(iNode, mParent, locProp);
if (!isConstant)
{
mData.mLocObjList.push_back(xformObj);
}
}
if (xformObj != MObject::kNullObj)
{
addProps(arbProp, xformObj, false);
addProps(userProp, xformObj, false);
setConstantVisibility(visProp, xformObj);
}
if ( mAction >= CONNECT )
{
if (!xformObj.hasFn(MFn::kLocator))
{
MString theError("No connection done for node '");
theError += MString(iNode.getName().c_str());
theError += MString("' with ");
theError += mConnectDagNode.fullPathName();
printError(theError);
return status;
}
addToPropList(firstProp, xformObj);
}
if (hasDag)
{
mConnectDagNode.pop();
}
}
}
else // transform node
{
MString name(iNode.getName().c_str());
size_t numChildren = iNodeObject->getNumChildren();
bool isConstant = iNode.getSchema().isConstant();
Alembic::Abc::IScalarProperty visProp = getVisible(iNode,
isConstant, mData.mPropList, mData.mAnimVisStaticObjList);
Alembic::AbcGeom::XformSample samp;
iNode.getSchema().get(samp, 0);
if (!isConstant)
{
mData.mXformList.push_back(iNode);
mData.mIsComplexXform.push_back(isComplex(samp));
}
if (isConstant && visProp.valid() && !visProp.isConstant())
{
mData.mAnimVisStaticObjList.push_back(iNode);
}
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
xformObj = mConnectDagNode.node();
}
}
// There might be children under the current DAG node that
// doesn't exist in the file.
// Remove them if the -removeIfNoUpdate flag is set
if ((mAction == REMOVE || mAction == CREATE_REMOVE) && hasDag)
{
unsigned int numDags = mConnectDagNode.childCount();
std::vector<MDagPath> dagToBeRemoved;
// get names of immediate children so we can compare with
// the hierarchy in the scene
std::set< std::string > childNodesInFile;
for (size_t j = 0; j < numChildren; ++j)
{
Alembic::Abc::IObject child = iNodeObject->getChild(j)->object();
childNodesInFile.insert(child.getName());
}
for (unsigned int i = 0; i < numDags; i++)
{
MObject child = mConnectDagNode.child(i);
MFnDagNode fn(child, &status);
if ( status == MS::kSuccess )
{
std::string childName = fn.fullPathName().asChar();
size_t found = childName.rfind("|");
if (found != std::string::npos)
{
childName = childName.substr(
found+1, childName.length() - found);
if (childNodesInFile.find(childName)
== childNodesInFile.end())
{
MDagPath dagPath;
getDagPathByName(fn.fullPathName(), dagPath);
dagToBeRemoved.push_back(dagPath);
}
}
}
}
if (dagToBeRemoved.size() > 0)
{
unsigned int dagSize =
static_cast<unsigned int>(dagToBeRemoved.size());
for ( unsigned int i = 0; i < dagSize; i++ )
removeDagNode(dagToBeRemoved[i]);
}
}
// just create the node
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE ))
{
MFnTransform trans;
xformObj = trans.create(mParent, &status);
if (status != MS::kSuccess)
{
MString theError("Failed to create transform node ");
theError += name;
printError(theError);
return status;
}
trans.setName(name);
}
if (xformObj != MObject::kNullObj)
{
setConstantVisibility(visProp, xformObj);
addProps(arbProp, xformObj, false);
addProps(userProp, xformObj, false);
}
if (mAction >= CONNECT)
{
if (xformObj.hasFn(MFn::kTransform))
{
std::vector<std::string> transopNameList;
connectToXform(samp, isConstant, xformObj, transopNameList,
mData.mPropList, firstProp);
if (!isConstant)
{
SampledPair sampPair(xformObj, transopNameList);
mData.mXformOpList.push_back(sampPair);
}
addToPropList(firstProp, xformObj);
}
else if (xformObj != MObject::kNullObj)
{
MString theError = mConnectDagNode.partialPathName();
theError += MString(" is not compatible as a transform node. ");
theError += MString("Connection failed.");
printError(theError);
return MS::kFailure;
}
}
MObject saveParent = xformObj;
for (size_t i = 0; i < numChildren; ++i)
{
mParent = saveParent;
this->visit(iNodeObject->getChild(i));
}
if (hasDag)
{
mConnectDagNode.pop();
}
}
return status;
}
MStatus CreateSceneVisitor::createEmptyObject(AlembicObjectPtr iNodeObject)
{
Alembic::Abc::IObject iNode = iNodeObject->object();
MStatus status = MS::kSuccess;
MString name(iNode.getName().c_str());
size_t numChildren = iNodeObject->getNumChildren();
bool hasDag = false;
if (mAction != NONE && mConnectDagNode.isValid())
{
hasDag = getDagPathByChildName(mConnectDagNode, iNode.getName());
if (hasDag)
{
xformObj = mConnectDagNode.node();
}
}
// There might be children under the current DAG node that
// doesn't exist in the file.
// Remove them if the -removeIfNoUpdate flag is set
if ((mAction == REMOVE || mAction == CREATE_REMOVE) &&
mConnectDagNode.isValid())
{
unsigned int numDags = mConnectDagNode.childCount();
std::vector<MDagPath> dagToBeRemoved;
// get names of immediate children so we can compare with
// the hierarchy in the scene
std::set< std::string > childNodesInFile;
for (size_t j = 0; j < numChildren; ++j)
{
Alembic::Abc::IObject child = iNodeObject->getChild(j)->object();
childNodesInFile.insert(child.getName());
}
for (unsigned int i = 0; i < numDags; i++)
{
MObject child = mConnectDagNode.child(i);
MFnDagNode fn(child, &status);
if ( status == MS::kSuccess )
{
std::string childName = fn.fullPathName().asChar();
size_t found = childName.rfind("|");
if (found != std::string::npos)
{
childName = childName.substr(
found+1, childName.length() - found);
if (childNodesInFile.find(childName)
== childNodesInFile.end())
{
MDagPath dagPath;
getDagPathByName(fn.fullPathName(), dagPath);
dagToBeRemoved.push_back(dagPath);
}
}
}
}
if (dagToBeRemoved.size() > 0)
{
unsigned int dagSize =
static_cast<unsigned int>(dagToBeRemoved.size());
for ( unsigned int i = 0; i < dagSize; i++ )
removeDagNode(dagToBeRemoved[i]);
}
}
// just create the node
if (!hasDag && (mAction == CREATE || mAction == CREATE_REMOVE ))
{
MFnTransform trans;
xformObj = trans.create(mParent, &status);
if (status != MS::kSuccess)
{
MString theError("Failed to create transform node ");
theError += name;
printError(theError);
return status;
}
trans.setName(name);
}
MObject saveParent = xformObj;
for (size_t i = 0; i < numChildren; ++i)
{
mParent = saveParent;
this->visit(iNodeObject->getChild(i));
}
if (hasDag)
{
mConnectDagNode.pop();
}
return status;
}