# Attaching Alembic object to a SoftImage object
# ----------------------------------------------
# Instruction to Use:
# 1. Open Plug-In Manager
# 2. Expand AlembicSelectiveAttach
# 3. Right-click on property AttachAlembicToObjectDialog and choose Create Property
######################################################################
#
# import SoftImage constants
import win32com.client
from win32com.client import constants

# import Alembic
import imath
import alembic
from alembic import AbcGeom

# for file existence checking
import os.path

null = None
false = 0
true = 1

# The alembic object types
ABC_TYPE_POLYMESH = "pmsh"
ABC_TYPE_POINTCLOUD = "pts"
ABC_TYPE_TRANSFORM = "xform" # null in SI is xform as shape in alembic
ABC_TYPE_NONE = "none"

######################################################################
# Node Class
######################################################################

""" Used to control expansion and contraction of search result """
class Node:

    def __init__(self, name):
        self.name = name
        self.value = name
        self.isExpanded = false
        self.children = []

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and (self.name == other.name))

    def __ne__(self, other):
        return not self.__eq__(other)

    def __gt__(self, other):
        return (self.name > other.name)

    def __ge__(self, other):
        return (self.name >= other.name)

    def __lt__(self, other):
        return (self.name < other.name)

    def __le__(self, other):
        return (self.name <= other.name)

    def clear(self):
        self.isExpanded = false
        # There should be recursive delete here to all the children
        self.children = []

    def addChild(self, child):
        if (child not in self.children):
            self.children.append(child)

    def getChild(self, child):
        if (child in self.children):
            assert self.children.count(child) == 1
            index = self.children.index(child)
            return self.children[index]
        else:
            # Application.LogMessage("No such child")
            return null

    def getExpansionArray(self, depth, parentName):
        name = self.name

        if (len(parentName) != 0):
            name = name[(len(parentName)+1):]
        else:
            # Remove "/" from name
            name = name[1:]

        expansion = []
        if (len(self.children) == 0):
            expansion.append(" "*(5*depth) + "< > " + name)
        elif (self.isExpanded):
            expansion.append(" "*(5*depth) + "[-] < > " + name)
        else:
            expansion.append(" "*(5*depth) + "[+] < > " + name)

        # Application.LogMessage(self.name)
        # Application.LogMessage(self.isExpanded)

        if (self.isExpanded):
            boolIndex = self.value.rfind(".")
            assert boolIndex != -1
            newValue = self.value[:boolIndex] + ".true"
            expansion.append(newValue)
            for child in self.children:
                expansion.extend(child.getExpansionArray((depth+1), self.name))
        else:
            expansion.append(self.value)

        return expansion

    """ For debugging purpose """
    def printTree(self, depth):
        Application.LogMessage(" "*(3*depth) + self.name)
        for child in self.children:
            child.printTree(depth+1)

######################################################################

root = Node("")
root.value = "root"

# Register Plugin -----------------------------------------------------------------

def XSILoadPlugin( in_reg ):
    in_reg.Author = "Softimage"
    in_reg.Name = "AlembicSelectiveAttach"
    in_reg.Major = 1
    in_reg.Minor = 1

    in_reg.RegisterCommand("AttachAlembicToObject", "AttachAlembicToObject")

    #RegistrationInsertionPoint - do not remove this line

    in_reg.RegisterProperty("AttachAlembicToObjectDialog");

    return true

# def XSIUnloadPlugin( in_reg ):
#   strPluginName = in_reg.Name
#   Application.LogMessage(str(strPluginName) + str(" has been unloaded."),constants.siVerbose)
#   return true

# End of Register Plugin ----------------------------------------------------------


# Dialog Box ----------------------------------------------------------------------

def AttachAlembicToObjectDialog_Define( ctxt ):
    oCustomProperty = ctxt.Source

    oCustomProperty.AddParameter3("ObjectToUse", constants.siString) # full name for easy reference in command
    oCustomProperty.AddParameter3("AlembicFile", constants.siString)
    oCustomProperty.AddParameter3("AlembicObjectHierarchies", constants.siString) # store all objects and their hierarchies from the alembic file
    oCustomProperty.AddParameter3("SelectedAlembicObject", constants.siString) # for control list box and selection in command
    oCustomProperty.AddParameter3("IsUsingCacheForKine", constants.siBool, false, false, true, false, false)
    oCustomProperty.AddParameter3("IsUsingExistingICETreeIfPossible", constants.siBool, false, false, true, false, false)
    oCustomProperty.AddParameter3("isOnlyAlembicObjectsOfSameSIObjectType", constants.siBool, false, false, true, false, false)

    return true

def AttachAlembicToObjectDialog_DefineLayout( ctxt ):
    layout = ctxt.Source
    layout.Clear()

    return true

"""
This method is called when the dialog is created or when it is reopened

Thus, user would expect the last Alembic objects to be shown. As such,
if AlembicObjectHierarchies is not empty, we will reload the alembic
objects.
"""
def AttachAlembicToObjectDialog_OnInit():
    ClearSelectedAlembicObjectValue()

    # If we know the alembic object hierarchy, show it on init
    if (PPG.AlembicObjectHierarchies.Value):
        ProcessAlembicObjectHierarchiesWithFilter()
        content = populateObjectHierarchies()
        RebuildLayout(PPG, content)
    else:
        RebuildLayout(PPG, null)

def RebuildLayout( PPG, alembicObjects ):
    # Application.LogMessage("Rebuilding layout", constants.siInfo)

    layout = PPG.PPGLayout
    pset = PPG.Inspected(0)
    params = pset.Parameters

    layout.Clear()

    # Object Selection ------------------------------
    layout.AddGroup("Pick Object to use")

    layout.AddRow()
    item = layout.AddItem("ObjectToUse")
    item.SetAttribute(constants.siUIValueOnly, true)
    item = layout.AddButton("PickButton", "PICK")
    item.SetAttribute("cx", 60)
    layout.EndRow()

    layout.EndGroup()
    # End of Object Selection -----------------------

    # Alembic ---------------------------------------
    layout.AddGroup("Alembic")

    item = layout.AddItem("AlembicFile", "Alembic File", constants.siControlFilePath)
    item.SetAttribute(constants.siUIOpenFile, true)
    item.SetAttribute(constants.siUIFileMustExist, true)
    fileFormatFilter = "Alembic file (*.abc)|*.abc|"
    item.SetAttribute(constants.siUIFileFilter, fileFormatFilter)

    layout.addSpacer(0, 5)

    item = layout.AddItem("isOnlyAlembicObjectsOfSameSIObjectType",
        "Filter only objects of same type as picked object\n(only works for polymesh and point cloud)",
        constants.siControlBoolean)

    layout.addSpacer(0, 10)

    layout.AddGroup("Alembic Objects Available")
    item = layout.AddItem("SelectedAlembicObject", "Alembic Objects Available",
        constants.siControlListBox)
    item.SetAttribute(constants.siUICY, 400)
    item.SetAttribute(constants.siUINoLabel, true)

    if (alembicObjects):
        item.UIItems = alembicObjects

    layout.EndGroup()

    item = layout.AddItem("IsUsingCacheForKine", "Use Cache for object kine", constants.siControlBoolean)
    layout.EndGroup()
    # End of Alembic --------------------------------

    layout.AddRow()
    item = layout.AddItem("IsUsingExistingICETreeIfPossible", "Use Existing ICE Tree if possible", constants.siControlBoolean)
    item = layout.AddButton("ApplyButton", "Apply")
    item.SetAttribute("cx", 140)
    layout.EndRow()

    PPG.Refresh()

# End of Dialog Box ----------------------------------------------------------------


# Dialog Box Interaction -----------------------------------------------------------

""" We use ObjectToUse property as reference to the object """
def AttachAlembicToObjectDialog_PickButton_OnClicked():
    pickSession = Application.PickObject("Select Object", "Select Object")

    buttonChoice = pickSession.Value("ButtonPressed")
    item = pickSession.Value("PickedElement")

    if (buttonChoice != 0): # ButtonPressed 0 is RMB a.k.a. abort
        PPG.ObjectToUse.Value = item.FullName

        # Only refresh alembic object list if filter is on
        if (PPG.AlembicObjectHierarchies.Value and PPG.isOnlyAlembicObjectsOfSameSIObjectType.Value):
            ProcessAlembicObjectHierarchiesWithFilter()
            content = populateObjectHierarchies()
            RebuildLayout(PPG, content)

def AttachAlembicToObjectDialog_ObjectToUse_OnChanged():
    # Only refresh alembic object list if filter is on
    if (PPG.AlembicObjectHierarchies.Value and PPG.isOnlyAlembicObjectsOfSameSIObjectType.Value):
        ProcessAlembicObjectHierarchiesWithFilter()
        content = populateObjectHierarchies()
        RebuildLayout(PPG, content)

"""
Store Alembic file object hierarchies to AlembicObjectHierarchies
-----------------------------------------------------------------
1. Get object serialization as string
2. Populate initial object list in dialog
"""
def AttachAlembicToObjectDialog_AlembicFile_OnChanged():
    # Application.LogMessage(PPG.AlembicFile.Value)
    ClearSelectedAlembicObjectValue()
    PPG.AlembicObjectHierarchies.Value = ""

    alembicFilePath = str(PPG.AlembicFile.Value)

    if (alembicFilePath):
        try:
            iarch = alembic.Abc.IArchive(str(PPG.AlembicFile.Value))
            alembicTopObject = iarch.getTop()
            result = ""
            result = GetAlembicFileObjectHierarchy(result, alembicTopObject, "")
            PPG.AlembicObjectHierarchies.Value = result
            # Application.LogMessage(result)

            ProcessAlembicObjectHierarchiesWithFilter()

            content = populateObjectHierarchies()

            RebuildLayout(PPG, content)
        except Exception: # in case such alembic file does not exist or cannot be processed
            RebuildLayout(PPG, null)
    else:
        RebuildLayout(PPG, null)

"""
(Recursive) Create Alembic Object Serialization string to be stored
"""
def GetAlembicFileObjectHierarchy(result, parent, parentPath):
    for child in parent.children:
        path = parentPath + "/" + child.getName()
        result += path + ";"

        if (AbcGeom.IPolyMesh.matches(child.getMetaData())):
            result += ABC_TYPE_POLYMESH
        elif (AbcGeom.IPoints.matches(child.getMetaData())):
            result += ABC_TYPE_POINTCLOUD
        else:
            result += ABC_TYPE_NONE

        result += ";"

        result = GetAlembicFileObjectHierarchy(result, child, path)

    return result

""" Populate the dialog with Alembic objects """
def populateObjectHierarchies():
    global root
    result = []

    for child in root.children:
        result.extend(child.getExpansionArray(0, root.name))

    return result

def ConstructListOfAlembicObjects():
    parsedAlembicObjectHierarchies = str(PPG.AlembicObjectHierarchies.Value).split(";")
    result = zip(parsedAlembicObjectHierarchies[0::2], parsedAlembicObjectHierarchies[1::2])
    return result

""" Process Alembic object serialization and Populate internal result tree """
def ProcessAlembicObjectHierarchies( alembicObjectType = null ):
    global root
    root.clear()

    alembicObjects = ConstructListOfAlembicObjects()

    for alembicObject in alembicObjects:
        currentNode = root
        name = alembicObject[0]
        objectType = alembicObject[1]
        paths = name.split("/")
        paths.remove("")

        currentPath = ""

        if ((alembicObjectType is null) or
            (alembicObjectType == ABC_TYPE_NONE) or
            (objectType == alembicObjectType)):
            for path in paths:
                currentPath += "/" + path
                pathNode = Node(currentPath)
                pathNode.value = currentPath + ".false"

                # Application.LogMessage(path)
                # Application.LogMessage(currentPath)
                # Application.LogMessage(pathNode.value)

                currentNode.addChild(pathNode)

                childNode = currentNode.getChild(pathNode)

                if (childNode is not null):
                    currentNode = childNode

    # Application.LogMessage("Done populating tree")
    # root.printTree(0)

""" Driver function to abstract filtering system """
def ProcessAlembicObjectHierarchiesWithFilter():
    try:
        objectPicked = Application.Dictionary.GetObject(PPG.ObjectToUse.Value)
    except Exception:
        objectPicked = false

    if (PPG.isOnlyAlembicObjectsOfSameSIObjectType.Value and objectPicked):
        alembicObjectType = ABC_TYPE_NONE

        objectPicked = Application.Dictionary.GetObject(PPG.ObjectToUse.Value)
        objectPickedType = objectPicked.Type

        if (objectPickedType == "polymsh"):
            alembicObjectType = ABC_TYPE_POLYMESH
        elif (objectPickedType == "pointcloud"):
            alembicObjectType = ABC_TYPE_POINTCLOUD
        else:
            alembicObjectType = ABC_TYPE_NONE

        ProcessAlembicObjectHierarchies(alembicObjectType)
    else:
        ProcessAlembicObjectHierarchies()

"""
If filter is clicked
1. Make SelectedAlembicObject "" so that the command won't use
   previously selected object
2. repopulate SelectedAlembicObject
"""
def AttachAlembicToObjectDialog_isOnlyAlembicObjectsOfSameSIObjectType_OnChanged():
    # Application.LogMessage(PPG.isOnlyAlembicObjectsOfSameSIObjectType.Value)
    ClearSelectedAlembicObjectValue()

    if (PPG.AlembicObjectHierarchies.Value):
        ProcessAlembicObjectHierarchiesWithFilter()
        content = populateObjectHierarchies()
        RebuildLayout(PPG, content)

def ClearSelectedAlembicObjectValue():
    if (PPG.SelectedAlembicObject.Value):
        PPG.SelectedAlembicObject.Value = ""

"""
1. Get all current values in control list box
2. Get chosen item
3. Do toggling
"""
def AttachAlembicToObjectDialog_SelectedAlembicObject_OnChanged():
    # Application.LogMessage(PPG.SelectedAlembicObject)
    global root
    
    ProcessAlembicObjectHierarchiesWithFilter()

    selectedValue = str(PPG.SelectedAlembicObject)
    indexOfToggleValueSeparator = selectedValue.rfind(".")
    currentExpansion = selectedValue[(indexOfToggleValueSeparator+1):]
    selectedObject = selectedValue[:(indexOfToggleValueSeparator)]

    paths = selectedObject.split("/")
    paths.remove("")

    currentNode = root
    currentPath = ""

    for path in paths:
        currentPath += "/" + path

        nextNode = currentNode.getChild(Node(currentPath))
        # Application.LogMessage(currentPath)
        if (nextNode is not null):
            currentNode.isExpanded = true
            currentNode = nextNode

    if (currentExpansion.lower() == "true"):
        currentNode.isExpanded = false
    else:
        currentNode.isExpanded = true

    content = populateObjectHierarchies()

    RebuildLayout(PPG, content)

"""
This method will not check errors of input;
Error checking will be done in command.

This method extracts alembic object name from the control list value
which contains true/false at the end for toggling purpose.

Also, when filtering is on, we will do auto child select here.

Auto child select refers to the system where if filter is on,
when user select the parent of the "Shape" alembic object - which is of
type transform, we will automatically use the first child that is
of "Shape" type for command input
"""
def AttachAlembicToObjectDialog_ApplyButton_OnClicked():
    alembicPathNameWithToggle = str(PPG.SelectedAlembicObject.Value)
    indexOfToggleValueSeparator = alembicPathNameWithToggle.rfind(".")
    alembicPathName = alembicPathNameWithToggle[:indexOfToggleValueSeparator]
    # Application.Logmessage("Selected Alembic Path Name: " + alembicPathName)

    alembicObjectName = alembicPathName

    if (PPG.isOnlyAlembicObjectsOfSameSIObjectType.Value and
        IsPickedObjectTypeShouldBeFiltered()):
        alembicObjectName = GetFirstChildOfMatchingObjectType(alembicPathName)

    Application.AttachAlembicToObject(PPG.ObjectToUse.Value, PPG.AlembicFile.Value, alembicObjectName,
        PPG.IsUsingCacheForKine.Value, PPG.IsUsingExistingICETreeIfPossible.Value)

"""
This method will get the first alembic object of matching 
SI object type.

Algorithm:
1. Get all objects with matching parent path and matching
   object type
2. Get the object with shortest path length as it must be
   the first child found
"""
def GetFirstChildOfMatchingObjectType(selectedAlembicPathName):
    filteredAlembicObjectNames = []
    alembicObjects = ConstructListOfAlembicObjects()

    for alembicObject in alembicObjects:
        name = alembicObject[0]
        objectType = alembicObject[1]
        if ((selectedAlembicPathName in name) and (IsAlembicObjectSameTypeWithPickedObject(objectType))):
            filteredAlembicObjectNames.append(name)

    # Get filtered alembic object with shortest path
    # Same principle as finding minimum item
    # - Use first item as reference to minimum
    # - Iterate through object and find shorter
    #   item - O(n)
    filteredAlembicObjectWithShortestPath_Name = filteredAlembicObjectNames[0]
    paths = filteredAlembicObjectWithShortestPath_Name.split("/")
    paths.remove("")
    filteredAlembicObjectWithShortestPath_PathLength = len(paths)

    for filteredAlembicObjectName in filteredAlembicObjectNames[1:]:
        paths = filteredAlembicObjectName.split("/")
        paths.remove("")
        pathLength = len(paths)

        if (pathLength < filteredAlembicObjectWithShortestPath_PathLength):
            filteredAlembicObjectWithShortestPath_PathLength = pathLength
            filteredAlembicObjectWithShortestPath_Name = filteredAlembicObjectName

    assert filteredAlembicObjectWithShortestPath_Name

    return filteredAlembicObjectWithShortestPath_Name

def IsAlembicObjectSameTypeWithPickedObject(alembicObjectType):
    assert PPG.ObjectToUse.Value

    try:
        objectPicked = Application.Dictionary.GetObject(PPG.ObjectToUse.Value)
    except Exception:
        return false

    objectPickedType = objectPicked.Type

    if ((objectPickedType == "polymsh" and alembicObjectType == ABC_TYPE_POLYMESH) or
        (objectPickedType == "pointcloud" and alembicObjectType == ABC_TYPE_POINTCLOUD)):
        return true

    return false

""" This method checks if object picked needs filter selection system """
def IsPickedObjectTypeShouldBeFiltered():
    if (PPG.ObjectToUse.Value):
        try:
            objectPicked = Application.Dictionary.GetObject(PPG.ObjectToUse.Value)
        except Exception:
            return false

        objectPickedType = objectPicked.Type

        if ((objectPickedType == "polymsh") or
            (objectPickedType == "pointcloud")):
            return true

    return false

# End of Dialog Box Interaction ----------------------------------------------------


# Command --------------------------------------------------------------------------

def AttachAlembicToObject_Init ( in_ctxt ):
    oCmd = in_ctxt.Source
    oCmd.Description = "Attach the specified Alembic object to an object in the scene"
    oCmd.ReturnValue = true

    oArgs = oCmd.Arguments
    oArgs.Add("ObjectName", constants.siArgumentInput)
    oArgs.Add("AlembicFilePath", constants.siArgumentInput)
    oArgs.Add("AlembicObjectName", constants.siArgumentInput)
    oArgs.Add("isUsingCacheForKine", constants.siArgumentInput)
    oArgs.Add("IsUsingExistingICETreeIfPossible", constants.siArgumentInput)

    return true

"""
How this works:
1. Get Alembic Cache Node on object
   1.1. If there is only one alembic cache node,
        we will result that ICE Tree and alembic cache node
   1.2. If there is no or more than one alembic cache node,
        we will create a new ICE Tree for simplicity
2. Configure Alembic Cache Node as per needed
"""
def AttachAlembicToObject_Execute ( objectName, alembicFilePath, alembicObjectName, isUsingCacheForKine, isUsingExistingICETreeIfPossible ):
    # Application.LogMessage("AttachAlembicToObject_Execute called", siVerbose)

    if (objectName and alembicFilePath and alembicObjectName):

        if (not IsCommandParametersAreValid(objectName, alembicFilePath, alembicObjectName)):
            Application.LogMessage("The command was not executed", constants.siWarning)
            return false

        alembicCacheNodeCLSID = "{71BEC811-30CA-4F0A-AFD5-E07666749EB8}"

        alembicCacheNodesInScene = Application.ActiveSceneRoot.FindObjectsFromCLSID(alembicCacheNodeCLSID)

        alembicCacheNodesInSelectedObject = []

        for alembicCacheNodeInScene in alembicCacheNodesInScene:
            if (alembicCacheNodeInScene.FullName.startswith(objectName + ".")):
                # Application.LogMessage("objectName: " + objectName)
                # Application.LogMessage("alembicCacheNodeInScene.FullName: " + alembicCacheNodeInScene.FullName)
                alembicCacheNodesInSelectedObject.append(alembicCacheNodeInScene.FullName)

        alembicNodePath = ""
        # If there is no or more than one ICE Tree with Alembic Cache node, create a new ICE Tree
        # Otherwise, do the work under that ICE Tree
        if ((len(alembicCacheNodesInSelectedObject) < 1) or not isUsingExistingICETreeIfPossible ):
            # Create ICE Tree
            ICETree = str(Application.ApplyOp("ICETree", objectName, "siNode", "", "", 0))

            if ((not IsChangingTopology(alembicFilePath, alembicObjectName)) and
                (not isObjectTypeNull(objectName))):
                objectStackNameIndex = ICETree.rfind(".ICETree")
                objectStackName = ICETree[:objectStackNameIndex]
                Application.MoveOperatorBefore(objectStackName, ICETree, objectStackName + ".animationmarker")

            # Insert and Connect Required ICE Nodes into the newly created tree
            Application.AddICENode("$XSI_DSPRESETS\\ICENodes\\AlembicCacheNode", ICETree)
            Application.AddICENode("$XSI_DSPRESETS\\ICENodes\\CurrentFrameNode", ICETree)

            Application.ConnectICENodes(ICETree + ".AlembicCacheNode.frame", ICETree + ".CurrentFrameNode.frame")
            Application.ConnectICENodes(ICETree + ".port1", ICETree + ".AlembicCacheNode.execute")

            alembicNodePath = ICETree + ".AlembicCacheNode"

        elif (len(alembicCacheNodesInSelectedObject) == 1):
            alembicNodePath = alembicCacheNodesInSelectedObject[0]

            if (IsChangingTopology(alembicFilePath, alembicObjectName)):
                objectICETreeStackNameIndex = alembicNodePath.rfind(".")
                objectICETreeStackName = alembicNodePath[:objectICETreeStackNameIndex]
                objectStackNameIndex = objectICETreeStackName.rfind(".")
                objectStackName = objectICETreeStackName[:objectStackNameIndex]
                if (not IsOperatorBelowSpecifiedMarker(objectName, objectICETreeStackName, objectStackName + ".modelingmarker")):
                    Application.MoveOperatorBefore(objectStackName, objectICETreeStackName, objectStackName + ".modelingmarker")
            elif (not isObjectTypeNull(objectName)):
                objectICETreeStackNameIndex = alembicNodePath.rfind(".")
                objectICETreeStackName = alembicNodePath[:objectICETreeStackNameIndex]
                objectStackNameIndex = objectICETreeStackName.rfind(".")
                objectStackName = objectICETreeStackName[:objectStackNameIndex]
                if (not IsOperatorBelowSpecifiedMarker(objectName, objectICETreeStackName, objectStackName + ".animationmarker")):
                    Application.MoveOperatorBefore(objectStackName, objectICETreeStackName, objectStackName + ".animationmarker")

        elif (len(alembicCacheNodesInSelectedObject) > 1):
            operatorStack = GetObjectOperatorStack(objectName)

            for operator in operatorStack:
                alembicNodePath = ""

                for node in alembicCacheNodesInSelectedObject:
                    if (operator in node):
                        alembicNodePath = node
                        break

                if (alembicNodePath):
                    # Application.LogMessage(alembicNodePath)
                    break

        # Configure Alembic Cache Node as needed
        Application.SetValue(alembicNodePath + ".FilePath", alembicFilePath, "")
        #Get the grid data which defines the mapping
        gridData = Application.Dictionary.GetObject(alembicNodePath).Items.Value
        gridData.RowCount = 1
        gridData.ColumnCount = 3

        # Alembic Cache Object Full Path
        gridData.SetCell(0, 0, alembicObjectName)

        # XSI Object to apply to (in this case self)
        gridData.SetCell(1, 0, "self")

        # Whether object kine is using cache or not
        gridData.SetCell(2, 0, isUsingCacheForKine)

        Application.LogMessage(alembicObjectName + " from " + alembicFilePath + " has been attached to " + objectName)
        return true
    else:
        Application.LogMessage("The command was not executed because some parameters were missing", constants.siWarning)
        return false

def IsCommandParametersAreValid(objectName, alembicFilePath, alembicObjectName):
    if (not IsObjectExists(objectName)):
        Application.LogMessage(objectName + " does not exist", constants.siWarning)
        return false
    elif (not IsAlembicFileExists(alembicFilePath)):
        Application.LogMessage("Alembic file does not exist", constants.siWarning)
        return false
    elif (not IsAlembicFileCouldBeOpened(alembicFilePath)):
        Application.LogMessage("Alembic file could not be opened", constants.siWarning)
        return false
    elif (not IsAlembicObjectExists(alembicFilePath, alembicObjectName)):
        Application.LogMessage("Alembic object does not exist", constants.siWarning)
        return false

    return true

def IsObjectExists(objectName):
    # Application.LogMessage("Object existence: " + str(Application.ActiveSceneRoot.FindChild(objectName)))
    return (Application.ActiveSceneRoot.FindChild(objectName))

def IsAlembicFileExists(alembicFilePath):
    # Application.LogMessage("Alembic file existence: " + str(os.path.isfile(alembicFilePath)))
    return os.path.isfile(alembicFilePath)

def IsAlembicFileCouldBeOpened(alembicFilePath):
    try:
        iarch = alembic.Abc.IArchive(str(alembicFilePath))
        return true
    except Exception:
        return false

def IsAlembicObjectExists(alembicFilePath, alembicObjectName):
    iarch = alembic.Abc.IArchive(str(alembicFilePath))
    parent = iarch.getTop()

    paths = alembicObjectName.split("/")
    paths.remove("")
    paths.reverse() # so that we could uses pop without sacrificing performance

    while (len(paths) > 0):
        current = paths.pop()

        try:
            parent = parent.getChild(str(current))
        except Exception:
            return false

    return true

def IsChangingTopology(alembicFilePath, alembicObjectName):
    iarch = alembic.Abc.IArchive(str(alembicFilePath))
    parent = iarch.getTop()
    child = parent

    paths = alembicObjectName.split("/")
    paths.remove("")
    paths.reverse() # so that we could uses pop without sacrificing performance

    while (len(paths) > 0):
        parent = child
        current = paths.pop()
        child = parent.getChild(str(current))

    if (AbcGeom.IPolyMesh.matches(child.getMetaData())):
        polymeshToBeChecked = AbcGeom.IPolyMesh(parent, str(child.getName()))
        topologyVariance = AbcGeom.IPolyMeshSchema.getTopologyVariance(polymeshToBeChecked.getSchema())

        if (topologyVariance == AbcGeom.MeshTopologyVariance.kHeterogenousTopology):
            return true
        
    return false

def isObjectTypeNull(objectName):
    objectPicked = Application.Dictionary.GetObject(objectName)
    objectPickedType = objectPicked.Type
    return (objectPickedType == "null")

def GetObjectOperatorStack(objectName):
    objectToBeInspected = Application.Dictionary.GetObject(objectName)
    operatorStack = []

    enum = objectToBeInspected.ActivePrimitive.ConstructionHistory

    for item in enum:
        # Application.LogMessage(item)
        operatorStack.append(str(item))

    return operatorStack

def IsOperatorBelowSpecifiedMarker(objectName, operator, marker):
    objectToBeInspected = Application.Dictionary.GetObject(objectName)
    operatorStack = []

    enum = objectToBeInspected.ActivePrimitive.ConstructionHistory

    for item in enum:
        operatorStack.append(str(item))

    if (operatorStack.index(operator) == (operatorStack.index(marker) + 1)):
        return true

    return false

# End of Command --------------------------------------------------------------------