-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from yantor3d/rporter/MDAGModifier
Implment MDagModifier bindings
- Loading branch information
Showing
10 changed files
with
311 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,6 @@ tmp/* | |
MFn.Types.inl | ||
devkit.tgz | ||
devkitBase | ||
|
||
# IDE stuff | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
param ( | ||
[string]$maya_version = "2020" | ||
[string]$maya_version = "2022" | ||
) | ||
|
||
docker run -ti --rm ` | ||
-v ${env:DEVKIT_LOCATION}:/devkit ` | ||
-v ${PWD}:/workspace ` | ||
-e DEVKIT_LOCATION=/devkit ` | ||
cmdc ./build_linux.sh $maya_version | ||
cmdc:${maya_version} ` | ||
./build_linux.sh $maya_version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
param ( | ||
[string]$maya_version = "2022" | ||
) | ||
|
||
docker run -ti --rm ` | ||
-v ${PWD}:/workspace ` | ||
cmdc:${maya_version} ` | ||
mayapy -m nose -xv --exe ./tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
#include "MDGModifier.inl" | ||
|
||
py::class_<MDagModifier, MDGModifier>(m, "DagModifier") | ||
.def(py::init<>()) | ||
|
||
.def("createNode", [](MDagModifier & self, std::string type, MObject parent = MObject::kNullObj) -> MObject { | ||
if (!parent.isNull()) | ||
{ | ||
validate::has_fn( | ||
parent, MFn::kDagNode, | ||
"Cannot createNode - 'parent' must be a 'kDagNode' object , not a '^1s' object." | ||
); | ||
} | ||
|
||
MString type_name(type.c_str()); | ||
MStatus status; | ||
MObject result = self.createNode(type_name, parent, &status); | ||
|
||
if (status == MS::kInvalidParameter) | ||
{ | ||
MString error_msg("Cannot create dependency node '^1s' - use DGModifier instead."); | ||
error_msg.format(error_msg, type_name); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} else if (result.isNull()) { | ||
MString error_msg("Cannot create unknown node type '^1s'."); | ||
error_msg.format(error_msg, type_name); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} | ||
|
||
CHECK_STATUS(status) | ||
|
||
return result; | ||
}, | ||
R"pbdoc(Adds an operation to the modifier to create a DAG node of the specified type. | ||
If a parent DAG node is provided the new node will be parented under it. | ||
If no parent is provided and the new DAG node is a transform type then it will be parented under the world. | ||
In both of these cases, the method returns the new DAG node. | ||
If no parent is provided and the new DAG node is not a transform type | ||
then a transform node will be created and the child parented under that. | ||
The new transform will be parented under the world \ | ||
and it is the transform node which will be returned by the method, not the child. | ||
None of the newly created nodes will be added to the DAG until the modifier's doIt() method is called.)pbdoc") | ||
|
||
.def("createNode", [](MDagModifier & self, MTypeId typeId, MObject parent = MObject::kNullObj) -> MObject { | ||
if (!parent.isNull()) | ||
{ | ||
validate::has_fn( | ||
parent, MFn::kDagNode, | ||
"Cannot createNode - 'parent' must be a 'kDagNode' object , not a '^1s' object." | ||
); | ||
} | ||
|
||
MString type_id_str = MString() + typeId.id(); | ||
|
||
MStatus status; | ||
MObject result = self.createNode(typeId, parent, &status); | ||
|
||
if (status == MS::kInvalidParameter) | ||
{ | ||
MString error_msg("Cannot create dependency node with type ID '^1s'' - use DGModifier instead."); | ||
error_msg.format(error_msg, type_id_str); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} else if (result.isNull()) { | ||
MString error_msg("Cannot create unknown node with type ID '^1s'."); | ||
error_msg.format(error_msg, type_id_str); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} | ||
|
||
CHECK_STATUS(status) | ||
|
||
return result; | ||
}, | ||
R"pbdoc(Adds an operation to the modifier to create a DAG node of the specified type. | ||
If a parent DAG node is provided the new node will be parented under it. | ||
If no parent is provided and the new DAG node is a transform type then it will be parented under the world. | ||
In both of these cases the method returns the new DAG node. | ||
If no parent is provided and the new DAG node is not a transform type | ||
then a transform node will be created and the child parented under that. | ||
The new transform will be parented under the world \ | ||
and it is the transform node which will be returned by the method, not the child. | ||
None of the newly created nodes will be added to the DAG until the modifier's doIt() method is called.)pbdoc") | ||
|
||
.def("reparentNode", [](MDagModifier & self, MObject node, MObject newParent = MObject::kNullObj) { | ||
validate::is_not_null(node, "Cannot reparent a null object."); | ||
|
||
if (!node.hasFn(MFn::kDagNode)) | ||
{ | ||
MString error_msg("Cannot parent '^1s' to '^2s' - must specify a 'kDagNode' object , not a '^3s' object."); | ||
error_msg.format( | ||
error_msg, | ||
MFnDependencyNode(node).name(), | ||
newParent.isNull() ? "the world" : MFnDependencyNode(newParent).name(), | ||
node.apiTypeStr() | ||
); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} | ||
if (!newParent.isNull()) | ||
{ | ||
if (!newParent.hasFn(MFn::kDagNode)) | ||
{ | ||
MString error_msg("Cannot parent '^1s' to '^2s' - must specify a 'kDagNode' object , not a '^3s' object."); | ||
error_msg.format( | ||
error_msg, | ||
MFnDependencyNode(node).name(), | ||
newParent.isNull() ? "the world" : MFnDependencyNode(newParent).name(), | ||
newParent.apiTypeStr() | ||
); | ||
throw pybind11::type_error(error_msg.asChar()); | ||
} | ||
|
||
MFnDagNode fn(newParent); | ||
|
||
if (fn.isChildOf(node)) | ||
{ | ||
MString error_msg("Cannot parent '^1s' to one of its children, '^2s'."); | ||
error_msg.format( | ||
error_msg, | ||
MFnDagNode(node).partialPathName(), | ||
MFnDagNode(newParent).partialPathName() | ||
); | ||
throw std::invalid_argument(error_msg.asChar()); | ||
} | ||
} | ||
|
||
if (node == newParent) | ||
{ | ||
MString error_msg("Cannot parent '^1s' to itself."); | ||
error_msg.format( | ||
error_msg, | ||
MFnDagNode(node).partialPathName() | ||
); | ||
throw std::invalid_argument(error_msg.asChar()); | ||
} | ||
|
||
MStatus status = self.reparentNode(node, newParent); | ||
|
||
CHECK_STATUS(status) | ||
}, | ||
R"pbdoc(Adds an operation to the modifier to reparent a DAG node under a specified parent. | ||
If no parent is provided then the DAG node will be reparented under the world, so long as it is a transform type. | ||
If it is not a transform type then the doIt() will raise a RuntimeError.)pbdoc"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import cmdc | ||
import nose | ||
|
||
from nose.plugins.skip import SkipTest | ||
|
||
from maya import cmds | ||
from maya.api import OpenMaya | ||
|
||
from . import assert_equals, as_obj, as_plug, new_scene | ||
|
||
def test_createNode(): | ||
return | ||
|
||
node = cmds.createNode('transform', name='root') | ||
node_obj = as_obj(node) | ||
null_obj = cmdc.Object() | ||
type_id = cmdc.FnDependencyNode(node_obj).typeId() | ||
|
||
for doc, (value, parent) in ( | ||
['a valid type name', ('transform', null_obj)], | ||
['a valid type name and parent', ('transform', node_obj)], | ||
['a valid typeId', (cmdc.TypeId(type_id), null_obj)], | ||
['a valid typeId and parent', (cmdc.TypeId(type_id), node_obj)], | ||
): | ||
test_createNode.__doc__ = """Test MDagModifier::createNode if called with {}.""".format(doc) | ||
|
||
yield _createNode_pass, value, parent | ||
|
||
not_a_dag = as_obj('time1') | ||
not_a_node = as_plug('persp.message').attribute() | ||
type_id = cmdc.FnDependencyNode(as_obj('time1')).typeId() | ||
|
||
for doc, (value, parent) in ( | ||
['an invalid type name', ('foobar', null_obj)], | ||
['a non-DAG type name', ('network', null_obj)], | ||
['an invalid typeId', (cmdc.TypeId(0xdeadbeef), null_obj)], | ||
['an non-DAG typeId', (cmdc.TypeId(type_id), null_obj)], | ||
['an invalid parent (not a DAG node)', ('transform', not_a_dag)], | ||
['an invalid parent (not a node)', ('transform', not_a_node)], | ||
): | ||
test_createNode.__doc__ = """Test MDagGModifier::createNode raises error if called with {}.""".format(doc) | ||
|
||
yield _createNode_fail, value, parent | ||
|
||
|
||
@nose.with_setup(teardown=new_scene) | ||
def _createNode_fail(value, parent): | ||
old_nodes = cmds.ls(long=True) | ||
|
||
nose.tools.assert_raises( | ||
TypeError, _createNode_pass, value, parent | ||
) | ||
|
||
new_nodes = cmds.ls(long=True) | ||
|
||
assert len(old_nodes) == len(new_nodes), "DagModifier.createNode modified the scene graph." | ||
|
||
|
||
@nose.with_setup(teardown=new_scene) | ||
def _createNode_pass(value, parent): | ||
old_nodes = cmds.ls(long=True) | ||
|
||
mod = cmdc.DagModifier() | ||
node = mod.createNode(value, parent) | ||
mod.doIt() | ||
|
||
new_nodes = cmds.ls(long=True) | ||
|
||
add_nodes = set(new_nodes) - set(old_nodes) | ||
|
||
assert not node.isNull(), "Created node is not valid." | ||
assert len(add_nodes) == 1, "`ls` did not return new node." | ||
|
||
|
||
def test_reparentNode(): | ||
node_a = cmds.createNode('transform') | ||
node_b = cmds.createNode('transform') | ||
node_c = cmds.createNode('transform', parent=node_a) | ||
node_d = cmds.createNode('transform', parent=node_c) | ||
|
||
node_obj_a = as_obj(node_a) | ||
node_obj_b = as_obj(node_b) | ||
node_obj_c = as_obj(node_c) | ||
node_obj_d = as_obj(node_d) | ||
null_obj = cmdc.Object() | ||
|
||
for doc, (node, new_parent) in ( | ||
['a null object (parent to world)', (node_obj_c, null_obj)], | ||
['a valid object', (node_obj_c, node_obj_b)], | ||
): | ||
test_reparentNode.__doc__ = """Test MDagModifier::reparentNode if called with {}.""".format(doc) | ||
|
||
yield _reparentNode_pass, node, new_parent | ||
|
||
not_a_dag = as_obj('time1') | ||
not_a_node = as_plug('persp.message').attribute() | ||
|
||
for exc, doc, (node, new_parent) in ( | ||
[TypeError, 'an invalid object (not a DAG node)', (node_obj_c, not_a_dag)], | ||
[TypeError, 'an invalid object (not a node)', (node_obj_c, not_a_node)], | ||
[ValueError, 'the same object', (node_obj_c, node_obj_c)], | ||
[ValueError, 'a parent and one of its children', (node_obj_c, node_obj_d)], | ||
): | ||
test_reparentNode.__doc__ = """Test MDagModifier::reparentNode raises an error if called with {}.""".format(doc) | ||
|
||
yield _reparentNode_fail, exc, node, new_parent | ||
|
||
|
||
def _reparentNode_pass(node, new_parent): | ||
fn_node = cmdc.FnDagNode(node) | ||
|
||
old_parent = fn_node.parent(0) | ||
|
||
mod = cmdc.DagModifier() | ||
mod.reparentNode(node, new_parent) | ||
|
||
mod.doIt() | ||
parent = fn_node.parent(0) | ||
|
||
if new_parent.isNull(): | ||
assert parent == fn_node.dagRoot(), "DagModifier.reparentNode doIt failed" | ||
else: | ||
assert parent == new_parent, "DagModifier.reparentNode doIt failed" | ||
|
||
mod.undoIt() | ||
parent = fn_node.parent(0) | ||
assert parent == old_parent, "DagModifier.reparentNode undo failed" | ||
|
||
# Parent the node to world before the next test. | ||
mod = cmdc.DagModifier() | ||
mod.reparentNode(node, old_parent) | ||
mod.doIt() | ||
|
||
|
||
def _reparentNode_fail(exception, node, new_parent): | ||
nose.tools.assert_raises( | ||
exception, | ||
cmdc.DagModifier().reparentNode, | ||
node, new_parent | ||
) |