text: # Copyright 2021 Autodesk
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  
  import fnmatch
  from functools import partial
  import ufe
  import maya.mel as mel
  import maya.cmds as cmds
  import mayaUsd.ufe as mayaUsdUfe
  import mayaUsd.lib as mayaUsdLib
  import maya.internal.common.ufe_ae.template as ufeAeTemplate
  
  try:
      # This helper class was only added recently to Maya.
      import maya.internal.ufeSupport.attributes as attributes
      hasAEPopupMenu = 'AEPopupMenu' in dir(attributes)
  except:
      hasAEPopupMenu = False
  
  from maya.common.ui import LayoutManager, ParentManager
  from maya.common.ui import setClipboardData
  from maya.OpenMaya import MGlobal
  
  # We manually import all the classes which have a 'GetSchemaAttributeNames'
  # method so we have access to it and the 'pythonClass' method.
  from pxr import Usd, UsdGeom, UsdLux, UsdRender, UsdRi, UsdShade, UsdSkel, UsdUI, UsdVol, Kind, Tf
  
  nameTxt = 'nameTxt'
  attrValueFld = 'attrValueFld'
  attrTypeFld = 'attrTypeFld'
  
  def getPrettyName(name):
      # Put a space in the name when preceded by a capital letter.
      # Exceptions: Number followed by capital
      #             Multiple capital letters together
      prettyName = str(name[0])
      nbChars = len(name)
      for i in range(1, nbChars):
          if name[i].isupper() and not name[i-1].isdigit():
              if (i < (nbChars-1)) and not name[i+1].isupper():
                  prettyName += ' '
              prettyName += name[i]
          elif name[i] == '_':
              continue
          else:
              prettyName += name[i]
  
      # Make each word start with an uppercase.
      return prettyName.title()
  
  # Helper class to push/pop the Attribute Editor Template. This makes
  # sure that controls are aligned properly.
  class AEUITemplate:
      def __enter__(self):
          cmds.setUITemplate('attributeEditorTemplate', pst=True)
          return self
  
      def __exit__(self, mytype, value, tb):
          cmds.setUITemplate(ppt=True)
  
  # Custom control, but does not have any UI. Instead we use
  # this control to be notified from UFE when any attribute has changed
  # so we can update the AE. This is to fix refresh issue
  # when transform is added to a prim.
  class UfeAttributesObserver(ufe.Observer):
      def __init__(self, item):
          super(UfeAttributesObserver, self).__init__()
          self._item = item
  
      def __del__(self):
          ufe.Attributes.removeObserver(self)
  
      def __call__(self, notification):
          if isinstance(notification, ufe.AttributeValueChanged):
              if notification.name() == UsdGeom.Tokens.xformOpOrder:
                  mel.eval("evalDeferred -low \"refreshEditorTemplates\";")
  
      def onCreate(self, *args):
          ufe.Attributes.addObserver(self._item, self)
  
      def onReplace(self, *args):
          # Nothing needed here since we don't create any UI.
          pass
  
  class MetaDataCustomControl(object):
      # Custom control for all prim metadata we want to display.
      def __init__(self, item, prim, useNiceName):
          # In Maya 2022.1 we need to hold onto the Ufe SceneItem to make
          # sure it doesn't go stale. This is not needed in latest Maya.
          mayaVer = '%s.%s' % (cmds.about(majorVersion=True), cmds.about(minorVersion=True))
          self.item = item if mayaVer == '2022.1' else None
          self.prim = prim
          self.useNiceName = useNiceName
  
          # There are four metadata that we always show: primPath, kind, active, instanceable
          # We use a dictionary to store the various other metadata that this prim contains.
          self.extraMetadata = dict()
  
      def onCreate(self, *args):
          # Metadata: PrimPath
          # The prim path is for display purposes only - it is not editable, but we
          # allow keyboard focus so you copy the value.
          self.primPath = cmds.textFieldGrp(label='Prim Path', editable=False, enableKeyboardFocus=True)
  
          # Metadata: Kind
          # We add the known Kind types, in a certain order ("model hierarchy") and then any
          # extra ones that were added by extending the kind registry.
          # Note: we remove the "model" kind because in the USD docs it states, 
          #       "No prim should have the exact kind "model".
          allKinds = Kind.Registry.GetAllKinds()
          allKinds.remove(Kind.Tokens.model)
          knownKinds = [Kind.Tokens.group, Kind.Tokens.assembly, Kind.Tokens.component, Kind.Tokens.subcomponent]
          temp1 = [ele for ele in allKinds if ele not in knownKinds]
          knownKinds.extend(temp1)
  
          # If this prim's kind is not registered, we need to manually
          # add it to the list.
          model = Usd.ModelAPI(self.prim)
          primKind = model.GetKind()
          if primKind not in knownKinds:
              knownKinds.insert(0, primKind)
          if '' not in knownKinds:
              knownKinds.insert(0, '')    # Set metadata value to "" (or empty).
  
          self.kind = cmds.optionMenuGrp(label='Kind',
                                         cc=self._onKindChanged,
                                         ann='Kind is a type of metadata (a pre-loaded string value) used to classify prims in USD. Set the classification value from the dropdown to assign a kind category to a prim. Set a kind value to activate selection by kind.')
  
          for ele in knownKinds:
              cmds.menuItem(label=ele)
  
          # Metadata: Active
          self.active = cmds.checkBoxGrp(label='Active',
                                         ncb=1,
                                         cc1=self._onActiveChanged,
                                         ann="If selected, the prim is set to active and contributes to the composition of a stage. If a prim is set to inactive, it doesn't contribute to the composition of a stage (it gets striked out in the Outliner and is deactivated from the Viewport).")
  
          # Metadata: Instanceable
          self.instan = cmds.checkBoxGrp(label='Instanceable',
                                         ncb=1,
                                         cc1=self._onInstanceableChanged,
                                         ann='If selected, instanceable is set to true for the prim and the prim is considered a candidate for instancing. If deselected, instanceable is set to false.')
  
          # Get all the other Metadata and remove the ones above, as well as a few
          # we don't ever want to show.
          allMetadata = self.prim.GetAllMetadata()
          keysToDelete = ['kind', 'active', 'instanceable', 'typeName', 'documentation']
          for key in keysToDelete:
              allMetadata.pop(key, None)
          if allMetadata:
              cmds.separator(h=10, style='single', hr=True)
  
              for k in allMetadata:
                  # All extra metadata is for display purposes only - it is not editable, but we
                  # allow keyboard focus so you copy the value.
                  mdLabel = getPrettyName(k) if self.useNiceName else k
                  self.extraMetadata[k] = cmds.textFieldGrp(label=mdLabel, editable=False, enableKeyboardFocus=True)
  
          # Update all metadata values.
          self.refresh()
  
      def onReplace(self, *args):
          # Nothing needed here since USD data is not time varying. Normally this template
          # is force rebuilt all the time, except in response to time change from Maya. In
          # that case we don't need to update our controls since none will change.
          pass
  
      def refresh(self):
          # PrimPath
          cmds.textFieldGrp(self.primPath, edit=True, text=str(self.prim.GetPath()))
  
          # Kind
          model = Usd.ModelAPI(self.prim)
          primKind = model.GetKind()
          if not primKind:
              # Special case to handle the empty string (for meta data value empty).
              cmds.optionMenuGrp(self.kind, edit=True, select=1)
          else:
              cmds.optionMenuGrp(self.kind, edit=True, value=primKind)
  
          # Active
          cmds.checkBoxGrp(self.active, edit=True, value1=self.prim.IsActive())
  
          # Instanceable
          cmds.checkBoxGrp(self.instan, edit=True, value1=self.prim.IsInstanceable())
  
          # All other metadata types
          for k in self.extraMetadata:
              v = self.prim.GetMetadata(k) if k != 'customData' else self.prim.GetCustomData()
              cmds.textFieldGrp(self.extraMetadata[k], edit=True, text=str(v))
  
      def _onKindChanged(self, value):
          with mayaUsdLib.UsdUndoBlock():
              model = Usd.ModelAPI(self.prim)
              model.SetKind(value)
  
      def _onActiveChanged(self, value):
          with mayaUsdLib.UsdUndoBlock():
              self.prim.SetActive(value)
  
      def _onInstanceableChanged(self, value):
          with mayaUsdLib.UsdUndoBlock():
              self.prim.SetInstanceable(value)
  
  # Custom control for all array attribute.
  class ArrayCustomControl(object):
  
      if hasAEPopupMenu:
          class ArrayAEPopup(attributes.AEPopupMenu):
              '''Override the attribute AEPopupMenu so we can add extra menu items.
              '''
              def __init__(self, uiControl, ufeAttr, hasValue, values):
                  self.hasValue = hasValue
                  self.values = values
                  super(ArrayCustomControl.ArrayAEPopup, self).__init__(uiControl, ufeAttr)
  
              def _copyAttributeValue(self):
                  setClipboardData(str(self.values))
  
              def _printToScriptEditor(self):
                  MGlobal.displayInfo(str(self.values))
  
              COPY_ACTION  = ('Copy Attribute Value', _copyAttributeValue, [])
              PRINT_ACTION = ('Print to Script Editor', _printToScriptEditor, [])
  
              HAS_VALUE_MENU = [COPY_ACTION, PRINT_ACTION]
  
              def _buildMenu(self, addItemCmd):
                  super(ArrayCustomControl.ArrayAEPopup, self)._buildMenu(addItemCmd)
                  if self.hasValue:
                      cmds.menuItem(divider=True, parent=self.popupMenu)
                      self._buildFromActions(self.HAS_VALUE_MENU, addItemCmd)
  
      def __init__(self, ufeAttr, prim, attrName, useNiceName):
          self.ufeAttr = ufeAttr
          self.prim = prim
          self.attrName = attrName
          self.useNiceName = useNiceName
          super(ArrayCustomControl, self).__init__()
  
      def onCreate(self, *args):
          attr = self.prim.GetAttribute(self.attrName)
          typeName = attr.GetTypeName()
          if typeName.isArray:
              values = attr.Get()
              hasValue = True if values and len(values) > 0 else False
  
              # build the array type string
              # We want something like int[size] or int[] if empty
              typeNameStr = str(typeName.scalarType)
              typeNameStr += ("[" + str(len(values)) + "]") if hasValue else "[]"
  
              attrLabel = getPrettyName(self.attrName) if self.useNiceName else self.attrName
              singleWidgetWidth = mel.eval('global int $gAttributeEditorTemplateSingleWidgetWidth; $gAttributeEditorTemplateSingleWidgetWidth += 0')
              with AEUITemplate():
                  # See comment in ConnectionsCustomControl below for why nc=5.
                  rl = cmds.rowLayout(nc=5, adj=3)
                  with LayoutManager(rl):
                      cmds.text(nameTxt, al='right', label=attrLabel, annotation=attr.GetDocumentation())
                      cmds.textField(attrTypeFld, editable=False, text=typeNameStr, font='obliqueLabelFont', width=singleWidgetWidth*1.5)
  
                  if hasAEPopupMenu:
                      pMenu = self.ArrayAEPopup(rl, self.ufeAttr, hasValue, values)
                      self.updateUi(self.ufeAttr, rl)
                      self.attachCallbacks(self.ufeAttr, rl, None)
                  else:
                      if hasValue:
                          cmds.popupMenu()
                          cmds.menuItem( label="Copy Attribute Value",   command=lambda *args: setClipboardData(str(values)) )
                          cmds.menuItem( label="Print to Script Editor", command=lambda *args: MGlobal.displayInfo(str(values)) )
  
          else:
              cmds.error(self.attrName + " must be an array!")
  
      def onReplace(self, *args):
          pass
  
      # Only used when hasAEPopupMenu is True.
      def updateUi(self, attr, uiControlName):
          if not hasAEPopupMenu:
              return
  
          with ParentManager(uiControlName):
              bgClr = attributes.getAttributeColorRGB(self.ufeAttr)
              if bgClr:
                  isLocked = attributes.isAttributeLocked(self.ufeAttr)
                  cmds.textField(attrTypeFld, edit=True, backgroundColor=bgClr)
  
      # Only used when hasAEPopupMenu is True.
      def attachCallbacks(self, ufeAttr, uiControl, changedCommand):
          if not hasAEPopupMenu:
              return
  
          # Create change callback for UFE locked/unlock synchronization.
          cb = attributes.createChangeCb(self.updateUi, ufeAttr, uiControl)
          cmds.textField(attrTypeFld, edit=True, parent=uiControl, changeCommand=cb)
  
  
  def showEditorForUSDPrim(usdPrimPathStr):
      # Simple helper to open the AE on input prim.
      mel.eval('evalDeferred "showEditor(\\\"%s\\\")"' % usdPrimPathStr)
  
  # Custom control for all attributes that have connections.
  class ConnectionsCustomControl(object):
      def __init__(self, ufeItem, prim, attrName, useNiceName):
          self.path = ufeItem.path()
          self.prim = prim
          self.attrName = attrName
          self.useNiceName = useNiceName
          super(ConnectionsCustomControl, self).__init__()
  
      def onCreate(self, *args):
          frontPath = self.path.popSegment()
          attr = self.prim.GetAttribute(self.attrName)
          attrLabel = getPrettyName(self.attrName) if self.useNiceName else self.attrName
          attrType = attr.GetMetadata('typeName')
  
          singleWidgetWidth = mel.eval('global int $gAttributeEditorTemplateSingleWidgetWidth; $gAttributeEditorTemplateSingleWidgetWidth += 0')
          with AEUITemplate():
              # Because of the way the Maya AE template is defined we use a 5 column setup, even
              # though we only have two fields. We resize the main field and purposely set the
              # adjustable column to 3 (one we don't have a field in). We want the textField to
              # remain at a given width.
              rl = cmds.rowLayout(nc=5, adj=3)
              with LayoutManager(rl):
                  cmds.text(nameTxt, al='right', label=attrLabel, annotation=attr.GetDocumentation())
                  cmds.textField(attrTypeFld, editable=False, text=attrType, backgroundColor=[0.945, 0.945, 0.647], font='obliqueLabelFont', width=singleWidgetWidth*1.5)
  
                  # Add a menu item for each connection.
                  cmds.popupMenu()
                  for c in attr.GetConnections():
                      parentPath = c.GetParentPath()
                      primName = parentPath.MakeRelativePath(parentPath.GetParentPath())
                      mLabel = '%s%s...' % (primName, c.elementString)
  
                      usdSeg = ufe.PathSegment(str(c.GetPrimPath()), mayaUsdUfe.getUsdRunTimeId(), '/')
                      newPath = (frontPath + usdSeg)
                      newPathStr = ufe.PathString.string(newPath)
                      cmds.menuItem(label=mLabel, command=lambda *args: showEditorForUSDPrim(newPathStr))
  
      def onReplace(self, *args):
          # We only display the attribute name and type. Neither of these are time
          # varying, so we don't need to implement the replace.
          pass
  
  class NoticeListener(object):
      # Inserted as a custom control, but does not have any UI. Instead we use
      # this control to be notified from USD when any metadata has changed
      # so we can update the AE fields.
      def __init__(self, prim, metadataControls):
          self.prim = prim
          self.metadataControls = metadataControls
  
      def onCreate(self, *args):
          self.listener = Tf.Notice.Register(Usd.Notice.ObjectsChanged,
                                             self.__OnPrimsChanged, self.prim.GetStage())
          pname = cmds.setParent(q=True)
          cmds.scriptJob(uiDeleted=[pname, self.onClose], runOnce=True)
  
      def onReplace(self, *args):
          # Nothing needed here since we don't create any UI.
          pass
  
      def onClose(self):
          if self.listener:
              self.listener.Revoke()
              self.listener = None
  
      def __OnPrimsChanged(self, notice, sender):
          if notice.HasChangedFields(self.prim):
              # Iterate thru all the metadata controls (we were given when created) and
              # call the refresh method (if it exists).
              for ctrl in self.metadataControls:
                  if hasattr(ctrl, 'refresh'):
                      ctrl.refresh()
  
  
  # SchemaBase template class for categorization of the attributes.
  # We no longer use the base class ufeAeTemplate.Template as we want to control
  # the placement of the metadata at the bottom (after extra attributes).
  class AETemplate(object):
      '''
      This system of schema inherits groups attributes per schema helping the user
      learn how attributes are stored.
      '''
      def __init__(self, ufeSceneItem):
          self.item = ufeSceneItem
          self.prim = mayaUsdUfe.ufePathToPrim(ufe.PathString.string(self.item.path()))
  
          # Get the UFE Attributes interface for this scene item.
          self.attrS = ufe.Attributes.attributes(self.item)
          self.addedAttrs = []
          self.suppressedAttrs = []
  
          self.showArrayAttributes = False
          if cmds.optionVar(exists="mayaUSD_AEShowArrayAttributes"):
              self.showArrayAttributes = cmds.optionVar(query="mayaUSD_AEShowArrayAttributes")
  
          # Should we display nice names in AE?
          self.useNiceName = True
          if cmds.optionVar(exists='attrEditorIsLongName'):
              self.useNiceName = (cmds.optionVar(q='attrEditorIsLongName') ==1)
  
          cmds.editorTemplate(beginScrollLayout=True)
          self.buildUI()
          self.createAppliedSchemasSection()
          self.createCustomExtraAttrs()
          self.createMetadataSection()
          cmds.editorTemplate(endScrollLayout=True)
  
          if ('%s.%s' % (cmds.about(majorVersion=True), cmds.about(minorVersion=True))) > '2022.1':
              # Because of how we dynamically build the Transform attribute section,
              # we need this template to rebuild each time it is needed. This will
              # also restore the collapse/expand state of the sections.
              # Note: in Maya 2022 all UFE templates were forcefully rebuilt, but
              #       no restore of section states.
              try:
                  cmds.editorTemplate(forceRebuild=True)
              except:
                  pass
  
      def addControls(self, controls):
          for c in controls:
              if c not in self.suppressedAttrs:
                  if self.attributeHasConnections(c):
                      connectionsCustomControl = ConnectionsCustomControl(self.item, self.prim, c, self.useNiceName)
                      self.defineCustom(connectionsCustomControl, c)
                  elif self.isArrayAttribute(c):
                      ufeAttr = self.attrS.attribute(c)
                      arrayCustomControl = ArrayCustomControl(ufeAttr, self.prim, c, self.useNiceName)
                      self.defineCustom(arrayCustomControl, c)
                  else:
                      cmds.editorTemplate(addControl=[c])
                  self.addedAttrs.append(c)
  
      def suppress(self, control):
          cmds.editorTemplate(suppress=control)
          self.suppressedAttrs.append(control)
  
      @staticmethod
      def defineCustom(customObj, attrs=[]):
          create = lambda *args : customObj.onCreate(args)
          replace = lambda *args : customObj.onReplace(args)
          cmds.editorTemplate(attrs, callCustom=[create, replace])
  
      def createSection(self, layoutName, attrList, collapse=False):
          # We create the section named "layoutName" if at least one
          # of the attributes from the input list exists.
          for attr in attrList:
              if attr in self.suppressedAttrs:
                  continue
              if self.attrS.hasAttribute(attr):
                  with ufeAeTemplate.Layout(self, layoutName, collapse):
                      # Note: addControls will silently skip any attributes
                      #       which do not exist.
                      self.addControls(attrList)
                  return
  
      def sectionNameFromSchema(self, schemaTypeName):
          '''Return a section name from the input schema type name. This section
          name is a pretty name used in the UI.'''
  
          # List of special rules for adjusting the base schema names.
          prefixToAdjust = {
              'UsdAbc' : '',
              'UsdGeomGprim' : 'GeometricPrim',
              'UsdGeom' : '',
              'UsdHydra' : '',
              'UsdImagingGL' : '',
              'UsdLux' : '',
              'UsdMedia' : '',
              'UsdRender' : '',
              'UsdRi' : '',
              'UsdShade' : '',
              'UsdSkelAnimation' : 'SkelAnimation',
              'UsdSkelBlendShape': 'BlendShape',
              'UsdSkelSkeleton': 'Skeleton',
              'UsdSkelRoot' : 'SkelRoot', 
              'UsdUI' : '',
              'UsdUtils' : '',
              'UsdVol' : ''
          }
          for p, r in prefixToAdjust.items():
              if schemaTypeName.startswith(p):
                  schemaTypeName = schemaTypeName.replace(p, r, 1)
                  break
  
          schemaTypeName = getPrettyName(schemaTypeName)
  
          # if the schema name ends with "api", replace it with
          # "API" and make sure to only replace the last occurence.
          # Example: "Shaping api" which contains "api" twice most become "Shaping API"
          if schemaTypeName.endswith("api"): 
              schemaTypeName = " API".join(schemaTypeName.rsplit("api", 1))
  
          return schemaTypeName
  
      def createTransformAttributesSection(self, sectionName, attrsToAdd):
          # Get the xformOp order and add those attributes (in order)
          # followed by the xformOp order attribute.
          allAttrs = self.attrS.attributeNames
          geomX = UsdGeom.Xformable(self.prim)
          xformOps = geomX.GetOrderedXformOps()
          xformOpOrderNames = [op.GetOpName() for op in xformOps]
          xformOpOrderNames.append(UsdGeom.Tokens.xformOpOrder)
  
          # Don't use createSection because we want a sub-sections.
          with ufeAeTemplate.Layout(self, sectionName):
              with ufeAeTemplate.Layout(self, 'Transform Attributes'):
                  attrsToAdd.remove(UsdGeom.Tokens.xformOpOrder)
                  self.addControls(xformOpOrderNames)
  
                  # Get the remainder of the xformOps and add them in an Unused section.
                  xformOpUnusedNames = fnmatch.filter(allAttrs, 'xformOp:*')
                  xformOpUnusedNames = [ele for ele in xformOpUnusedNames if ele not in xformOpOrderNames]
                  self.createSection('Unused Transform Attributes', xformOpUnusedNames, collapse=True)
  
              # Then add any reamining Xformable attributes
              self.addControls(attrsToAdd)
  
              # Add a custom control for UFE attribute changed.
              t3dObs = UfeAttributesObserver(self.item)
              self.defineCustom(t3dObs)
  
      def createMetadataSection(self):
          # We don't use createSection() because these are metadata (not attributes).
          with ufeAeTemplate.Layout(self, 'Metadata', collapse=True):
              metaDataControl = MetaDataCustomControl(self.item, self.prim, self.useNiceName)
              usdNoticeControl = NoticeListener(self.prim, [metaDataControl])
              self.defineCustom(metaDataControl)
              self.defineCustom(usdNoticeControl)
  
  
      def createCustomExtraAttrs(self):
          # We are not using the maya default "Extra Attributes" section
          # because we are using custom widget for array type and it's not
          # possible to inject our widget inside the maya "Extra Attributes" section.
  
          # The extraAttrs will contains suppressed attribute but this is not a big deal as
          # long as the suppressed attributes are suppressed by suppress(self, control).
          # This function will keep all suppressed attributes into a list which will be use
          # by addControls(). So any suppressed attributes in extraAttrs will be ignored later.
          extraAttrs = [attr for attr in self.attrS.attributeNames if attr not in self.addedAttrs]
          sectionName = mel.eval("uiRes(\"s_TPStemplateStrings.rExtraAttributes\");")
          self.createSection(sectionName, extraAttrs, True)
  
      def createAppliedSchemasSection(self):
          # USD version 0.21.2 is required because of
          # Usd.SchemaRegistry().GetPropertyNamespacePrefix()
          if Usd.GetVersion() < (0, 21, 2):
              return
  
          showAppliedSchemasSection = False
  
          # loop on all applied schemas and store all those
          # schema into a dictionary with the attributes.
          # Storing the schema into a dictionary allow us to
          # group all instances of a MultipleApply schema together
          # so we can later display them into the same UI section.
          #
          # By example, if UsdCollectionAPI is applied twice, UsdPrim.GetAppliedSchemas()
          # will return ["CollectionAPI:instance1","CollectionAPI:instance2"] but we want to group
          # both instance inside a "CollectionAPI" section.
          #
          schemaAttrsDict = {}
          appliedSchemas = self.prim.GetAppliedSchemas()
          for schema in appliedSchemas:
              if Usd.GetVersion() > (0, 21, 5):
                  typeAndInstance = Usd.SchemaRegistry().GetTypeNameAndInstance(schema)
              else:
                  typeAndInstance = Usd.SchemaRegistry().GetTypeAndInstance(schema)
              typeName        = typeAndInstance[0]
              schemaType      = Usd.SchemaRegistry().GetTypeFromName(typeName)
  
              if schemaType.pythonClass:
                  isMultipleApplyAPISchema = Usd.SchemaRegistry().IsMultipleApplyAPISchema(typeName)
                  if isMultipleApplyAPISchema:
                      # get the attributes names. They will not include the namespace and instance name.
                      instanceName = typeAndInstance[1]
                      attrList = schemaType.pythonClass.GetSchemaAttributeNames(False, instanceName)
                      # build the real attr name
                      # By example, collection:lightLink:includeRoot
                      namespace = Usd.SchemaRegistry().GetPropertyNamespacePrefix(typeName)
                      prefix = namespace + ":" + instanceName + ":"
                      attrList = [prefix + i for i in attrList]
  
                      if typeName in schemaAttrsDict:
                          schemaAttrsDict[typeName] += attrList
                      else:
                          schemaAttrsDict[typeName] = attrList
                  else:
                      attrList = schemaType.pythonClass.GetSchemaAttributeNames(False)
                      schemaAttrsDict[typeName] = attrList
  
                  # The "Applied Schemas" will be only visible if at least
                  # one applied Schemas has attribute.
                  if not showAppliedSchemasSection:
                      for attr in attrList:
                          if self.attrS.hasAttribute(attr):
                              showAppliedSchemasSection = True
                              break
  
          # Create the "Applied Schemas" section
          # with all the applied schemas
          if showAppliedSchemasSection:
              with ufeAeTemplate.Layout(self, 'Applied Schemas', collapse=True):
                  for typeName, attrs in schemaAttrsDict.items():
                      typeName = self.sectionNameFromSchema(typeName)
                      self.createSection(typeName, attrs, False)
  
  
      def buildUI(self):
          usdSch = Usd.SchemaRegistry()
  
          self.suppressArrayAttribute()
  
          # We use UFE for the ancestor node types since it caches the
          # results by node type.
          for schemaType in self.item.ancestorNodeTypes():
              schemaType = usdSch.GetTypeFromName(schemaType)
              schemaTypeName = schemaType.typeName
              sectionName = self.sectionNameFromSchema(schemaTypeName)
              if schemaType.pythonClass:
                  attrsToAdd = schemaType.pythonClass.GetSchemaAttributeNames(False)
  
                  # We have a special case when building the Xformable section.
                  if schemaTypeName == 'UsdGeomXformable':
                      self.createTransformAttributesSection(sectionName, attrsToAdd)
                  else:
                      sectionsToCollapse = ['Curves', 'Point Based', 'Geometric Prim', 'Boundable',
                                            'Imageable', 'Field Asset', 'Light']
                      collapse = sectionName in sectionsToCollapse
                      self.createSection(sectionName, attrsToAdd, collapse)
  
      def suppressArrayAttribute(self):
          # Suppress all array attributes except UsdGeom.Tokens.xformOpOrder
          if not self.showArrayAttributes:
              for attrName in self.attrS.attributeNames:
                  if self.isArrayAttribute(attrName):
                      self.suppress(attrName)
  
      def isArrayAttribute(self, attrName):
          # Note: UsdGeom.Tokens.xformOpOrder is a exception,
          #       we are not considering this attribute as an array.
          if self.attrS.attributeType(attrName) == ufe.Attribute.kGeneric and \
             attrName != UsdGeom.Tokens.xformOpOrder:
              attr = self.prim.GetAttribute(attrName)
              typeName = attr.GetTypeName()
              return typeName.isArray
          return False
  
      def attributeHasConnections(self, attrName):
          # Simple helper to return whether the input attribute (by name) has
          # any connections.
          attr = self.prim.GetAttribute(attrName)
          return attr.HasAuthoredConnections() if attr else False
  