Last active
June 4, 2025 18:03
-
-
Save tbttfox/2b0715ac49820b8aa2686b78cf47fbab to your computer and use it in GitHub Desktop.
A quick script to print the hierarchy of an alembic file and its properties.Sorry, no nice command line interface. You've gotta edit it yourself
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from alembic.Abc import IArchive | |
from alembic.AbcGeom import ( | |
IXform, | |
IPolyMesh, | |
ICamera, | |
ICurves, | |
ILight, | |
INuPatch, | |
IPoints, | |
ISubD, | |
IFaceSet, | |
) | |
PREFIXES = [[" \u2560 ", " \u2551 "], [" \u255a ", " "]] | |
PROP_PREFIXES = [[" \u251c ", " \u2502 "], [" \u2514 ", " "]] | |
PRINT_ALL = object() | |
def getTypedIObject(obj): | |
"""Cast an alembic IObject to its AbcGeom subtype""" | |
ilist = [ | |
IXform, | |
IPolyMesh, | |
ICamera, | |
ICurves, | |
ILight, | |
INuPatch, | |
IPoints, | |
ISubD, | |
IFaceSet, | |
] | |
md = obj.getMetaData() | |
for abcType in ilist: | |
if abcType.matches(md): | |
return abcType(obj.getParent(), obj.getName()) | |
return None | |
def getFrange(schObj): | |
"""Get the frame range of animation for the given object with a schema""" | |
sch = schObj.getSchema() | |
ts = sch.getTimeSampling() | |
tst = ts.getTimeSamplingType() | |
if tst.isAcyclic(): | |
times = ts.getStoredTimes() | |
tpc = 1.0 | |
if len(times) > 1: | |
tpc = times[1] - times[0] | |
else: | |
tpc = tst.getTimePerCycle() | |
fps = 1.0 / tpc | |
# get the relevant values | |
numSamples = sch.getNumSamples() | |
start = round(ts.getSampleTime(0) * fps) | |
end = round(ts.getSampleTime(numSamples - 1) * fps) | |
return [start, end], fps | |
def _getSkippedChildren(chis, skipTypes): | |
"""Convenience function to Filter a list of objects by alembic type""" | |
if skipTypes: | |
chis = [c for c in chis if c.getMetaData().get("schema") not in skipTypes] | |
return chis | |
def printPropertyValue(iPar, printValueNames, parPfx=""): | |
"""Print a hierarchy of alembic properties | |
Arguments: | |
iPar (IObject): The parent object | |
printValueNames (None | list[str] | Literal[PRINT_ALL]): Which properties to print the actual | |
values of. If you pass the PRINT_ALL object, then all property values will be printed | |
parPfx (str): (INTERNAL USE) The current indented printing prefix | |
""" | |
numProps = iPar.getNumProperties() | |
for i in range(numProps): | |
prop = iPar.getProperty(i) | |
pfxs = PROP_PREFIXES[1 if i == numProps - 1 else 0] | |
propName = prop.getName() | |
print(parPfx + pfxs[0] + propName) | |
if (propName is PRINT_ALL) or (propName in printValueNames): | |
print(parPfx + ' > ', prop.getValue()) | |
if prop.isCompound(): | |
printPropertyValue(prop, printValueNames, parPfx + pfxs[1]) | |
def printObjects( | |
obj, | |
skipTypes=None, | |
doPrintProps=True, | |
printValueNames=None, | |
parPfx="", | |
): | |
"""Print a hierarchy of alembic objects | |
Arguments: | |
obj (IObject): The current alembic IObject | |
skipTypes (list[str]): Skip the objects whose types are in this list | |
Good for reducing the clutter in the hierarchy | |
doPrintProps (bool): Whether to print the property hierarchies under each object hierarchy | |
printValueNames (None | list[str] | Literal[PRINT_ALL]): Which properties to print the actual | |
values of. If you pass the PRINT_ALL object, then all property values will be printed | |
parPfx (str): (INTERNAL USE) The current indented printing prefix | |
""" | |
printValueNames = [] if printValueNames is None else printValueNames | |
chis = _getSkippedChildren(obj.children, skipTypes) | |
for i, child in enumerate(chis): | |
lastChild = i == len(chis) - 1 | |
pfxs = PREFIXES[1 if lastChild else 0] | |
tChild = getTypedIObject(child) | |
print( | |
parPfx + pfxs[0] + child.getName(), type(tChild).__name__, getFrange(tChild) | |
) | |
objPfx = PREFIXES[0][1] if _getSkippedChildren(child.children, skipTypes) else "" | |
if doPrintProps: | |
printPropertyValue(child.getProperties(), printValueNames, parPfx + pfxs[1] + objPfx) | |
printObjects( | |
child, | |
skipTypes=skipTypes, | |
doPrintProps=doPrintProps, | |
printValueNames=printValueNames, | |
parPfx=parPfx + pfxs[1], | |
) | |
def main(): | |
paths = [ | |
r'path/to/file.abc', | |
] | |
for a in paths: | |
if not os.path.exists(a): | |
print("\n\nArchive:", a) | |
raise ValueError("File does not exist: {0}".format(a)) | |
arch = IArchive(str(a)) | |
try: | |
print("\n\nArchive:", a, arch.getCoreType()) | |
top = arch.getTop() | |
printObjects( | |
top, | |
skipTypes=["AbcGeom_FaceSet_v1"], | |
doPrintProps=True, | |
printValueNames=['P'], | |
) | |
finally: | |
# If python crashes, and we don't do this, then windows | |
# will lock the files until the python process is killed | |
del arch, top | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment