C++ API Reference
AbcImport/AlembicNode.cpp
//-*****************************************************************************
//
// Copyright (c) 2009-2012,
// 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 "AlembicNode.h"
#include "CreateSceneHelper.h"
#include "CameraHelper.h"
#include "LocatorHelper.h"
#include "MeshHelper.h"
#include "NurbsCurveHelper.h"
#include "NurbsSurfaceHelper.h"
#include "PointHelper.h"
#include "XformHelper.h"
#include <maya/MAngle.h>
#include <maya/MGlobal.h>
#include <maya/MTime.h>
#include <maya/MFileObject.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnIntArrayData.h>
#include <maya/MFnVectorArrayData.h>
#include <maya/MFnStringArrayData.h>
#include <maya/MFnStringData.h>
#include <maya/MFnMeshData.h>
#include <maya/MFnNurbsCurveData.h>
#include <maya/MFnNurbsSurfaceData.h>
#include <maya/MFnGenericAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MExternalContentInfoTable.h>
#include <Alembic/AbcCoreFactory/IFactory.h>
#include <Alembic/AbcCoreHDF5/ReadWrite.h>
#include <Alembic/AbcCoreOgawa/ReadWrite.h>
#include <Alembic/AbcGeom/Visibility.h>
MObject AlembicNode::mTimeAttr;
MObject AlembicNode::mAbcFileNameAttr;
MObject AlembicNode::mAbcLayerFileNamesAttr;
MObject AlembicNode::mSpeedAttr;
MObject AlembicNode::mOffsetAttr;
MObject AlembicNode::mCycleTypeAttr;
MObject AlembicNode::mStartFrameAttr;
MObject AlembicNode::mEndFrameAttr;
MObject AlembicNode::mIncludeFilterAttr;
MObject AlembicNode::mExcludeFilterAttr;
MObject AlembicNode::mOutSubDArrayAttr;
MObject AlembicNode::mOutPolyArrayAttr;
MObject AlembicNode::mOutCameraArrayAttr;
MObject AlembicNode::mOutNurbsCurveGrpArrayAttr;
MObject AlembicNode::mOutNurbsSurfaceArrayAttr;
MObject AlembicNode::mOutTransOpArrayAttr;
MObject AlembicNode::mOutPropArrayAttr;
MObject AlembicNode::mOutLocatorPosScaleArrayAttr;
MStatus AlembicNode::initialize()
{
MStatus status;
MFnTypedAttribute tAttr, tAttr2;
// add the input attributes: time, file, sequence time
mTimeAttr = uAttr.create("time", "tm", MFnUnitAttribute::kTime, 0.0);
status = uAttr.setStorable(true);
status = addAttribute(mTimeAttr);
// input file name
MFnStringData fileFnStringData;
MObject fileNameDefaultObject = fileFnStringData.create("");
mAbcFileNameAttr = tAttr.create("abc_File", "fn",
MFnData::kString, fileNameDefaultObject);
status = tAttr.setStorable(true);
status = tAttr.setUsedAsFilename(true);
status = addAttribute(mAbcFileNameAttr);
// input layer file names
MFnStringArrayData fileFnStringArrayData;
MStringArray dummyStringArray;
MObject layerFileNamesDefaultObject = fileFnStringArrayData.create(dummyStringArray);
mAbcLayerFileNamesAttr = tAttr2.create("abc_layerFiles", "fns",
MFnData::kStringArray, layerFileNamesDefaultObject);
status = tAttr2.setStorable(true);
status = tAttr2.setUsedAsFilename(true);
status = tAttr2.setKeyable(false);
status = tAttr2.setWritable(true);
status = addAttribute(mAbcLayerFileNamesAttr);
// playback speed
mSpeedAttr = nAttr.create("speed", "sp",
MFnNumericData::kDouble, 1.0, &status);
status = nAttr.setWritable(true);
status = nAttr.setStorable(true);
status = nAttr.setKeyable(true);
status = addAttribute(mSpeedAttr);
// frame offset
mOffsetAttr = nAttr.create("offset", "of",
status = nAttr.setWritable(true);
status = nAttr.setStorable(true);
status = nAttr.setKeyable(true);
status = addAttribute(mOffsetAttr);
// cycle type
mCycleTypeAttr = eAttr.create("cycleType", "ct", 0, &status );
status = eAttr.addField("Hold", PLAYTYPE_HOLD);
status = eAttr.addField("Loop", PLAYTYPE_LOOP);
status = eAttr.addField("Reverse", PLAYTYPE_REVERSE);
status = eAttr.addField("Bounce", PLAYTYPE_BOUNCE);
status = eAttr.setWritable(true);
status = eAttr.setStorable(true);
status = eAttr.setKeyable(true);
status = addAttribute(mCycleTypeAttr);
// Regex Filter
// This is a hidden variable to preserve a regexIncludefilter string
// into a .ma file.
mIncludeFilterAttr = tAttr.create("regexIncludeFilter", "ift",
status = tAttr.setStorable(true);
status = tAttr.setHidden(true);
status = addAttribute(mIncludeFilterAttr);
// Regex Filter
// This is a hidden variable to preserve a regexExcludefilter string
// into a .ma file.
mExcludeFilterAttr = tAttr.create("regexExcludeFilter", "eft",
status = tAttr.setStorable(true);
status = tAttr.setHidden(true);
status = addAttribute(mExcludeFilterAttr);
// sequence min and max in frames
mStartFrameAttr = nAttr.create("startFrame", "sf",
status = nAttr.setWritable(false);
status = nAttr.setStorable(true);
status = addAttribute(mStartFrameAttr);
mEndFrameAttr = nAttr.create("endFrame", "ef",
status = nAttr.setWritable(false);
status = nAttr.setStorable(true);
status = addAttribute(mEndFrameAttr);
// add the output attributes
// sampled subD mesh
MFnMeshData fnMeshData;
MObject meshDefaultObject = fnMeshData.create(&status);
mOutSubDArrayAttr = tAttr.create("outSubDMesh", "osubd",
MFnData::kMesh, meshDefaultObject);
status = tAttr.setStorable(false);
status = tAttr.setWritable(false);
status = tAttr.setKeyable(false);
status = tAttr.setArray(true);
status = tAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutSubDArrayAttr);
// sampled poly mesh
mOutPolyArrayAttr = tAttr.create("outPolyMesh", "opoly",
MFnData::kMesh, meshDefaultObject);
status = tAttr.setStorable(false);
status = tAttr.setWritable(false);
status = tAttr.setKeyable(false);
status = tAttr.setArray(true);
status = tAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutPolyArrayAttr);
// sampled nurbs surface
MObject nsDefaultObject = fnNSData.create(&status);
mOutNurbsSurfaceArrayAttr = tAttr.create("outNSurface", "ons",
MFnData::kNurbsSurface, nsDefaultObject);
status = tAttr.setStorable(false);
status = tAttr.setWritable(false);
status = tAttr.setKeyable(false);
status = tAttr.setArray(true);
status = tAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutNurbsSurfaceArrayAttr);
// sampled nurbs curve group
MObject ncDefaultObject = fnNCData.create(&status);
mOutNurbsCurveGrpArrayAttr = tAttr.create("outNCurveGrp", "onc",
MFnData::kNurbsCurve, ncDefaultObject);
status = tAttr.setStorable(false);
status = tAttr.setWritable(false);
status = tAttr.setKeyable(false);
status = tAttr.setArray(true);
status = tAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutNurbsCurveGrpArrayAttr);
// sampled locator
mOutLocatorPosScaleArrayAttr = nAttr.create("outLoc", "olo",
MFnNumericData::kDouble, 0.0, &status);
status = nAttr.setStorable(false);
status = nAttr.setWritable(false);
status = nAttr.setArray(true);
status = nAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutLocatorPosScaleArrayAttr);
// sampled transform operations
mOutTransOpArrayAttr = nAttr.create("transOp", "to",
MFnNumericData::kDouble, 0.0, &status);
status = nAttr.setStorable(false);
status = nAttr.setWritable(false);
status = nAttr.setArray(true);
status = nAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutTransOpArrayAttr);
// sampled camera
// assume the boolean variables cannot be keyed
mOutCameraArrayAttr = nAttr.create("outCamera", "ocam",
MFnNumericData::kDouble, 0.0, &status);
status = nAttr.setStorable(false);
status = nAttr.setWritable(false);
status = nAttr.setArray(true);
status = nAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutCameraArrayAttr);
// sampled custom-attributes
mOutPropArrayAttr = gAttr.create("prop", "pr", &status);
status = gAttr.setWritable(false);
status = gAttr.setKeyable(false);
status = gAttr.setArray(true);
status = gAttr.setUsesArrayDataBuilder(true);
status = addAttribute(mOutPropArrayAttr);
// set up affection relationships
status = attributeAffects(mTimeAttr, mOutSubDArrayAttr);
status = attributeAffects(mTimeAttr, mOutPolyArrayAttr);
status = attributeAffects(mTimeAttr, mOutNurbsSurfaceArrayAttr);
status = attributeAffects(mTimeAttr, mOutNurbsCurveGrpArrayAttr);
status = attributeAffects(mTimeAttr, mOutTransOpArrayAttr);
status = attributeAffects(mTimeAttr, mOutCameraArrayAttr);
status = attributeAffects(mTimeAttr, mOutPropArrayAttr);
status = attributeAffects(mTimeAttr, mOutLocatorPosScaleArrayAttr);
status = attributeAffects(mSpeedAttr, mOutSubDArrayAttr);
status = attributeAffects(mSpeedAttr, mOutPolyArrayAttr);
status = attributeAffects(mSpeedAttr, mOutNurbsSurfaceArrayAttr);
status = attributeAffects(mSpeedAttr, mOutNurbsCurveGrpArrayAttr);
status = attributeAffects(mSpeedAttr, mOutTransOpArrayAttr);
status = attributeAffects(mSpeedAttr, mOutCameraArrayAttr);
status = attributeAffects(mSpeedAttr, mOutPropArrayAttr);
status = attributeAffects(mSpeedAttr, mOutLocatorPosScaleArrayAttr);
status = attributeAffects(mOffsetAttr, mOutSubDArrayAttr);
status = attributeAffects(mOffsetAttr, mOutPolyArrayAttr);
status = attributeAffects(mOffsetAttr, mOutNurbsSurfaceArrayAttr);
status = attributeAffects(mOffsetAttr, mOutNurbsCurveGrpArrayAttr);
status = attributeAffects(mOffsetAttr, mOutTransOpArrayAttr);
status = attributeAffects(mOffsetAttr, mOutCameraArrayAttr);
status = attributeAffects(mOffsetAttr, mOutPropArrayAttr);
status = attributeAffects(mOffsetAttr, mOutLocatorPosScaleArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutSubDArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutPolyArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutNurbsSurfaceArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutNurbsCurveGrpArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutTransOpArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutCameraArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutPropArrayAttr);
status = attributeAffects(mCycleTypeAttr, mOutLocatorPosScaleArrayAttr);
return status;
}
double AlembicNode::getFPS()
{
float fps = 24.0f;
if (unit!=MTime::kInvalid)
{
MTime time(1.0, MTime::kSeconds);
fps = static_cast<float>(time.as(unit));
}
if (fps <= 0.f )
{
fps = 24.0f;
}
return fps;
}
double AlembicNode::computeAdjustedTime(const double inputTime,
const double speed,
const double timeOffset)
{
return ( inputTime - timeOffset ) * speed;
}
double AlembicNode::computeRetime(const double inputTime,
const double firstTime,
const double lastTime,
const short playStyle)
{
const double playTime = lastTime - firstTime;
static const double eps = 0.001;
double retime = inputTime;
switch (playStyle)
{
case PLAYTYPE_HOLD:
break;
case PLAYTYPE_LOOP:
if (inputTime < (firstTime - eps) || inputTime > (lastTime + eps))
{
const double timeOffset = inputTime - firstTime;
const double playOffset = floor(timeOffset/playTime);
const double fraction = fabs(timeOffset/playTime - playOffset);
retime = firstTime + playTime * fraction;
}
break;
case PLAYTYPE_REVERSE:
if (inputTime > (firstTime + eps) && inputTime < (lastTime - eps))
{
const double timeOffset = inputTime - firstTime;
const double playOffset = floor(timeOffset/playTime);
const double fraction = fabs(timeOffset/playTime - playOffset);
retime = lastTime - playTime * fraction;
}
else if (inputTime < (firstTime + eps))
{
retime = lastTime;
}
else
{
retime = firstTime;
}
break;
case PLAYTYPE_BOUNCE:
if (inputTime < (firstTime - eps) || inputTime > (lastTime + eps))
{
const double timeOffset = inputTime - firstTime;
const double playOffset = floor(timeOffset/playTime);
const double fraction = fabs(timeOffset/playTime - playOffset);
// forward loop
if (fmod(playOffset, 2.0)==0.0)
{
retime = firstTime + playTime * fraction;
}
// backward loop
else
{
retime = lastTime - playTime * fraction;
}
}
break;
}
return retime;
}
MStatus AlembicNode::setDependentsDirty(const MPlug& plug, MPlugArray& plugArray)
{
if (plug == mAbcFileNameAttr)
{
/* This code was to force refresh of the AlembicNode when there is a file name change
But since it was only working in particular very simple case, we decided to not enable it
and only display a warning.
In all other cases it could result in undesired behavior even scene corruption.
See issue MAYA-47471
mFileInitialized = false;
mCurTime = DBL_MAX; // to force update
*/
if(mFileInitialized)
{
MGlobal::displayWarning("Repathing Alembic Nodes is not supported");
}
}
return MPxNode::setDependentsDirty(plug, plugArray);
}
MStatus AlembicNode::compute(const MPlug & plug, MDataBlock & dataBlock)
{
MStatus status;
// update the frame number to be imported
MDataHandle speedHandle = dataBlock.inputValue(mSpeedAttr, &status);
double speed = speedHandle.asDouble();
MDataHandle offsetHandle = dataBlock.inputValue(mOffsetAttr, &status);
double offset = offsetHandle.asDouble();
MDataHandle timeHandle = dataBlock.inputValue(mTimeAttr, &status);
MTime t = timeHandle.asTime();
double inputTime = t.as(MTime::kSeconds);
double fps = getFPS();
// scale and offset inputTime.
inputTime = computeAdjustedTime(inputTime, speed, offset/fps);
// this should be done only once per file
if (mFileInitialized == false)
{
mFileInitialized = true;
//Get list of input filenames
MFnDependencyNode depNode(thisMObject());
MPlug layerFilesPlug = depNode.findPlug(mAbcLayerFileNamesAttr, true);
MFnStringArrayData fnSAD( layerFilesPlug.asMObject() );
MStringArray storedFilenames = fnSAD.array();
//Legacy support for single-filename input
if( storedFilenames.length() == 0 )
{
MDataHandle dataHandle = dataBlock.inputValue(mAbcFileNameAttr);
storedFilenames.append( dataHandle.asString() );
}
std::vector<std::string> abcFilenames;
MStringArray filenames;
bool filenameChanged = false;
// FIXME MAYA-92896: remove path resolution when Maya will be able to deal with arrays of filepaths
for(unsigned int i = 0; i < storedFilenames.length(); i++)
{
MFileObject fileObject;
fileObject.setResolveMethod(MFileObject::MFileResolveMethod::kInputFile);
fileObject.setRawFullName(storedFilenames[i]);
MString fileName = fileObject.resolvedFullName();
// FIXME The current directory for the project file is not available (again, MAYA-92896),
// take the one from mAbcFileNameAttr. This cannot handle relative paths to the project.
if (fileName.length() == 0) {
MFileObject abc_file_path;
abc_file_path.setRawFullName (dataBlock.inputValue(mAbcFileNameAttr).asString());
MFileObject abc_file_name;
abc_file_name.setRawFullName (storedFilenames[i]);
fileName = abc_file_path.rawPath() + abc_file_name.rawName();
}
filenameChanged = filenameChanged || (fileObject.resolvedFullName() != storedFilenames[i]);
filenames.append( fileName );
abcFilenames.push_back( fileName.asChar() );
}
if (filenameChanged)
{
MObject newData = fnSAD.create(filenames, nullptr);
layerFilesPlug.setValue(newData);
}
Alembic::Abc::IArchive archive;
Alembic::AbcCoreFactory::IFactory factory;
factory.setPolicy(Alembic::Abc::ErrorHandler::kQuietNoopPolicy);
archive = factory.getArchive( abcFilenames );
if (!archive.valid())
{
MString theError = "Error opening these alembic files: ";
const unsigned int numFilenames = storedFilenames.length();
for( unsigned int i = 0; i < numFilenames; i++ )
{
theError += storedFilenames[ i ];
if( i != (numFilenames - 1) )
{
theError += ", ";
}
}
printError(theError);
}
// initialize some flags for plug update
mSubDInitialized = false;
mPolyInitialized = false;
// When an alembic cache will be imported at the first time using
// AbcImport, we need to set mIncludeFilterAttr (filterHandle) to be
// mIncludeFilterString for later use. When we save a maya scene(.ma)
// mIncludeFilterAttr will be saved. Then when we load the saved
// .ma file, mIncludeFilterString will be set to be mIncludeFilterAttr.
MDataHandle includeFilterHandle =
dataBlock.inputValue(mIncludeFilterAttr, &status);
MString& includeFilterString = includeFilterHandle.asString();
if (mIncludeFilterString.length() > 0)
{
includeFilterHandle.set(mIncludeFilterString);
dataBlock.setClean(mIncludeFilterAttr);
}
else if (includeFilterString.length() > 0)
{
mIncludeFilterString = includeFilterString;
}
MDataHandle excludeFilterHandle =
dataBlock.inputValue(mExcludeFilterAttr, &status);
MString& excludeFilterString = excludeFilterHandle.asString();
if (mExcludeFilterString.length() > 0)
{
excludeFilterHandle.set(mExcludeFilterString);
dataBlock.setClean(mExcludeFilterAttr);
}
else if (excludeFilterString.length() > 0)
{
mExcludeFilterString = excludeFilterString;
}
MFnDependencyNode dep(thisMObject());
MPlug allSetsPlug = dep.findPlug("allColorSets", true);
CreateSceneVisitor visitor(inputTime, !allSetsPlug.isNull(),
MObject::kNullObj, CreateSceneVisitor::NONE, "",
mIncludeFilterString, mExcludeFilterString);
visitor.walk(archive);
if (visitor.hasSampledData())
{
// information retrieved from the hierarchy traversal
// and given to AlembicNode to provide update
visitor.getData(mData);
mData.getFrameRange(mSequenceStartTime, mSequenceEndTime);
MDataHandle startFrameHandle = dataBlock.inputValue(
mStartFrameAttr, &status);
startFrameHandle.set(mSequenceStartTime*fps);
MDataHandle endFrameHandle = dataBlock.inputValue(
mEndFrameAttr, &status);
endFrameHandle.set(mSequenceEndTime*fps);
}
}
// Retime
MDataHandle cycleHandle = dataBlock.inputValue(mCycleTypeAttr, &status);
short playType = cycleHandle.asShort();
inputTime = computeRetime(inputTime, mSequenceStartTime, mSequenceEndTime,
playType);
clamp<double>(mSequenceStartTime, mSequenceEndTime, inputTime);
// update only when the time lapse is big enough
if (fabs(inputTime - mCurTime) > 0.00001)
{
mOutRead = std::vector<bool>(mOutRead.size(), false);
mCurTime = inputTime;
}
if (plug == mOutPropArrayAttr)
{
if (mOutRead[0])
{
dataBlock.setClean(plug);
return MS::kSuccess;
}
mOutRead[0] = true;
unsigned int propSize =
static_cast<unsigned int>(mData.mPropList.size());
if (propSize > 0)
{
MArrayDataHandle outArrayHandle = dataBlock.outputValue(
mOutPropArrayAttr, &status);
unsigned int outHandleIndex = 0;
MDataHandle outHandle;
// for all of the nodes with sampled attributes
for (unsigned int i = 0; i < propSize; i++)
{
// only use the handle if it matches the index.
// The index wont line up in the sparse case so we
// can just skip that element.
if (outArrayHandle.elementIndex() == outHandleIndex++)
{
outHandle = outArrayHandle.outputValue();
}
else
{
continue;
}
if (mData.mPropList[i].mArray.valid())
{
readProp(mCurTime, mData.mPropList[i].mArray, outHandle);
}
else if (mData.mPropList[i].mScalar.valid())
{
// for visibility only
if (mData.mPropList[i].mScalar.getName() ==
Alembic::AbcGeom::kVisibilityPropertyName)
{
Alembic::Util::int8_t visVal = 1;
mData.mPropList[i].mScalar.get(&visVal,
Alembic::Abc::ISampleSelector(mCurTime,
Alembic::Abc::ISampleSelector::kNearIndex ));
outHandle.setGenericBool(visVal != 0, false);
}
else
{
// for all scalar props
readProp(mCurTime, mData.mPropList[i].mScalar, outHandle);
}
}
outArrayHandle.next();
}
outArrayHandle.setAllClean();
}
}
else if (plug == mOutTransOpArrayAttr )
{
if (mOutRead[1])
{
dataBlock.setClean(plug);
return MS::kSuccess;
}
mOutRead[1] = true;
unsigned int xformSize =
static_cast<unsigned int>(mData.mXformList.size());
if (xformSize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutTransOpArrayAttr, &status);
MPlug arrayPlug(thisMObject(), mOutTransOpArrayAttr);
MDataHandle outHandle;
unsigned int outHandleIndex = 0;
for (unsigned int i = 0; i < xformSize; i++)
{
std::vector<double> sampleList;
if (mData.mIsComplexXform[i])
{
readComplex(mCurTime, mData.mXformList[i], sampleList);
}
else
{
Alembic::AbcGeom::XformSample samp;
read(mCurTime, mData.mXformList[i], sampleList, samp);
}
unsigned int sampleSize = (unsigned int)sampleList.size();
for (unsigned int j = 0; j < sampleSize; j++)
{
// only use the handle if it matches the index.
// The index wont line up in the sparse case so we
// can just skip that element.
if (outArrayHandle.elementIndex() == outHandleIndex++)
{
outHandle = outArrayHandle.outputValue(&status);
}
else
continue;
outArrayHandle.next();
outHandle.set(sampleList[j]);
}
}
outArrayHandle.setAllClean();
}
}
else if (plug == mOutLocatorPosScaleArrayAttr )
{
if (mOutRead[8])
{
dataBlock.setClean(plug);
return MS::kSuccess;
}
mOutRead[8] = true;
unsigned int locSize =
static_cast<unsigned int>(mData.mLocList.size());
if (locSize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutLocatorPosScaleArrayAttr, &status);
MPlug arrayPlug(thisMObject(), mOutLocatorPosScaleArrayAttr);
MDataHandle outHandle;
unsigned int outHandleIndex = 0;
for (unsigned int i = 0; i < locSize; i++)
{
std::vector< double > sampleList;
read(mCurTime, mData.mLocList[i], sampleList);
unsigned int sampleSize = (unsigned int)sampleList.size();
for (unsigned int j = 0; j < sampleSize; j++)
{
// only use the handle if it matches the index.
// The index wont line up in the sparse case so we
// can just skip that element.
if (outArrayHandle.elementIndex() == outHandleIndex++)
{
outHandle = outArrayHandle.outputValue(&status);
}
else
continue;
outArrayHandle.next();
outHandle.set(sampleList[j]);
}
}
outArrayHandle.setAllClean();
}
}
else if (plug == mOutSubDArrayAttr)
{
if (mOutRead[2])
{
// Reference the output to let EM know we are the writer
// of the data. EM sets the output to holder and causes
// race condition when evaluating fan-out destinations.
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutSubDArrayAttr, &status);
const unsigned int elementCount = outArrayHandle.elementCount();
for (unsigned int j = 0; j < elementCount; j++)
{
outArrayHandle.outputValue().data();
outArrayHandle.next();
}
outArrayHandle.setAllClean();
return MS::kSuccess;
}
mOutRead[2] = true;
unsigned int subDSize =
static_cast<unsigned int>(mData.mSubDList.size());
if (subDSize > 0)
{
MArrayDataHandle outArrayHandle = dataBlock.outputValue(
mOutSubDArrayAttr, &status);
MDataHandle outHandle;
for (unsigned int j = 0; j < subDSize; j++)
{
// these elements can be sparse if they have been deleted
if (outArrayHandle.elementIndex() != j)
{
continue;
}
outHandle = outArrayHandle.outputValue(&status);
outArrayHandle.next();
MObject obj = outHandle.data();
if (obj.hasFn(MFn::kMesh))
{
MFnMesh fnMesh(obj);
readSubD(mCurTime, fnMesh, obj, mData.mSubDList[j],
mSubDInitialized);
outHandle.set(obj);
}
}
mSubDInitialized = true;
outArrayHandle.setAllClean();
}
// for the case where we don't have any nodes, we want to make sure
// to push out empty meshes on our connections, this can happen if
// the input file was offlined, currently we only need to do this for
// meshes as Nurbs, curves, and the other channels don't crash Maya
else
{
MArrayDataHandle outArrayHandle = dataBlock.outputValue(
mOutSubDArrayAttr, &status);
if (outArrayHandle.elementCount() > 0)
{
do
{
MDataHandle outHandle = outArrayHandle.outputValue();
MObject obj = outHandle.data();
if (obj.hasFn(MFn::kMesh))
{
MFloatPointArray emptyVerts;
MIntArray emptyCounts;
MIntArray emptyConnects;
MFnMesh emptyMesh;
emptyMesh.create(0, 0, emptyVerts, emptyCounts,
emptyConnects, obj);
outHandle.set(obj);
}
}
while (outArrayHandle.next() == MS::kSuccess);
}
mSubDInitialized = true;
outArrayHandle.setAllClean();
}
}
else if (plug == mOutPolyArrayAttr)
{
if (mOutRead[3])
{
// Reference the output to let EM know we are the writer
// of the data. EM sets the output to holder and causes
// race condition when evaluating fan-out destinations.
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutPolyArrayAttr, &status);
const unsigned int elementCount = outArrayHandle.elementCount();
for (unsigned int j = 0; j < elementCount; j++)
{
outArrayHandle.outputValue().data();
outArrayHandle.next();
}
outArrayHandle.setAllClean();
return MS::kSuccess;
}
mOutRead[3] = true;
unsigned int polySize =
static_cast<unsigned int>(mData.mPolyMeshList.size());
if (polySize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutPolyArrayAttr, &status);
MDataHandle outHandle;
for (unsigned int j = 0; j < polySize; j++)
{
// these elements can be sparse if they have been deleted
if (outArrayHandle.elementIndex() != j)
{
continue;
}
outHandle = outArrayHandle.outputValue(&status);
outArrayHandle.next();
MObject obj = outHandle.data();
if (obj.hasFn(MFn::kMesh))
{
MFnMesh fnMesh(obj);
readPoly(mCurTime, fnMesh, obj, mData.mPolyMeshList[j],
mPolyInitialized);
outHandle.set(obj);
}
}
mPolyInitialized = true;
outArrayHandle.setAllClean();
}
// for the case where we don't have any nodes, we want to make sure
// to push out empty meshes on our connections, this can happen if
// the input file was offlined, currently we only need to do this for
// meshes as Nurbs, curves, and the other channels don't crash Maya
else
{
MArrayDataHandle outArrayHandle = dataBlock.outputValue(
mOutPolyArrayAttr, &status);
if (outArrayHandle.elementCount() > 0)
{
do
{
MDataHandle outHandle = outArrayHandle.outputValue(&status);
MObject obj = outHandle.data();
if (obj.hasFn(MFn::kMesh))
{
MFloatPointArray emptyVerts;
MIntArray emptyCounts;
MIntArray emptyConnects;
MFnMesh emptyMesh;
emptyMesh.create(0, 0, emptyVerts, emptyCounts,
emptyConnects, obj);
outHandle.set(obj);
}
}
while (outArrayHandle.next() == MS::kSuccess);
}
mPolyInitialized = true;
outArrayHandle.setAllClean();
}
}
else if (plug == mOutCameraArrayAttr)
{
if (mOutRead[4])
{
dataBlock.setClean(plug);
return MS::kSuccess;
}
mOutRead[4] = true;
unsigned int cameraSize =
static_cast<unsigned int>(mData.mCameraList.size());
if (cameraSize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutCameraArrayAttr, &status);
MPlug arrayPlug(thisMObject(), mOutCameraArrayAttr);
double angleConversion = 1.0;
switch (MAngle::uiUnit())
{
angleConversion = 0.017453292519943295;
break;
angleConversion = 60.0;
break;
angleConversion = 3600.0;
break;
default:
break;
}
MDataHandle outHandle;
unsigned int index = 0;
for (unsigned int cameraIndex = 0; cameraIndex < cameraSize;
cameraIndex++)
{
Alembic::AbcGeom::ICamera & cam =
mData.mCameraList[cameraIndex];
std::vector<double> array;
read(mCurTime, cam, array);
for (unsigned int dataIndex = 0; dataIndex < array.size();
dataIndex++, index++)
{
// skip over sparse elements
if (index != outArrayHandle.elementIndex())
{
continue;
}
outHandle = outArrayHandle.outputValue(&status);
outArrayHandle.next();
// not shutter angle index, so not an angle
if (dataIndex != 11)
{
outHandle.set(array[dataIndex]);
}
else
{
outHandle.set(array[dataIndex] * angleConversion);
}
} // for the per camera data handles
} // for each camera
outArrayHandle.setAllClean();
}
}
else if (plug == mOutNurbsSurfaceArrayAttr)
{
if (mOutRead[5])
{
// Reference the output to let EM know we are the writer
// of the data. EM sets the output to holder and causes
// race condition when evaluating fan-out destinations.
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutNurbsSurfaceArrayAttr, &status);
const unsigned int elementCount = outArrayHandle.elementCount();
for (unsigned int j = 0; j < elementCount; j++)
{
outArrayHandle.outputValue().data();
outArrayHandle.next();
}
outArrayHandle.setAllClean();
return MS::kSuccess;
}
mOutRead[5] = true;
unsigned int nSurfaceSize =
static_cast<unsigned int>(mData.mNurbsList.size());
if (nSurfaceSize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutNurbsSurfaceArrayAttr, &status);
MDataHandle outHandle;
for (unsigned int j = 0; j < nSurfaceSize; j++)
{
// these elements can be sparse if they have been deleted
if (outArrayHandle.elementIndex() != j)
continue;
outHandle = outArrayHandle.outputValue(&status);
outArrayHandle.next();
MObject obj = outHandle.data();
{
readNurbs(mCurTime, mData.mNurbsList[j], obj);
outHandle.set(obj);
}
}
outArrayHandle.setAllClean();
}
}
else if (plug == mOutNurbsCurveGrpArrayAttr)
{
if (mOutRead[6])
{
// Reference the output to let EM know we are the writer
// of the data. EM sets the output to holder and causes
// race condition when evaluating fan-out destinations.
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutNurbsCurveGrpArrayAttr, &status);
const unsigned int elementCount = outArrayHandle.elementCount();
for (unsigned int j = 0; j < elementCount; j++)
{
outArrayHandle.outputValue().data();
outArrayHandle.next();
}
outArrayHandle.setAllClean();
return MS::kSuccess;
}
mOutRead[6] = true;
unsigned int nCurveGrpSize =
static_cast<unsigned int>(mData.mCurvesList.size());
if (nCurveGrpSize > 0)
{
MArrayDataHandle outArrayHandle =
dataBlock.outputValue(mOutNurbsCurveGrpArrayAttr, &status);
MDataHandle outHandle;
std::vector<MObject> curvesObj;
for (unsigned int i = 0; i < nCurveGrpSize; ++i)
{
readCurves(mCurTime, mData.mCurvesList[i],
mData.mNumCurves[i], curvesObj);
}
std::size_t numChild = curvesObj.size();
// not the best way to do this
// only reading bunches of curves based on the connections would be
// more efficient when there is a bunch of broken connections
for (unsigned int i = 0; i < numChild; i++)
{
if (outArrayHandle.elementIndex() != i)
{
continue;
}
outHandle = outArrayHandle.outputValue(&status);
outArrayHandle.next();
status = outHandle.set(curvesObj[i]);
}
outArrayHandle.setAllClean();
}
}
else
{
return MS::kUnknownParameter;
}
dataBlock.setClean(plug);
return status;
}
// make the far clipping plane passive, as packages like C4D don't have a
// far clipping plane and when it doesn't exist it isn't easy to guess what
// the correct value should be
bool AlembicNode::isPassiveOutput(const MPlug & plug) const
{
MStatus status = MS::kSuccess;
MPlug arrayPlug(thisMObject(), mOutCameraArrayAttr);
unsigned int farClipPlaneIndex = 11;
MPlug farClipPlanePlug =
arrayPlug.elementByPhysicalIndex(farClipPlaneIndex, &status);
if (status == MS::kSuccess && plug == farClipPlanePlug)
return true;
return MPxNode::isPassiveOutput( plug );
}
AlembicNode::SchedulingType AlembicNode::schedulingType()const
{
// Globally serialize this node because the compute method is not thread safe
return kGloballySerialize;
}
// returns the list of files to archive.
MStringArray AlembicNode::getFilesToArchive(
bool /* shortName */,
bool unresolvedName,
bool /* markCouldBeImageSequence */) const
{
MStringArray files;
MStatus status = MS::kSuccess;
MPlug layerFilenamesPlug(thisMObject(), mAbcLayerFileNamesAttr);
MFnStringArrayData fnSAD( layerFilenamesPlug.asMObject() );
MStringArray layerFilenames = fnSAD.array();
for( unsigned int i = 0; i < layerFilenames.length(); i++ )
{
MString fileName = layerFilenames[i];
if (status == MS::kSuccess && fileName.length() > 0)
{
if(unresolvedName)
{
files.append(fileName);
}
else
{
//unresolvedName is false, resolve the path via MFileObject.
MFileObject fileObject;
// FIXME MAYA-92896: remove path resolution when Maya will be able to deal with arrays of filepaths
fileObject.setResolveMethod(MFileObject::MFileResolveMethod::kInputFile);
fileObject.setRawFullName(fileName);
files.append(fileObject.resolvedFullName());
}
}
}
return files;
}
void AlembicNode::getExternalContent(MExternalContentInfoTable& table) const
{
addExternalContentForFileAttr(table, mAbcLayerFileNamesAttr);
}
void AlembicNode::setExternalContent(const MExternalContentLocationTable& table)
{
setExternalContentForFileAttr(mAbcLayerFileNamesAttr, table);
}