-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmetaData.py
More file actions
2167 lines (1867 loc) · 79.7 KB
/
metaData.py
File metadata and controls
2167 lines (1867 loc) · 79.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
MetaData is data about data. This module provides a base class `MetaData` that creates
a **metaNode** inside of maya. The base class `MetaData` then provides methods to write
attributes to the created **metaNode**. This can be achieved by simply setting attributes
onto the the MetaData instance or by modifing this behaviour in a sub class. To then attach this
data into your system you can call `ConnectMetaDataTo` which physically connects to nodes together.
MetaData can also be connected to other **metaNodes** and therefore can be used to describe complex
systems, with a custom **meta hierarchy**.
MetaData can be connected to multiple nodes and nodes can have multiple **metaNodes** attached.
MetaData also has the concept of **Parts** providing methods such as GetPart and IsPart. Part data
is a **dagNode** connected to the **metaNode** as if it was standard MetaData. The only difference is
the dagNode has a attribute that starts with the "MetaData.__class__.__name__"+ "_Part". This is useful
to describe subsystems within the MetaData.
'''
# Research a undo callback to remove MetaNode[0] attributes from connected nodes if metaNode is deleted
# see http://bit.ly/Ozotoz to read on how to handle problem
# code to reproduce the problem
# Add 2 message links. Then delete on node to see the attribute isn't cleaned up. Must leave in a referenced pipeline
# meta.eMetaData.MetaData("pCube1")
# meta.eMetaData.MetaData("pCube1")
# pCore.mel.removeMultiInstance("pCube1.MetaNode[1]") # cleans up the attr
import json
import inspect
import logging
import pymel.core as pCore
import maya.cmds as cmds
import mCore
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.INFO)
def registerMClassInheritanceMapping():
global RIGISTERED_METACLASS
RIGISTERED_METACLASS = {}
RIGISTERED_METACLASS['MetaClass'] = MetaData
for mclass in mCore.itersubclasses(MetaData):
_logger.debug('registering MetaClass : %s' % mclass)
RIGISTERED_METACLASS[mclass.__name__] = mclass
def registerMClass(mclass):
global RIGISTERED_METACLASS
RIGISTERED_METACLASS = {}
RIGISTERED_METACLASS['MetaClass'] = MetaData
RIGISTERED_METACLASS[mclass.__name__] = mclass
ANIMATED_EXPORT_PLUGS = ["matrix", "float3", "float", "double3", "double", "long"]
META_NODES = ['network']
ROOT_IGNORE_PLUGS = ['caching', 'isHistoricallyInteresting', 'binMembership', 'nodeState']
META_TRANSFORM_IGNORE_PLUGS = ['publishedNodeInfo']
META_NODE_IGNORE_PLUGS = {'metaNode': ROOT_IGNORE_PLUGS,
'network': ROOT_IGNORE_PLUGS,
'EMetaTransform': ROOT_IGNORE_PLUGS + META_TRANSFORM_IGNORE_PLUGS}
def IterFilterMetaNodesForClass(Nodes, ClassData, asMetaData=True):
toFind = ""
isList = False
def toString(node):
if issubclass(type(node), basestring):
return node
elif issubclass(node, MetaData):
return node.__name__
def isNode(node):
if isList:
return pCore.cmds.getAttr("%s.%s" % (node, "metaClass")) in toFind
else:
return pCore.cmds.getAttr("%s.%s" % (node, "metaClass")) == toFind
if getattr(ClassData, "__iter__", None):
toFind = [toString(n) for n in ClassData]
isList = True
else:
toFind = toString(ClassData)
if toFind:
for m in Nodes:
if isNode(m):
if asMetaData:
yield MetaData(m)
else:
yield m
def IterMetaNodesForClass(ClassData, asMetaData=True):
"""
Iterates over the scene looking for a particular metaNode and yields any that match
:param ClassData: str or Class of type META_NODES
:param asMetaData: bool
:return: [str,] or [MetaData,]
"""
toFind = ""
isList = False
def toString(node):
if issubclass(type(node), basestring):
return node
elif issubclass(node, MetaData):
return node.__name__
def isNode(node):
if pCore.cmds.objExists("%s.%s" % (node, "metaClass")):
if isList:
return pCore.cmds.getAttr("%s.%s" % (node, "metaClass")) in toFind
else:
return pCore.cmds.getAttr("%s.%s" % (node, "metaClass")) == toFind
if getattr(ClassData, "__iter__", None):
toFind = [toString(n) for n in ClassData]
isList = True
else:
toFind = toString(ClassData)
if toFind:
for m in IterAllMetaNodes(asMetaData=False):
if isNode(m):
if asMetaData:
yield MetaData(m)
else:
yield m
def IterMetaNodesForBaseClass(MetaNodeClass, asMetaData=True):
"""
Iterates over the scene looking for a particular metaNode and yields any that match
:param MetaNodeClass: str or Class of type META_NODES
:param asMetaData: bool
:return: [str,] or [MetaData,]
"""
toFind = ""
if isinstance(MetaNodeClass, basestring):
toFind = MetaNodeClass
elif issubclass(MetaNodeClass, MetaData):
toFind = MetaNodeClass.__name__
if toFind:
for m in pCore.cmds.ls(type=META_NODES):
if pCore.objExists("%s.%s" % (m, "metaInheritance")):
if toFind in json.loads(pCore.cmds.getAttr("%s.%s" % (m, "metaInheritance"))):
if asMetaData:
yield MetaData(m)
else:
yield m
else:
# This is for MetaData that doesn't have the metaInheritance attr and we split the name
# by _ and returns [0]
if pCore.objExists("%s.%s" % (m, "metaClass")):
klasses = pCore.cmds.getAttr("%s.%s" % (m, "metaClass")).split("_")
if toFind in klasses:
if asMetaData:
yield MetaData(m)
else:
yield m
def IterAllMetaNodes(asMetaData=True):
"""
Wrapper the iterates over all metaNodes in the Scene
:param asMetaData: bool
:return: [str,] or [MetaData,]
"""
for m in pCore.cmds.ls(type="network"):
if pCore.cmds.objExists("%s.%s" % (m, "metaClass")):
if asMetaData:
yield MetaData(m)
else:
yield m
def GetMetaNodeClass(MayaNode):
'''
:Returns: <<Class>> class MetaNode cls from the Maya MetaNode
'''
global RIGISTERED_METACLASS
MetaNode = pCore.PyNode(MayaNode)
__metaKlass__ = str(MetaNode.metaClass.get())
if RIGISTERED_METACLASS:
_logger.debug("Using Cached mCore.itersubclasses set")
for s in RIGISTERED_METACLASS:
if RIGISTERED_METACLASS[s].__name__ == __metaKlass__:
_logger.debug("mCore.itersubclasses >> %s" % s)
return RIGISTERED_METACLASS[s]
else:
_logger.debug("Using non cached mCore.itersubclasses set")
for s in mCore.itersubclasses(MetaData):
if s.__name__ == __metaKlass__:
_logger.debug("mCore.itersubclasses >> %s" % s)
return s
def IsValidMetaNode(node):
'''
A Valid MetaNode as connections either to other metaNodes or tagged objects via
the message arrays metaLinks and metaTagged
:param node: str or PyNode
:return: bool
'''
try:
if any([pCore.listConnections("%s.metaLinks" % node),
pCore.listConnections("%s.%s" % (node, "metaTagged"))]):
return True
except pCore.MayaAttributeError as e:
pass
return False
def GetConnectedMetaNode(MayaNode):
node = pCore.PyNode(MayaNode)
if getattr(node, "MetaNode", None):
res = [n for n in node.MetaNode.inputs() if IsValidMetaNode(n)]
if res:
return res[0]
class MetaEnumValue(pCore.util.EnumValue):
'''
SubClass of pCore.util.EnumValue returned by Enum attributes on a MetaNode
Useful because it has no had coded link to the pCore.util.EnumValue as __enumtype
Also added a more generic __eq__ override so only key and index gets compared
'''
def __init__(self, enumtype, index, key, doc=None):
""" Create an enumeration instance """
self.__enumtype = enumtype
self.__index = index
self.__key = key
self.__doc = doc
super(MetaEnumValue, self).__init__(enumtype, index, key, doc=None)
def __repr__(self):
if self.__doc:
return "MetaEnumValue({0!r:s}, {1!r:s}, {2!r:s}, {3!r:s}, {4!r:s})".format(
self.__enumtype,
self.__index,
self.__key,
self.__doc)
else:
return "MetaEnumValue({0!r:s}, {1!r:s}, {2!r:s})".format(self.__enumtype,
self.__index,
self.__key)
def __eq__(self, Obj):
if issubclass(type(Obj), pCore.util.EnumValue):
if self.index == Obj.index and str(self.key) == str(self.key):
return True
return False
class MetaDataDecorators:
"""
Decorator's for use with this module
"""
@staticmethod
def DebugInheritance(Log):
import inspect
def __init__(func):
def with_logging(self, *args, **kwargs):
argspec = inspect.getargspec(func)
MRO = inspect.getmro(self.__class__)
# Can't seem to get self.__class__ to be the current __init__ being logged
_logger.debug("\nIntializing ... %s" % self.__class__)
_logger.debug("%s" % argspec.__repr__())
string = ("MRO:", [x.__name__ for x in MRO])
_logger.debug(string)
string = ("*args:", [x for x in args])
_logger.debug(string)
string = ("**kwargs:", [x for x in kwargs])
_logger.debug(string)
return func(self, *args, **kwargs)
return with_logging
return __init__
@staticmethod
def FilterGetParts(Index=0, List=False):
'''
Get Parts returns a tuple with (PyNode, PartData).
If your only interested in the PyNode then you can get
the PyNode using this decorator and then return the list or
just the 1st element
'''
def funcWrap(func):
def wrap(*args):
res = func(*args)
if res:
if List:
return [j[Index] for j in res]
else:
filteredList = [j[Index] for j in res]
if filteredList:
return filteredList[0]
else:
return None
else:
if List: return []
return None
return wrap
return funcWrap
@staticmethod
def SearchPartDataDict(func):
'''
Search for data in dict stored on a MetaNode as PartData
:param *args: Pass (Key, Value) pairs as tuples or a single `dict`
:keyword Absolute: `bool` returns joints with *all* of the args passed
'''
def SeachFunc(self, *args, **kw):
kw.setdefault("Absolute", True)
if args:
if isinstance(args[0], dict):
args = tuple(args[0].items())
def Compare(Key, Value, Data):
if not Data.has_key(Key): return False
if issubclass(type(Value), str) and issubclass(type(Data[Key]), str):
if Value != None:
return Data[Key].upper() == Value.upper()
return Key.upper() == Data[Key].upper()
else:
if Value != None:
return Data[Key] == Value
else:
return Data[Key] == Key
def CompareWrap(Data):
for Key, Value in args:
if not Compare(Key, Value, Data):
return False
return True
res = []
if kw["Absolute"]:
for Joint, Data in self.m_GetParts():
dataKey = CompareWrap(Data)
if dataKey:
res.append(Joint)
return list(set(res))
else:
for Joint, Data in self.m_GetParts():
for Key, Value in args:
dataKey = Compare(Key, Value, Data)
if dataKey:
res.append(Joint)
heatDict = {}
cache = []
for each in res:
if each not in cache:
cache.append(each)
if heatDict.has_key(res.count(each)):
heatDict[res.count(each)].append(each)
else:
heatDict[res.count(each)] = [each]
res = []
for keys in reversed(sorted(heatDict)):
res.append(heatDict[keys])
return res
return SeachFunc
class MetaData(object):
'''
This class is the base class of all MetaNodes in Maya. It's role is to
support the metaNode plug-in. Creating a MetaNode instance will create a metaNode
with in the Maya scene. You can then add attributes to this class and the attribute
will automatically added to the metaNode in Maya. In truth this is a utility class that
extends the metaNode plug-in so that interaction is easy through code.
:param Node: `str` or `PyNode` that is either a dagNode you want to add metaData to or
a **metaNode** itself.
:keyword: METAVERSION, default = 1
'''
# This is the attribute that gets added to tagged/connected nodes
MetaNodeMessageAttr = "MetaNode"
def __new__(cls, *args, **kw):
Node = None
if args:
Node = args[0]
if Node:
if isinstance(Node, MetaData):
Node = pCore.PyNode(Node.MetaNode)
else:
Node = pCore.PyNode(Node)
if Node.type() in META_NODES:
if Node.hasAttr("metaClass"):
try:
metaClass = GetMetaNodeClass(Node)
if metaClass:
_logger.debug("Found MetaNode Class >> %r" % metaClass)
return super(cls.__class__, cls).__new__(metaClass)
else:
pass
except StandardError, Err:
_logger.exception(Err)
raise Err
else:
return super(cls.__class__, cls).__new__(cls)
return super(cls.__class__, cls).__new__(cls)
@MetaDataDecorators.DebugInheritance(_logger)
def __init__(self, Node=None, **kw):
# super at this level upsets multiple inheritance model
# Prove my once more, do NOT do this with meta Data multiple inheritence
# eg Mcharacter(MCharacterABC)
# super(MetaData,self).__init__()
kw = self.__CapKwargs(**kw)
# Capture the default if they haven't been over-ridden
kw.setdefault('METAVERSION', 1.0)
kw.setdefault('NAME', "")
self._HiddenAttributes = set(["__dict__",
"__doc__",
"__weakref__",
"__module__",
"nodeState",
"caching",
"_HiddenAttributes",
"_LockedAttributes",
"_PrivateAttributes",
"_SerializeForExportAttributes",
"_MetaNodeName",
"_eHealthObject",
"MetaNode",
"PartAttributeName",
"_STOPSET"])
self._LockedAttributes = set(["metaClass",
"metaVersion",
"SerializeForExport",
"metaInheritance"])
self._PrivateAttributes = set([])
self._SerializeForExportAttributes = set([])
self._eHealthObject = None
self._STOPSET = False
# set the base internal properties, these are the ones on the MetaNode plug-in by default
self.metaClass = self.__class__.__name__
self.metaVersion = kw['METAVERSION']
self.MetaNode = None
self._MetaNodeName = kw['NAME']
self.PartAttributeName = self.__class__.__name__ + "_Part"
# If a Node has been given. This node may be a MetaNode of a Node that we
# want to tag with metaData. Determine they type of node and the either
# read the data back or create a new MetaNode
if Node:
if isinstance(Node, MetaData):
Node = pCore.PyNode(Node.MetaNode)
else:
Node = pCore.PyNode(Node)
# nType = Node.type(i=True)
if Node.type() in META_NODES:
self.__InstantiatedFromMetaDataNode(Node)
# elif 'dagNode' in nType:
# We need to check that the dagNode doesn't already have metaData. If it does then
# the best way to add extra data to the node is not passing the dagNode. But instead
# passing no Node and then connecting the metaData with ConnectMetaDataTo()
if self.HasMetaNodeMessageAttr(Node):
_logger.debug("dagNode %s has the MetaNode message attribute" % Node.name())
metaNodeInputs = self.m_HasMetaData(Node)
if metaNodeInputs:
self.__create__(Node, Name=kw["NAME"])
# raise StandardError("%s already has MetaNode" % Node.name())
else:
self.__create__(Node, Name=kw["NAME"])
else:
self.__create__(Node, Name=kw["NAME"])
# else:
# raise StandardError("Expected MetaNode or a dagNode")
else:
self.__create__(Name=kw["NAME"])
self._STOPSET = False
if self.__MetaNodeExists():
if not any([self.MetaNode.isReferenced(), self.MetaNode.isLocked()]):
self.__fillInheritanceAttr()
# sync the object data
# self.__MetaNodeUpdate()
def __create__(self, Node=None, Name="", metaType="network"):
'''
Create the MetaNode for this instance, Override to handle connecting
data other nodes at creation time. This where the self.__metaClass is
set via the property metaClass
'''
NodeName = self.__class__.__name__
if not self.m_Exists():
if Name:
NodeName = Name
self.MetaNode = pCore.createNode(metaType, ss=True)
if metaType == "network":
self.__ensureAttrs__()
self.m_SetName(NodeName)
self.__MetaNodeSetAttr("metaClass", self.metaClass)
if Node:
self.m_ConnectMetaDataTo(Node)
return True
return False
def __ensureAttrs__(self):
import maya.OpenMaya as OpenMaya
mAttr = OpenMaya.MFnMessageAttribute()
MetaTagNode = self.MetaNode.__apimfn__()
MetaTagNode.TagNetwork = mAttr.create("metaLinks", "mNetwork")
mAttr.setArray(1)
mAttr.setConnectable(1)
mAttr.setIndexMatters(0)
mAttr.setDisconnectBehavior(OpenMaya.MFnAttribute.kDelete)
MetaTagNode.TaggedObject = mAttr.create("metaTagged", "mTagged")
mAttr.setConnectable(1)
mAttr.setArray(1)
mAttr.setIndexMatters(0)
mAttr.setDisconnectBehavior(OpenMaya.MFnAttribute.kDelete)
MetaTagNode.addAttribute(MetaTagNode.TagNetwork)
MetaTagNode.addAttribute(MetaTagNode.TaggedObject)
def __setattr__(self, item, value):
"""
Override the default implementation to include adding attributes to the MetaNode
"""
super(MetaData, self).__setattr__(item, value)
if not callable(value):
if self.__MetaNodeExists():
if self.__IsSerializable(item, value):
self.__MetaNodeSetAttr(item, value)
else:
_logger.debug("MetaNode not set on instance yet to add %s :: %s" % (item, value))
def __getattribute__(self, attrName):
'''
Always try to get properties from the MetaNode in Maya before the PyObject instance
'''
# return any functions straight away
attr_ = object.__getattribute__(self, attrName)
if callable(attr_):
_logger.debug("Getting callable from MetaData :: %s" % attrName)
return attr_
## return attributes which are not serialised to the MetaNode or static attrs
elif attrName in object.__getattribute__(self, "_HiddenAttributes"):
return attr_
else:
try:
object.__getattribute__(self, "MetaNode")
try:
if object.__getattribute__(self, "_MetaNodeGetAttr"):
try:
func = object.__getattribute__(self, "_MetaNodeGetAttr")
data = func(attrName)
if type(data) == unicode:
return str(data)
else:
return data
except:
_logger.debug("Error Getting data from MetaNode :: %s" % attrName)
return object.__getattribute__(self, attrName)
return object.__getattribute__(self, attrName)
else:
_logger.debug("Getting data from Object Memory :: No MetaNode exists yet")
return object.__getattribute__(self, attrName)
except StandardError, Err:
_logger.exception(Err)
return object.__getattribute__(self, attrName)
except StandardError, Err:
# _logger.exception(Err)
return attr_
def __delattr__(self, name):
"""
Override the default implementation to include deleting attributes from the MetaNode
"""
object.__delattr__(self, name)
metaProperty = None
if self.MetaNode:
if self.MetaNode.hasAttr(name):
metaProperty = pCore.Attribute('%s.%s' % (self.MetaNode, name))
if metaProperty.isLocked():
metaProperty.setLocked(False)
metaProperty.delete()
self.m_RefreshAE()
def __eq__(self, obj):
if isinstance(obj, self.__class__):
if obj.MetaNode and self.MetaNode:
if obj.MetaNode == self.MetaNode:
return True
else:
return False
elif obj.__dict__ == self.__dict__:
return True
else:
return False
else:
return False
def __repr__(self):
'''
Debug class representation
'''
Node = ""
if self.__MetaNodeExists():
Node = self.MetaNode
return "%s(%r)" % (self.__class__.__name__, Node)
def __fillInheritanceAttr(self):
'''
fills the metaInheritance attribute on the metaNode
:return:
'''
try:
i = []
for c in reversed(self.__class__.__mro__):
if c != object:
i.append(c.__name__)
self.metaInheritance = i
except:
pass
def __AddMetaNodeMessageAttr(self, Node, **kw):
'''
Adds the message attribute responsible for hooking MetaData to nodes
'''
kw.setdefault("m", True)
kw.setdefault("im", False)
Node = str(Node)
cmds.addAttr(Node, longName=self.MetaNodeMessageAttr, **kw)
_attr = Node + "." + self.MetaNodeMessageAttr
return _attr
@classmethod
def HasMetaNodeMessageAttr(cls, Node):
'''
:returns: if the message attribute responsible for hooking MetaData to nodes exists
'''
_attr = str(Node) + "." + cls.MetaNodeMessageAttr
return cmds.objExists(_attr)
@classmethod
def GetMetaNodeMessageAttr(cls, Node, asString=False):
'''
:returns: if the message attribute responsible for hooking MetaData to nodes exists
'''
if cls.HasMetaNodeMessageAttr(Node):
if asString:
return str(Node) + "." + cls.MetaNodeMessageAttr
else:
Node = pCore.PyNode(Node)
return getattr(Node, cls.MetaNodeMessageAttr)
def __InstantiatedFromMetaDataNode(self, Node):
'''
Grabs all the Attributes on the MetaNode and sets them on the instance __dict__
Used when we are creating a new instance of a metaNode from and existing metaNode in the scene
IE MetaData("MyMetaNode")
'''
self.MetaNode = Node
MetaNodeAttrs = self.m_GetMetaNodeAttributes()
for attr in MetaNodeAttrs:
name = str(attr.plugAttr(longName=True))
value = self._MetaNodeGetAttr(attr.plugAttr(longName=True))
super(MetaData, self).__setattr__(name, value)
def __MetaNodeExists(self):
'''
Checks that this instance has the MetaNode and that the Node Exists in the Maya scene
'''
if self.__dict__.has_key('MetaNode') and self.__dict__['MetaNode']:
if pCore.objExists(self.__dict__['MetaNode']):
return True
return False
def __MetaNodeHasAttr(self, attrName):
'''
Wrap that checks for the MetaNode then the attribute
:returns: `bool`
'''
if self.__MetaNodeExists():
return self.MetaNode.hasAttr(attrName)
return False
def __IsSerializable(self, attributeName, value):
'''
:returns: If this attribute should be written to the MetaNode
:rtype: `bool`
'''
_logger.debug("__IsSerializable :: %s , %s" % (attributeName, value))
if attributeName in self._HiddenAttributes:
_logger.debug("__IsSerializable >> FALSE")
return False
return True
def __MetaNodeSetAttr(self, attributeName, value):
'''
A prerequisite is that the MetaNode exists and the property is a valid MetaProperty
to set on the instance. The __setattr__ method will then call this method.
'''
if self._STOPSET:
_logger.warning("Set to not set Data via _STOPSET")
return
AttributeData = self.__GetAttributeDataDict(attributeName, value)
if _logger.getEffectiveLevel() == logging.DEBUG:
print "__MetaNodeSetAttr :: AttributeName=%s , Value=%s" % (attributeName, value)
for keys in AttributeData:
print "AttributeData[%s] = %s" % (keys, AttributeData[keys])
print "\n"
if AttributeData["Delete"] and AttributeData["PyNodeAttribute"]:
if AttributeData["Locked"]:
AttributeData["PyNodeAttribute"].setLocked(False)
AttributeData["PyNodeAttribute"].delete()
AttributeData["AddMethod"](attributeName, **AttributeData)
elif AttributeData["PyNodeAttribute"]:
try:
if AttributeData["PyNodeAttributeType"] in [pCore.util.Enum, pCore.util.EnumValue,
MetaEnumValue]:
self.__SetEnumAttr(**AttributeData)
elif AttributeData["AddMethod"] == self.__AddJsonAttr:
self.__SetJsonData(**AttributeData)
else:
self.__SetStandardAttr(**AttributeData)
except StandardError, Err:
_logger.exception(Err)
else:
if AttributeData["Locked"] and AttributeData["PyNodeAttribute"]:
AttributeData["PyNodeAttribute"].setLocked(False)
AttributeData["AddMethod"](attributeName, **AttributeData)
if AttributeData["Locked"]:
try:
# If it's the 1st time the attribute is created then this doesn't exists in the
# dict
AttributeData["PyNodeAttribute"].setLocked(True)
except:
try:
# Manually try to lock the 1st time created attrs
attr = pCore.Attribute("%s.%s" % (self.MetaNode, attributeName))
attr.setLocked(True)
except:
pass
def _MetaNodeGetAttr(self, PropertyName):
'''
prerequisite :: self.MetaNode and PropertyName must exist
Gets the property from self.MetaNode. IE self.MetaNode.PropertyName.get(). Inspects
the attribute on the MetaNode to understand what to return. Attributes that shortName start
with json_ will be decoded by the json parser.
:returns: decoded attribute value
'''
pnAttr = pCore.PyNode("%s.%s" % (object.__getattribute__(self, "MetaNode"), PropertyName))
pnAttrType = self.m_AttributeTypeToPythonType(pnAttr)
if str(pnAttr.shortName()).startswith("json_"):
_logger.debug("Getting data from MetaNode %s as JSON)" % pnAttr.longName())
# strip unicode
try:
data = pnAttr.get().replace("u'", "'")
return json.loads(str(data))
except:
return ""
if pnAttrType == bool:
_logger.debug("Getting data from MetaNode %s as bool(int)" % pnAttr.longName())
return bool(pnAttr.get())
elif pnAttrType == pCore.util.Enum:
_logger.debug("Getting data from MetaNode %s as enum()" % pnAttr.longName())
data = MetaEnumValue(pnAttr.longName(),
pnAttr.get(),
str(pnAttr.get(asString=True)))
return data
else:
_logger.debug("Getting data from MetaNode %s" % pnAttr.longName())
return pnAttr.get()
def __MetaNodeUpdate(self):
'''
Loop through all the attributes on the object updating the MetaData
'''
if self.__MetaNodeExists():
for property_ in self.__dict__:
if self.__IsSerializable(property_, self.__dict__[property_]):
self.__MetaNodeSetAttr(property_, self.__dict__[property_])
def __GetMetaNodeAttribute(self, AttributeName):
try:
return pCore.PyNode("%s.%s" % (object.__getattribute__(self, "MetaNode"), AttributeName))
except:
return None
def __GetAttributeDataDict(self, attributeName, value):
'''
Collects data about the attribute and value into a dict so we can process it
efficiently when added or updating the data
:returns: `dict`
'''
StandardMayaTypes = [str, unicode, int, bool, float, pCore.util.Enum, pCore.util.EnumValue, MetaEnumValue]
setLocked = attributeName in self._LockedAttributes
setPrivate = attributeName in self._PrivateAttributes
DataDict = dict(PyNodeAttribute=None, PyNodeAttributeType=None,
Delete=False, Locked=setLocked, Private=setPrivate, AddMethod=None, Value=value,
StandardTypes=StandardMayaTypes,
ValueType=self.m_GetPyObjectType(value))
if self.__MetaNodeHasAttr(attributeName):
DataDict["PyNodeAttribute"] = self.__GetMetaNodeAttribute(attributeName)
DataDict["PyNodeAttributeType"] = self.m_AttributeTypeToPythonType(DataDict["PyNodeAttribute"])
DataDict["Locked"] = DataDict["PyNodeAttribute"].isLocked()
DataDict["Private"] = DataDict["PyNodeAttribute"].isHidden()
if DataDict["ValueType"] in DataDict["StandardTypes"]:
if DataDict["PyNodeAttribute"]:
if DataDict["ValueType"] != DataDict["PyNodeAttributeType"]:
if DataDict["ValueType"] not in [pCore.util.EnumValue, MetaEnumValue]:
DataDict["Delete"] = True
if DataDict["ValueType"] in [pCore.util.Enum]:
DataDict["AddMethod"] = self.__AddEnumAttr
else:
DataDict["AddMethod"] = self.__AddStandardAttr
if DataDict["PyNodeAttribute"].shortName().startswith("json_"):
DataDict["Delete"] = True
else:
if DataDict["ValueType"] == pCore.util.Enum:
DataDict["AddMethod"] = self.__AddEnumAttr
else:
DataDict["AddMethod"] = self.__AddStandardAttr
else:
if DataDict["PyNodeAttributeType"] != str:
DataDict["Delete"] = True
elif DataDict["PyNodeAttribute"] and not (DataDict["PyNodeAttribute"].shortName().startswith("json_")):
DataDict["Delete"] = True
DataDict["AddMethod"] = self.__AddJsonAttr
## we have to delete Private Attributes because we can't set a visible attr to hidden
if DataDict["Private"]:
DataDict["Delete"] = True
return DataDict
def __AddEnumAttr(self, attributeName, **DataDict):
valuesList = ":".join(["%s=%s" % (v.key, v.index) for v in DataDict["Value"].values()])
if DataDict["Private"]:
_logger.debug("setting property as as Private")
self.MetaNode.addAttr(attributeName, at="enum", en=valuesList, h=True)
else:
self.MetaNode.addAttr(attributeName, at="enum", en=valuesList)
DataDict["PyNodeAttribute"] = self.__GetMetaNodeAttribute(attributeName)
def __SetEnumAttr(self, **DataDict):
'''
You can only set an EnumAttr if DataDict["ValueType"] is a EnumValue
'''
if DataDict["ValueType"] in [pCore.util.EnumValue, MetaEnumValue]:
if DataDict["Value"].key in DataDict["PyNodeAttribute"].getEnums().keys() and \
DataDict["Value"].index == DataDict["PyNodeAttribute"].getEnums()[DataDict["Value"].key]:
if DataDict["PyNodeAttribute"].isLocked():
DataDict["PyNodeAttribute"].setLocked(False)
else:
raise TypeError("Attemped to set and Invalid Enum, please make sure EnumValues match attr.getEnums")
DataDict["PyNodeAttribute"].set(DataDict["Value"].index)
def __AddJsonAttr(self, attributeName, **DataDict):
if DataDict["Private"]:
_logger.debug("setting property as as Private")
self.MetaNode.addAttr(attributeName, sn="json_" + attributeName, dt="string", h=True)
else:
self.MetaNode.addAttr(attributeName, sn="json_" + attributeName, dt="string")
DataDict["PyNodeAttribute"] = self.__GetMetaNodeAttribute(attributeName)
_logger.debug("Adding %s as json data" % attributeName)
self.__SetJsonData(**DataDict)
# DataDict["PyNodeAttribute"].set(json.dumps(DataDict["Value"]))
return DataDict
def __SetJsonData(self, **DataDict):
if DataDict["PyNodeAttribute"].isLocked():
DataDict["PyNodeAttribute"].setLocked(False)
DataDict["PyNodeAttribute"].set(json.dumps(DataDict["Value"]))
def __AddStandardAttr(self, attributeName, **DataDict):
if DataDict["ValueType"] == str:
if DataDict["Private"]:
_logger.debug("setting property as as Private")
self.MetaNode.addAttr(attributeName, dt="string", h=True)
else:
self.MetaNode.addAttr(attributeName, dt="string", )
else:
if DataDict["Private"]:
_logger.debug("setting property as as Private")
self.MetaNode.addAttr(attributeName, at=DataDict["ValueType"], h=True)
else:
self.MetaNode.addAttr(attributeName, at=DataDict["ValueType"])
DataDict["PyNodeAttribute"] = self.__GetMetaNodeAttribute(attributeName)
self.__SetStandardAttr(**DataDict)
return DataDict
def __SetStandardAttr(self, **DataDict):
_logger.debug("__SetStandardAttr :: %s" % DataDict["Value"])
if DataDict["PyNodeAttribute"].isLocked():
DataDict["PyNodeAttribute"].setLocked(False)
DataDict["PyNodeAttribute"].set(DataDict["Value"])
def __CapKwargs(self, **kw):
'''
Convert all the **kw to upper so that so it's easier to filter data
'''
res = {}
for keys in kw:
res[keys.upper()] = kw[keys]
return res
@staticmethod
def m_FindConnectedMetaClass(StartNode, Node, upstream=True, ExcludeGroups=True, AsMetaData=True):
'''
Given a some data about the class your looking for. This will return all
the metaNode found on a metaNode.metaLinks direction.
:param Node: `str`, `PyNode` or `MetaNode` where we're expecting the metaClass
name as a `str` or the metaNode via a `MetaNode` or a `PyNode`
:param upstream: `bool` set the direction
:rtype `MetaNode`:
'''
if isinstance(Node, MetaData):
data = pCore.PyNode(Node.MetaNode).metaClass.get()
elif issubclass(type(Node), basestring):
try:
data = pCore.PyNode(Node.MetaNode).metaClass.get()
except:
data = Node
else:
try:
if Node.__bases__[0] == MetaData or object:
data = Node.__name__
else:
data = Node
except:
data = Node
iter = []
def getMetaClass(n, className):
return pCore.cmds.getAttr("%s.metaClass" % n) == className
if upstream:
iter = (m for m in MetaData.m_FastIterParents(StartNode=StartNode, ExcludeGroups=ExcludeGroups) \
if getMetaClass(m, data))
else:
iter = (m for m in MetaData.m_FastIterChildren(StartNode=StartNode, ExcludeGroups=ExcludeGroups) \
if getMetaClass(m, data))
result = []
for n in iter: