44# GLOBAL
55#############################################
66import collections
7+
8+ import maya .cmds as cmds
9+ from maya .api import OpenMaya
10+
711import mgear
812import mgear .pymaya as pm
9- import maya .cmds as cmds
1013import mgear .pymaya .datatypes as datatypes
11- from maya .api import OpenMaya
1214from .six import string_types
1315
1416#############################################
@@ -32,28 +34,34 @@ def addAttribute(
3234 channelBox = False ,
3335 softMinValue = None ,
3436 softMaxValue = None ,
37+ usedAsColor = None ,
38+ childSuffixes = None ,
3539):
36- """Add attribute to a node
40+ """Add attribute to a node.
3741
3842 Arguments:
39- node (dagNode ): The object to add the new attribute.
43+ node (pm.node._Node | str ): The object to add the new attribute.
4044 longName (str): The attribute name.
41- attributeType (str): The Attribute Type. Exp: 'string', 'bool',
42- 'long', etc..
43- value (float or int): The default value.
45+ attributeType (str): The Attribute Type. Exp: 'string', 'bool', 'long', etc...
46+ value (float | int | bool | list[float] | tuple[float]): The default value.
4447 niceName (str): The attribute nice name. (optional)
4548 shortName (str): The attribute short name. (optional)
46- minValue (float or int): minimum value. (optional)
47- maxValue (float or int): maximum value. (optional)
49+ minValue (float | int): minimum value. (optional)
50+ maxValue (float | int): maximum value. (optional)
4851 keyable (bool): Set if the attribute is keyable or not. (optional)
4952 readable (bool): Set if the attribute is readable or not. (optional)
5053 storable (bool): Set if the attribute is storable or not. (optional)
5154 writable (bool): Set if the attribute is writable or not. (optional)
5255 channelBox (bool): Set if the attribute is in the channelBox or not,
5356 when the attribute is not keyable. (optional)
57+ softMinValue (float): Lower limit of Maya Attribute Editor sliders. (optional)
58+ softMaxValue (float): Upper limit of Maya Attribute Editor sliders. (optional)
59+ usedAsColor (bool): Whether the added attribute is a color. (optional)
60+ childSuffixes (list[str]): List of child attribute suffixes if creating a
61+ compound attribute. Default is XYZ, or RGB if ``usedAsColor=True``.
5462
5563 Returns:
56- pm.Attribute: A pymaya `Attribute` wrapper of the new attribute.
64+ pm.Attribute: A pymaya `` Attribute` ` wrapper of the new attribute.
5765 """
5866 if isinstance (node , str ):
5967 try :
@@ -85,16 +93,50 @@ def addAttribute(
8593 if softMaxValue is not None and softMaxValue is not False :
8694 data ["softMaxValue" ] = softMaxValue
8795
96+ if usedAsColor is not None :
97+ data ["usedAsColor" ] = usedAsColor
98+
8899 data ["keyable" ] = keyable
89100 data ["readable" ] = readable
90101 data ["storable" ] = storable
91102 data ["writable" ] = writable
92103
93- if value is not None and attributeType not in ["string" ]:
104+ compoundTypes = [
105+ "float2" ,
106+ "float3" ,
107+ "double2" ,
108+ "double3" ,
109+ "long2" ,
110+ "long3" ,
111+ "short2" ,
112+ "short3" ,
113+ ]
114+
115+ if (value is not None ) and (attributeType not in compoundTypes + ["string" ]):
94116 data ["defaultValue" ] = value
95117
96118 node .addAttr (longName , ** data )
97119
120+ # Add compound children.
121+ if attributeType in compoundTypes :
122+ compoundSize = int (attributeType [- 1 ])
123+ childType = attributeType [:- 1 ]
124+ childValues = value
125+
126+ # Resolve defaults if not specified.
127+ if value is None :
128+ childValues = [None ] * compoundSize
129+ if childSuffixes is None :
130+ childSuffixes = "RGB" if usedAsColor else "XYZ"
131+ childSuffixes = list (childSuffixes [:compoundSize ])
132+
133+ for childSuffix , childValue in zip (childSuffixes , childValues ):
134+ childAttr = f"{ longName } { childSuffix } "
135+ childData = {"attributeType" : childType , "parent" : longName }
136+ if childValue is not None :
137+ childData ["defaultValue" ] = childValue
138+ node .addAttr (childAttr , ** childData )
139+
98140 if value is not None :
99141 node .setAttr (longName , value )
100142
@@ -113,18 +155,18 @@ def addVector3Attribute(
113155 writable = True ,
114156 niceName = None ,
115157 shortName = None ,
116- childLabels = [ "X" , "Y" , "Z" ] ,
158+ childLabels = ( "X" , "Y" , "Z" ) ,
117159 usedAsColor = False ,
118160 attributeType = "float3" ,
119161):
120162 """
121- Add a vector3 attribute to a node
163+ Add a vector3 attribute to a node.
122164
123165 Arguments:
124- node (dagNode ): The object to add the new attribute.
166+ node (pm.node._Node | str ): The object to add the new attribute.
125167 longName (str): The attribute name.
126- value (list of flotat ): The default value in a list for RGB.
127- exp [1.0, 0.99, 0.13].
168+ value (list[float] ): The default value in a list for RGB.
169+ E.g. [1.0, 0.99, 0.13].
128170 keyable (bool): Set if the attribute is keyable or not. (optional)
129171 readable (bool): Set if the attribute is readable or not. (optional)
130172 storable (bool): Set if the attribute is storable or not. (optional)
@@ -133,9 +175,13 @@ def addVector3Attribute(
133175 shortName (str): The attribute short name. (optional)
134176
135177 Returns:
136- str: The long name of the new attribute
137-
178+ pm.Attribute: A pymaya ``Attribute`` wrapper of the new attribute.
138179 """
180+ if isinstance (node , str ):
181+ try :
182+ node = pm .PyNode (node )
183+ except pm .MayaNodeError :
184+ pm .displayError ("{} doesn't exist or is not unique" .format (node ))
139185 if node .hasAttr (longName ):
140186 mgear .log ("Attribute already exists" , mgear .sev_error )
141187 return
@@ -249,9 +295,7 @@ def addEnumAttribute(
249295 """
250296
251297 if node .hasAttr (longName ):
252- mgear .log (
253- "Attribute '" + longName + "' already exists" , mgear .sev_warning
254- )
298+ mgear .log ("Attribute '" + longName + "' already exists" , mgear .sev_warning )
255299 return
256300
257301 data = {}
@@ -308,7 +352,9 @@ def addProxyAttribute(sourceAttrs, targets, duplicatedPolicy=None):
308352 if target .hasAttr (base_name ):
309353 if duplicatedPolicy == "index" :
310354 # Cache existing attrs for faster lookup
311- target_name = target .name () if hasattr (target , 'name' ) else str (target )
355+ target_name = (
356+ target .name () if hasattr (target , "name" ) else str (target )
357+ )
312358 existing_attrs = set (cmds .listAttr (target_name ) or [])
313359 i = 0
314360 while f"{ base_name } { i } " in existing_attrs :
@@ -375,16 +421,18 @@ def moveChannel(
375421
376422 newAtt = None
377423 attrName = attr
378- nName = pm .attributeQuery (
379- at .shortName (), node = at .node (), niceName = True
380- )
424+ nName = pm .attributeQuery (at .shortName (), node = at .node (), niceName = True )
381425 # define duplicated attribute policy
382426 if sourceNode .name () != targetNode .name ():
383427 # this policy doesn't apply for rearrange channels
384428 if pm .attributeQuery (attr , node = targetNode , exists = True ):
385429 if duplicatedPolicy == "index" :
386430 # Cache existing attrs for faster lookup
387- target_name = targetNode .name () if hasattr (targetNode , 'name' ) else str (targetNode )
431+ target_name = (
432+ targetNode .name ()
433+ if hasattr (targetNode , "name" )
434+ else str (targetNode )
435+ )
388436 existing_attrs = set (cmds .listAttr (target_name ) or [])
389437 i = 0
390438 while f"{ attr } { i } " in existing_attrs :
@@ -423,9 +471,7 @@ def moveChannel(
423471 kwargs ["max" ] = max
424472 elif atType == "enum" :
425473 en = at .getEnums ()
426- oEn = collections .OrderedDict (
427- sorted (en .items (), key = lambda t : t [1 ])
428- )
474+ oEn = collections .OrderedDict (sorted (en .items (), key = lambda t : t [1 ]))
429475 enStr = ":" .join ([n for n in oEn ])
430476
431477 # delete old attr
@@ -440,7 +486,7 @@ def moveChannel(
440486 at = atType ,
441487 dv = value ,
442488 k = True ,
443- ** kwargs
489+ ** kwargs ,
444490 )
445491 elif atType == "enum" :
446492 pm .addAttr (
@@ -603,7 +649,7 @@ def setNotKeyableAttributes(
603649
604650 # Use cmds for faster attribute setting
605651 for node in nodes :
606- node_name = node .name () if hasattr (node , ' name' ) else str (node )
652+ node_name = node .name () if hasattr (node , " name" ) else str (node )
607653 for attr_name in attributes :
608654 cmds .setAttr (f"{ node_name } .{ attr_name } " , lock = False , keyable = False , cb = True )
609655
@@ -1000,9 +1046,7 @@ class FCurveParamDef(ParamDef):
10001046
10011047 """
10021048
1003- def __init__ (
1004- self , scriptName , keys = None , interpolation = 0 , extrapolation = 0
1005- ):
1049+ def __init__ (self , scriptName , keys = None , interpolation = 0 , extrapolation = 0 ):
10061050
10071051 self .scriptName = scriptName
10081052 self .keys = keys
@@ -1021,14 +1065,10 @@ def create(self, node):
10211065 """
10221066 attr_name = addAttribute (node , self .scriptName , "double" , 0 )
10231067
1024- attrDummy_name = addAttribute (
1025- node , self .scriptName + "_dummy" , "double" , 0
1026- )
1068+ attrDummy_name = addAttribute (node , self .scriptName + "_dummy" , "double" , 0 )
10271069
10281070 for key in self .keys :
1029- pm .setDrivenKeyframe (
1030- attr_name , cd = attrDummy_name , dv = key [0 ], v = key [1 ]
1031- )
1071+ pm .setDrivenKeyframe (attr_name , cd = attrDummy_name , dv = key [0 ], v = key [1 ])
10321072
10331073 # clean dummy attr
10341074 pm .deleteAttr (attrDummy_name )
@@ -1342,9 +1382,7 @@ def get_selected_channels_full_path():
13421382 if node :
13431383 node = node [0 ]
13441384
1345- collect_attrs (
1346- node , attrs , pm .channelBox (get_channelBox (), q = True , sma = True )
1347- )
1385+ collect_attrs (node , attrs , pm .channelBox (get_channelBox (), q = True , sma = True ))
13481386
13491387 # if the attr is from shape node, we need to search in all shapes
13501388 collect_attrs (
@@ -1354,12 +1392,8 @@ def get_selected_channels_full_path():
13541392 shapes = True ,
13551393 )
13561394
1357- collect_attrs (
1358- node , attrs , pm .channelBox (get_channelBox (), q = True , sha = True )
1359- )
1360- collect_attrs (
1361- node , attrs , pm .channelBox (get_channelBox (), q = True , soa = True )
1362- )
1395+ collect_attrs (node , attrs , pm .channelBox (get_channelBox (), q = True , sha = True ))
1396+ collect_attrs (node , attrs , pm .channelBox (get_channelBox (), q = True , soa = True ))
13631397
13641398 return attrs
13651399
@@ -1405,8 +1439,7 @@ def getSelectedObjectChannels(oSel=None, userDefine=False, animatable=False):
14051439 oSel = pm .selected ()[0 ]
14061440
14071441 channels = [
1408- x .name ().rsplit ("." , 1 )[1 ]
1409- for x in oSel .listAttr (ud = userDefine , k = animatable )
1442+ x .name ().rsplit ("." , 1 )[1 ] for x in oSel .listAttr (ud = userDefine , k = animatable )
14101443 ]
14111444
14121445 return channels
@@ -1632,9 +1665,7 @@ def connect_message(source, attr):
16321665 raise TypeError ("Source attribute is not a message attribute." )
16331666
16341667 if dest_attr_type != "message" :
1635- raise TypeError (
1636- "Destination attribute is not a message attribute."
1637- )
1668+ raise TypeError ("Destination attribute is not a message attribute." )
16381669
16391670 pm .connectAttr ("{}.message" .format (src_str ), attr_name )
16401671
0 commit comments