11# -*- coding: utf-8 -*-
22
3- # Hack for static typing analysis:
4- # the test below only passes in the IDE (eg VsCode); Maya doesn't know or care about typing.
5- MYPY = False
6- if MYPY :
7- from typing import *
8- # fake-declare some Python2-only types to fix Pylance analyzer false positives (Pylance is Python3-only)
9- basestring = unicode = str
10- long = int
11- buffer = bytearray
12- file = object
13- del MYPY
14-
153import os
164import sys
175import json
2816from maya .api import OpenMaya as om , OpenMayaAnim as oma , OpenMayaUI as omui
2917from maya import OpenMaya as om1 , OpenMayaMPx as ompx1 , OpenMayaUI as omui1
3018
31- __version__ = "0.4.9 "
19+ __version__ = "0.4.10 "
3220
3321PY3 = sys .version_info [0 ] == 3
3422
7361if not IGNORE_VERSION :
7462 assert __maya_version__ >= 2015 , "Requires Maya 2015 or newer"
7563
64+ # Hack for static typing analysis
65+ #
66+ # the test below only passes in the IDE such as VSCode
67+ # Maya doesn't know or care about the `typing` library
68+ MYPY = False
69+ if MYPY :
70+ from typing import *
71+ # Fake-declare some Python2-only types to fix Pylance
72+ # analyzer false positives (Pylance is Python3-only)
73+ basestring = unicode = str
74+ long = int
75+ buffer = bytearray
76+ file = object
77+ del MYPY
78+
7679self = sys .modules [__name__ ]
7780self .installed = False
7881log = logging .getLogger ("cmdx" )
@@ -282,7 +285,13 @@ def __call__(self, enum):
282285
283286
284287def AngleUiUnit ():
285- """Unlike other angle units, this can be modified by the user at run-time"""
288+ """Dynamic angle UI unit
289+
290+ Unlike other angle units, this can be modified by the user at run-time
291+ hence it needs to be a function rather than a variable.
292+
293+ """
294+
286295 return _Unit (om .MAngle , om .MAngle .uiUnit ())
287296
288297
@@ -298,7 +307,13 @@ def AngleUiUnit():
298307
299308
300309def DistanceUiUnit ():
301- """Unlike other distance units, this can be modified by the user at run-time"""
310+ """Dynamic distance UI unit
311+
312+ Unlike other distance units, this can be modified by the user at run-time
313+ hence it needs to be a function rather than a variable.
314+
315+ """
316+
302317 return _Unit (om .MDistance , om .MDistance .uiUnit ())
303318
304319
@@ -1005,7 +1020,11 @@ def dump(self, ignore_error=True, preserve_order=False):
10051020
10061021 def dumps (self , indent = 4 , sort_keys = True , preserve_order = False ):
10071022 """Return a JSON compatible dictionary of all attributes"""
1008- return json .dumps (self .dump (preserve_order ), indent = indent , sort_keys = sort_keys )
1023+ return json .dumps (
1024+ self .dump (preserve_order ),
1025+ indent = indent ,
1026+ sort_keys = sort_keys
1027+ )
10091028
10101029 def type (self ):
10111030 """Return type name
@@ -2730,14 +2749,17 @@ def typeClass(self):
27302749 k = om .MFnNumericAttribute (attr ).numericType ()
27312750 if k == om .MFnNumericData .kBoolean :
27322751 return Boolean
2733- elif k in (om .MFnNumericData .kLong , om .MFnNumericData .kInt ):
2752+ elif k in (om .MFnNumericData .kLong ,
2753+ om .MFnNumericData .kInt ):
27342754 return Long
27352755 elif k == om .MFnNumericData .kDouble :
27362756 return Double
27372757
2738- elif k in (om .MFn .kDoubleAngleAttribute , om .MFn .kFloatAngleAttribute ):
2758+ elif k in (om .MFn .kDoubleAngleAttribute ,
2759+ om .MFn .kFloatAngleAttribute ):
27392760 return Angle
2740- elif k in (om .MFn .kDoubleLinearAttribute , om .MFn .kFloatLinearAttribute ):
2761+ elif k in (om .MFn .kDoubleLinearAttribute ,
2762+ om .MFn .kFloatLinearAttribute ):
27412763 return Distance
27422764 elif k == om .MFn .kTimeAttribute :
27432765 return Time
@@ -2762,7 +2784,8 @@ def typeClass(self):
27622784
27632785 elif k == om .MFn .kCompoundAttribute :
27642786 return Compound
2765- elif k in (om .Mfn .kMatrixAttribute , om .MFn .kFloatMatrixAttribute ):
2787+ elif k in (om .Mfn .kMatrixAttribute ,
2788+ om .MFn .kFloatMatrixAttribute ):
27662789 return Matrix
27672790 elif k == om .MFn .kMessageAttribute :
27682791 return Message
@@ -4237,7 +4260,10 @@ def addAttr(self, node, plug):
42374260 def deleteAttr (self , plug ):
42384261 node = plug .node ()
42394262 node .clear ()
4240- return self ._modifier .removeAttribute (node ._mobject , plug ._mplug .attribute ())
4263+
4264+ return self ._modifier .removeAttribute (
4265+ node ._mobject , plug ._mplug .attribute ()
4266+ )
42414267
42424268 @record_history
42434269 def setAttr (self , plug , value ):
@@ -4254,6 +4280,32 @@ def resetAttr(self, plug):
42544280
42554281 @record_history
42564282 def connect (self , src , dst , force = True ):
4283+ """Connect one attribute to another, with undo
4284+
4285+ Examples:
4286+ >>> tm = createNode("transform")
4287+ >>> with DagModifier() as mod:
4288+ ... mod.connect(tm["rx"], tm["ry"])
4289+ ...
4290+ >>> tx = createNode("animCurveTL")
4291+
4292+ # Connect without undo
4293+ >>> tm["tx"] << tx["output"]
4294+ >>> tm["tx"].connection() is tx
4295+ True
4296+
4297+ # Automatically disconnects any connected attribute
4298+ >>> with DagModifier() as mod:
4299+ ... mod.connect(tm["sx"], tm["tx"])
4300+ ...
4301+ >>> tm["tx"].connection() is tm
4302+ True
4303+ >>> cmds.undo() if ENABLE_UNDO else DoNothing
4304+ >>> tm["tx"].connection() is tx
4305+ True
4306+
4307+ """
4308+
42574309 if isinstance (src , Plug ):
42584310 src = src ._mplug
42594311
@@ -4262,21 +4314,23 @@ def connect(self, src, dst, force=True):
42624314
42634315 if force :
42644316 # Disconnect any plug connected to `other`
4317+ disconnected = False
4318+
42654319 for plug in dst .connectedTo (True , False ):
4266- self .disconnect (plug , dst )
4320+ self .disconnect (a = plug , b = dst )
4321+ disconnected = True
4322+
4323+ if disconnected :
4324+ # Connecting after disconnecting breaks undo,
4325+ # unless we do it first.
4326+ self .doIt ()
42674327
42684328 self ._modifier .connect (src , dst )
42694329
42704330 @record_history
42714331 def disconnect (self , a , b = None , source = True , destination = True ):
42724332 """Disconnect `a` from `b`
42734333
4274- Arguments:
4275- a (Plug): Starting point of a connection
4276- b (Plug, optional): End point of a connection, defaults to all
4277- source (bool, optional): Disconnect b, if it is a source
4278- destination (bool, optional): Disconnect b, if it is a destination
4279-
42804334 Normally, Maya only performs a disconnect if the
42814335 connection is incoming. Bidirectional
42824336
@@ -4292,6 +4346,63 @@ def disconnect(self, a, b=None, source=True, destination=True):
42924346 | nodeA o---->o nodeB |
42934347 |__________| |_________|
42944348
4349+ Examples:
4350+ >>> tm1 = createNode("transform", name="tm1")
4351+ >>> tm2 = createNode("transform", name="tm2")
4352+ >>> tm3 = createNode("transform", name="tm3")
4353+
4354+ # Disconnects of unconnected attributes are ignored
4355+ >>> with DagModifier() as mod:
4356+ ... _ = mod.disconnect(tm1["rx"], tm1["ry"])
4357+ ... _ = mod.disconnect(tm1["rx"], tm1["rz"])
4358+ ... _ = mod.disconnect(tm1["rx"], tm1["sy"])
4359+ ... _ = mod.disconnect(tm1["rx"], tm1["ty"])
4360+ ...
4361+
4362+ # This doesn't throw an error
4363+ >>> cmds.undo() if ENABLE_UNDO else DoNothing
4364+
4365+ # Disconnect either source or destination, only
4366+ >>> a, b = tm1["tx"], tm2["ty"]
4367+ >>> a << b
4368+ >>> a.connection() is tm2
4369+ True
4370+
4371+ # b is a source, not a destination, so this does nothing
4372+ >>> a.disconnect(b, source=False, destination=True)
4373+ >>> a.connection() is tm2
4374+ True
4375+
4376+ # This on the other hand..
4377+ >>> a.disconnect(b, source=True, destination=False)
4378+ >>> a.connection() is tm2
4379+ False
4380+ >>> a.connection() is None
4381+ True
4382+
4383+ # Default is to disconnect both
4384+ >>> tm1["tx"] >> tm2["tx"]
4385+ >>> tm2["tx"] >> tm3["tx"]
4386+ >>> tm1["tx"].connection() is tm2
4387+ True
4388+ >>> tm3["tx"].connection() is tm2
4389+ True
4390+ >>> tm2["tx"].disconnect()
4391+ >>> tm1["tx"].connection() is tm2
4392+ False
4393+ >>> tm3["tx"].connection() is tm2
4394+ False
4395+
4396+ Arguments:
4397+ a (Plug): Starting point of a connection
4398+ b (Plug, optional): End point of a connection, defaults to all
4399+ source (bool, optional): Disconnect b, if it is a source
4400+ destination (bool, optional): Disconnect b, if it
4401+ is a destination
4402+
4403+ Returns:
4404+ count (int): Number of disconnected attributes
4405+
42954406 """
42964407
42974408 if isinstance (a , Plug ):
@@ -4300,22 +4411,29 @@ def disconnect(self, a, b=None, source=True, destination=True):
43004411 if isinstance (b , Plug ):
43014412 b = b ._mplug
43024413
4303- if b is None :
4304- # Disconnect any plug connected to `other`
4305- if source :
4306- for plug in a .connectedTo (True , False ):
4307- self ._modifier .disconnect (plug , a )
4414+ count = 0
4415+ incoming = (True , False )
4416+ outgoing = (False , True )
43084417
4309- if destination :
4310- for plug in a .connectedTo (False , True ):
4311- self ._modifier .disconnect (a , plug )
4418+ if source :
4419+ for other in a .connectedTo (* incoming ):
43124420
4313- else :
4314- if source :
4315- self ._modifier .disconnect (a , b )
4421+ # Limit disconnects to the attribute provided
4422+ if b is not None and other != b :
4423+ continue
4424+
4425+ self ._modifier .disconnect (other , a )
4426+ count += 1
4427+
4428+ if destination :
4429+ for other in a .connectedTo (* outgoing ):
4430+ if b is not None and other != b :
4431+ continue
4432+
4433+ self ._modifier .disconnect (a , other )
4434+ count += 1
43164435
4317- if destination :
4318- self ._modifier .disconnect (b , a )
4436+ return count
43194437
43204438 if ENABLE_PEP8 :
43214439 do_it = doIt
@@ -4479,14 +4597,15 @@ def __enter__(self):
44794597 return self
44804598 else :
44814599 cmds .error (
4482- "'%s' does not support context manager functionality for Maya 2017 "
4483- "and below" % self .__class__ .__name__
4600+ "'%s' does not support context manager functionality "
4601+ "for Maya 2017 and below" % self .__class__ .__name__
44844602 )
44854603
44864604 def __exit__ (self , exc_type , exc_value , tb ):
44874605 if self ._previousContext :
44884606 self ._previousContext .makeCurrent ()
44894607
4608+
44904609# Alias
44914610Context = DGContext
44924611
@@ -5189,7 +5308,10 @@ def __init__(self, label, **kwargs):
51895308 kwargs .pop ("name" , None )
51905309 kwargs .pop ("fields" , None )
51915310 kwargs .pop ("label" , None )
5192- super (Divider , self ).__init__ (label , fields = (label ,), label = " " , ** kwargs )
5311+
5312+ super (Divider , self ).__init__ (
5313+ label , fields = (label ,), label = " " , ** kwargs
5314+ )
51935315
51945316
51955317class String (_AbstractAttribute ):
@@ -5333,7 +5455,7 @@ def default(self, cls=None):
53335455
53345456class Compound (_AbstractAttribute ):
53355457 """One or more nested attributes
5336-
5458+
53375459 Examples:
53385460 >>> _ = cmds.file(new=True, force=True)
53395461 >>> node = createNode("transform")
@@ -5346,7 +5468,7 @@ class Compound(_AbstractAttribute):
53465468 1.0
53475469 >>> node["compoundAttr"]["child2"].read()
53485470 5.0
5349-
5471+
53505472 # Also supports nested attributes
53515473 >>> node.addAttr(
53525474 ... Compound("parent", children=[
0 commit comments