Skip to content

Commit

Permalink
Implement DGModifier.connect
Browse files Browse the repository at this point in the history
  • Loading branch information
yantor3d committed May 31, 2021
1 parent d13fac8 commit eb79c64
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 14 deletions.
71 changes: 65 additions & 6 deletions src/MDGModifier.inl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ py::class_<MDGModifier>(m, "DGModifier")
{
throw std::invalid_argument("Cannot add attribute to a null object.");
} else if (!node.hasFn(MFn::kDependencyNode)) {
MString error_msg("Cannot add attribute - must specify a 'node' object, not a '^1s' object.");
MString error_msg("Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a '^1s' object.");
error_msg.format(error_msg, node.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}
Expand All @@ -15,7 +15,7 @@ py::class_<MDGModifier>(m, "DGModifier")
{
throw std::invalid_argument("Cannot add null attribute to an object.");
} else if (!node.hasFn(MFn::kAttribute)) {
MString error_msg("Cannot add attribute - must specify an 'attribute' object, not a(n) '^1s' object.");
MString error_msg("Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object.");
error_msg.format(error_msg, attribute.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}
Expand Down Expand Up @@ -45,14 +45,68 @@ They will still be undone together, as a single undo action by the user,
but Maya will better be able to recover if one of the commands fails.)pbdoc")

.def("connect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) {
throw std::logic_error{"Function not yet implemented."};
},
if (sourceNode.isNull())
{
throw std::invalid_argument("Cannot connect - sourceNode is null.");
} else if (!sourceNode.hasFn(MFn::kDependencyNode)) {
MString error_msg("Cannot connect - sourceNode must be a 'node' object , not a '^1s' object.");
error_msg.format(error_msg, sourceNode.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}

if (sourceAttr.isNull())
{
throw std::invalid_argument("Cannot connect - sourceAttr is null.");
} else if (!sourceAttr.hasFn(MFn::kAttribute)) {
MString error_msg("Cannot add attribute - sourceAttr must be a 'kAttribute' object, not a(n) '^1s' object.");
error_msg.format(error_msg, sourceAttr.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}

if (destNode.isNull())
{
throw std::invalid_argument("Cannot connect - destNode is null.");
} else if (!destNode.hasFn(MFn::kDependencyNode)) {
MString error_msg("Cannot connect - destNode must be a 'kDependencyNode' object , not a '^1s' object.");
error_msg.format(error_msg, destNode.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}

if (destAttr.isNull())
{
throw std::invalid_argument("Cannot connect - destAttr is null.");
} else if (!destAttr.hasFn(MFn::kAttribute)) {
MString error_msg("Cannot add attribute - destAttr must be a 'kAttribute' object, not a(n) '^1s' object.");
error_msg.format(error_msg, destAttr.apiTypeStr());
throw pybind11::type_error(error_msg.asChar());
}

// TODO: Once the MFnAttribute classes are implemented,
// add additional validation to ensure that the attributes can be connected
MStatus status = self.connect(sourceNode, sourceAttr, destNode, destAttr);

CHECK_STATUS(status)
},
R"pbdoc(Adds an operation to the modifier that connects two plugs in the dependency graph.
It is the user's responsibility to ensure that the source and destination attributes are of compatible types.
For instance, if the source attribute is a nurbs surface then the destination must also be a nurbs surface.)pbdoc")

.def("connect", [](MDGModifier & self, MPlug source, MPlug dest) {
throw std::logic_error{"Function not yet implemented."};
if (source.isNull())
{
throw std::invalid_argument("Cannot connect - source is null.");
}

if (dest.isNull())
{
throw std::invalid_argument("Cannot connect - dest is null.");
}

// TODO: Once the MFnAttribute classes are implemented,
// add additional validation to ensure that the attributes can be connected
MStatus status = self.connect(source, dest);

CHECK_STATUS(status)
},
R"pbdoc(Adds an operation to the modifier that connects two plugs in the dependency graph.
It is the user's responsibility to ensure that the source and destination attributes are of compatible types.
Expand Down Expand Up @@ -252,7 +306,12 @@ There should be no function sets attached to the attribute at the time of the ca
}, R"pbdoc(Adds an operation to the modifier to set the lockState of a node.)pbdoc")

.def("undoIt", [](MDGModifier & self) {
throw std::logic_error{"Function not yet implemented."};
MStatus status = self.undoIt();

if (!status)
{
throw std::runtime_error(status.errorString().asChar());
}
}, R"pbdoc(Undoes all of the operations that have been given to this modifier. It is only valid to call this method after the doIt() method has been called.)pbdoc")

.def("unlinkExtensionAttributeFromPlugin", [](MDGModifier & self, MObject mPlugin, MObject mAttribute) {
Expand Down
79 changes: 71 additions & 8 deletions tests/test_MDGModifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
from . import as_obj, as_plug, new_scene


@nose.with_setup(teardown=new_scene)
def test_addAttribute_pass():
raise SkipTest("Cannot test DGModifier.addAttribute until MFnAttribute classes are implemented.")


@nose.with_setup(teardown=new_scene)
def test_addAttribute_fail():
node = as_obj(cmds.createNode('network'))
null = cmdc.Object()
Expand All @@ -25,7 +27,7 @@ def test_addAttribute_fail():
[TypeError, 'a non-node object', attr, null],
[TypeError, 'a non-attribute object', node, node],
):
test_addAttribute_fail.__doc__ = """Test MDGModifier::addAttribute errors if passed {}.""".format(doc)
test_addAttribute_fail.__doc__ = """Test MDGModifier::addAttribute raises error if called with {}.""".format(doc)

yield _addAttribute_fail, exc, node, attr

Expand All @@ -39,32 +41,92 @@ def _addAttribute_fail(exception, node, attr):


@nose.with_setup(teardown=new_scene)
def test_connect_fail():
src_plug = as_plug(cmds.createNode('transform') + '.visibility')
src_node = src_plug.node()
src_attr = src_plug.attribute()

dst_plug = as_plug(cmds.createNode('transform') + '.visibility')
dst_node = dst_plug.node()
dst_attr = dst_plug.attribute()

null = cmdc.Object()
nop_plug = cmdc.Plug()

for exc, doc, args in (
[ValueError, 'a null source object', (null, null, null, null)],
[ValueError, 'a null source attribute', (src_node, null, null, null)],
[ValueError, 'a null destination object', (src_node, src_attr, null, null)],
[ValueError, 'a null destination attribute', (src_node, src_attr, dst_node, null)],
[TypeError, 'a non-node source object', (src_attr, src_attr, null, null)],
[TypeError, 'a non-attribute source attribute',(src_node, src_node, null, null)],
[TypeError, 'a non-node destination object', (src_node, src_attr, dst_attr, dst_attr)],
[TypeError, 'a non-attribute destination attribute', (src_node, src_attr, dst_node, dst_node)],
[ValueError, 'a null source plug', (nop_plug, dst_plug)],
[ValueError, 'a null destination plug', (src_plug, nop_plug)],
):
test_connect_fail.__doc__ = """Test MDGModifier::connect raises exception if called with {}.""".format(doc)

yield _connect_fail, exc, args


def _connect_fail(exception, args):
nose.tools.assert_raises(
exception,
cmdc.DGModifier().connect,
*args
)


@nose.with_setup(teardown=new_scene)
def test_connect_pass():
src_plug = as_plug(cmds.createNode('transform') + '.visibility')
dst_plug = as_plug(cmds.createNode('transform') + '.visibility')

for doc, args in (
["plugs", (src_plug, dst_plug)],
["node/attribute pairs", (src_plug.node(), src_plug.attribute(), dst_plug.node(), dst_plug.attribute())]
):
test_connect_pass.__doc__ = """Test MDGModifier::connect if called with {}.""".format(doc)

yield _connect_pass, src_plug, dst_plug, args


def _connect_pass(src_plug, dst_plug, args):
mod = cmdc.DGModifier()
mod.connect(*args)

mod.doIt()
assert cmds.isConnected(src_plug.name(), dst_plug.name()), "DGModifier.connect doIt failed"

mod.undoIt()
assert not cmds.isConnected(src_plug.name(), dst_plug.name()), 'DGModifier.connect undoIt failed'


def test_createNode_pass():
node = cmds.createNode('network')
node = OpenMaya.MSelectionList().add(node).getDependNode(0)
type_id = OpenMaya.MFnDependencyNode(node).typeId.id()
node = as_obj(cmds.createNode('network'))
type_id = cmdc.FnDependencyNode(node).typeId()

for name, value in (
['typeName', 'network'],
['typeId', cmdc.TypeId(type_id)]
):
test_createNode_pass.__doc__ = """Test for MDGModifier::createNode({}) binding with valid arguments.""".format(name)
test_createNode_pass.__doc__ = """Test for MDGModifier::createNode if called with a valid {}.""".format(name)

yield _createNode_pass, value


@nose.with_setup(teardown=new_scene)
def test_createNode_fail():

for name, value in (
['typeName', 'foobar'],
['typeId', cmdc.TypeId(0xdeadbeef)]
):
test_createNode_fail.__doc__ = """Test MDGModifier::createNode({}) binding with invalid arguments.""".format(name)
test_createNode_fail.__doc__ = """Test MDGModifier::createNode if called with an invalid {}.""".format(name)

yield _createNode_fail, value


@nose.with_setup(teardown=new_scene)
def _createNode_fail(arg):
old_nodes = cmds.ls(long=True)

Expand All @@ -77,6 +139,7 @@ def _createNode_fail(arg):
assert len(old_nodes) == len(new_nodes), "DGModifier.createNode modified the scene graph."


@nose.with_setup(teardown=new_scene)
def _createNode_pass(arg):
old_nodes = cmds.ls(long=True)

Expand Down

0 comments on commit eb79c64

Please sign in to comment.