Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

651 lines
23 KiB
C++

//-*****************************************************************************
//
// Copyright (c) 2016,
// 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 <Alembic/Abc/All.h>
#include <Alembic/AbcCoreFactory/All.h>
#include <Alembic/AbcCoreOgawa/All.h>
#include <Alembic/AbcCoreLayer/Util.h>
#include <Alembic/AbcGeom/All.h>
// util which compares the property headers and returns if they are the same
bool headerCmp(const Alembic::Abc::PropertyHeader * iHeaderA,
const Alembic::Abc::PropertyHeader * iHeaderB)
{
// we've already done the name check
if (iHeaderA->getPropertyType() != iHeaderB->getPropertyType() &&
iHeaderA->getMetaData().serialize() != iHeaderB->getMetaData().serialize())
{
return false;
}
// nothing more to check for compounds
if (iHeaderA->isCompound())
{
return true;
}
return (iHeaderA->getDataType() == iHeaderB->getDataType()) &&
(*(iHeaderA->getTimeSampling()) == *(iHeaderB->getTimeSampling()));
}
// util used in verbose mode to get the full property name
void fillFullPropName(Alembic::Abc::ICompoundProperty iProp,
std::string & ioName)
{
while(iProp.getParent().valid())
{
if (ioName.empty())
{
ioName = iProp.getName();
}
else
{
ioName = iProp.getName() + "/" + ioName;
}
iProp = iProp.getParent();
}
}
// copies the entire array property to iParent
void copyArrayProp(Alembic::Abc::IArrayProperty & iProp,
Alembic::Abc::OCompoundProperty & iParent, bool iVerbose)
{
if (iVerbose)
{
std::string propName = iProp.getName();
fillFullPropName(iProp.getParent(), propName);
printf ("%s array prop diff on %s\n",
iProp.getObject().getFullName().c_str(),
propName.c_str());
}
Alembic::Abc::OArrayProperty outProp(iParent, iProp.getName(),
iProp.getDataType(), iProp.getMetaData(), iProp.getTimeSampling());
std::size_t numSamples = iProp.getNumSamples();
for (std::size_t i = 0; i < numSamples; ++i)
{
Alembic::Abc::ArraySamplePtr samp;
Alembic::Abc::ISampleSelector sel((Alembic::Abc::index_t) i);
iProp.get(samp, sel);
outProp.set(*samp);
}
}
// copies the entire static array property to iParent
void copyStaticProp(Alembic::Abc::IScalarProperty & iProp,
Alembic::Abc::OCompoundProperty & iParent, bool iVerbose)
{
if (iVerbose)
{
std::string propName = iProp.getName();
fillFullPropName(iProp.getParent(), propName);
printf ("%s scalar prop diff on %s\n",
iProp.getObject().getFullName().c_str(),
propName.c_str());
}
Alembic::Abc::OScalarProperty outProp(iParent, iProp.getName(),
iProp.getDataType(), iProp.getMetaData(), iProp.getTimeSampling());
std::size_t numSamples = iProp.getNumSamples();
std::vector<std::string> sampStrVec;
std::vector<std::wstring> sampWStrVec;
Alembic::Util::PlainOldDataType ptype = iProp.getDataType().getPod();
if (ptype == Alembic::Util::kStringPOD)
{
sampStrVec.resize(iProp.getDataType().getExtent());
}
else if (ptype == Alembic::Util::kWstringPOD)
{
sampWStrVec.resize(iProp.getDataType().getExtent());
}
char samp[4096];
for (std::size_t i = 0; i < numSamples; ++i)
{
Alembic::Abc::ISampleSelector sel((Alembic::Abc::index_t) i);
if (ptype == Alembic::Util::kStringPOD)
{
iProp.get(&sampStrVec.front(), sel);
outProp.set(&sampStrVec.front());
}
else if (ptype == Alembic::Abc::kWstringPOD)
{
iProp.get(&sampWStrVec.front(), sel);
outProp.set(&sampWStrVec.front());
}
else
{
iProp.get(samp, sel);
outProp.set(samp);
}
}
}
// copies all of the compound hierachy to iWrite
void copyProps(Alembic::Abc::ICompoundProperty & iRead,
Alembic::Abc::OCompoundProperty & iWrite, bool iVerbose)
{
if (iVerbose)
{
std::string propName;
fillFullPropName(iRead, propName);
printf ("%s compound prop diff on %s\n",
iRead.getObject().getFullName().c_str(),
propName.c_str());
}
std::size_t numChildren = iRead.getNumProperties();
for (std::size_t i = 0; i < numChildren; ++i)
{
Alembic::Abc::PropertyHeader header = iRead.getPropertyHeader(i);
if (header.isArray())
{
Alembic::Abc::IArrayProperty inProp(iRead, header.getName());
copyArrayProp(inProp, iWrite, false);
}
else if (header.isScalar())
{
Alembic::Abc::IScalarProperty inProp(iRead, header.getName());
copyStaticProp(inProp, iWrite, false);
}
else if (header.isCompound())
{
Alembic::Abc::OCompoundProperty outProp(iWrite,
header.getName(), header.getMetaData());
Alembic::Abc::ICompoundProperty inProp(iRead, header.getName());
copyProps(inProp, outProp, false);
}
}
}
// copies object hierarchy and its property
void copyObject(Alembic::Abc::IObject & iIn, Alembic::Abc::OObject & iOut)
{
std::size_t numChildren = iIn.getNumChildren();
Alembic::Abc::ICompoundProperty inProps = iIn.getProperties();
Alembic::Abc::OCompoundProperty outProps = iOut.getProperties();
copyProps(inProps, outProps, false);
for (std::size_t i = 0; i < numChildren; ++i)
{
Alembic::Abc::IObject childIn(iIn.getChild(i));
Alembic::Abc::OObject childOut(iOut, childIn.getName(),
childIn.getMetaData());
copyObject(childIn, childOut);
}
}
// class which walks the hierarchy, writes out hiearchy and properties
// that are different in iInFileB from iInFileA, and prunes hierarchy and
// properties which are in iInFileA but not iInFileB.
class DiffWalker
{
public:
DiffWalker(const char * iInFileA, const char * iInFileB,
const char * iOutFile, bool iVerbose)
{
m_verbose = iVerbose;
m_inFileA = iInFileA;
m_inFileB = iInFileB;
m_outFile = iOutFile;
}
int walk()
{
Alembic::AbcCoreFactory::IFactory factory;
Alembic::AbcCoreFactory::IFactory::CoreType coreType;
Alembic::Abc::IArchive arc1 = factory.getArchive(m_inFileA, coreType);
if (coreType != Alembic::AbcCoreFactory::IFactory::kOgawa)
{
printf("Error: %s is not a valid Alembic Ogawa file.\n",
m_inFileA.c_str());
return 1;
}
Alembic::Abc::IArchive arc2 = factory.getArchive(m_inFileB, coreType);
if (coreType != Alembic::AbcCoreFactory::IFactory::kOgawa)
{
printf("Error: %s is not a valid Alembic Ogawa file.\n",
m_inFileB.c_str());
return 1;
}
Alembic::Abc::IObject topA = arc1.getTop();
Alembic::Abc::IObject topB = arc2.getTop();
walk(topA, topB);
if (m_outStack.empty())
{
printf("No differences detected, %s was not written.\n",
m_outFile.c_str());
}
return 0;
}
private:
void walkProps(Alembic::Abc::ICompoundProperty & iPropA,
Alembic::Abc::ICompoundProperty & iPropB,
Alembic::Abc::OCompoundProperty & oProp)
{
for (size_t i = 0; i < iPropA.getNumProperties(); ++i)
{
Alembic::Abc::PropertyHeader childAHeader =
iPropA.getPropertyHeader(i);
const Alembic::Abc::PropertyHeader * childBHeader =
iPropB.getPropertyHeader(childAHeader.getName());
// prune this property
if (!childBHeader)
{
if (m_verbose)
{
std::string propName = childAHeader.getName();
fillFullPropName(iPropA, propName);
printf("%s pruning prop: %s.\n",
iPropA.getObject().getFullName().c_str(),
propName.c_str());
}
Alembic::Abc::MetaData md;
Alembic::AbcCoreLayer::SetPrune(md, true);
Alembic::Abc::OCompoundProperty pruneProp(oProp,
childAHeader.getName(), md);
continue;
}
if (!headerCmp(&childAHeader, childBHeader))
{
if (childBHeader->isArray())
{
Alembic::Abc::IArrayProperty inProp(iPropB,
childBHeader->getName());
copyArrayProp(inProp, oProp, m_verbose);
}
else if (childBHeader->isScalar())
{
Alembic::Abc::IScalarProperty inProp(iPropB,
childBHeader->getName());
copyStaticProp(inProp, oProp, m_verbose);
}
else if (childBHeader->isCompound())
{
Alembic::Abc::OCompoundProperty outProp(oProp,
childBHeader->getName(), childBHeader->getMetaData());
Alembic::Abc::ICompoundProperty inProp(iPropB,
childBHeader->getName());
copyProps(inProp, outProp, m_verbose);
}
continue;
} // if childAHeader != *childBHeader
// headers are equal do more checks
if (childAHeader.isCompound())
{
Alembic::Abc::ICompoundProperty childA(iPropA,
childAHeader.getName());
Alembic::Abc::ICompoundProperty childB(iPropB,
childAHeader.getName());
// dont copy metadata here, since it was the same
Alembic::Abc::OCompoundProperty outProp(oProp,
childAHeader.getName());
walkProps(childA, childB, outProp);
}
else if (childAHeader.isScalar())
{
Alembic::Abc::IScalarProperty childA(iPropA,
childAHeader.getName());
Alembic::Abc::IScalarProperty childB(iPropB,
childAHeader.getName());
if (childA.getNumSamples() != childB.getNumSamples())
{
copyStaticProp(childB, oProp, m_verbose);
continue;
}
std::vector<std::string> sampStrVecA, sampStrVecB;
std::vector<std::wstring> sampWStrVecA, sampWStrVecB;
char sampA[4096];
char sampB[4096];
std::size_t numBytes = 0;
Alembic::Util::PlainOldDataType ptype =
childB.getDataType().getPod();
if (ptype == Alembic::Util::kStringPOD)
{
sampStrVecA.resize(childB.getDataType().getExtent());
sampStrVecB.resize(childB.getDataType().getExtent());
}
else if (ptype == Alembic::Util::kWstringPOD)
{
sampWStrVecA.resize(childB.getDataType().getExtent());
sampWStrVecB.resize(childB.getDataType().getExtent());
}
else
{
memset(sampA, 0, 4096);
memset(sampB, 0, 4096);
numBytes = childB.getDataType().getNumBytes();
}
Alembic::Abc::index_t j;
Alembic::Abc::index_t numSamples = childB.getNumSamples();
for (j = 0; j < numSamples; ++j)
{
if (ptype == Alembic::Util::kStringPOD)
{
childA.get(&sampStrVecA.front(), j);
childB.get(&sampStrVecB.front(), j);
if (sampStrVecA != sampStrVecB)
{
copyStaticProp(childB, oProp, m_verbose);
break;
}
}
else if (ptype == Alembic::Util::kWstringPOD)
{
childA.get(&sampWStrVecA.front(), j);
childB.get(&sampWStrVecB.front(), j);
if (sampWStrVecA != sampWStrVecB)
{
copyStaticProp(childB, oProp, m_verbose);
break;
}
}
else
{
childA.get(sampA, j);
childB.get(sampB, j);
if (memcmp(sampA, sampB, numBytes) != 0)
{
copyStaticProp(childB, oProp, m_verbose);
break;
}
}
}
}
else if (childAHeader.isArray())
{
Alembic::Abc::IArrayProperty childA(iPropA,
childAHeader.getName());
Alembic::Abc::IArrayProperty childB(iPropB,
childAHeader.getName());
if (childA.getNumSamples() != childB.getNumSamples())
{
copyArrayProp(childB, oProp, m_verbose);
continue;
}
Alembic::Abc::index_t j;
Alembic::Abc::index_t numSamples = childB.getNumSamples();
for (j = 0; j < numSamples; ++j)
{
Alembic::Abc::ArraySampleKey keyA, keyB;
childA.getKey(keyA, j);
childB.getKey(keyB, j);
if (keyA != keyB)
{
copyArrayProp(childB, oProp, m_verbose);
break;
}
}
}
}
}
void walkProps(Alembic::Abc::ICompoundProperty & iPropA,
Alembic::Abc::ICompoundProperty & iPropB)
{
if (Alembic::AbcGeom::IXform::matches(iPropB.getMetaData()))
{
fillStack(iPropB.getObject().getParent().getFullName());
Alembic::Abc::MetaData md = iPropB.getMetaData();
Alembic::AbcCoreLayer::SetReplace(md, true);
m_outStack.push_back(Alembic::Abc::OObject(m_outStack.back(),
iPropB.getObject().getName(), md));
Alembic::Abc::OCompoundProperty oprop =
m_outStack.back().getProperties();
copyProps(iPropB, oprop, m_verbose);
// This does not handle arbGeomParam and user props that are in
// iPropA but not iPropB (normally would prune)
return;
}
else if (iPropA.getMetaData().serialize() !=
iPropB.getMetaData().serialize() &&
iPropB.getObject().getParent().valid())
{
fillStack(iPropB.getObject().getParent().getFullName());
m_outStack.push_back(Alembic::Abc::OObject(m_outStack.back(),
iPropB.getObject().getName(), iPropB.getMetaData()));
}
else
{
fillStack(iPropB.getObject().getFullName());
}
Alembic::Abc::OCompoundProperty prop =
m_outStack.back().getProperties();
walkProps(iPropA, iPropB, prop);
}
void walk(Alembic::Abc::IObject & iObjA, Alembic::Abc::IObject & iObjB)
{
Alembic::Util::Digest hashPropA, hashPropB;
iObjA.getPropertiesHash(hashPropA);
iObjB.getPropertiesHash(hashPropB);
// lets check our properties
if (hashPropA != hashPropB)
{
Alembic::Abc::ICompoundProperty propA = iObjA.getProperties();
Alembic::Abc::ICompoundProperty propB = iObjB.getProperties();
walkProps(propA, propB);
}
Alembic::Util::Digest hashChildrenA, hashChildrenB;
iObjA.getChildrenHash(hashChildrenA);
iObjB.getChildrenHash(hashChildrenB);
if (hashChildrenA == hashChildrenB)
{
if (m_outStack.size() > 1 &&
m_outStack.back().getFullName() == iObjA.getFullName())
{
m_outStack.pop_back();
}
return;
}
for (size_t i = 0; i < iObjA.getNumChildren(); ++i)
{
Alembic::Abc::ObjectHeader headerA = iObjA.getChildHeader(i);
const Alembic::Abc::ObjectHeader * headerB =
iObjB.getChildHeader(headerA.getName());
// prune
if (!headerB)
{
fillStack(iObjA.getParent().getFullName());
Alembic::Abc::MetaData md;
Alembic::AbcCoreLayer::SetPrune(md, true);
Alembic::Abc::OObject pruneObj(m_outStack.back(),
headerA.getName(), md);
if (m_verbose)
{
printf("%s pruned.\n", headerA.getFullName().c_str());
}
}
else
{
Alembic::Abc::IObject childA(iObjA, headerA.getName());
Alembic::Abc::IObject childB(iObjB, headerA.getName());
walk(childA, childB);
}
}
// check to see if B introduced any new children
for (size_t i = 0; i < iObjB.getNumChildren(); ++i)
{
Alembic::Abc::ObjectHeader headerB = iObjB.getChildHeader(i);
const Alembic::Abc::ObjectHeader * headerA =
iObjA.getChildHeader(headerB.getName());
if (headerA == NULL)
{
Alembic::Abc::IObject childB(iObjB, headerB.getName());
fillStack(iObjB.getParent().getFullName());
Alembic::Abc::OObject out(m_outStack.back(), childB.getName(),
childB.getMetaData());
if (m_verbose)
{
printf("%s copying hierarchy.\n",
childB.getFullName().c_str());
}
copyObject(childB, out);
}
}
if (m_outStack.size() > 1 &&
m_outStack.back().getFullName() == iObjA.getFullName())
{
m_outStack.pop_back();
}
}
// fills in m_outStack with empty OObjects based on where we are in the
// hierarchy
void fillStack(const std::string & iFullName)
{
if (m_outStack.empty())
{
std::string appStr = "AbcDiff";
std::string userDesc = m_inFileA + " " + m_inFileB;
Alembic::Abc::OArchive arc = Alembic::Abc::CreateArchiveWithInfo(
Alembic::AbcCoreOgawa::WriteArchive(),
m_outFile, appStr, userDesc);
m_outStack.push_back(arc.getTop());
}
// and we are done
if (iFullName.empty() || iFullName == m_outStack.back().getFullName())
{
return;
}
// only create what we need in the stack
size_t lastIdx = m_outStack.back().getFullName().size();
if (lastIdx > 1)
{
lastIdx ++;
}
size_t curIdx = std::string::npos;
do
{
curIdx = iFullName.find('/', lastIdx);
m_outStack.push_back(Alembic::Abc::OObject(m_outStack.back(),
iFullName.substr(lastIdx, curIdx - lastIdx)));
if (curIdx != std::string::npos)
{
lastIdx = curIdx + 1;
}
}
while (curIdx != std::string::npos);
}
bool m_verbose;
std::string m_inFileA;
std::string m_inFileB;
std::string m_outFile;
std::vector<Alembic::Abc::OObject> m_outStack;
};
void displayHelp()
{
printf("Usage:\n");
printf("abcdiff [-v] inputFilename1 inputFilename2 outputFilename\n\n");
printf("Used to compare two Alembic files and write an Alembic file that contains the differences.\n\n");
printf("inputFilename1 is the \"base\" file. If there a difference in the object hierarchy of inputFilename2, "
"that different object will be added to the diff, along with a skeleton of its parent hierarchy. If an "
"object exists in both inputFilename1 and inputFilename2 but the properties are different, an object will "
"be added to the diff that contains only the differing properties.\n\n");
printf("Parameters:\n");
printf("-v\t\tOPTIONAL\tVerbose mode prints more detailed information about the diff process\n");
printf("inputFilename1\tREQUIRED\tThe first Alembic file to compare\n");
printf("inputFilename2\tREQUIRED\tThe second Alembic file to compare\n");
printf("outputFilename\tREQUIRED\tThe filename to write out the Alembic diff file\n");
}
int main(int argc, char *argv[])
{
if ((argc != 4 && argc != 5)|| (argc == 5 && std::string("-v") != argv[1]))
{
displayHelp();
return 1;
}
bool verbose = false;
// we've already checked for -v above
if (argc == 5)
{
verbose = true;
}
DiffWalker dw(argv[argc-3], argv[argc-2], argv[argc-1], verbose);
dw.walk();
}