Skip to content

Commit 11c48af

Browse files
authored
Merge pull request #628 from timothyliu3d/dev/addAttribute-float2
Add compound support to `addAttribute` function.
2 parents 80cd5ec + 8a1c11d commit 11c48af

1 file changed

Lines changed: 85 additions & 54 deletions

File tree

release/scripts/mgear/core/attribute.py

Lines changed: 85 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
# GLOBAL
55
#############################################
66
import collections
7+
8+
import maya.cmds as cmds
9+
from maya.api import OpenMaya
10+
711
import mgear
812
import mgear.pymaya as pm
9-
import maya.cmds as cmds
1013
import mgear.pymaya.datatypes as datatypes
11-
from maya.api import OpenMaya
1214
from .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

Comments
 (0)