From 2ef5b00942cb3eef8705992689f45c44b7d2479c Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 24 May 2021 08:59:04 +0100 Subject: [PATCH 01/33] Touchup parse_header.py --- scripts/parse_header.py | 180 +++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 76 deletions(-) diff --git a/scripts/parse_header.py b/scripts/parse_header.py index 583209c..c329ab2 100644 --- a/scripts/parse_header.py +++ b/scripts/parse_header.py @@ -1,64 +1,65 @@ -#/usr/bin/python +#!/usr/bin/python """Parse Header. This script generate a Pythong binding stub from a Maya devkit header. """ +import re +import os +import logging +import argparse import collections -import logging -import re -import os -import sys -import textwrap -import maya.api.OpenMaya -import maya.api.OpenMayaAnim -import maya.api.OpenMayaRender -import maya.api.OpenMayaUI +from maya.api import ( + OpenMaya, + OpenMayaUI, + OpenMayaAnim, + OpenMayaRender, +) + +log = logging.getLogger('parse_header') +handler = logging.StreamHandler() +handler.setFormatter(logging.Formatter('%(message)s')) +log.handlers.append(handler) +log.setLevel(logging.INFO) -LOG = logging.getLogger('parse_header') -LOG.setLevel(logging.INFO) -logging.basicConfig() -TEMP_DOCSTRING = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' +TEMP_DOCSTRING = ( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' +) -IGNORED_METHODS = [ +IGNORED_METHODS = ( 'className', -] +) -IGNORED_CPP_TOKENS = [ +IGNORED_CPP_TOKENS = ( '*', '&', 'const', -] +) -TEMPLATE_STR = ( -"""\ +TEMPLATE_STR = """\ py::class_(m, "{class_name}") {class_body}\ """ -) -INSTANCE_METHOD = ( -"""\ +INSTANCE_METHOD = """\ .def("{method_name}", []({class_name} & self{arguments}){return_type} {{ throw std::logic_error{{"Function not yet implemented."}}; }}, R"pbdoc({doctstring})pbdoc")\ """ -) -STATIC_METHOD = ( -"""\ +STATIC_METHOD = """\ .def_static("{method_name}", []({arguments}){return_type} {{ throw std::logic_error{{"Function not yet implemented."}}; }}, R"pbdoc({doctstring})pbdoc")\ """ -) + def parse_header(header_name): """Parse the given header. - + Args: header_name (str): Name of the header to parse (eg, 'MDagPath') """ @@ -68,8 +69,11 @@ def parse_header(header_name): else: class_name = header_name header_name = '{}.h'.format(header_name) - - header_path = os.path.join(os.environ['DEVKIT_LOCATION'], 'include', 'maya', header_name) + + header_path = os.path.join( + os.environ['DEVKIT_LOCATION'], + 'include', 'maya', header_name + ) if not os.path.exists(header_path): raise LookupError("No '{}' header in the devkit.".format(header_name)) @@ -85,13 +89,14 @@ def parse_header(header_name): m_class = find_maya_class(class_name) for (signature, arguments) in filter_function_lines(lines): - method_name, arguments, return_type = parse_method(signature, arguments) + method_name, arguments, return_type = parse_method(signature, + arguments) if method_name in IGNORED_METHODS: continue if not hasattr(m_class, method_name): - continue + continue if signature.startswith('static'): method_str_fmt = STATIC_METHOD @@ -123,22 +128,27 @@ def parse_header(header_name): code_str = ( TEMPLATE_STR.format( - class_name=class_name[1:], - class_body=class_body_str + class_name=class_name[1:], + class_body=class_body_str ) ) - tmp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'tmp') + out_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "src" + ) + out_dir = os.path.abspath(out_dir) file_name = '{}.inl'.format(class_name) - file_path = os.path.join(tmp_dir, file_name) + file_path = os.path.join(out_dir, file_name) - if not os.path.isdir(tmp_dir): - os.mkdir(tmp_dir) + if os.path.exists(file_path): + raise OSError("File '%s' already exist" % file_path) with open(file_path, 'w') as fp: fp.write(code_str) + log.info("Successfully generated '%s'" % file_path) + def parse_method(signature, arguments): """Parse the given method components. @@ -156,16 +166,18 @@ def parse_method(signature, arguments): signature_list = signature.split() - method_name = None + method_name = None return_type = None - if len(signature_list) == 1: - method_name, = signature_list + if len(signature_list) == 1: + method_name, = signature_list + elif len(signature_list) == 2: if signature_list[0] == 'virtual': method_name = signature_list[-1][1:] - else: + else: return_type, method_name = signature_list + elif len(signature_list) == 3: __, return_type, method_name = signature_list elif len(signature_list) == 4: @@ -173,14 +185,15 @@ def parse_method(signature, arguments): else: raise RuntimeError("Cannot parse signature: {}".format(signature)) - in_args, out_args = parse_arguments(argument_list, has_outs=return_type=='MStatus') + in_args, out_args = parse_arguments( + argument_list, has_outs=return_type == 'MStatus') if out_args: if len(out_args) == 1: return_type, = out_args else: return_type = 'std::tuple<{}>'.format(', '.join(out_args)) - + if return_type == 'MStatus': return_type = '' else: @@ -194,7 +207,8 @@ def parse_arguments(argument_list, has_outs): Args: argument_list (list[str]): C++ argument strings. - has_outs (bool): True if the method has an out argument (mutable value passed by reference). + has_outs (bool): True if the method has an out argument + (mutable value passed by reference). Returns: tuple[list] @@ -212,6 +226,7 @@ def parse_arguments(argument_list, has_outs): 'MStatus * ReturnStatus = NULL', 'MStatus *ReturnStatus = nullptr', 'MStatus *ReturnStatus = NULL', + 'MStatus ReturnStatus = NULL', ]: continue @@ -223,7 +238,7 @@ def parse_arguments(argument_list, has_outs): if has_outs: if arg == 'unsigned int': pass - else: + else: try: arg, __ = arg.split(' ') except ValueError: @@ -234,8 +249,8 @@ def parse_arguments(argument_list, has_outs): in_args.append(arg) else: in_args.append(arg) - - return in_args, out_args + + return in_args, out_args def sanitize_argument(arg_str): @@ -245,7 +260,10 @@ def sanitize_argument(arg_str): parts = [p for p in parts if p not in IGNORED_CPP_TOKENS] for i, _ in enumerate(parts): - parts[i] = ''.join([c for c in parts[i] if c not in IGNORED_CPP_TOKENS]) + parts[i] = ''.join([ + c for c in parts[i] + if c not in IGNORED_CPP_TOKENS + ]) return ' '.join(parts) @@ -253,15 +271,13 @@ def sanitize_argument(arg_str): def find_maya_class(class_name): """Return the maya.api class (for docstrings).""" - for module in [ - maya.api.OpenMaya, - maya.api.OpenMayaAnim, - maya.api.OpenMayaRender, - maya.api.OpenMayaUI - ]: + for module in (OpenMaya, + OpenMayaAnim, + OpenMayaRender, + OpenMayaUI): if hasattr(module, class_name): return getattr(module, class_name) - + return type('NoModule', (), {'__doc__': 'TODO: Add docstring'}) @@ -298,19 +314,19 @@ def filter_header_lines(class_name, lines): line = line.strip() if line == 'BEGIN_NO_SCRIPT_SUPPORT:': - no_script = True - continue + no_script = True + continue if line == 'END_NO_SCRIPT_SUPPORT:': - no_script = False - continue + no_script = False + continue if no_script: continue - if line.startswith('OPENMAYA_DEPRECATED'): + if line.startswith('OPENMAYA_DEPRECATED'): skip_next_statement = True - continue + continue try: # Remove trailing comments @@ -319,18 +335,18 @@ def filter_header_lines(class_name, lines): pass if not in_class_definition: - in_class_definition = class_def_re.match(line) is not None + in_class_definition = class_def_re.match(line) is not None if in_class_definition: statements.append(line) if line.endswith(','): - continue + continue statement = ' '.join(statements) if not _is_complete_statement(statement): - continue + continue if skip_next_statement: skip_next_statement = False @@ -345,20 +361,32 @@ def filter_header_lines(class_name, lines): def _is_complete_statement(statement): if '(' in statement: - return '(' in statement and ')' in statement and statement.endswith(';') - else: - return True + return ( + '(' in statement and + ')' in statement and + statement.endswith(';') + ) + return True -def main(*argv): - """Parse the given headers.""" - for name in argv: - try: - parse_header(name) - except LookupError as e: - LOG.error(str(e)) +def main(): + """Parse the given header""" + + parser = argparse.ArgumentParser() + parser.add_argument("header", help="Name of Maya header, e.g. MPlug.h") + + opts = parser.parse_args() + + try: + parse_header(opts.header) + + except OSError as e: + log.error(str(e)) + + except LookupError as e: + log.error(str(e)) if __name__ == '__main__': - main(*sys.argv[1:]) \ No newline at end of file + main() From 5e1cf1d37e9f3aabf1c72741218cd754b002880a Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 24 May 2021 09:00:06 +0100 Subject: [PATCH 02/33] PEP8-ify --- tests/test_MDagPath.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/test_MDagPath.py b/tests/test_MDagPath.py index 2c7ae76..d823fb9 100644 --- a/tests/test_MDagPath.py +++ b/tests/test_MDagPath.py @@ -12,6 +12,7 @@ def test_equality(): b = sel.getDagPath(0) assert a == b + def test_inequality(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -19,6 +20,7 @@ def test_inequality(): b = sel.getDagPath(1) assert a != b + def test_apiType(): sel = cmdc.SelectionList().add("persp") @@ -28,12 +30,13 @@ def test_apiType(): invalid_dag = cmdc.DagPath() assert invalid_dag.apiType() == cmdc.Fn.kInvalid + def test_child(): sel = cmdc.SelectionList().add("persp").add("perspShape") dag_with_child = sel.getDagPath(0) assert isinstance(dag_with_child.child(0), cmdc.Object) - + dag_without_children = sel.getDagPath(1) nose.tools.assert_raises( IndexError, @@ -48,6 +51,7 @@ def test_child(): 0 ) + def test_childCount(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -63,6 +67,7 @@ def test_childCount(): invalid_dag.childCount, ) + def test_exclusiveMatrix(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -103,6 +108,7 @@ def test_extendToShape(): invalid_dag.extendToShape, ) + @nose.with_setup(teardown=new_scene) def test_extendToShapeDirectlyBelow(): transform = cmds.polyCube()[0] @@ -143,7 +149,7 @@ def test_fullPathName(): sel = cmdc.SelectionList().add("persp").add("perspShape") persp = sel.getDagPath(0) - persp_path_name = persp.fullPathName() + persp_path_name = persp.fullPathName() assert persp_path_name == "|persp" persp_shape = sel.getDagPath(1) @@ -155,12 +161,13 @@ def test_fullPathName(): invalid_dag.fullPathName, ) + def test_getAPathTo(): sel = cmdc.SelectionList().add("persp").add("time1") persp_obj = sel.getDependNode(0) persp_dag = sel.getDagPath(0) - result = cmdc.DagPath.getAPathTo(persp_obj) + result = cmdc.DagPath.getAPathTo(persp_obj) assert result == persp_dag time_obj = sel.getDependNode(1) @@ -174,9 +181,10 @@ def test_getAPathTo(): nose.tools.assert_raises( RuntimeError, cmdc.DagPath.getAPathTo, - invalid_obj + invalid_obj ) + def test_hasFn(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -193,6 +201,7 @@ def test_hasFn(): cmdc.Fn.kTransform ) + def test_inclusiveMatrix(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -218,6 +227,7 @@ def test_inclusiveMatrixInverse(): invalid_dag.inclusiveMatrixInverse, ) + @nose.with_setup(teardown=new_scene) def test_instancenumber(): sel = cmdc.SelectionList().add("|persp|perspShape") @@ -240,6 +250,7 @@ def test_instancenumber(): invalid_dag.instanceNumber, ) + @nose.with_setup(teardown=new_scene) def test_isInstanced(): sel = cmdc.SelectionList().add("perspShape") @@ -256,6 +267,7 @@ def test_isInstanced(): invalid_dag.isInstanced, ) + @nose.with_setup(teardown=new_scene) def test_isTemplated(): class DisplayType(object): @@ -276,6 +288,7 @@ class DisplayType(object): invalid_dag.isTemplated, ) + def test_isValid(): sel = cmdc.SelectionList().add("persp") persp = sel.getDagPath(0) @@ -284,6 +297,7 @@ def test_isValid(): invalid_dag = cmdc.DagPath() assert not invalid_dag.isValid() + @nose.with_setup(teardown=new_scene) def test_isVisible(): sel = cmdc.SelectionList().add("persp") @@ -300,6 +314,7 @@ def test_isVisible(): invalid_dag.isVisible, ) + def test_length(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -315,6 +330,7 @@ def test_length(): invalid_dag.length, ) + def test_node(): sel = cmdc.SelectionList().add("persp") @@ -328,6 +344,7 @@ def test_node(): invalid_dag.length, ) + def test_numberOfShapesDirectlyBelow(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -343,6 +360,7 @@ def test_numberOfShapesDirectlyBelow(): invalid_dag.numberOfShapesDirectlyBelow, ) + def test_partialPathName(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -358,6 +376,7 @@ def test_partialPathName(): invalid_dag.partialPathName, ) + def test_pop(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -372,6 +391,7 @@ def test_pop(): invalid_dag.pop, ) + def test_push(): sel = cmdc.SelectionList().add("persp").add("perspShape") @@ -389,6 +409,7 @@ def test_push(): cmdc.Object() ) + def test_set(): sel = cmdc.SelectionList().add("persp") @@ -398,6 +419,7 @@ def test_set(): dag.set(persp_dag) assert dag == persp_dag + def test_transform(): sel = cmdc.SelectionList().add("persp").add("perspShape") From 9cc4d8d22aa696ef65a9db5518b7c3d1027a6c7c Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 24 May 2021 09:02:54 +0100 Subject: [PATCH 03/33] Initial work on MFnDagNode --- .gitignore | 3 +- src/MFnDagNode.inl | 303 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 5 + tests/test_MFnDagNode.py | 14 ++ 4 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 src/MFnDagNode.inl create mode 100644 tests/test_MFnDagNode.py diff --git a/.gitignore b/.gitignore index 0b37fae..8249d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.pyd build/** tmp/* -*.pyc \ No newline at end of file +*.pyc +MFn.Types.inl \ No newline at end of file diff --git a/src/MFnDagNode.inl b/src/MFnDagNode.inl new file mode 100644 index 0000000..930e55c --- /dev/null +++ b/src/MFnDagNode.inl @@ -0,0 +1,303 @@ +py::class_(m, "FnDagNode") + .def(py::init<>()) + + .def("addChild", [](MFnDagNode& self, + MObject& child, + unsigned int index = MFnDagNode::kNextPos, + bool keepExistingParents = false) { + MStatus status = self.addChild(child, index, keepExistingParents); + + if (!status) { + /** + * There are three know possible reasons this might not work. + * + */ + + // 1. Not a valid pointer + if (child.isNull()) { + throw std::runtime_error("MObject was null."); + } + + // 2. Not a DAG node + if (!child.hasFn(MFn::kDagNode)) { + MFnDagNode fn { child }; + MString message = fn.partialPathName(); + message += " was not a DAG node."; + throw std::runtime_error(message.asChar()); + } + + // 3. Node does not exist + MObjectHandle handle { child }; + if (!(handle.isValid() && handle.isAlive())) { + throw std::runtime_error("MObject did not exist"); + } + + // 4. The docs don't say why this happens + throw std::runtime_error("Undefined error occurred"); + } + + }, R"pbdoc(addChild(node, index=kNextPos, keepExistingParents=False) + +Makes a node a child of this one.)pbdoc") + + .def("boundingBox", [](MFnDagNode& self) -> MBoundingBox { + return self.boundingBox(); + }, R"pbdoc(Node's bounding box, in object space.)pbdoc") + + .def("child", [](MFnDagNode& self, unsigned int i) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(child(index) -> MObject + +Returns the specified child of this node.)pbdoc") + + .def("childCount", [](MFnDagNode& self) -> int { + return self.childCount(); + }, R"pbdoc(childCount() -> int + +Returns the number of nodes which are children of this one.)pbdoc") + + .def("create", [](MFnDagNode& self, MString type, MObject parent = MObject::kNullObj) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + +Creates a new DAG node of the specified type, with the given name. +The type may be either a type name or a type ID. If no name is given +then a unique name will be generated by combining the type name with +an integer. + +If a parent is given then the new node will be parented under it and +the functionset will be attached to the newly-created node. The +newly-created node will be returned. + +If no parent is given and the new node is a transform, it will be +parented under the world and the functionset will be attached to the +newly-created transform. The newly-created transform will be returned. + +If no parent is given and the new node is not a transform then a +transform node will be created under the world, the new node will be +parented under it, and the functionset will be attached to the +transform. The transform will be returned.)pbdoc") + + .def("create", [](MFnDagNode& self, MString type, MString name, MObject parent = MObject::kNullObj) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + +Creates a new DAG node of the specified type, with the given name. +The type may be either a type name or a type ID. If no name is given +then a unique name will be generated by combining the type name with +an integer. + +If a parent is given then the new node will be parented under it and +the functionset will be attached to the newly-created node. The +newly-created node will be returned. + +If no parent is given and the new node is a transform, it will be +parented under the world and the functionset will be attached to the +newly-created transform. The newly-created transform will be returned. + +If no parent is given and the new node is not a transform then a +transform node will be created under the world, the new node will be +parented under it, and the functionset will be attached to the +transform. The transform will be returned.)pbdoc") + + .def("create", [](MFnDagNode& self, MTypeId typeId, MObject parent = MObject::kNullObj) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + +Creates a new DAG node of the specified type, with the given name. +The type may be either a type name or a type ID. If no name is given +then a unique name will be generated by combining the type name with +an integer. + +If a parent is given then the new node will be parented under it and +the functionset will be attached to the newly-created node. The +newly-created node will be returned. + +If no parent is given and the new node is a transform, it will be +parented under the world and the functionset will be attached to the +newly-created transform. The newly-created transform will be returned. + +If no parent is given and the new node is not a transform then a +transform node will be created under the world, the new node will be +parented under it, and the functionset will be attached to the +transform. The transform will be returned.)pbdoc") + + .def("create", [](MFnDagNode& self, MTypeId typeId, MString name, MObject parent = MObject::kNullObj) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + +Creates a new DAG node of the specified type, with the given name. +The type may be either a type name or a type ID. If no name is given +then a unique name will be generated by combining the type name with +an integer. + +If a parent is given then the new node will be parented under it and +the functionset will be attached to the newly-created node. The +newly-created node will be returned. + +If no parent is given and the new node is a transform, it will be +parented under the world and the functionset will be attached to the +newly-created transform. The newly-created transform will be returned. + +If no parent is given and the new node is not a transform then a +transform node will be created under the world, the new node will be +parented under it, and the functionset will be attached to the +transform. The transform will be returned.)pbdoc") + + .def("dagPath", [](MFnDagNode& self) -> MDagPath { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(dagPath() -> MDagPath + +Returns the DAG path to which this function set is attached. Raises a TypeError if the function set is attached to an MObject rather than a path.)pbdoc") + + .def("dagRoot", [](MFnDagNode& self) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(dagRoot() -> MObject + +Returns the root node of the first path leading to this node.)pbdoc") + + .def("duplicate", [](MFnDagNode& self, bool instance = false, bool instanceLeaf = false) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(duplicate(instance=False, instanceLeaf=False) -> MObject + +Duplicates the DAG hierarchy rooted at the current node.)pbdoc") + + .def("fullPathName", [](MFnDagNode& self) -> MString { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(fullPathName() -> string + +Returns the full path of the attached object, from the root of the DAG on down.)pbdoc") + + .def("getAllPaths", [](MFnDagNode& self) -> MDagPathArray { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(getAllPaths() -> MDagPathArray + +Returns all of the DAG paths which lead to the object to which this function set is attached.)pbdoc") + + .def("getConnectedSetsAndMembers", [](MFnDagNode& self, unsigned int instanceNumber, bool renderableSetsOnly) -> std::tuple { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(getConnectedSetsAndMembers(instance, renderableSetsOnly) -> (MObjectArray, MObjectArray) + +Returns a tuple containing an array of sets and an array of the +components of the DAG object which are in those sets. If the entire object is in a set, then the corresponding entry in the comps array will have no elements in it. +)pbdoc") + + .def("getPath", [](MFnDagNode& self) -> MDagPath { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(getPath() -> MDagPath + +Returns the DAG path to which this function set is attached, or the first path to the node if the function set is attached to an MObject.)pbdoc") + + .def("hasChild", [](MFnDagNode& self, MObject node) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(hasChild(node) -> bool + +Returns True if the specified node is a child of this one.)pbdoc") + + .def("hasParent", [](MFnDagNode& self, MObject node) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(hasParent(node) -> bool + +Returns True if the specified node is a parent of this one.)pbdoc") + + .def("inModel", [](MFnDagNode& self) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(True if the node has been added to the model.)pbdoc") + + .def("inUnderWorld", [](MFnDagNode& self) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(True if this node is in the underworld of another node (e.g. a curve on surface is in the underworld of the surface).)pbdoc") + + .def("instanceCount", [](MFnDagNode& self, bool total) -> int { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(instanceCount(indirect) -> int + +Returns the number of instances for this node.)pbdoc") + + .def("isChildOf", [](MFnDagNode& self, MObject node) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(isChildOf(node) -> bool + +Returns True if the specified node is a parent of this one.)pbdoc") + + .def("isInstanceable", [](MFnDagNode& self) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(True if instancing is allowed for this node.)pbdoc") + + .def("isInstanced", [](MFnDagNode& self, bool indirect = true) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(isInstanced(indirect=True) -> bool + +Returns True if this node is instanced.)pbdoc") + + .def("isInstancedAttribute", [](MFnDagNode& self, MObject attr) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(isInstancedAttribute(attr) -> bool + +Returns True if the specified attribute is an instanced attribute of this node.)pbdoc") + + .def("isIntermediateObject", [](MFnDagNode& self) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(True if this node is just an intermediate in part of a larger calculation (e.g. input to a deformer).)pbdoc") + + .def("isParentOf", [](MFnDagNode& self, MObject node) -> bool { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(isParentOf(node) -> bool + +Returns True if the specified node is a child of this one.)pbdoc") + + .def("objectColorRGB", [](MFnDagNode& self) -> MColor { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(RGB value indicating the color in which the node is to be drawn when inactive, assuming that it is drawable.)pbdoc") + + .def("objectColorType", [](MFnDagNode& self) -> MFnDagNode::MObjectColorType { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(Determines whether the default color, indexed object color, orRGB object color is used for this object.)pbdoc") + + .def("parent", [](MFnDagNode& self, unsigned int i) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(parent(index) -> MObject + +Returns the specified parent of this node.)pbdoc") + + .def("parentCount", [](MFnDagNode& self) -> int { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(parentCount() -> int + +Returns the number of parents this node has.)pbdoc") + + .def("partialPathName", [](MFnDagNode& self) -> MString { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(partialPathName() -> string + +Returns the minimum path string necessary to uniquely identify the attached object.)pbdoc") + + .def("removeChild", [](MFnDagNode& self) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeChild(node) -> self + +Removes the child, specified by MObject, reparenting it under the world.)pbdoc") + + .def("removeChildAt", [](MFnDagNode& self, unsigned int index) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeChildAt(index) -> self + +Removes the child, specified by index, reparenting it under the world.)pbdoc") + + .def("setObject", [](MFnDagNode& self) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(setObject(MObject or MDagPath) -> self + +Attaches the function set to the specified node or DAG path.)pbdoc") + + .def("setObject", [](MFnDagNode& self, MDagPath path) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(setObject(MObject or MDagPath) -> self + +Attaches the function set to the specified node or DAG path.)pbdoc") + + .def("transformationMatrix", [](MFnDagNode& self) -> MMatrix { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(transformationMatrix() -> MMatrix + +Returns the object space transformation matrix for this DAG node.)pbdoc"); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d4f27fe..d731cdf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,9 +33,13 @@ #include #include #include +#include // MFnDagNode +#include // MFnDagNode +#include // MFnDagNode // Function sets #include +#include #include "util/atov.hpp" #include "util/plug.hpp" @@ -62,6 +66,7 @@ PYBIND11_MODULE(cmdc, m) { #include "MDagPath.inl" #include "MFn.inl" #include "MFnDependencyNode.inl" + #include "MFnDagNode.inl" #include "MObject.inl" #include "MPlug.inl" #include "MSelectionList.inl" diff --git a/tests/test_MFnDagNode.py b/tests/test_MFnDagNode.py new file mode 100644 index 0000000..9878496 --- /dev/null +++ b/tests/test_MFnDagNode.py @@ -0,0 +1,14 @@ +import cmdc +from nose.tools import ( + assert_equals, +) + + +def test_addChild(): + fn = cmdc.FnDagNode() + child = fn.createNode("transform", name="child") + + fn.createNode("transform", name="parent") + assert_equals(fn.childCount(), 0) + fn.addChild(child) + assert_equals(fn.childCount(), 1) From c55cea965601489461e3bf498c7cf20fbd7c2977 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 13:39:46 -0700 Subject: [PATCH 04/33] Handle OPENMAYA_PRIVATE namespace in headers --- scripts/parse_header.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/parse_header.py b/scripts/parse_header.py index 583209c..ab252e0 100644 --- a/scripts/parse_header.py +++ b/scripts/parse_header.py @@ -312,6 +312,10 @@ def filter_header_lines(class_name, lines): skip_next_statement = True continue + # OPENMAYA_PRIVATE appears to aways be at the endo f a header + if line.startswith('OPENMAYA_PRIVATE'): + break + try: # Remove trailing comments line = line[:line.index('//')].strip() From 047977b5206df594e5fae8884beb16c660924e48 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 13:39:56 -0700 Subject: [PATCH 05/33] Add MDGModifier stub --- src/MDGModifier.inl | 310 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 src/MDGModifier.inl diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl new file mode 100644 index 0000000..8b24fac --- /dev/null +++ b/src/MDGModifier.inl @@ -0,0 +1,310 @@ +py::class_(m, "DGModifier") + .def(py::init<>()) + + .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(addAttribute(MObject node, MObject attribute) -> self + +Adds an operation to the modifier to add a new dynamic attribute to the +given dependency node. If the attribute is a compound its children will +be added as well, so only the parent needs to be added using this method.)pbdoc") + + .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(addExtensionAttribute(MNodeClass nodeClass, MObject attribute) -> self + +Adds an operation to the modifier to add a new extension attribute to +the given node class. If the attribute is a compound its children will be +added as well, so only the parent needs to be added using this method.)pbdoc") + + .def("commandToExecute", [](MDGModifier & self, MString command) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(commandToExecute(command) -> self + +Adds an operation to the modifier to execute a MEL command. The command +should be fully undoable otherwise unexpected results may occur. If +the command contains no undoable portions whatsoever, the call to +doIt() may fail, but only after executing the command. It is best to +use multiple commandToExecute() calls rather than batching multiple +commands into a single call to commandToExecute(). 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."}; + }, R"pbdoc(connect(MPlug source, MPlug dest) -> self +connect(MObject sourceNode, MObject sourceAttr, + MObject destNode, MObject destAttr) -> self + +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. +Plugs can either be specified with node and attribute MObjects or with +MPlugs.)pbdoc") + + .def("connect", [](MDGModifier & self, MPlug source, MPlug dest) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(connect(MPlug source, MPlug dest) -> self +connect(MObject sourceNode, MObject sourceAttr, + MObject destNode, MObject destAttr) -> self + +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. +Plugs can either be specified with node and attribute MObjects or with +MPlugs.)pbdoc") + + .def("createNode", [](MDGModifier & self, MString type) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(createNode(typeName) -> MObject +createNode(MTypeId typeId) -> MObject + +Adds an operation to the modifier to create a node of the given type. +The new node is created and returned but will not be added to the +Dependency Graph until the modifier's doIt() method is called. Raises +TypeError if the named node type does not exist or if it is a DAG node +type.)pbdoc") + + .def("createNode", [](MDGModifier & self, MTypeId typeId) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(createNode(typeName) -> MObject +createNode(MTypeId typeId) -> MObject + +Adds an operation to the modifier to create a node of the given type. +The new node is created and returned but will not be added to the +Dependency Graph until the modifier's doIt() method is called. Raises +TypeError if the named node type does not exist or if it is a DAG node +type.)pbdoc") + + .def("deleteNode", [](MDGModifier & self, MObject node) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(deleteNode(MObject node) -> self + +Adds an operation to the modifier which deletes the specified node from +the Dependency Graph. If deleteNode() is called to delete nodes in a graph +while other items are also in the queue, it might end up deleting the nodes +before all the other tasks in the queue.In order to prevent unexpected outcomes, the modifier's doIt() should be called +before the deleteNode operation is added so that the queue is emptied. Then, +deleteNode() can be called and added to the queue. doIt() should be called +immediately after to ensure that the queue is emptied before any other +operations are added to it.)pbdoc") + + .def("disconnect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(disconnect(MPlug source, MPlug dest) -> self +disconnect(MObject sourceNode, MObject sourceAttr, + MObject destNode, MObject destAttr) -> self + +Adds an operation to the modifier that breaks a connection between two +plugs in the dependency graph. +Plugs can either be specified with node and attribute MObjects or with +MPlugs.)pbdoc") + + .def("disconnect", [](MDGModifier & self, MPlug source, MPlug dest) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(disconnect(MPlug source, MPlug dest) -> self +disconnect(MObject sourceNode, MObject sourceAttr, + MObject destNode, MObject destAttr) -> self + +Adds an operation to the modifier that breaks a connection between two +plugs in the dependency graph. +Plugs can either be specified with node and attribute MObjects or with +MPlugs.)pbdoc") + + .def("doIt", [](MDGModifier & self) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(doIt() -> self + +Executes the modifier's operations. If doIt() is called multiple times +in a row, without any intervening calls to undoIt(), then only the +operations which were added since the previous doIt() call will be +executed. If undoIt() has been called then the next call to doIt() will +do all operations.)pbdoc") + + .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(linkExtensionAttributeToPlugin(MObject plugin, MObject attribute) -> self + +The plugin can call this method to indicate that the extension attribute +defines part of the plugin, regardless of the node type to which it +attaches itself. This requirement is used when the plugin is checked to +see if it is in use or if is able to be unloaded or if it is required as +part of a stored file. For compound attributes only the topmost parent +attribute may be passed in and all of its children will be included, +recursively. Thus it's not possible to link a child attribute to a +plugin by itself. Note that the link is established immediately and is +not affected by the modifier's doIt() or undoIt() methods.)pbdoc") + + .def("newPlugValue", [](MDGModifier & self, MPlug plug) -> MObject { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValue(MPlug plug, MObject value) -> self + +Adds an operation to the modifier to set the value of a plug, where +value is an MObject data wrapper, such as created by the various +MFn*Data classes.)pbdoc") + + .def("newPlugValueBool", [](MDGModifier & self, MPlug plug, bool plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueBool(MPlug plug, bool value) -> self + +Adds an operation to the modifier to set a value onto a bool plug.)pbdoc") + + .def("newPlugValueChar", [](MDGModifier & self, MPlug plug, char plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueChar(MPlug plug, int value) -> self + +Adds an operation to the modifier to set a value onto a char (single +byte signed integer) plug.)pbdoc") + + .def("newPlugValueDouble", [](MDGModifier & self, MPlug plug, double plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueDouble(MPlug plug, float value) -> self + +Adds an operation to the modifier to set a value onto a double-precision +float plug.)pbdoc") + + .def("newPlugValueFloat", [](MDGModifier & self, MPlug plug, float plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueFloat(MPlug plug, float value) -> self + +Adds an operation to the modifier to set a value onto a single-precision +float plug.)pbdoc") + + .def("newPlugValueInt", [](MDGModifier & self, MPlug plug, int plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueInt(MPlug plug, int value) -> self + +Adds an operation to the modifier to set a value onto an int plug.)pbdoc") + + .def("newPlugValueMAngle", [](MDGModifier & self, MPlug plug, MAngle plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueMAngle(MPlug plug, MAngle value) -> self + +Adds an operation to the modifier to set a value onto an angle plug.)pbdoc") + + .def("newPlugValueMDistance", [](MDGModifier & self, MPlug plug, MDistance plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueMDistance(MPlug plug, MDistance value) -> self + +Adds an operation to the modifier to set a value onto a distance plug.)pbdoc") + + .def("newPlugValueMTime", [](MDGModifier & self, MPlug plug, MTime plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueMTime(MPlug plug, MTime value) -> self + +Adds an operation to the modifier to set a value onto a time plug.)pbdoc") + + .def("newPlugValueShort", [](MDGModifier & self, MPlug plug, short plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueShort(MPlug plug, int value) -> self + +Adds an operation to the modifier to set a value onto a short +integer plug.)pbdoc") + + .def("newPlugValueString", [](MDGModifier & self, MPlug plug, MString plugValue) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(newPlugValueString(MPlug plug, string value) -> self + +Adds an operation to the modifier to set a value onto a string plug.)pbdoc") + + .def("pythonCommandToExecute", [](MDGModifier & self, MString command) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(pythonCommandToExecute(callable) -> selfpythonCommandToExecute(commandString) -> self + +Adds an operation to the modifier to execute a Python command, which +can be passed as either a Python callable or a string containing the +text of the Python code to be executed. The command should be fully +undoable otherwise unexpected results may occur. If the command +contains no undoable portions whatsoever, the call to doIt() may fail, +but only after executing the command. It is best to use multiple calls +rather than batching multiple commands into a single call to +pythonCommandToExecute(). 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("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeAttribute(MObject node, MObject attribute) -> self + +Adds an operation to the modifier to remove a dynamic attribute from the +given dependency node. If the attribute is a compound its children will +be removed as well, so only the parent needs to be removed using this +method. The attribute MObject passed in will be set to kNullObj. There +should be no function sets attached to the attribute at the time of the +call as their behaviour may become unpredictable.)pbdoc") + + .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeExtensionAttribute(MNodeClass nodeClass, MObject attribute) -> self + +Adds an operation to the modifier to remove an extension attribute from +the given node class. If the attribute is a compound its children will +be removed as well, so only the parent needs to be removed using this +method. The attribute MObject passed in will be set to kNullObj. There +should be no function sets attached to the attribute at the time of the +call as their behaviour may become unpredictable.)pbdoc") + + .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeExtensionAttributeIfUnset(MNodeClass nodeClass, + MObject attribute) -> self + +Adds an operation to the modifier to remove an extension attribute from +the given node class, but only if there are no nodes in the graph with +non-default values for this attribute. If the attribute is a compound +its children will be removed as well, so only the parent needs to be +removed using this method. The attribute MObject passed in will be set +to kNullObj. There should be no function sets attached to the attribute +at the time of the call as their behaviour may become unpredictable.)pbdoc") + + .def("removeMultiInstance", [](MDGModifier & self, MPlug plug, bool breakConnections) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(removeMultiInstance(MPlug plug, bool breakConnections) -> self + +Adds an operation to the modifier to remove an element of a multi (array) plug.)pbdoc") + + .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, MString shortName, MString longName) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(renameAttribute(MObject node, MObject attribute, +string newShortName, string newShortName) -> self + +Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") + + .def("renameNode", [](MDGModifier & self, MObject node, MString newName) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(renameNode(MObject node, string newName) -> self + +Adds an operation to the modifer to rename a node.)pbdoc") + + .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(setNodeLockState(MObject node, bool newState) -> self + +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."}; + }, R"pbdoc(undoIt() -> self + +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) { + throw std::logic_error{"Function not yet implemented."}; + }, R"pbdoc(unlinkExtensionAttributeFromPlugin(MObject plugin, + MObject attribute) -> self + +The plugin can call this method to indicate that it no longer requires +an extension attribute for its operation. This requirement is used when +the plugin is checked to see if it is in use or if is able to be unloaded +or if it is required as part of a stored file. For compound attributes +only the topmost parent attribute may be passed in and all of its +children will be unlinked, recursively. Thus it's not possible to unlink +a child attribute from a plugin by itself. Note that the link is broken +immediately and is not affected by the modifier's doIt() or undoIt() +methods.)pbdoc"); \ No newline at end of file From aac0ade0d34bc5c29bd5a3e944e666958620ccfe Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 13:55:09 -0700 Subject: [PATCH 06/33] Cleanup docstrings --- src/MDGModifier.inl | 303 +++++++++++++++----------------------------- 1 file changed, 101 insertions(+), 202 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 8b24fac..edf6ba6 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -3,308 +3,207 @@ py::class_(m, "DGModifier") .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(addAttribute(MObject node, MObject attribute) -> self - -Adds an operation to the modifier to add a new dynamic attribute to the -given dependency node. If the attribute is a compound its children will -be added as well, so only the parent needs to be added using this method.)pbdoc") + }, +R"pbdoc(Adds an operation to the modifier to add a new dynamic attribute to the given dependency node. +If the attribute is a compound its children will ae added as well, so only the parent needs to be added using this method.)pbdoc") .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(addExtensionAttribute(MNodeClass nodeClass, MObject attribute) -> self - -Adds an operation to the modifier to add a new extension attribute to -the given node class. If the attribute is a compound its children will be -added as well, so only the parent needs to be added using this method.)pbdoc") + }, +R"pbdoc(Adds an operation to the modifier to add a new extension attribute to the given node class. +If the attribute is a compound its children will be added as well, so only the parent needs to be added using this method.)pbdoc") .def("commandToExecute", [](MDGModifier & self, MString command) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(commandToExecute(command) -> self - -Adds an operation to the modifier to execute a MEL command. The command -should be fully undoable otherwise unexpected results may occur. If -the command contains no undoable portions whatsoever, the call to -doIt() may fail, but only after executing the command. It is best to -use multiple commandToExecute() calls rather than batching multiple -commands into a single call to commandToExecute(). 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") + }, +R"pbdoc(Adds an operation to the modifier to execute a MEL command. +The command should be fully undoable otherwise unexpected results may occur. +If the command contains no undoable portions whatsoever, the call to doIt() may fail, +but only after executing the command. It is best to use multiple commandToExecute() calls +rather than batching multiple commands into a single call to commandToExecute(). +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."}; - }, R"pbdoc(connect(MPlug source, MPlug dest) -> self -connect(MObject sourceNode, MObject sourceAttr, - MObject destNode, MObject destAttr) -> self - -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. -Plugs can either be specified with node and attribute MObjects or with -MPlugs.)pbdoc") + }, +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."}; - }, R"pbdoc(connect(MPlug source, MPlug dest) -> self -connect(MObject sourceNode, MObject sourceAttr, - MObject destNode, MObject destAttr) -> self - -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. -Plugs can either be specified with node and attribute MObjects or with -MPlugs.)pbdoc") + }, +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("createNode", [](MDGModifier & self, MString type) -> MObject { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(createNode(typeName) -> MObject -createNode(MTypeId typeId) -> MObject - -Adds an operation to the modifier to create a node of the given type. -The new node is created and returned but will not be added to the -Dependency Graph until the modifier's doIt() method is called. Raises -TypeError if the named node type does not exist or if it is a DAG node -type.)pbdoc") + }, +R"pbdoc(Adds an operation to the modifier to create a node of the given type. +The new node is created and returned but will not be added to the dependency graph until the modifier's doIt() method is called. +Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("createNode", [](MDGModifier & self, MTypeId typeId) -> MObject { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(createNode(typeName) -> MObject -createNode(MTypeId typeId) -> MObject - -Adds an operation to the modifier to create a node of the given type. -The new node is created and returned but will not be added to the -Dependency Graph until the modifier's doIt() method is called. Raises -TypeError if the named node type does not exist or if it is a DAG node -type.)pbdoc") + }, +R"pbdoc(Adds an operation to the modifier to create a node of the given type. +The new node is created and returned but will not be added to the dependency graph until the modifier's doIt() method is called. +Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("deleteNode", [](MDGModifier & self, MObject node) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(deleteNode(MObject node) -> self + }, +R"pbdoc(Adds an operation to the modifier which deletes the specified node from the dependency graph. + +If deleteNode() is called to delete nodes in a graph while other items are also in the queue, +it might end up deleting the nodes before all the other tasks in the queue. -Adds an operation to the modifier which deletes the specified node from -the Dependency Graph. If deleteNode() is called to delete nodes in a graph -while other items are also in the queue, it might end up deleting the nodes -before all the other tasks in the queue.In order to prevent unexpected outcomes, the modifier's doIt() should be called -before the deleteNode operation is added so that the queue is emptied. Then, -deleteNode() can be called and added to the queue. doIt() should be called -immediately after to ensure that the queue is emptied before any other -operations are added to it.)pbdoc") +In order to prevent unexpected outcomes, the modifier's doIt() should be called before the deleteNode +operation is added so that the queue is emptied. Then, deleteNode() can be called and added to the queue. +doIt() should be called immediately after to ensure that the queue is emptied before any other operations are added to it.)pbdoc") .def("disconnect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(disconnect(MPlug source, MPlug dest) -> self -disconnect(MObject sourceNode, MObject sourceAttr, - MObject destNode, MObject destAttr) -> self - -Adds an operation to the modifier that breaks a connection between two -plugs in the dependency graph. -Plugs can either be specified with node and attribute MObjects or with -MPlugs.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier that breaks a connection between two plugs in the dependency graph.)pbdoc") .def("disconnect", [](MDGModifier & self, MPlug source, MPlug dest) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(disconnect(MPlug source, MPlug dest) -> self -disconnect(MObject sourceNode, MObject sourceAttr, - MObject destNode, MObject destAttr) -> self - -Adds an operation to the modifier that breaks a connection between two -plugs in the dependency graph. -Plugs can either be specified with node and attribute MObjects or with -MPlugs.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier that breaks a connection between two plugs in the dependency graph.)pbdoc") .def("doIt", [](MDGModifier & self) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(doIt() -> self + }, +R"pbdoc(Executes the modifier's operations. + +If doIt() is called multiple times in a row, without any intervening calls to undoIt(), +then only the operations which were added since the previous doIt() call will be executed. -Executes the modifier's operations. If doIt() is called multiple times -in a row, without any intervening calls to undoIt(), then only the -operations which were added since the previous doIt() call will be -executed. If undoIt() has been called then the next call to doIt() will -do all operations.)pbdoc") +If undoIt() has been called then the next call to doIt() will do all operations.)pbdoc") .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(linkExtensionAttributeToPlugin(MObject plugin, MObject attribute) -> self + }, +R"pbdoc(The plugin can call this method to indicate that the extension attribute defines part of the plugin, regardless of the node type to which it attaches itself. -The plugin can call this method to indicate that the extension attribute -defines part of the plugin, regardless of the node type to which it -attaches itself. This requirement is used when the plugin is checked to -see if it is in use or if is able to be unloaded or if it is required as -part of a stored file. For compound attributes only the topmost parent -attribute may be passed in and all of its children will be included, -recursively. Thus it's not possible to link a child attribute to a -plugin by itself. Note that the link is established immediately and is -not affected by the modifier's doIt() or undoIt() methods.)pbdoc") +This requirement is used when the plugin is checked to see if it is in use or if is able to be unloaded or if it is required as part of a stored file. +For compound attributes only the topmost parent attribute may be passed in and all of its children will be included, recursively. +Thus it's not possible to link a child attribute to a plugin by itself. + +Note that the link is established immediately and is not affected by the modifier's doIt() or undoIt() methods.)pbdoc") .def("newPlugValue", [](MDGModifier & self, MPlug plug) -> MObject { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValue(MPlug plug, MObject value) -> self - -Adds an operation to the modifier to set the value of a plug, where -value is an MObject data wrapper, such as created by the various -MFn*Data classes.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set the value of a plug, where value is an MObject data wrapper, such as created by the various MFn*Data classes.)pbdoc") .def("newPlugValueBool", [](MDGModifier & self, MPlug plug, bool plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueBool(MPlug plug, bool value) -> self - -Adds an operation to the modifier to set a value onto a bool plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a bool plug.)pbdoc") .def("newPlugValueChar", [](MDGModifier & self, MPlug plug, char plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueChar(MPlug plug, int value) -> self - -Adds an operation to the modifier to set a value onto a char (single -byte signed integer) plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a char (single byte signed integer) plug.)pbdoc") .def("newPlugValueDouble", [](MDGModifier & self, MPlug plug, double plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueDouble(MPlug plug, float value) -> self - -Adds an operation to the modifier to set a value onto a double-precision -float plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a double-precision float plug.)pbdoc") .def("newPlugValueFloat", [](MDGModifier & self, MPlug plug, float plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueFloat(MPlug plug, float value) -> self - -Adds an operation to the modifier to set a value onto a single-precision -float plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a single-precision float plug.)pbdoc") .def("newPlugValueInt", [](MDGModifier & self, MPlug plug, int plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueInt(MPlug plug, int value) -> self - -Adds an operation to the modifier to set a value onto an int plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto an int plug.)pbdoc") .def("newPlugValueMAngle", [](MDGModifier & self, MPlug plug, MAngle plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueMAngle(MPlug plug, MAngle value) -> self - -Adds an operation to the modifier to set a value onto an angle plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto an angle plug.)pbdoc") .def("newPlugValueMDistance", [](MDGModifier & self, MPlug plug, MDistance plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueMDistance(MPlug plug, MDistance value) -> self - -Adds an operation to the modifier to set a value onto a distance plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a distance plug.)pbdoc") .def("newPlugValueMTime", [](MDGModifier & self, MPlug plug, MTime plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueMTime(MPlug plug, MTime value) -> self - -Adds an operation to the modifier to set a value onto a time plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a time plug.)pbdoc") .def("newPlugValueShort", [](MDGModifier & self, MPlug plug, short plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueShort(MPlug plug, int value) -> self - -Adds an operation to the modifier to set a value onto a short -integer plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a short integer plug.)pbdoc") .def("newPlugValueString", [](MDGModifier & self, MPlug plug, MString plugValue) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(newPlugValueString(MPlug plug, string value) -> self - -Adds an operation to the modifier to set a value onto a string plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to set a value onto a string plug.)pbdoc") .def("pythonCommandToExecute", [](MDGModifier & self, MString command) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(pythonCommandToExecute(callable) -> selfpythonCommandToExecute(commandString) -> self + }, +R"pbdoc(Adds an operation to the modifier to execute a Python command, +which can be passed as either a Python callable or a string containing the text of the Python code to be executed. -Adds an operation to the modifier to execute a Python command, which -can be passed as either a Python callable or a string containing the -text of the Python code to be executed. The command should be fully -undoable otherwise unexpected results may occur. If the command -contains no undoable portions whatsoever, the call to doIt() may fail, -but only after executing the command. It is best to use multiple calls -rather than batching multiple commands into a single call to -pythonCommandToExecute(). 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") +The command should be fully undoable otherwise unexpected results may occur. + +If the command contains no undoable portions whatsoever, the call to doIt() may fail, but only after executing the command. +It is best to use multiple calls rather than batching multiple commands into a single call to pythonCommandToExecute(). +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("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeAttribute(MObject node, MObject attribute) -> self + }, +R"pbdoc(Adds an operation to the modifier to remove a dynamic attribute from the given dependency node. +If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. +The attribute MObject passed in will be set to kNullObj. -Adds an operation to the modifier to remove a dynamic attribute from the -given dependency node. If the attribute is a compound its children will -be removed as well, so only the parent needs to be removed using this -method. The attribute MObject passed in will be set to kNullObj. There -should be no function sets attached to the attribute at the time of the -call as their behaviour may become unpredictable.)pbdoc") +There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeExtensionAttribute(MNodeClass nodeClass, MObject attribute) -> self + }, +R"pbdoc(Adds an operation to the modifier to remove an extension attribute from the given node class. +If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. +The attribute MObject passed in will be set to kNullObj. -Adds an operation to the modifier to remove an extension attribute from -the given node class. If the attribute is a compound its children will -be removed as well, so only the parent needs to be removed using this -method. The attribute MObject passed in will be set to kNullObj. There -should be no function sets attached to the attribute at the time of the -call as their behaviour may become unpredictable.)pbdoc") +There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeExtensionAttributeIfUnset(MNodeClass nodeClass, - MObject attribute) -> self + }, +R"pbdoc(Adds an operation to the modifier to remove an extension attribute from the given node class, +but only if there are no nodes in the graph with non-default values for this attribute. +If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. +The attribute MObject passed in will be set to kNullObj. -Adds an operation to the modifier to remove an extension attribute from -the given node class, but only if there are no nodes in the graph with -non-default values for this attribute. If the attribute is a compound -its children will be removed as well, so only the parent needs to be -removed using this method. The attribute MObject passed in will be set -to kNullObj. There should be no function sets attached to the attribute -at the time of the call as their behaviour may become unpredictable.)pbdoc") +There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeMultiInstance", [](MDGModifier & self, MPlug plug, bool breakConnections) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeMultiInstance(MPlug plug, bool breakConnections) -> self - -Adds an operation to the modifier to remove an element of a multi (array) plug.)pbdoc") + }, R"pbdoc(Adds an operation to the modifier to remove an element of a multi (array) plug.)pbdoc") .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, MString shortName, MString longName) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(renameAttribute(MObject node, MObject attribute, -string newShortName, string newShortName) -> self - -Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") + }, R"pbdoc(Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") .def("renameNode", [](MDGModifier & self, MObject node, MString newName) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(renameNode(MObject node, string newName) -> self - -Adds an operation to the modifer to rename a node.)pbdoc") + }, R"pbdoc(Adds an operation to the modifer to rename a node.)pbdoc") .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(setNodeLockState(MObject node, bool newState) -> self - -Adds an operation to the modifier to set the lockState of a node.)pbdoc") + }, 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."}; - }, R"pbdoc(undoIt() -> self - -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") + }, 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) { throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(unlinkExtensionAttributeFromPlugin(MObject plugin, - MObject attribute) -> self - -The plugin can call this method to indicate that it no longer requires -an extension attribute for its operation. This requirement is used when -the plugin is checked to see if it is in use or if is able to be unloaded -or if it is required as part of a stored file. For compound attributes -only the topmost parent attribute may be passed in and all of its -children will be unlinked, recursively. Thus it's not possible to unlink -a child attribute from a plugin by itself. Note that the link is broken -immediately and is not affected by the modifier's doIt() or undoIt() -methods.)pbdoc"); \ No newline at end of file + }, +R"pbdoc(The plugin can call this method to indicate that it no longer requires an extension attribute for its operation. +This requirement is used when the plugin is checked to see if it is in use, or if is able to be unloaded, or if it is required as part of a stored file. +For compound attributes only the topmost parent attribute may be passed in and all of its children will be unlinked, recursively. +Thus it's not possible to unlink a child attribute from a plugin by itself. + +Note that the link is broken immediately and is not affected by the modifier's doIt() or undoIt() methods.)pbdoc"); \ No newline at end of file From 716e8ad4307c9b7834400d7575970de33f449b58 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 13:58:00 -0700 Subject: [PATCH 07/33] Include MDgModifier.inl in main --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index d4f27fe..583e945 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,12 +14,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -60,6 +62,7 @@ PYBIND11_MODULE(cmdc, m) { #include "Math.inl" #include "MDagPath.inl" + #include "MDGModifier.inl" #include "MFn.inl" #include "MFnDependencyNode.inl" #include "MObject.inl" From 52b53f31ebe6b13f3402ce224e2ad4b88d5b18fd Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 14:22:23 -0700 Subject: [PATCH 08/33] Implement DGModifier.createNode methods --- src/MDGModifier.inl | 45 +++++++++++++++++++++++--- tests/test_MDGModifier.py | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 tests/test_MDGModifier.py diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index edf6ba6..9c9efd6 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -38,15 +38,47 @@ R"pbdoc(Adds an operation to the modifier that connects two plugs in the depende 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("createNode", [](MDGModifier & self, MString type) -> MObject { - throw std::logic_error{"Function not yet implemented."}; + .def("createNode", [](MDGModifier & self, std::string type) -> MObject { + MString type_name(type.c_str()); + + MStatus status; + MObject result = self.createNode(type_name, &status); + + 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()); + } else if (result.hasFn(MFn::kDagNode)) { + MString error_msg("Cannot create DAG node '^1s' - use DAGModifier instead."); + error_msg.format(error_msg, type_name); + throw pybind11::type_error(error_msg.asChar()); + } + + return result; }, R"pbdoc(Adds an operation to the modifier to create a node of the given type. The new node is created and returned but will not be added to the dependency graph until the modifier's doIt() method is called. Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("createNode", [](MDGModifier & self, MTypeId typeId) -> MObject { - throw std::logic_error{"Function not yet implemented."}; + MString type_id_str = MString() + typeId.id(); + + MStatus status; + MObject result = self.createNode(typeId, &status); + + 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()); + } else if (result.hasFn(MFn::kDagNode)) { + MString error_msg("Cannot create DAG node with type ID '^1s' - use DAGModifier instead."); + error_msg.format(error_msg, type_id_str); + throw pybind11::type_error(error_msg.asChar()); + } + + return result; }, R"pbdoc(Adds an operation to the modifier to create a node of the given type. The new node is created and returned but will not be added to the dependency graph until the modifier's doIt() method is called. @@ -73,7 +105,12 @@ doIt() should be called immediately after to ensure that the queue is emptied be }, R"pbdoc(Adds an operation to the modifier that breaks a connection between two plugs in the dependency graph.)pbdoc") .def("doIt", [](MDGModifier & self) { - throw std::logic_error{"Function not yet implemented."}; + MStatus status = self.doIt(); + + if (!status) + { + throw std::exception(status.errorString().asChar()); + } }, R"pbdoc(Executes the modifier's operations. diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py new file mode 100644 index 0000000..dcd0595 --- /dev/null +++ b/tests/test_MDGModifier.py @@ -0,0 +1,68 @@ +import cmdc +import nose + +from maya import cmds +from maya.api import OpenMaya + +from . import new_scene + + +def _node_type_id(): + node = cmds.createNode('network') + node = OpenMaya.MSelectionList().add(node).getDependNode(0) + type_id = OpenMaya.MFnDependencyNode(node).typeId.id() + + return cmdc.TypeId(type_id) + +@nose.with_setup(teardown=new_scene) +def test_createNode_pass(): + node = cmds.createNode('network') + node = OpenMaya.MSelectionList().add(node).getDependNode(0) + type_id = OpenMaya.MFnDependencyNode(node).typeId.id() + + 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) + + 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) + + yield _createNode_fail, value + + +def _createNode_fail(arg): + old_nodes = cmds.ls(long=True) + + nose.tools.assert_raises( + TypeError, _createNode_pass, arg + ) + + new_nodes = cmds.ls(long=True) + + assert len(old_nodes) == len(new_nodes), "DGModifier.createNode modified the scene graph." + + +def _createNode_pass(arg): + old_nodes = cmds.ls(long=True) + + mod = cmdc.DGModifier() + node = mod.createNode(arg) + 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." From 67fce729af9badf28ec08ea8c32d2d707ba6a4a4 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 30 May 2021 16:42:33 -0700 Subject: [PATCH 09/33] Fix Linux build error --- src/MDGModifier.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 9c9efd6..6091c46 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -109,7 +109,7 @@ doIt() should be called immediately after to ensure that the queue is emptied be if (!status) { - throw std::exception(status.errorString().asChar()); + throw std::runtime_error(status.errorString().asChar()); } }, R"pbdoc(Executes the modifier's operations. From d13fac800000905b28a5a215e9e7d0fee24f304c Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Mon, 31 May 2021 11:50:52 -0700 Subject: [PATCH 10/33] Implement MDGModifier::addAttribute binding --- src/MDGModifier.inl | 22 +++++++++++++++++++++- tests/__init__.py | 17 +++++++++++++++++ tests/test_MDGModifier.py | 36 ++++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 6091c46..6bb8c08 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -2,7 +2,27 @@ py::class_(m, "DGModifier") .def(py::init<>()) .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; + if (node.isNull()) + { + 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."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (attribute.isNull()) + { + 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."); + error_msg.format(error_msg, attribute.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + MStatus status = self.addAttribute(node, attribute); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to add a new dynamic attribute to the given dependency node. If the attribute is a compound its children will ae added as well, so only the parent needs to be added using this method.)pbdoc") diff --git a/tests/__init__.py b/tests/__init__.py index e9f8e23..1b63a65 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,11 +3,28 @@ import maya.standalone from maya import cmds +import cmdc + + def setup(): maya.standalone.initialize() + def new_scene(): cmds.file(new=True, force=True) + def teardown(): maya.standalone.uninitialize() + + +def as_obj(arg): + """Return the Maya Object for the given node.""" + + return cmdc.SelectionList().add(arg).getDependNode(0) + + +def as_plug(arg): + """Return the Maya Plug for the given plug.""" + + return cmdc.SelectionList().add(arg).getPlug(0) diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index dcd0595..76ea968 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -1,18 +1,42 @@ import cmdc import nose +from nose.plugins.skip import SkipTest + from maya import cmds from maya.api import OpenMaya -from . import new_scene +from . import as_obj, as_plug, new_scene -def _node_type_id(): - node = cmds.createNode('network') - node = OpenMaya.MSelectionList().add(node).getDependNode(0) - type_id = OpenMaya.MFnDependencyNode(node).typeId.id() +def test_addAttribute_pass(): + raise SkipTest("Cannot test DGModifier.addAttribute until MFnAttribute classes are implemented.") + + +def test_addAttribute_fail(): + node = as_obj(cmds.createNode('network')) + null = cmdc.Object() + + attr = as_plug('persp.message').attribute() + + for exc, doc, node, attr in ( + [ValueError, 'a null object', null, null], + [ValueError, 'a null attribute', node, null], + [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) + + yield _addAttribute_fail, exc, node, attr + + +def _addAttribute_fail(exception, node, attr): + nose.tools.assert_raises( + exception, + cmdc.DGModifier().addAttribute, + node, attr + ) - return cmdc.TypeId(type_id) @nose.with_setup(teardown=new_scene) def test_createNode_pass(): From eb79c6448c89f03c7008b3d8d6333b6e9f866dee Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Mon, 31 May 2021 13:05:46 -0700 Subject: [PATCH 11/33] Implement DGModifier.connect --- src/MDGModifier.inl | 71 ++++++++++++++++++++++++++++++++--- tests/test_MDGModifier.py | 79 +++++++++++++++++++++++++++++++++++---- 2 files changed, 136 insertions(+), 14 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 6bb8c08..29beba6 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -6,7 +6,7 @@ py::class_(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()); } @@ -15,7 +15,7 @@ py::class_(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()); } @@ -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. @@ -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) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 76ea968..851df03 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -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() @@ -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 @@ -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) @@ -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) From 2f700151b7b1495cab50c822a33018668a8d8e44 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Mon, 31 May 2021 14:52:45 -0700 Subject: [PATCH 12/33] Implement MDGModifier::disconnect binding --- src/MDGModifier.inl | 52 ++++++++++++++- tests/test_MDGModifier.py | 134 ++++++++++++++++++++++++++++++-------- 2 files changed, 157 insertions(+), 29 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 29beba6..34d35e0 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -171,11 +171,59 @@ operation is added so that the queue is emptied. Then, deleteNode() can be calle doIt() should be called immediately after to ensure that the queue is emptied before any other operations are added to it.)pbdoc") .def("disconnect", [](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 disconnect - sourceNode is null."); + } else if (!sourceNode.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot disconnect - 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 disconnect - 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 disconnect - destNode is null."); + } else if (!destNode.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot disconnect - 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 disconnect - 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()); + } + + MStatus status = self.disconnect(sourceNode, sourceAttr, destNode, destAttr); + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier that breaks a connection between two plugs in the dependency graph.)pbdoc") .def("disconnect", [](MDGModifier & self, MPlug source, MPlug dest) { - throw std::logic_error{"Function not yet implemented."}; + if (source.isNull()) + { + throw std::invalid_argument("Cannot disconnect - source is null."); + } + + if (dest.isNull()) + { + throw std::invalid_argument("Cannot disconnect - dest is null."); + } + + MStatus status = self.disconnect(source, dest); + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier that breaks a connection between two plugs in the dependency graph.)pbdoc") .def("doIt", [](MDGModifier & self) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 851df03..f6afefd 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -40,6 +40,31 @@ def _addAttribute_fail(exception, node, attr): ) +@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' + + @nose.with_setup(teardown=new_scene) def test_connect_fail(): src_plug = as_plug(cmds.createNode('transform') + '.visibility') @@ -78,31 +103,6 @@ def _connect_fail(exception, 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 = as_obj(cmds.createNode('network')) type_id = cmdc.FnDependencyNode(node).typeId() @@ -111,7 +111,7 @@ def test_createNode_pass(): ['typeName', 'network'], ['typeId', cmdc.TypeId(type_id)] ): - test_createNode_pass.__doc__ = """Test for MDGModifier::createNode if called with a valid {}.""".format(name) + test_createNode_pass.__doc__ = """Test MDGModifier::createNode if called with a valid {}.""".format(name) yield _createNode_pass, value @@ -121,7 +121,7 @@ def test_createNode_fail(): ['typeName', 'foobar'], ['typeId', cmdc.TypeId(0xdeadbeef)] ): - test_createNode_fail.__doc__ = """Test MDGModifier::createNode if called with an invalid {}.""".format(name) + test_createNode_fail.__doc__ = """Test MDGModifier::createNode raises error if called with an invalid {}.""".format(name) yield _createNode_fail, value @@ -153,3 +153,83 @@ def _createNode_pass(arg): assert not node.isNull(), "Created node is not valid." assert len(add_nodes) == 1, "`ls` did not return new node." + + + +def test_disconnect_pass(): + def _plugs(): + src_plug = as_plug(cmds.createNode('transform') + '.visibility') + dst_plug = as_plug(cmds.createNode('transform') + '.visibility') + + return ( + src_plug, dst_plug, + (src_plug, dst_plug) + ) + + def _objects(): + src_plug = as_plug(cmds.createNode('transform') + '.visibility') + dst_plug = as_plug(cmds.createNode('transform') + '.visibility') + + return ( + src_plug, dst_plug, + (src_plug.node(), src_plug.attribute(), dst_plug.node(), dst_plug.attribute()) + ) + + for doc, setup_fn in ( + ["plugs", _plugs], + ["node/attribute pairs", _objects] + ): + test_disconnect_pass.__doc__ = """Test MDGModifier::disconnect if called with {}.""".format(doc) + + yield _disconnect_pass, setup_fn + + +@nose.with_setup(teardown=new_scene) +def _disconnect_pass(setup_fn): + src_plug, dst_plug, args = setup_fn() + cmds.connectAttr(src_plug.name(), dst_plug.name()) + + mod = cmdc.DGModifier() + mod.disconnect(*args) + + mod.doIt() + assert not cmds.isConnected(src_plug.name(), dst_plug.name()), "DGModifier.disconnect doIt failed" + + mod.undoIt() + assert cmds.isConnected(src_plug.name(), dst_plug.name()), 'DGModifier.disconnect undoIt failed' + + +def test_disconnect_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_disconnect_fail.__doc__ = """Test MDGModifier::disconnect raises exception if called with {}.""".format(doc) + + yield _disconnect_fail, exc, args + +def _disconnect_fail(exception, args): + nose.tools.assert_raises( + exception, + cmdc.DGModifier().disconnect, + *args + ) \ No newline at end of file From e99916afe782ab6433d46a2421e49560e3cc5f10 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Mon, 31 May 2021 15:07:15 -0700 Subject: [PATCH 13/33] Implement MDGModifier::deleteNode binding --- src/MDGModifier.inl | 17 ++++++++++++++- tests/test_MDGModifier.py | 45 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 34d35e0..f73ddd5 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -159,7 +159,22 @@ The new node is created and returned but will not be added to the dependency gra Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("deleteNode", [](MDGModifier & self, MObject node) { - throw std::logic_error{"Function not yet implemented."}; + if (node.isNull()) + { + throw std::invalid_argument("Cannot delete a null object."); + } else if (!node.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot delete a(n) '^1s' object."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } else if (node.hasFn(MFn::kDagNode)) { + MString error_msg("Cannot delete a(n) DAG object - use DAGModifier instead."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + MStatus status = self.deleteNode(node); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier which deletes the specified node from the dependency graph. diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index f6afefd..abec791 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -6,7 +6,7 @@ from maya import cmds from maya.api import OpenMaya -from . import as_obj, as_plug, new_scene +from . import as_dag_path, as_obj, as_plug, new_scene @nose.with_setup(teardown=new_scene) @@ -156,6 +156,49 @@ def _createNode_pass(arg): +def test_deleteNode_pass(): + """Test MDGModifier::createNode if called with a valid node.""" + + node = cmds.createNode('network') + obj = as_obj(node) + + mod = cmdc.DGModifier() + mod.deleteNode(obj) + + mod.doIt() + assert not cmds.objExists(node), 'DGModifier.deleteNode doIt failed' + + mod.undoIt() + assert cmds.objExists(node), 'DGModifier.deleteNode undoIt failed' + + +def test_deleteNode_fail(): + for exc, name, value in ( + [ValueError, 'null object', cmdc.Object()], + [TypeError, 'non-node object', as_plug('persp.message').attribute()], + [TypeError, 'DAG object', as_obj(cmds.createNode('transform'))], + ): + test_deleteNode_fail.__doc__ = """Test MDGModifier::deleteNode raises error if called with a(n) {}.""".format(name) + + yield _deleteNode_fail, exc, value + + +@nose.with_setup(teardown=new_scene) +def _deleteNode_fail(exception, arg): + old_nodes = cmds.ls(long=True) + + nose.tools.assert_raises( + exception, + cmdc.DGModifier().deleteNode, + arg + ) + + new_nodes = cmds.ls(long=True) + + assert len(old_nodes) == len(new_nodes), "DGModifier.deleteNode modified the scene graph." + + + def test_disconnect_pass(): def _plugs(): src_plug = as_plug(cmds.createNode('transform') + '.visibility') From 986dd6c6154e1d0b61b62c26cf373d724792b5f2 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Mon, 31 May 2021 15:32:59 -0700 Subject: [PATCH 14/33] Implement MDGModifier::removeAttribute binding --- src/MDGModifier.inl | 24 ++++++++++++++++-- tests/test_MDGModifier.py | 51 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index f73ddd5..12252d4 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -324,8 +324,28 @@ It is best to use multiple calls rather than batching multiple commands into a s 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("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; + .def("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { + if (node.isNull()) + { + throw std::invalid_argument("Cannot remove an attribute from a null node."); + } else if (!node.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot remove a null attribute."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot remove 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()); + } + + MStatus status = self.removeAttribute(node, attribute); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to remove a dynamic attribute from the given dependency node. If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index abec791..e70ac26 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -6,7 +6,7 @@ from maya import cmds from maya.api import OpenMaya -from . import as_dag_path, as_obj, as_plug, new_scene +from . import as_obj, as_plug, new_scene @nose.with_setup(teardown=new_scene) @@ -266,7 +266,7 @@ def test_disconnect_fail(): [ValueError, 'a null source plug', (nop_plug, dst_plug)], [ValueError, 'a null destination plug', (src_plug, nop_plug)], ): - test_disconnect_fail.__doc__ = """Test MDGModifier::disconnect raises exception if called with {}.""".format(doc) + test_disconnect_fail.__doc__ = """Test MDGModifier::disconnect raises error if called with {}.""".format(doc) yield _disconnect_fail, exc, args @@ -275,4 +275,51 @@ def _disconnect_fail(exception, args): exception, cmdc.DGModifier().disconnect, *args + ) + + +@nose.with_setup(teardown=new_scene) +def test_removeAttribute_pass(): + """Test MDGModifier::removeAttribute if called with a valid attribute.""" + + node = cmds.createNode('network') + attr = node + '.test' + + cmds.addAttr(node, ln='test') + + plug = as_plug(attr) + + mod = cmdc.DGModifier() + mod.removeAttribute(plug.node(), plug.attribute()) + + mod.doIt() + assert not cmds.objExists(attr), 'DGModifier.removeAttribute doIt failed' + + mod.undoIt() + assert cmds.objExists(attr), 'DGModifier.removeAttribute undoIt failed' + + +def test_removeAttribute_fail(): + node = cmds.createNode('network') + cmds.addAttr(node, ln='test') + + plug = as_plug(node + '.test') + null = cmdc.Object() + + for exc, doc, (node, attr) in ( + [ValueError, "a null node", (null, null)], + [ValueError, "a null attribute", (plug.node(), null)], + [TypeError, "a non-node object", (plug.attribute(), null)], + [TypeError, "a non-attribute object", (plug.node(), plug.node())], + ): + test_removeAttribute_fail.__doc__ = """Test MDGModifier::removeAttribute raises an error if with {}.""".format(doc) + + yield _removeAttribute_fail, exc, node, attr + + +def _removeAttribute_fail(exception, node, attr): + nose.tools.assert_raises( + exception, + cmdc.DGModifier().removeAttribute, + node, attr ) \ No newline at end of file From 15915fa2dd81c3e0b128f5ca4b8db98ec62b18f8 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 16:11:48 -0700 Subject: [PATCH 15/33] Implement DGModifier::newPlugValue* bindings --- src/MDGModifier.inl | 88 +++++++++++++++++++++++++++++---------- tests/__init__.py | 7 ++++ tests/test_MDGModifier.py | 59 +++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 23 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 12252d4..e0ed348 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -267,48 +267,92 @@ Thus it's not possible to link a child attribute to a plugin by itself. Note that the link is established immediately and is not affected by the modifier's doIt() or undoIt() methods.)pbdoc") - .def("newPlugValue", [](MDGModifier & self, MPlug plug) -> MObject { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValue", [](MDGModifier & self, MPlug plug, MObject value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValue(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set the value of a plug, where value is an MObject data wrapper, such as created by the various MFn*Data classes.)pbdoc") - .def("newPlugValueBool", [](MDGModifier & self, MPlug plug, bool plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueBool", [](MDGModifier & self, MPlug plug, bool value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueBool(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a bool plug.)pbdoc") - .def("newPlugValueChar", [](MDGModifier & self, MPlug plug, char plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueChar", [](MDGModifier & self, MPlug plug, int value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueChar(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a char (single byte signed integer) plug.)pbdoc") - .def("newPlugValueDouble", [](MDGModifier & self, MPlug plug, double plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueDouble", [](MDGModifier & self, MPlug plug, double value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueDouble(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a double-precision float plug.)pbdoc") - .def("newPlugValueFloat", [](MDGModifier & self, MPlug plug, float plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueFloat", [](MDGModifier & self, MPlug plug, float value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueFloat(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a single-precision float plug.)pbdoc") - .def("newPlugValueInt", [](MDGModifier & self, MPlug plug, int plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueInt", [](MDGModifier & self, MPlug plug, int value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueInt(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto an int plug.)pbdoc") - .def("newPlugValueMAngle", [](MDGModifier & self, MPlug plug, MAngle plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueMAngle", [](MDGModifier & self, MPlug plug, MAngle value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueMAngle(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto an angle plug.)pbdoc") - .def("newPlugValueMDistance", [](MDGModifier & self, MPlug plug, MDistance plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueMDistance", [](MDGModifier & self, MPlug plug, MDistance value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueMDistance(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a distance plug.)pbdoc") - .def("newPlugValueMTime", [](MDGModifier & self, MPlug plug, MTime plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueMTime", [](MDGModifier & self, MPlug plug, MTime value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueMTime(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a time plug.)pbdoc") - .def("newPlugValueShort", [](MDGModifier & self, MPlug plug, short plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueShort", [](MDGModifier & self, MPlug plug, short value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueShort(plug, value); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a short integer plug.)pbdoc") - .def("newPlugValueString", [](MDGModifier & self, MPlug plug, MString plugValue) { - throw std::logic_error{"Function not yet implemented."}; + .def("newPlugValueString", [](MDGModifier & self, MPlug plug, std::string value) { + plug::assert_not_null(plug); + + MStatus status = self.newPlugValueString(plug, MString(value.c_str())); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a string plug.)pbdoc") .def("pythonCommandToExecute", [](MDGModifier & self, MString command) { diff --git a/tests/__init__.py b/tests/__init__.py index 1b63a65..4a6771b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,6 +18,13 @@ def teardown(): maya.standalone.uninitialize() +def assert_equals(expected, actual, error_message): + if isinstance(expected, float): + assert abs(expected - actual) <= 1e-5, error_message + else: + assert expected == actual, error_message + + def as_obj(arg): """Return the Maya Object for the given node.""" diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index e70ac26..6d55649 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -6,7 +6,7 @@ from maya import cmds from maya.api import OpenMaya -from . import as_obj, as_plug, new_scene +from . import assert_equals, as_obj, as_plug, new_scene @nose.with_setup(teardown=new_scene) @@ -278,6 +278,63 @@ def _disconnect_fail(exception, args): ) +def test_newPlugValue(): + """Test MDGModifier::newPlugValue*.""" + + identity = lambda x: x + + for value, method_name, add_attr_kwargs in ( + [True, 'newPlugValueBool', {'at': 'bool'}], + [ord('A'), 'newPlugValueChar', {'at': 'char'}], + [42.0, 'newPlugValueDouble', {'at': 'double'}], + [42.0, 'newPlugValueFloat', {'at': 'float'}], + [42, 'newPlugValueInt', {'at': 'long'}], + # TODO: Enable when bindings are available + # [None, 'newPlugValueMAngle', {'at': 'doubleAngle'}, {}], + # [None, 'newPlugValueMDistance', {'at': 'doubleLinear'}, {}], + # [None, 'newPlugValueMTime', {'at': 'time'}, {}], + [2, 'newPlugValueShort', {'at': 'enum', 'enumName': 'a:b:c:d'}], + ["Hello, World!", 'newPlugValueString', {'dt': 'string'}], + ): + test_newPlugValue.__doc__ = """Test MDGModifier::{}.""".format(method_name) + + yield _newPlugValue, value, method_name, add_attr_kwargs + + +def _newPlugValue(new_value, method_name, add_attr_kwargs): + node = cmds.createNode('network') + cmds.addAttr(node, longName='test', **add_attr_kwargs) + attr = node + '.test' + + old_value = new_value.__class__(cmds.getAttr(attr)) + + plug = as_plug(attr) + + mod = cmdc.DGModifier() + mod_fn = getattr(mod, method_name) + mod_fn(plug, new_value) + mod.doIt() + + # My kingdom for fStrings + doit_value = new_value.__class__(cmds.getAttr(attr)) + assert_equals( + new_value, doit_value, + 'DGModifier.{} doIt failed to set the value - expected: {}, actual: {}'.format( + method_name, new_value, doit_value + ) + ) + + mod.undoIt() + + undo_value = new_value.__class__(cmds.getAttr(attr)) + assert_equals( + old_value, undo_value, + 'DGModifier.{} undo failed to set the value - expected: {}, actual: {}'.format( + method_name, old_value, undo_value + ) + ) + + @nose.with_setup(teardown=new_scene) def test_removeAttribute_pass(): """Test MDGModifier::removeAttribute if called with a valid attribute.""" From d11c9c63674efd0cd596210453b7ea76ab685a85 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 16:23:47 -0700 Subject: [PATCH 16/33] Implement extension attribute method bindings --- src/MDGModifier.inl | 77 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index e0ed348..f6d3803 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -256,8 +256,29 @@ then only the operations which were added since the previous doIt() call will be If undoIt() has been called then the next call to doIt() will do all operations.)pbdoc") - .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; + .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { + if (plugin.isNull()) + { + throw std::invalid_argument("Cannot link extension attribute from a null plugin."); + } else if (!plugin.hasFn(MFn::kPlugin)) + { + MString error_msg("Cannot link extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); + error_msg.format(error_msg, plugin.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot link null extension attribute from a plugin."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot link extension 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()); + } + + MStatus status = self.linkExtensionAttributeToPlugin(plugin, attribute); + + CHECK_STATUS(status) }, R"pbdoc(The plugin can call this method to indicate that the extension attribute defines part of the plugin, regardless of the node type to which it attaches itself. @@ -398,7 +419,18 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot remove null extension attribute."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot remove extension 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()); + } + + MStatus status = self.removeExtensionAttribute(nodeClass, attribute); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to remove an extension attribute from the given node class. If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. @@ -407,8 +439,18 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; - }, + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot remove null extension attribute (if unset)."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot remove extension attribute (if unset) - '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()); + } + + MStatus status = self.removeExtensionAttribute(nodeClass, attribute); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to remove an extension attribute from the given node class, but only if there are no nodes in the graph with non-default values for this attribute. If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. @@ -441,8 +483,29 @@ There should be no function sets attached to the attribute at the time of the ca } }, 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) { - throw std::logic_error{"Function not yet implemented."}; + .def("unlinkExtensionAttributeFromPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { + if (plugin.isNull()) + { + throw std::invalid_argument("Cannot unlink extension attribute from a null plugin."); + } else if (!plugin.hasFn(MFn::kPlugin)) + { + MString error_msg("Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); + error_msg.format(error_msg, plugin.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot unlink null extension attribute from a plugin."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot unlink extension 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()); + } + + MStatus status = self.unlinkExtensionAttributeFromPlugin(plugin, attribute); + + CHECK_STATUS(status) }, R"pbdoc(The plugin can call this method to indicate that it no longer requires an extension attribute for its operation. This requirement is used when the plugin is checked to see if it is in use, or if is able to be unloaded, or if it is required as part of a stored file. From 7da6f80fb798717cad76974fa1de0f58d18c3d31 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 16:38:11 -0700 Subject: [PATCH 17/33] Implement MDGModifier::setNodeLockState binding --- src/MDGModifier.inl | 13 ++++++++++++- tests/test_MDGModifier.py | 25 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index f6d3803..a83e91d 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -471,7 +471,18 @@ There should be no function sets attached to the attribute at the time of the ca }, R"pbdoc(Adds an operation to the modifer to rename a node.)pbdoc") .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { - throw std::logic_error{"Function not yet implemented."}; + if (node.isNull()) + { + throw std::invalid_argument("Cannot un/lock a null node."); + } else if (!node.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + MStatus status = self.setNodeLockState(node, newState); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to set the lockState of a node.)pbdoc") .def("undoIt", [](MDGModifier & self) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 6d55649..31f4eb1 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -379,4 +379,27 @@ def _removeAttribute_fail(exception, node, attr): exception, cmdc.DGModifier().removeAttribute, node, attr - ) \ No newline at end of file + ) + + +def test_setNodeLockState(): + """Test MDGModifier::setNodeLockState.""" + + node = cmds.createNode('network') + node_obj = as_obj(node) + null_obj = cmdc.Object() + + mod = cmdc.DGModifier() + mod.setNodeLockState(node_obj, True) + + mod.doIt() + assert cmds.lockNode(node, query=True, lock=True)[0], 'DGModifier.setNodeLockState doIt failed' + + mod.undoIt() + assert not cmds.lockNode(node, query=True, lock=True)[0], 'DGModifier.setNodeLockState undo failed' + + nose.tools.assert_raises( + TypeError, + cmdc.DGModifier().setNodeLockState, + null_obj + ) From c5c2f4f0630405af1fb3085985be5e5aa64c1601 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 16:41:45 -0700 Subject: [PATCH 18/33] Implement MDGModifier::addExtensionAttribute binding --- src/MDGModifier.inl | 14 ++++++++++++-- tests/test_MDGModifier.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index a83e91d..2131909 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -28,8 +28,18 @@ R"pbdoc(Adds an operation to the modifier to add a new dynamic attribute to the If the attribute is a compound its children will ae added as well, so only the parent needs to be added using this method.)pbdoc") .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - throw std::logic_error{"Function not yet implemented."}; - }, + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot add null extension attribute."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot add extension 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()); + } + + MStatus status = self.addExtensionAttribute(nodeClass, attribute); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifier to add a new extension attribute to the given node class. If the attribute is a compound its children will be added as well, so only the parent needs to be added using this method.)pbdoc") diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 31f4eb1..c63cd84 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -40,6 +40,13 @@ def _addAttribute_fail(exception, node, attr): ) +@nose.with_setup(teardown=new_scene) +def test_addExtensionAttribute_pass(): + """Test MDGModifier::addExtensionAttribute binding.""" + + raise SkipTest("Cannot test DGModifier.addExtensionAttribute until MFnAttribute classes are implemented.") + + @nose.with_setup(teardown=new_scene) def test_connect_pass(): src_plug = as_plug(cmds.createNode('transform') + '.visibility') @@ -382,6 +389,13 @@ def _removeAttribute_fail(exception, node, attr): ) +@nose.with_setup(teardown=new_scene) +def test_removeExtensionAttribute_pass(): + """Test MDGModfifier::removeExtensionAttribute binding.""" + + raise SkipTest("Cannot test DGModifier.removeExtensionAttribute until MFnAttribute classes are implemented.") + + def test_setNodeLockState(): """Test MDGModifier::setNodeLockState.""" From 18dca1a7549f9d4577f97830c4b7bbd71c493a94 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 16:48:21 -0700 Subject: [PATCH 19/33] Implement MDGModifier::renameNode binding --- src/MDGModifier.inl | 20 ++++++++++++++++++-- tests/test_MDGModifier.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 2131909..9593c83 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -476,8 +476,24 @@ There should be no function sets attached to the attribute at the time of the ca throw std::logic_error{"Function not yet implemented."}; }, R"pbdoc(Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") - .def("renameNode", [](MDGModifier & self, MObject node, MString newName) { - throw std::logic_error{"Function not yet implemented."}; + .def("renameNode", [](MDGModifier & self, MObject node, std::string newName) { + if (node.isNull()) + { + throw std::invalid_argument("Cannot rename a null node."); + } else if (!node.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (newName.empty()) + { + throw std::invalid_argument("Cannot rename a node to an empty string."); + } + + MStatus status = self.renameNode(node, MString(newName.c_str())); + + CHECK_STATUS(status) }, R"pbdoc(Adds an operation to the modifer to rename a node.)pbdoc") .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index c63cd84..c59cbe1 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -396,6 +396,45 @@ def test_removeExtensionAttribute_pass(): raise SkipTest("Cannot test DGModifier.removeExtensionAttribute until MFnAttribute classes are implemented.") + + +@nose.with_setup(teardown=new_scene) +def test_renameNode(): + """Test MDGModfifier::renameNode binding.""" + + node = cmds.createNode('network') + node_obj = as_obj(node) + null_obj = cmdc.Object() + + fn_node = cmdc.FnDependencyNode(node_obj) + old_name, new_name = node, 'foobar123' + + mod = cmdc.DGModifier() + mod.renameNode(node_obj, new_name) + + mod.doIt() + assert fn_node.name() == new_name, 'DGModifier.renameNode doIt failed' + + mod.undoIt() + assert fn_node.name() == old_name, 'DGModifier.renameNode undo failed' + + mod.renameNode(node_obj, 'invalid name') + mod.doIt() + assert fn_node.name() == 'invalid_name', 'DGModifier.renameNode doIt failed with an invalid node name' + + nose.tools.assert_raises( + ValueError, + cmdc.DGModifier().renameNode, + node_obj, '' + ) + + nose.tools.assert_raises( + ValueError, + cmdc.DGModifier().renameNode, + null_obj, 'foobar' + ) + + def test_setNodeLockState(): """Test MDGModifier::setNodeLockState.""" From 79f223a477a5cdd256b2017296092c97a7e21c0b Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 17:10:50 -0700 Subject: [PATCH 20/33] Implement MDGModifier::renameAttribute binding --- src/MDGModifier.inl | 34 ++++++++++++++++++++++-- tests/test_MDGModifier.py | 56 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 9593c83..090e0cc 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -472,8 +472,38 @@ There should be no function sets attached to the attribute at the time of the ca throw std::logic_error{"Function not yet implemented."}; }, R"pbdoc(Adds an operation to the modifier to remove an element of a multi (array) plug.)pbdoc") - .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, MString shortName, MString longName) { - throw std::logic_error{"Function not yet implemented."}; + .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, std::string shortName, std::string longName) { + if (node.isNull()) + { + throw std::invalid_argument("Cannot rename an attribute from a null node."); + } else if (!node.hasFn(MFn::kDependencyNode)) { + MString error_msg("Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); + error_msg.format(error_msg, node.apiTypeStr()); + throw pybind11::type_error(error_msg.asChar()); + } + + if (attribute.isNull()) + { + throw std::invalid_argument("Cannot rename a null attribute."); + } else if (!attribute.hasFn(MFn::kAttribute)) { + MString error_msg("Cannot rename 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()); + } + + if (shortName.empty() || longName.empty()) + { + throw std::invalid_argument("Cannot rename an attribute to an empty string."); + } + + // TODO: When MFnAttribute is implement, raise a TypeError if `attribute` is not dynamic. + // TODO: Regex to restrict names to [a-zA-Z0-9_]? + // TODO: Do short/long name have length constraints? + + MStatus status = self.renameAttribute(node, attribute, MString(shortName.c_str()), MString(longName.c_str())); + + CHECK_STATUS(status) + }, R"pbdoc(Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") .def("renameNode", [](MDGModifier & self, MObject node, std::string newName) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index c59cbe1..e98a7bf 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -376,7 +376,7 @@ def test_removeAttribute_fail(): [TypeError, "a non-node object", (plug.attribute(), null)], [TypeError, "a non-attribute object", (plug.node(), plug.node())], ): - test_removeAttribute_fail.__doc__ = """Test MDGModifier::removeAttribute raises an error if with {}.""".format(doc) + test_removeAttribute_fail.__doc__ = """Test MDGModifier::removeAttribute raises an error if called with {}.""".format(doc) yield _removeAttribute_fail, exc, node, attr @@ -398,6 +398,60 @@ def test_removeExtensionAttribute_pass(): +@nose.with_setup(teardown=new_scene) +def test_renameAttribute(): + """Test MDGModfifier::renameAttribute binding.""" + + node = cmds.createNode('network') + + cmds.addAttr(node, ln='fizz') + + plug = as_plug(node + '.fizz') + + node_obj = plug.node() + attr_obj = plug.attribute() + null_obj = cmdc.Object() + + mod = cmdc.DGModifier() + mod.renameAttribute(node_obj, attr_obj, 'buzz', 'buzz') + + mod.doIt() + assert plug.name() == node + '.buzz', 'DGModifier.renameAttribute doIt failed' + + mod.undoIt() + assert plug.name() == node + '.fizz', 'DGModifier.renameAttribute undo failed' + + +@nose.with_setup(teardown=new_scene) +def test_renameAttribute_fail(): + """Test MDGModfifier::renameAttribute binding error handling.""" + + node = cmds.createNode('network') + cmds.addAttr(node, ln='fizz') + plug = as_plug(node + '.fizz') + + node_obj = plug.node() + attr_obj = plug.attribute() + null_obj = cmdc.Object() + + for exception, doc, (node, attr, short_name, long_name) in ( + [ValueError, "a null node", (null_obj, attr_obj, '', '')], + [ValueError, "a null attribute", (node_obj, null_obj, '', '')], + [ValueError, "an empty short name", (node_obj, attr_obj, '', '')], + [ValueError, "an empty long name", (node_obj, attr_obj, 'buzz', '')], + ): + test_renameAttribute_fail.__doc__ = """Test MDGModifier::renameAttribute raises an error if called with {}.""".format(doc) + yield _renameAttribute_fail, exception, node, attr, short_name, long_name + + +def _renameAttribute_fail(exception, node_obj, attr_obj, short_name, long_name): + nose.tools.assert_raises( + exception, + cmdc.DGModifier().renameAttribute, + node_obj, attr_obj, short_name, long_name + ) + + @nose.with_setup(teardown=new_scene) def test_renameNode(): """Test MDGModfifier::renameNode binding.""" From fca84a0fa81a0e2d78e8272afe2da358ec4e3941 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 17:19:56 -0700 Subject: [PATCH 21/33] Implement MDGModfifier::removeMultiInstance binding --- src/MDGModifier.inl | 7 ++++- tests/test_MDGModifier.py | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 090e0cc..1f98450 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -469,7 +469,12 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeMultiInstance", [](MDGModifier & self, MPlug plug, bool breakConnections) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(plug); + plug::assert_is_element(plug); + + MStatus status = self.removeMultiInstance(plug, breakConnections); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to remove an element of a multi (array) plug.)pbdoc") .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, std::string shortName, std::string longName) { diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index e98a7bf..548fc74 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -397,6 +397,62 @@ def test_removeExtensionAttribute_pass(): +@nose.with_setup(teardown=new_scene) +def test_removeMultiInstance_pass(): + """Test MDGModfifier::removeMultiInstance binding.""" + + node = cmds.createNode('network') + + cmds.addAttr(node, ln='pass', multi=True) + + plug_obj = as_plug(node + '.pass') + null_obj = cmdc.Plug() + + cmds.setAttr(node + '.pass[0]', 1.0) + cmds.setAttr(node + '.pass[1]', 1.0) + cmds.setAttr(node + '.pass[2]', 1.0) + + index_obj = plug_obj.elementByLogicalIndex(1) + + mod = cmdc.DGModifier() + mod.removeMultiInstance(index_obj, True) + + mod.doIt() + assert plug_obj.getExistingArrayAttributeIndices() == [0, 2], 'DGMOdifier.removeMultiInstance doIt failed' + + mod.undoIt() + assert plug_obj.getExistingArrayAttributeIndices() == [0, 1, 2], 'DGMOdifier.removeMultiInstance undoIt failed' + + + + +@nose.with_setup(teardown=new_scene) +def test_removeMultiInstance_fail(): + """Test MDGModfifier::removeMultiInstance binding error handling.""" + + node = cmds.createNode('network') + + cmds.addAttr(node, ln='fail') + + plug_obj = as_plug(node + '.fail') + null_obj = cmdc.Plug() + + for exception, doc, plug_obj in ( + [ValueError, 'a null plug', null_obj], + [TypeError, 'a non-element plug', plug_obj], + ): + test_removeMultiInstance_fail.__doc__ = """Test MDGModifier::removeMultiInstance raises an error if called with {}.""".format(doc) + + yield _removeMultiInstance_fail, exception, plug_obj + + +def _removeMultiInstance_fail(exception, plug_obj): + nose.tools.assert_raises( + exception, + cmdc.DGModifier().removeMultiInstance, + plug_obj, True + ) + @nose.with_setup(teardown=new_scene) def test_renameAttribute(): From c840da7ed4d37a10434e49760eed2fe8536600ee Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 17:25:38 -0700 Subject: [PATCH 22/33] Implement MDGModifier::commandToExecute bindings --- src/MDGModifier.inl | 22 ++++++++++++++++---- tests/test_MDGModifier.py | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 1f98450..47c5313 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -43,8 +43,15 @@ If the attribute is a compound its children will ae added as well, so only the p R"pbdoc(Adds an operation to the modifier to add a new extension attribute to the given node class. If the attribute is a compound its children will be added as well, so only the parent needs to be added using this method.)pbdoc") - .def("commandToExecute", [](MDGModifier & self, MString command) { - throw std::logic_error{"Function not yet implemented."}; + .def("commandToExecute", [](MDGModifier & self, std::string command) { + if (command.empty()) + { + throw std::invalid_argument("Cannot execute an empty MEL command."); + } + + MStatus status = self.commandToExecute(MString(command.c_str())); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to execute a MEL command. The command should be fully undoable otherwise unexpected results may occur. @@ -386,8 +393,15 @@ Note that the link is established immediately and is not affected by the modifie CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to set a value onto a string plug.)pbdoc") - .def("pythonCommandToExecute", [](MDGModifier & self, MString command) { - throw std::logic_error{"Function not yet implemented."}; + .def("pythonCommandToExecute", [](MDGModifier & self, std::string command) { + if (command.empty()) + { + throw std::invalid_argument("Cannot execute an empty Python command."); + } + + MStatus status = self.pythonCommandToExecute(MString(command.c_str())); + + CHECK_STATUS(status); }, R"pbdoc(Adds an operation to the modifier to execute a Python command, which can be passed as either a Python callable or a string containing the text of the Python code to be executed. diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 548fc74..88c1bcd 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -11,6 +11,8 @@ @nose.with_setup(teardown=new_scene) def test_addAttribute_pass(): + """Test MDGModifier::addAttribute binding.""" + raise SkipTest("Cannot test DGModifier.addAttribute until MFnAttribute classes are implemented.") @@ -47,6 +49,26 @@ def test_addExtensionAttribute_pass(): raise SkipTest("Cannot test DGModifier.addExtensionAttribute until MFnAttribute classes are implemented.") +@nose.with_setup(teardown=new_scene) +def test_commandToExecute(): + """Test MDGModifier::commandToExecute binding.""" + + mod = cmdc.DGModifier() + mod.commandToExecute('createNode -name "foobar" "transform";') + + mod.doIt() + assert cmds.objExists('foobar'), 'DGModifier::commandToExecute doIt failed' + + mod.undoIt() + assert not cmds.objExists('foobar'), 'DGModifier::commandToExecute undo failed' + + nose.tools.assert_raises( + ValueError, + cmdc.DGModifier().commandToExecute, + '' + ) + + @nose.with_setup(teardown=new_scene) def test_connect_pass(): src_plug = as_plug(cmds.createNode('transform') + '.visibility') @@ -342,6 +364,26 @@ def _newPlugValue(new_value, method_name, add_attr_kwargs): ) +@nose.with_setup(teardown=new_scene) +def test_pythonCommandToExecute(): + """Test MDGModifier::pythonCommandToExecute binding.""" + + mod = cmdc.DGModifier() + mod.pythonCommandToExecute('from maya import cmds; cmds.createNode("transform", name="foobar")') + + mod.doIt() + assert cmds.objExists('foobar'), 'DGModifier::pythonCommandToExecute doIt failed' + + mod.undoIt() + assert not cmds.objExists('foobar'), 'DGModifier::pythonCommandToExecute undo failed' + + nose.tools.assert_raises( + ValueError, + cmdc.DGModifier().commandToExecute, + '' + ) + + @nose.with_setup(teardown=new_scene) def test_removeAttribute_pass(): """Test MDGModifier::removeAttribute if called with a valid attribute.""" From 3079817581f932462fc3f99e105c739703706111 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sat, 5 Jun 2021 17:56:00 -0700 Subject: [PATCH 23/33] Refactor repeated validations as inline functions --- src/MDGModifier.inl | 270 ++++++++++---------------------------------- src/main.cpp | 1 + src/util/obj.hpp | 18 +++ 3 files changed, 80 insertions(+), 209 deletions(-) create mode 100644 src/util/obj.hpp diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 47c5313..7f50afa 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -2,24 +2,12 @@ py::class_(m, "DGModifier") .def(py::init<>()) .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot add attribute to a null object."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - 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()); - } - - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot add null attribute to an object."); - } else if (!node.hasFn(MFn::kAttribute)) { - 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()); - } + validate::object::assert_not_null(node, "Cannot add attribute to a null object."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a(n) '^1s' object."); + validate::object::assert_not_null(attribute, "Cannot add null attribute to a node."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + MStatus status = self.addAttribute(node, attribute); CHECK_STATUS(status) @@ -28,14 +16,8 @@ R"pbdoc(Adds an operation to the modifier to add a new dynamic attribute to the If the attribute is a compound its children will ae added as well, so only the parent needs to be added using this method.)pbdoc") .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot add null extension attribute."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot add extension 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()); - } + validate::object::assert_not_null(attribute, "Cannot add null extension attribute."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot add extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.addExtensionAttribute(nodeClass, attribute); @@ -62,41 +44,17 @@ 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) { - 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()); - } + validate::object::assert_not_null(sourceNode, "Cannot connect - sourceNode is null."); + validate::object::assert_has_fn(sourceNode, MFn::kDependencyNode, "Cannot connect - 'sourceNode' must be a 'node' object , not a '^1s' object."); - 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()); - } + validate::object::assert_not_null(sourceAttr, "Cannot connect - 'sourceAttr' is null."); + validate::object::assert_has_fn(sourceAttr, MFn::kAttribute, "Cannot connect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); - 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()); - } + validate::object::assert_not_null(destNode, "Cannot connect - 'destNode' is null."); + validate::object::assert_has_fn(destNode, MFn::kDependencyNode, "Cannot connect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + + validate::object::assert_not_null(destAttr, "Cannot connect - 'destAttr' is null."); + validate::object::assert_has_fn(destAttr, MFn::kAttribute, "Cannot connect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); // TODO: Once the MFnAttribute classes are implemented, // add additional validation to ensure that the attributes can be connected @@ -176,14 +134,10 @@ The new node is created and returned but will not be added to the dependency gra Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("deleteNode", [](MDGModifier & self, MObject node) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot delete a null object."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot delete a(n) '^1s' object."); - error_msg.format(error_msg, node.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } else if (node.hasFn(MFn::kDagNode)) { + validate::object::assert_not_null(node, "Cannot delete a null object."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot delete a(n) '^1s' object."); + + if (node.hasFn(MFn::kDagNode)) { MString error_msg("Cannot delete a(n) DAG object - use DAGModifier instead."); error_msg.format(error_msg, node.apiTypeStr()); throw pybind11::type_error(error_msg.asChar()); @@ -203,41 +157,18 @@ operation is added so that the queue is emptied. Then, deleteNode() can be calle doIt() should be called immediately after to ensure that the queue is emptied before any other operations are added to it.)pbdoc") .def("disconnect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) { - if (sourceNode.isNull()) - { - throw std::invalid_argument("Cannot disconnect - sourceNode is null."); - } else if (!sourceNode.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot disconnect - 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 disconnect - 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()); - } + validate::object::assert_not_null(sourceNode, "Cannot disconnect - sourceNode is null."); + validate::object::assert_has_fn(sourceNode, MFn::kDependencyNode, "Cannot disconnect - 'sourceNode' must be a 'node' object , not a '^1s' object."); - if (destNode.isNull()) - { - throw std::invalid_argument("Cannot disconnect - destNode is null."); - } else if (!destNode.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot disconnect - destNode must be a 'kDependencyNode' object , not a '^1s' object."); - error_msg.format(error_msg, destNode.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } + validate::object::assert_not_null(sourceAttr, "Cannot disconnect - 'sourceAttr' is null."); + validate::object::assert_has_fn(sourceAttr, MFn::kAttribute, "Cannot disconnect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); - if (destAttr.isNull()) - { - throw std::invalid_argument("Cannot disconnect - 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()); - } + validate::object::assert_not_null(destNode, "Cannot disconnect - 'destNode' is null."); + validate::object::assert_has_fn(destNode, MFn::kDependencyNode, "Cannot disconnect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + + validate::object::assert_not_null(destAttr, "Cannot disconnect - 'destAttr' is null."); + validate::object::assert_has_fn(destAttr, MFn::kAttribute, "Cannot disconnect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + MStatus status = self.disconnect(sourceNode, sourceAttr, destNode, destAttr); CHECK_STATUS(status) @@ -261,10 +192,7 @@ doIt() should be called immediately after to ensure that the queue is emptied be .def("doIt", [](MDGModifier & self) { MStatus status = self.doIt(); - if (!status) - { - throw std::runtime_error(status.errorString().asChar()); - } + CHECK_STATUS(status) }, R"pbdoc(Executes the modifier's operations. @@ -274,24 +202,11 @@ then only the operations which were added since the previous doIt() call will be If undoIt() has been called then the next call to doIt() will do all operations.)pbdoc") .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { - if (plugin.isNull()) - { - throw std::invalid_argument("Cannot link extension attribute from a null plugin."); - } else if (!plugin.hasFn(MFn::kPlugin)) - { - MString error_msg("Cannot link extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); - error_msg.format(error_msg, plugin.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } - - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot link null extension attribute from a plugin."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot link extension 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()); - } + validate::object::assert_not_null(plugin, "Cannot link extension attribute to a null plugin."); + validate::object::assert_has_fn(plugin, MFn::kPlugin, "Cannot link extension attribute to plugin - must specify a 'kPlugin' object, not a '^1s' object."); + + validate::object::assert_not_null(attribute, "Cannot link null extension attribute from a plugin."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot link extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.linkExtensionAttributeToPlugin(plugin, attribute); @@ -413,24 +328,12 @@ It is best to use multiple calls rather than batching multiple commands into a s 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("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot remove an attribute from a null node."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); - error_msg.format(error_msg, node.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } + .def("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { + validate::object::assert_not_null(node, "Cannot remove an attribute from a null node."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot remove a null attribute."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot remove 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()); - } + validate::object::assert_not_null(attribute, "Cannot remove a null attribute."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeAttribute(node, attribute); @@ -443,14 +346,8 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot remove null extension attribute."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot remove extension 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()); - } + validate::object::assert_not_null(attribute, "Cannot remove null extension attribute."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); @@ -463,18 +360,13 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot remove null extension attribute (if unset)."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot remove extension attribute (if unset) - '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()); - } + validate::object::assert_not_null(attribute, "Cannot remove null extension attribute (if unset)."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute (if unset) - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); - CHECK_STATUS(status) }, + CHECK_STATUS(status) + }, R"pbdoc(Adds an operation to the modifier to remove an extension attribute from the given node class, but only if there are no nodes in the graph with non-default values for this attribute. If the attribute is a compound its children will be removed as well, so only the parent needs to be removed using this method. @@ -492,23 +384,11 @@ 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 remove an element of a multi (array) plug.)pbdoc") .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, std::string shortName, std::string longName) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot rename an attribute from a null node."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); - error_msg.format(error_msg, node.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } - - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot rename a null attribute."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot rename 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()); - } + validate::object::assert_not_null(node, "Cannot rename an attribute from a null node."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); + + validate::object::assert_not_null(attribute, "Cannot rename a null attribute."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot rename attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); if (shortName.empty() || longName.empty()) { @@ -526,14 +406,8 @@ There should be no function sets attached to the attribute at the time of the ca }, R"pbdoc(Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") .def("renameNode", [](MDGModifier & self, MObject node, std::string newName) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot rename a null node."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); - error_msg.format(error_msg, node.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } + validate::object::assert_not_null(node, "Cannot rename a null node."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); if (newName.empty()) { @@ -546,14 +420,8 @@ There should be no function sets attached to the attribute at the time of the ca }, R"pbdoc(Adds an operation to the modifer to rename a node.)pbdoc") .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { - if (node.isNull()) - { - throw std::invalid_argument("Cannot un/lock a null node."); - } else if (!node.hasFn(MFn::kDependencyNode)) { - MString error_msg("Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); - error_msg.format(error_msg, node.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } + validate::object::assert_not_null(node, "Cannot un/lock a null node."); + validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); MStatus status = self.setNodeLockState(node, newState); @@ -563,31 +431,15 @@ There should be no function sets attached to the attribute at the time of the ca .def("undoIt", [](MDGModifier & self) { MStatus status = self.undoIt(); - if (!status) - { - throw std::runtime_error(status.errorString().asChar()); - } + CHECK_STATUS(status) }, 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 plugin, MObject attribute) { - if (plugin.isNull()) - { - throw std::invalid_argument("Cannot unlink extension attribute from a null plugin."); - } else if (!plugin.hasFn(MFn::kPlugin)) - { - MString error_msg("Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); - error_msg.format(error_msg, plugin.apiTypeStr()); - throw pybind11::type_error(error_msg.asChar()); - } + validate::object::assert_not_null(plugin, "Cannot unlink extension attribute from a null plugin."); + validate::object::assert_has_fn(plugin, MFn::kPlugin, "Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); - if (attribute.isNull()) - { - throw std::invalid_argument("Cannot unlink null extension attribute from a plugin."); - } else if (!attribute.hasFn(MFn::kAttribute)) { - MString error_msg("Cannot unlink extension 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()); - } + validate::object::assert_not_null(attribute, "Cannot unlink null extension attribute from a plugin."); + validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot unlink extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.unlinkExtensionAttributeFromPlugin(plugin, attribute); diff --git a/src/main.cpp b/src/main.cpp index 583e945..dcef4d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,7 @@ #include "util/atov.hpp" #include "util/plug.hpp" +#include "util/obj.hpp" #define STRINGIFY(x) #x #define MACRO_STRINGIFY(x) STRINGIFY(x) diff --git a/src/util/obj.hpp b/src/util/obj.hpp new file mode 100644 index 0000000..b7af8f2 --- /dev/null +++ b/src/util/obj.hpp @@ -0,0 +1,18 @@ +namespace validate { + namespace object { + inline void assert_not_null(MObject &o, std::string error_message) { + if (o.isNull()) { + throw std::invalid_argument(error_message.c_str()); + } + } + + inline void assert_has_fn(MObject &o, MFn::Type type, std::string error_message) { + if (!o.hasFn(type)) { + MString msg(error_message.c_str()); + msg.format(msg, o.apiTypeStr()); + + throw pybind11::type_error(msg.asChar()); + } + } + } +} \ No newline at end of file From 7cc464bce53759519ba7c76549fc380df24310b0 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 09:30:01 +0100 Subject: [PATCH 24/33] Add contributing.md --- .github/contributing.md | 99 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/contributing.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..9fe61e0 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,99 @@ +### Bullet Points + +- No returning of MStatus, instead throw an exception + +
+ +### Style Guide + +- [80 character line width](#80-characters-wide) +- [Formatting](formatting) + +
+ +#### 80 Characters Wide + +Maximise screen realestate and let users with big screens work with multiple vertical panes of code. No line should exceed 80 characters wide. + +
+ +#### Formatting + +It's primarily a pybind11 project, so let's stick to the pybind11 formatting. + +- https://github.com/pybind/pybind11/blob/master/.github/CONTRIBUTING.md#formatting + +In a nutshell, it's basically Python-esque. + +```cpp +void good_function_name() {} +void BadFunctionName() {} +void badFunctionName() {} + +class GoodClass {}; +class badClass {}; +class bad_class {}; + +obj.good_variable = true; +obj.goodVariable = false; +obj.GoodVariable = false; +``` + +
+ +### FAQ + +> Are we aiming to be more consistent with the C++ API or OpenMaya2? + +Good question. There are subtle and not-so-subtle differences between the two, some of which I don't understand. + +```py +# C++ +MTransformationMatrix::addTranslation() +MTransformationMatrix::rotation() -> MQuaternion + +# Python +MTransformationMatrix.translateBy() +MTransformationMatrix.rotation() -> MEulerRotation + +# But whyyyy? +``` + +They've made a few creative choices here, presumably to make it more user-friendly but it's so subjective.. + +I think we should stick to the C++ documentation as closely as possible and leave creative choices to the user. When that is not possible, such as.. + +```c++ +// C++ +MDagPath dagPath; +MFnDagNode(mobj).getPath(dagPath); +``` + +..where it's modifying a variable from a function rather than return a value (classic C++), we'll need to make some less-creative choices that I expect to become a predictable pattern anywhere this happens in C++. + +```py +# Python +dagPath = MFnDagNode(mobj).getPath(dagPath) +``` + +In this case, the reason they're doing it the way that they are in C++ is because they choose not to throw exceptions. Instead, they return a `MStatus` value expecting the user to handle it. + +```c++ +MDagPath dagPath; +MStatus status = MFnDagNode(mobj).getPath(dagPath); +if (status == MS::kSuccess) { + // It's ok to use dagPath +} +``` + +With Python, I expect we'll be able to completely replace MStatus with exceptions. + +> Do we have a standardized format for the error messages? I've just copied OM2's messages so far. + +Let's start there. I think it's hard to know until we have enough of a foundation to actually start using cmdc for actual code and experience what messages make the most sense given the context. At this point, some message is better than no message. + +> I was expecting exclusiveMatrix and exclusiveMatrixInverse to fail when called from an invalid DagPath but they return an identity matrix instead. + +Yes, the API loves this. It's perfectly happy letting you continue working with bad memory and chooses to randomly crash on you whenever it feels like it instead. This is one of the things I'd like cmdc to get rid of. It's a bad habit. + +In this case, if the API isn't returning a bad MStatus, but we know for certain the value is bad, we should throw an exception ourselves to prevent the user from experiencing a crash. \ No newline at end of file From 69455e03bad0c001f61262f4dd644a2795b4e17e Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:07:13 +0100 Subject: [PATCH 25/33] More MFnDagNode --- src/MBoundingBox.inl | 42 ++++ src/MFnDagNode.inl | 469 +++++++++++++++++++++++-------------- src/MFnDependencyNode.inl | 2 +- src/MObject.inl | 13 +- src/Math.inl | 17 -- src/Types.inl | 21 +- src/main.cpp | 9 +- tests/test_MBoundingBox.py | 43 ++++ tests/test_MFnDagNode.py | 28 ++- 9 files changed, 436 insertions(+), 208 deletions(-) create mode 100644 src/MBoundingBox.inl create mode 100644 tests/test_MBoundingBox.py diff --git a/src/MBoundingBox.inl b/src/MBoundingBox.inl new file mode 100644 index 0000000..ed0d6e6 --- /dev/null +++ b/src/MBoundingBox.inl @@ -0,0 +1,42 @@ +py::class_(m, "BoundingBox") + .def(py::init<>()) + + .def("center", [](MBoundingBox& self) -> MPoint { + return self.center(); + }, R"pbdoc(Center point)pbdoc") + + .def("clear", [](MBoundingBox& self) -> void { + self.clear(); + }, R"pbdoc(Empties the bounding box, setting its corners to (0, 0, 0).)pbdoc") + + .def("contains", [](MBoundingBox& self, MPoint point) -> bool { + return self.contains(point); + }, R"pbdoc(Returns True if a point lies within the bounding box.)pbdoc") + + .def("depth", [](MBoundingBox& self) -> double { + return self.depth(); + }, R"pbdoc(Size in Z)pbdoc") + + .def("expand", [](MBoundingBox& self, MBoundingBox box) -> void { + self.expand(box); + }, R"pbdoc(Expands the bounding box to include a point or other bounding box.)pbdoc") + + .def("expand", [](MBoundingBox& self, MPoint point) -> void { + self.expand(point); + }, R"pbdoc(Expands the bounding box to include a point or other bounding box.)pbdoc") + + .def("height", [](MBoundingBox& self) -> double { + return self.height(); + }, R"pbdoc(Size in Y)pbdoc") + + .def("intersects", [](MBoundingBox& self, MBoundingBox box, double tol = 0.0) -> bool { + return self.intersects(box, tol); + }, R"pbdoc(Returns True if any part of a given bounding box lies within this one.)pbdoc") + + .def("transformUsing", [](MBoundingBox& self, MMatrix matrix) -> void { + self.transformUsing(matrix); + }, R"pbdoc(Multiplies the bounding box's corners by a matrix.)pbdoc") + + .def("width", [](MBoundingBox& self) -> double { + return self.width(); + }, R"pbdoc(Size in X)pbdoc"); \ No newline at end of file diff --git a/src/MFnDagNode.inl b/src/MFnDagNode.inl index 930e55c..0cbf3eb 100644 --- a/src/MFnDagNode.inl +++ b/src/MFnDagNode.inl @@ -1,9 +1,172 @@ +#define _doc_create R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + +Creates a new DAG node of the specified type, with the given name. +The type may be either a type name or a type ID. If no name is given +then a unique name will be generated by combining the type name with +an integer. + +If a parent is given then the new node will be parented under it and +the functionset will be attached to the newly-created node. The +newly-created node will be returned. + +If no parent is given and the new node is a transform, it will be +parented under the world and the functionset will be attached to the +newly-created transform. The newly-created transform will be returned. + +If no parent is given and the new node is not a transform then a +transform node will be created under the world, the new node will be +parented under it, and the functionset will be attached to the +transform. The transform will be returned.)pbdoc" + +#define _doc_addChild R"pbdoc(addChild(child, index=FnDagNode.kNextPos, keepExistingParents=False) + +Makes this node a parent of `child`.)pbdoc" + +#define _doc_child R"pbdoc(child(index) -> MObject + +Returns the specified child of this node.)pbdoc" + +#define _doc_childCount R"pbdoc(childCount() -> int + +Returns the number of nodes which are children of this one.)pbdoc" + +#define _doc_getConnectedSetsAndMembers R"pbdoc(getConnectedSetsAndMembers(instance, renderableSetsOnly) -> (MObjectArray, MObjectArray) + +Returns a tuple containing an array of sets and an array of the +components of the DAG object which are in those sets. If the entire object is in a set, then the corresponding entry in the comps array will have no elements in it. +)pbdoc" + +#define _doc_dagPath R"pbdoc(dagPath() -> MDagPath + +Returns the DAG path to which this function set is attached. Raises a TypeError if the function set is attached to an MObject rather than a path.)pbdoc" + +#define _doc_dagRoot R"pbdoc(dagRoot() -> MObject + +Returns the root node of the first path leading to this node.)pbdoc" + +#define _doc_duplicate R"pbdoc(duplicate(instance=False, instanceLeaf=False) -> MObject + +Duplicates the DAG hierarchy rooted at the current node.)pbdoc" + +#define _doc_fullPathName R"pbdoc(fullPathName() -> string + +Returns the full path of the attached object, from the root of the DAG on down.)pbdoc" + +#define _doc_getAllPaths R"pbdoc(getAllPaths() -> MDagPathArray + +Returns all of the DAG paths which lead to the object to which this function set is attached.)pbdoc" + +#define _doc_getPath R"pbdoc(getPath() -> MDagPath + +Returns the DAG path to which this function set is attached, or the first path to the node if the function set is attached to an MObject.)pbdoc" + +#define _doc_hasChild R"pbdoc(hasChild(node) -> bool + +Returns True if the specified node is a child of this one.)pbdoc" + +#define _doc_hasParent R"pbdoc(hasParent(node) -> bool + +Returns True if the specified node is a parent of this one.)pbdoc" + +#define _doc_instanceCount R"pbdoc(instanceCount(indirect) -> int + +Returns the number of instances for this node.)pbdoc" + +#define _doc_isChildOf R"pbdoc(isChildOf(node) -> bool + +Returns True if the specified node is a parent of this one.)pbdoc" + +#define _doc_isInstanced R"pbdoc(isInstanced(indirect=True) -> bool + +Returns True if this node is instanced.)pbdoc" + +#define _doc_isInstancedAttribute R"pbdoc(isInstancedAttribute(attr) -> bool + +Returns True if the specified attribute is an instanced attribute of this node.)pbdoc" + +#define _doc_isParentOf R"pbdoc(isParentOf(node) -> bool + +Returns True if the specified node is a child of this one.)pbdoc" + +#define _doc_parent R"pbdoc(parent(index) -> MObject + +Returns the specified parent of this node.)pbdoc" + +#define _doc_parentCount R"pbdoc(parentCount() -> int + +Returns the number of parents this node has.)pbdoc" + +#define _doc_partialPathName R"pbdoc(partialPathName() -> string + +Returns the minimum path string necessary to uniquely identify the attached object.)pbdoc" + +#define _doc_removeChild R"pbdoc(removeChild(node) -> self + +Removes the child, specified by MObject, reparenting it under the world.)pbdoc" + +#define _doc_removeChildAt R"pbdoc(removeChildAt(index) -> self + +Removes the child, specified by index, reparenting it under the world.)pbdoc" + +#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self + +Attaches the function set to the specified node or DAG path.)pbdoc" + +#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self + +Attaches the function set to the specified node or DAG path.)pbdoc" + +#define _doc_transformationMatrix R"pbdoc(transformationMatrix() -> MMatrix + +Returns the object space transformation matrix for this DAG node.)pbdoc" + + py::class_(m, "FnDagNode") + .def_property_readonly_static("kNextPos", [](py::object /* self */) { + return static_cast(MFnDagNode::kNextPos); + }) + .def(py::init<>()) + .def(py::init([](MObject& object) { + MStatus status; + MFnDagNode fn { object, &status }; + + if (!status) { + throw std::runtime_error( + "Invalid parameter passed for object - " + "not a DAG Node, " + "Node does not exist or " + "no valid pointer to Node" + ); + } + + return std::unique_ptr(new MFnDagNode(object)); + })) + + .def(py::init([](MDagPath& dagPath) { + MStatus status; + MFnDagNode fn { dagPath, &status }; + + if (!status) { + throw std::runtime_error( + "Invalid parameter passed for object - " + "not a DAG Node, " + "Node does not exist or " + "no valid pointer to Node" + ); + } + + return std::unique_ptr(new MFnDagNode(dagPath)); + })) + + .def("__repr__", [](const MObject &a) { + return ""; + }) + .def("addChild", [](MFnDagNode& self, MObject& child, - unsigned int index = MFnDagNode::kNextPos, + unsigned int index = static_cast(MFnDagNode::kNextPos), bool keepExistingParents = false) { MStatus status = self.addChild(child, index, keepExistingParents); @@ -36,268 +199,222 @@ py::class_(m, "FnDagNode") throw std::runtime_error("Undefined error occurred"); } - }, R"pbdoc(addChild(node, index=kNextPos, keepExistingParents=False) - -Makes a node a child of this one.)pbdoc") + }, py::arg("child"), + py::arg("index") = static_cast(MFnDagNode::kNextPos), + py::arg("keepExistingParents") = false, + _doc_addChild) .def("boundingBox", [](MFnDagNode& self) -> MBoundingBox { return self.boundingBox(); }, R"pbdoc(Node's bounding box, in object space.)pbdoc") .def("child", [](MFnDagNode& self, unsigned int i) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(child(index) -> MObject - -Returns the specified child of this node.)pbdoc") + return self.child(i); + }, _doc_child) .def("childCount", [](MFnDagNode& self) -> int { return self.childCount(); - }, R"pbdoc(childCount() -> int + }, _doc_childCount) -Returns the number of nodes which are children of this one.)pbdoc") + .def("create", [](MFnDagNode& self, std::string type, MObject parent = MObject::kNullObj) -> MObject { + return self.create(type.c_str(), parent); + }, py::arg("type"), + py::arg("parent") = MObject::kNullObj, + _doc_create) - .def("create", [](MFnDagNode& self, MString type, MObject parent = MObject::kNullObj) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + .def("create", [](MFnDagNode& self, std::string type, std::string name, MObject parent = MObject::kNullObj) -> MObject { + return self.create(type.c_str(), name.c_str(), parent); + }, py::arg("type"), + py::arg("name"), + py::arg("parent") = MObject::kNullObj, + _doc_create) -Creates a new DAG node of the specified type, with the given name. -The type may be either a type name or a type ID. If no name is given -then a unique name will be generated by combining the type name with -an integer. + .def("create", [](MFnDagNode& self, MTypeId typeId, MObject parent = MObject::kNullObj) -> MObject { + return self.create(typeId, parent); + }, py::arg("type"), + py::arg("parent") = MObject::kNullObj, + _doc_create) + + .def("create", [](MFnDagNode& self, MTypeId typeId, std::string name, MObject parent = MObject::kNullObj) -> MObject { + return self.create(typeId, name.c_str(), parent); + }, py::arg("type"), + py::arg("name"), + py::arg("parent") = MObject::kNullObj, + _doc_create) -If a parent is given then the new node will be parented under it and -the functionset will be attached to the newly-created node. The -newly-created node will be returned. + .def("dagPath", [](MFnDagNode& self) -> MDagPath { + return self.dagPath(); + }, _doc_dagPath) -If no parent is given and the new node is a transform, it will be -parented under the world and the functionset will be attached to the -newly-created transform. The newly-created transform will be returned. + .def("dagRoot", [](MFnDagNode& self) -> MObject { + return self.dagRoot(); + }, _doc_dagRoot) -If no parent is given and the new node is not a transform then a -transform node will be created under the world, the new node will be -parented under it, and the functionset will be attached to the -transform. The transform will be returned.)pbdoc") + .def("duplicate", [](MFnDagNode& self, bool instance = false, bool instanceLeaf = false) -> MObject { + return self.duplicate(instance, instanceLeaf); + }, py::arg("instance") = false, + py::arg("instanceLeaf") = false, + _doc_duplicate) - .def("create", [](MFnDagNode& self, MString type, MString name, MObject parent = MObject::kNullObj) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + .def("fullPathName", [](MFnDagNode& self) -> std::string { + return self.fullPathName().asChar(); + }, _doc_fullPathName) -Creates a new DAG node of the specified type, with the given name. -The type may be either a type name or a type ID. If no name is given -then a unique name will be generated by combining the type name with -an integer. + .def("getAllPaths", [](MFnDagNode& self) -> MDagPathArray { -If a parent is given then the new node will be parented under it and -the functionset will be attached to the newly-created node. The -newly-created node will be returned. -If no parent is given and the new node is a transform, it will be -parented under the world and the functionset will be attached to the -newly-created transform. The newly-created transform will be returned. -If no parent is given and the new node is not a transform then a -transform node will be created under the world, the new node will be -parented under it, and the functionset will be attached to the -transform. The transform will be returned.)pbdoc") - .def("create", [](MFnDagNode& self, MTypeId typeId, MObject parent = MObject::kNullObj) -> MObject { + // Need MDagPathArray throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject -Creates a new DAG node of the specified type, with the given name. -The type may be either a type name or a type ID. If no name is given -then a unique name will be generated by combining the type name with -an integer. -If a parent is given then the new node will be parented under it and -the functionset will be attached to the newly-created node. The -newly-created node will be returned. -If no parent is given and the new node is a transform, it will be -parented under the world and the functionset will be attached to the -newly-created transform. The newly-created transform will be returned. -If no parent is given and the new node is not a transform then a -transform node will be created under the world, the new node will be -parented under it, and the functionset will be attached to the -transform. The transform will be returned.)pbdoc") + }, _doc_getAllPaths) - .def("create", [](MFnDagNode& self, MTypeId typeId, MString name, MObject parent = MObject::kNullObj) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject + .def("getConnectedSetsAndMembers", [](MFnDagNode& self, unsigned int instanceNumber, bool renderableSetsOnly) -> std::tuple { -Creates a new DAG node of the specified type, with the given name. -The type may be either a type name or a type ID. If no name is given -then a unique name will be generated by combining the type name with -an integer. -If a parent is given then the new node will be parented under it and -the functionset will be attached to the newly-created node. The -newly-created node will be returned. -If no parent is given and the new node is a transform, it will be -parented under the world and the functionset will be attached to the -newly-created transform. The newly-created transform will be returned. -If no parent is given and the new node is not a transform then a -transform node will be created under the world, the new node will be -parented under it, and the functionset will be attached to the -transform. The transform will be returned.)pbdoc") - .def("dagPath", [](MFnDagNode& self) -> MDagPath { + // Need std::tuple throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(dagPath() -> MDagPath -Returns the DAG path to which this function set is attached. Raises a TypeError if the function set is attached to an MObject rather than a path.)pbdoc") - .def("dagRoot", [](MFnDagNode& self) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(dagRoot() -> MObject - -Returns the root node of the first path leading to this node.)pbdoc") - .def("duplicate", [](MFnDagNode& self, bool instance = false, bool instanceLeaf = false) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(duplicate(instance=False, instanceLeaf=False) -> MObject -Duplicates the DAG hierarchy rooted at the current node.)pbdoc") - .def("fullPathName", [](MFnDagNode& self) -> MString { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(fullPathName() -> string -Returns the full path of the attached object, from the root of the DAG on down.)pbdoc") - - .def("getAllPaths", [](MFnDagNode& self) -> MDagPathArray { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(getAllPaths() -> MDagPathArray - -Returns all of the DAG paths which lead to the object to which this function set is attached.)pbdoc") - - .def("getConnectedSetsAndMembers", [](MFnDagNode& self, unsigned int instanceNumber, bool renderableSetsOnly) -> std::tuple { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(getConnectedSetsAndMembers(instance, renderableSetsOnly) -> (MObjectArray, MObjectArray) - -Returns a tuple containing an array of sets and an array of the -components of the DAG object which are in those sets. If the entire object is in a set, then the corresponding entry in the comps array will have no elements in it. -)pbdoc") + }, _doc_getConnectedSetsAndMembers) .def("getPath", [](MFnDagNode& self) -> MDagPath { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(getPath() -> MDagPath + MDagPath path; + const MStatus status = self.getPath(path); + + if (!status) { + throw std::runtime_error( + "No valid DAG Node attached to Function Set" + ); + } -Returns the DAG path to which this function set is attached, or the first path to the node if the function set is attached to an MObject.)pbdoc") + return path; + }, _doc_getPath) .def("hasChild", [](MFnDagNode& self, MObject node) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(hasChild(node) -> bool - -Returns True if the specified node is a child of this one.)pbdoc") + return self.hasChild(node); + }, py::arg("node"), _doc_hasChild) .def("hasParent", [](MFnDagNode& self, MObject node) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(hasParent(node) -> bool - -Returns True if the specified node is a parent of this one.)pbdoc") + return self.hasParent(node); + }, py::arg("node"), _doc_hasParent) .def("inModel", [](MFnDagNode& self) -> bool { - throw std::logic_error{"Function not yet implemented."}; + return self.inModel(); }, R"pbdoc(True if the node has been added to the model.)pbdoc") .def("inUnderWorld", [](MFnDagNode& self) -> bool { - throw std::logic_error{"Function not yet implemented."}; + return self.inUnderWorld(); }, R"pbdoc(True if this node is in the underworld of another node (e.g. a curve on surface is in the underworld of the surface).)pbdoc") .def("instanceCount", [](MFnDagNode& self, bool total) -> int { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(instanceCount(indirect) -> int - -Returns the number of instances for this node.)pbdoc") + return self.instanceCount(total); + }, py::arg("total"), _doc_instanceCount) .def("isChildOf", [](MFnDagNode& self, MObject node) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(isChildOf(node) -> bool - -Returns True if the specified node is a parent of this one.)pbdoc") + return self.isChildOf(node); + }, py::arg("node"), _doc_isChildOf) .def("isInstanceable", [](MFnDagNode& self) -> bool { - throw std::logic_error{"Function not yet implemented."}; + return self.isInstanceable(); }, R"pbdoc(True if instancing is allowed for this node.)pbdoc") .def("isInstanced", [](MFnDagNode& self, bool indirect = true) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(isInstanced(indirect=True) -> bool - -Returns True if this node is instanced.)pbdoc") + return self.isInstanced(indirect); + }, py::arg("indirect"), _doc_isInstanced) .def("isInstancedAttribute", [](MFnDagNode& self, MObject attr) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(isInstancedAttribute(attr) -> bool - -Returns True if the specified attribute is an instanced attribute of this node.)pbdoc") + return self.isInstancedAttribute(attr); + }, py::arg("attr"), _doc_isInstancedAttribute) .def("isIntermediateObject", [](MFnDagNode& self) -> bool { - throw std::logic_error{"Function not yet implemented."}; + return self.isIntermediateObject(); }, R"pbdoc(True if this node is just an intermediate in part of a larger calculation (e.g. input to a deformer).)pbdoc") .def("isParentOf", [](MFnDagNode& self, MObject node) -> bool { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(isParentOf(node) -> bool - -Returns True if the specified node is a child of this one.)pbdoc") + return self.isParentOf(node); + }, py::arg("node"), _doc_isParentOf) .def("objectColorRGB", [](MFnDagNode& self) -> MColor { - throw std::logic_error{"Function not yet implemented."}; + return self.objectColorRGB(); }, R"pbdoc(RGB value indicating the color in which the node is to be drawn when inactive, assuming that it is drawable.)pbdoc") .def("objectColorType", [](MFnDagNode& self) -> MFnDagNode::MObjectColorType { - throw std::logic_error{"Function not yet implemented."}; + return self.objectColorType(); }, R"pbdoc(Determines whether the default color, indexed object color, orRGB object color is used for this object.)pbdoc") .def("parent", [](MFnDagNode& self, unsigned int i) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(parent(index) -> MObject - -Returns the specified parent of this node.)pbdoc") + return self.parent(i); + }, _doc_parent) .def("parentCount", [](MFnDagNode& self) -> int { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(parentCount() -> int + return self.parentCount(); + }, _doc_parentCount) -Returns the number of parents this node has.)pbdoc") + .def("partialPathName", [](MFnDagNode& self) -> std::string { + return self.partialPathName().asChar(); + }, _doc_partialPathName) - .def("partialPathName", [](MFnDagNode& self) -> MString { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(partialPathName() -> string + .def("removeChild", [](MFnDagNode& self, MObject& child) -> void { + MStatus status = self.removeChild(child); -Returns the minimum path string necessary to uniquely identify the attached object.)pbdoc") + if (status == MStatus::kSuccess) { return; } - .def("removeChild", [](MFnDagNode& self) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeChild(node) -> self - -Removes the child, specified by MObject, reparenting it under the world.)pbdoc") + else if (status == MStatus::kInvalidParameter) { + throw std::runtime_error( + "Invalid parameter passed as child - not a DAG Node, Node does not exist or no valid pointer to Nod" + ); + } - .def("removeChildAt", [](MFnDagNode& self, unsigned int index) { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(removeChildAt(index) -> self + else if (status == MStatus::kFailure) { + throw std::runtime_error( + "No valid DAG Node attached to Function Set" + ); + } -Removes the child, specified by index, reparenting it under the world.)pbdoc") + else { + throw std::runtime_error( + "Unknown error occurred." + ); + } - .def("setObject", [](MFnDagNode& self) -> MObject { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(setObject(MObject or MDagPath) -> self + }, _doc_removeChild) -Attaches the function set to the specified node or DAG path.)pbdoc") + .def("removeChildAt", [](MFnDagNode& self, unsigned int index) { + return self.removeChildAt(index); + }, py::arg("index"), _doc_removeChildAt) + + .def("setObject", [](MFnDagNode& self, MObject& object) -> void { + if (!self.setObject(object)) throw std::runtime_error( + "Invalid parameter passed for node - " + "not a DAG Node, " + "Node does not exist or " + "no valid pointer to Node" + ); + }, _doc_setObject) .def("setObject", [](MFnDagNode& self, MDagPath path) { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(setObject(MObject or MDagPath) -> self - -Attaches the function set to the specified node or DAG path.)pbdoc") + if (!self.setObject(path)) throw std::runtime_error( + "Invalid parameter passed for objectPath - " + "invalid Path or " + "target Node is not a DAG Node, " + "does not exist or " + "has no accessible pointer " + ); + }, py::arg("path"), _doc_setObject) .def("transformationMatrix", [](MFnDagNode& self) -> MMatrix { - throw std::logic_error{"Function not yet implemented."}; - }, R"pbdoc(transformationMatrix() -> MMatrix - -Returns the object space transformation matrix for this DAG node.)pbdoc"); \ No newline at end of file + return self.transformationMatrix(); + }, _doc_transformationMatrix +); \ No newline at end of file diff --git a/src/MFnDependencyNode.inl b/src/MFnDependencyNode.inl index 0b2b974..7029ecf 100644 --- a/src/MFnDependencyNode.inl +++ b/src/MFnDependencyNode.inl @@ -70,6 +70,6 @@ py::class_(m, "FnDependencyNode") }) .def("__repr__", [](const MObject &a) { - return ""; + return ""; } ); \ No newline at end of file diff --git a/src/MObject.inl b/src/MObject.inl index 038b874..b2095d2 100644 --- a/src/MObject.inl +++ b/src/MObject.inl @@ -1,4 +1,6 @@ py::class_(m, "Object") + .def_property_readonly_static("kNullObj", [](py::object /* self */) { return MObject::kNullObj; }) + .def(py::init<>()) .def(py::init()) @@ -23,4 +25,13 @@ py::class_(m, "Object") ret += ")>"; return ret; } -); \ No newline at end of file +); + +py::class_(m, "ObjectHandle") + .def(py::init<>()) + .def(py::init()) + + .def("__repr__", [](const MObjectHandle &a) { + return ""; + } +); diff --git a/src/Math.inl b/src/Math.inl index 861c2e6..87a0b7c 100644 --- a/src/Math.inl +++ b/src/Math.inl @@ -1,21 +1,4 @@ -py::class_(m, "String") - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - - .def(py::self += MString()) - .def(py::self += char()) - .def(py::self += double()) - .def(py::self += int()) - // .def(py::self += unsigned int()) # Not supported with GCC? - .def(py::self += float()) - - .def("__repr__", [](const MString &a) { - return ""; - } -); - py::class_(m, "Vector") .def(py::init<>()) .def(py::init(m, "TypeId") ); - -py::class_(m, "ObjectHandle") +py::class_(m, "String") .def(py::init<>()) - .def(py::init()) - - .def("__repr__", [](const MObjectHandle &a) { - return ""; + .def(py::init()) + .def(py::init()) + + .def(py::self += MString()) + .def(py::self += char()) + .def(py::self += double()) + .def(py::self += int()) + // .def(py::self += unsigned int()) # Not supported with GCC? + .def(py::self += float()) + + .def("__repr__", [](const MString &a) { + return ""; } -); \ No newline at end of file +); diff --git a/src/main.cpp b/src/main.cpp index d731cdf..538107e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ #include #include #include -#include // MFnDagNode +#include #include // MFnDagNode #include // MFnDagNode @@ -63,14 +63,15 @@ PYBIND11_MODULE(cmdc, m) { )pbdoc"; #include "Math.inl" - #include "MDagPath.inl" #include "MFn.inl" + #include "Types.inl" + #include "MObject.inl" + #include "MDagPath.inl" #include "MFnDependencyNode.inl" #include "MFnDagNode.inl" - #include "MObject.inl" + #include "MBoundingBox.inl" #include "MPlug.inl" #include "MSelectionList.inl" - #include "Types.inl" #ifdef VERSION_INFO m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); diff --git a/tests/test_MBoundingBox.py b/tests/test_MBoundingBox.py new file mode 100644 index 0000000..9c3159f --- /dev/null +++ b/tests/test_MBoundingBox.py @@ -0,0 +1,43 @@ +import cmdc + +from nose.tools import ( + assert_equals, +) + +# TODO: We need to be able to author meshes, like a polyCube + + +def test_center(): + pass + + +def test_clear(): + pass + + +def test_contains(): + pass + + +def test_depth(): + pass + + +def test_expand(): + pass + + +def test_height(): + pass + + +def test_intersects(): + pass + + +def test_transformUsing(): + pass + + +def test_width(): + pass diff --git a/tests/test_MFnDagNode.py b/tests/test_MFnDagNode.py index 9878496..a498841 100644 --- a/tests/test_MFnDagNode.py +++ b/tests/test_MFnDagNode.py @@ -1,14 +1,38 @@ import cmdc + from nose.tools import ( assert_equals, ) +def test_create(): + fn = cmdc.FnDagNode() + fn.create("transform", name="child") + assert_equals(fn.partialPathName(), "child") + + +def test_create_parent(): + fn = cmdc.FnDagNode() + parent = fn.create("transform", name="parent") + fn.create("transform", name="child", parent=parent) + assert_equals(fn.fullPathName(), "|parent|child") + + def test_addChild(): fn = cmdc.FnDagNode() - child = fn.createNode("transform", name="child") + child = fn.create("transform", name="child") - fn.createNode("transform", name="parent") + fn.create("transform", name="parent") assert_equals(fn.childCount(), 0) fn.addChild(child) assert_equals(fn.childCount(), 1) + + +def test_boundingBox(): + fn = cmdc.FnDagNode() + fn.create("transform") + bbox = fn.boundingBox() + + assert_equals(bbox.width(), 0) + assert_equals(bbox.height(), 0) + assert_equals(bbox.depth(), 0) From 9e1af418bc54ed5886a00bfad8845415e0929726 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:07:18 +0100 Subject: [PATCH 26/33] Update contributing --- .github/contributing.md | 60 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/.github/contributing.md b/.github/contributing.md index 9fe61e0..2fd7226 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -1,6 +1,62 @@ -### Bullet Points +The goal of cmdc is to not to be a drop-in replacement of Maya Python API 2.0, but a better version of it. -- No returning of MStatus, instead throw an exception +In practice, it means you will be able to drop-in replace it, but will likely require tweaks to your code in order to get it running. Hopefully, you should never again have to suffer the Maya Python API 2.0 and instead gain complete control over what and how the Python API works. + +
+ +### Functional + +- No M-prefix +- No MString arguments or return values, instead use std::string +- No MStatus arguments or return values, instead throw an exception +- Keyword arguments SHALL be preserved, via e.g. `py::arg` +- Default argument values SHALL be preserved, via e.g. `py::arg("name") = 1.0` +- [Properties SHALL NOT Compute](#properties-cannot-compute) +- [Anonymous enums SHALL be cast to unsigned int](anonymous-enums-shall-be-cast-to-unsigned-int) + +> [Reference](https://developer.lsst.io/pybind11/style.html#id37) + +
+ +### Properties Cannot Compute + +The Maya Python API 2.0 uses Python `@property` for any function call that takes no arguments, returns a value and cannot throw an exception. + +```py +bbox = mesh.boundingBox() + +# C++ +print(bbox.width()) + +# Python property +print(bbox.width) +``` + +But this not only differs from the C++ documentation it also makes it ambiguous what is a function and what is not. How is the user to know whether whether it takes any optional arguments or is able to throw an exception? + +As such, `@property` is reserved to *static* attributes only, and cannot compute. Not even the `width` of a bounding box. + +### Anonymous enums SHALL be cast to unsigned int + +Maya uses anonymous enums in places. + +```cpp +// MFnDagNode.h +class MFnDagNode : public MFnDependencyNode { +public: + enum { + kNextPos = 0xff + }; +``` + +These should be `static_cast` to an `unsigned int`, as they cannot (?) be wrapped to Python. + +```cpp +py::class_(m, "FnDagNode") + .def_property_readonly_static("kNextPos", [](py::object /* self */) { + return static_cast(MFnDagNode::kNextPos); + }) +```
From 928ecb7621d01cc05a38936fd9a0efd09c310baa Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:07:31 +0100 Subject: [PATCH 27/33] Update README --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 285a7d3..7c7ba26 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,11 @@ This should build on any platform, for any Maya with Python available. Including ```pwsh $env:DEVKIT_LOCATION="C:\github\maya-devkit\2020.2\windows" -.\build_win32.ps1 2020 +pwsh .\build_win32.ps1 2020 ``` +> **NOTE**: Keep `pwsh` to avoid existing your terminal on failure + **Linux** ```bash @@ -128,3 +130,47 @@ cd cmdc docker build -t cmdc . .\docker_build_linux.ps1 2020 ``` + +
+ +### Contributing + +Interested in helping out? Here's how to do it. + +1. Get a free compiler, like [Visual Studio 2019]() +1. Download a Maya devkit, such as [this one]() +2. Clone this repository +3. Write your header file +4. Submit a pull-request +5. Profit + +**FAQ** + +> Do I need to know C++? + +Not really! What we're doing here is mostly busy-work, filling in the many functions exposed by the Maya API. Most if not all trickery has already been done in other functions, so copy/paste is a valid option. + +> I don't have any other questions? + +Great, then here's what's next. + +```bash +# Tell cmdc about where your devkit is +$env:DEVKIT_LOCATION="C:\github\mayaexamples\maya-devkit\2020.2\windows" + +# Clone the big jeeves out of this repository +git clone https://github.com/mottosso/cmdc.git + +cd cmdc + +# Either write your own header from scratch.. +# ..or generate some boiler plate of your favourite header! +mayapy ./scripts/parse_header.py MFnDagNode.h +``` + +From here, you'll have a freshly generated header file, ready to fill in. As you fill things in, you build it like this. + + +```bash + +``` \ No newline at end of file From 6ad56cd4cfcb2f88f5bee7b440ffb67bc09fc5df Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:07:18 +0100 Subject: [PATCH 28/33] Update contributing --- .github/contributing.md | 155 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 .github/contributing.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..2fd7226 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,155 @@ +The goal of cmdc is to not to be a drop-in replacement of Maya Python API 2.0, but a better version of it. + +In practice, it means you will be able to drop-in replace it, but will likely require tweaks to your code in order to get it running. Hopefully, you should never again have to suffer the Maya Python API 2.0 and instead gain complete control over what and how the Python API works. + +
+ +### Functional + +- No M-prefix +- No MString arguments or return values, instead use std::string +- No MStatus arguments or return values, instead throw an exception +- Keyword arguments SHALL be preserved, via e.g. `py::arg` +- Default argument values SHALL be preserved, via e.g. `py::arg("name") = 1.0` +- [Properties SHALL NOT Compute](#properties-cannot-compute) +- [Anonymous enums SHALL be cast to unsigned int](anonymous-enums-shall-be-cast-to-unsigned-int) + +> [Reference](https://developer.lsst.io/pybind11/style.html#id37) + +
+ +### Properties Cannot Compute + +The Maya Python API 2.0 uses Python `@property` for any function call that takes no arguments, returns a value and cannot throw an exception. + +```py +bbox = mesh.boundingBox() + +# C++ +print(bbox.width()) + +# Python property +print(bbox.width) +``` + +But this not only differs from the C++ documentation it also makes it ambiguous what is a function and what is not. How is the user to know whether whether it takes any optional arguments or is able to throw an exception? + +As such, `@property` is reserved to *static* attributes only, and cannot compute. Not even the `width` of a bounding box. + +### Anonymous enums SHALL be cast to unsigned int + +Maya uses anonymous enums in places. + +```cpp +// MFnDagNode.h +class MFnDagNode : public MFnDependencyNode { +public: + enum { + kNextPos = 0xff + }; +``` + +These should be `static_cast` to an `unsigned int`, as they cannot (?) be wrapped to Python. + +```cpp +py::class_(m, "FnDagNode") + .def_property_readonly_static("kNextPos", [](py::object /* self */) { + return static_cast(MFnDagNode::kNextPos); + }) +``` + +
+ +### Style Guide + +- [80 character line width](#80-characters-wide) +- [Formatting](formatting) + +
+ +#### 80 Characters Wide + +Maximise screen realestate and let users with big screens work with multiple vertical panes of code. No line should exceed 80 characters wide. + +
+ +#### Formatting + +It's primarily a pybind11 project, so let's stick to the pybind11 formatting. + +- https://github.com/pybind/pybind11/blob/master/.github/CONTRIBUTING.md#formatting + +In a nutshell, it's basically Python-esque. + +```cpp +void good_function_name() {} +void BadFunctionName() {} +void badFunctionName() {} + +class GoodClass {}; +class badClass {}; +class bad_class {}; + +obj.good_variable = true; +obj.goodVariable = false; +obj.GoodVariable = false; +``` + +
+ +### FAQ + +> Are we aiming to be more consistent with the C++ API or OpenMaya2? + +Good question. There are subtle and not-so-subtle differences between the two, some of which I don't understand. + +```py +# C++ +MTransformationMatrix::addTranslation() +MTransformationMatrix::rotation() -> MQuaternion + +# Python +MTransformationMatrix.translateBy() +MTransformationMatrix.rotation() -> MEulerRotation + +# But whyyyy? +``` + +They've made a few creative choices here, presumably to make it more user-friendly but it's so subjective.. + +I think we should stick to the C++ documentation as closely as possible and leave creative choices to the user. When that is not possible, such as.. + +```c++ +// C++ +MDagPath dagPath; +MFnDagNode(mobj).getPath(dagPath); +``` + +..where it's modifying a variable from a function rather than return a value (classic C++), we'll need to make some less-creative choices that I expect to become a predictable pattern anywhere this happens in C++. + +```py +# Python +dagPath = MFnDagNode(mobj).getPath(dagPath) +``` + +In this case, the reason they're doing it the way that they are in C++ is because they choose not to throw exceptions. Instead, they return a `MStatus` value expecting the user to handle it. + +```c++ +MDagPath dagPath; +MStatus status = MFnDagNode(mobj).getPath(dagPath); +if (status == MS::kSuccess) { + // It's ok to use dagPath +} +``` + +With Python, I expect we'll be able to completely replace MStatus with exceptions. + +> Do we have a standardized format for the error messages? I've just copied OM2's messages so far. + +Let's start there. I think it's hard to know until we have enough of a foundation to actually start using cmdc for actual code and experience what messages make the most sense given the context. At this point, some message is better than no message. + +> I was expecting exclusiveMatrix and exclusiveMatrixInverse to fail when called from an invalid DagPath but they return an identity matrix instead. + +Yes, the API loves this. It's perfectly happy letting you continue working with bad memory and chooses to randomly crash on you whenever it feels like it instead. This is one of the things I'd like cmdc to get rid of. It's a bad habit. + +In this case, if the API isn't returning a bad MStatus, but we know for certain the value is bad, we should throw an exception ourselves to prevent the user from experiencing a crash. \ No newline at end of file From b2bdf3f0a2e71a92d101f6149be2e203f6b5de1e Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:07:31 +0100 Subject: [PATCH 29/33] Update README --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 285a7d3..7c7ba26 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,11 @@ This should build on any platform, for any Maya with Python available. Including ```pwsh $env:DEVKIT_LOCATION="C:\github\maya-devkit\2020.2\windows" -.\build_win32.ps1 2020 +pwsh .\build_win32.ps1 2020 ``` +> **NOTE**: Keep `pwsh` to avoid existing your terminal on failure + **Linux** ```bash @@ -128,3 +130,47 @@ cd cmdc docker build -t cmdc . .\docker_build_linux.ps1 2020 ``` + +
+ +### Contributing + +Interested in helping out? Here's how to do it. + +1. Get a free compiler, like [Visual Studio 2019]() +1. Download a Maya devkit, such as [this one]() +2. Clone this repository +3. Write your header file +4. Submit a pull-request +5. Profit + +**FAQ** + +> Do I need to know C++? + +Not really! What we're doing here is mostly busy-work, filling in the many functions exposed by the Maya API. Most if not all trickery has already been done in other functions, so copy/paste is a valid option. + +> I don't have any other questions? + +Great, then here's what's next. + +```bash +# Tell cmdc about where your devkit is +$env:DEVKIT_LOCATION="C:\github\mayaexamples\maya-devkit\2020.2\windows" + +# Clone the big jeeves out of this repository +git clone https://github.com/mottosso/cmdc.git + +cd cmdc + +# Either write your own header from scratch.. +# ..or generate some boiler plate of your favourite header! +mayapy ./scripts/parse_header.py MFnDagNode.h +``` + +From here, you'll have a freshly generated header file, ready to fill in. As you fill things in, you build it like this. + + +```bash + +``` \ No newline at end of file From 18ed4c9357c548587fdc3193db7475f26e6a821b Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 6 Jun 2021 13:26:28 +0100 Subject: [PATCH 30/33] Terminate those raw strings for poor GCC --- src/MFnDagNode.inl | 143 ++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/MFnDagNode.inl b/src/MFnDagNode.inl index 0cbf3eb..97958f5 100644 --- a/src/MFnDagNode.inl +++ b/src/MFnDagNode.inl @@ -1,123 +1,123 @@ -#define _doc_create R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject - -Creates a new DAG node of the specified type, with the given name. -The type may be either a type name or a type ID. If no name is given -then a unique name will be generated by combining the type name with -an integer. - -If a parent is given then the new node will be parented under it and -the functionset will be attached to the newly-created node. The -newly-created node will be returned. - -If no parent is given and the new node is a transform, it will be -parented under the world and the functionset will be attached to the -newly-created transform. The newly-created transform will be returned. - -If no parent is given and the new node is not a transform then a -transform node will be created under the world, the new node will be -parented under it, and the functionset will be attached to the +#define _doc_create R"pbdoc(create(type, name=None, parent=MObject.kNullObj) -> MObject\ +\ +Creates a new DAG node of the specified type, with the given name.\ +The type may be either a type name or a type ID. If no name is given\ +then a unique name will be generated by combining the type name with\ +an integer.\ +\ +If a parent is given then the new node will be parented under it and\ +the functionset will be attached to the newly-created node. The\ +newly-created node will be returned.\ +\ +If no parent is given and the new node is a transform, it will be\ +parented under the world and the functionset will be attached to the\ +newly-created transform. The newly-created transform will be returned.\ +\ +If no parent is given and the new node is not a transform then a\ +transform node will be created under the world, the new node will be\ +parented under it, and the functionset will be attached to the\ transform. The transform will be returned.)pbdoc" -#define _doc_addChild R"pbdoc(addChild(child, index=FnDagNode.kNextPos, keepExistingParents=False) - +#define _doc_addChild R"pbdoc(addChild(child, index=FnDagNode.kNextPos, keepExistingParents=False)\ +\ Makes this node a parent of `child`.)pbdoc" -#define _doc_child R"pbdoc(child(index) -> MObject - +#define _doc_child R"pbdoc(child(index) -> MObject\ +\ Returns the specified child of this node.)pbdoc" -#define _doc_childCount R"pbdoc(childCount() -> int - +#define _doc_childCount R"pbdoc(childCount() -> int\ +\ Returns the number of nodes which are children of this one.)pbdoc" -#define _doc_getConnectedSetsAndMembers R"pbdoc(getConnectedSetsAndMembers(instance, renderableSetsOnly) -> (MObjectArray, MObjectArray) - -Returns a tuple containing an array of sets and an array of the -components of the DAG object which are in those sets. If the entire object is in a set, then the corresponding entry in the comps array will have no elements in it. +#define _doc_getConnectedSetsAndMembers R"pbdoc(getConnectedSetsAndMembers(instance, renderableSetsOnly) -> (MObjectArray, MObjectArray)\ +\ +Returns a tuple containing an array of sets and an array of the\ +components of the DAG object which are in those sets. If the entire object is in a set, then the corresponding entry in the comps array will have no elements in it.\ )pbdoc" -#define _doc_dagPath R"pbdoc(dagPath() -> MDagPath - +#define _doc_dagPath R"pbdoc(dagPath() -> MDagPath\ +\ Returns the DAG path to which this function set is attached. Raises a TypeError if the function set is attached to an MObject rather than a path.)pbdoc" -#define _doc_dagRoot R"pbdoc(dagRoot() -> MObject - +#define _doc_dagRoot R"pbdoc(dagRoot() -> MObject\ +\ Returns the root node of the first path leading to this node.)pbdoc" -#define _doc_duplicate R"pbdoc(duplicate(instance=False, instanceLeaf=False) -> MObject - +#define _doc_duplicate R"pbdoc(duplicate(instance=False, instanceLeaf=False) -> MObject\ +\ Duplicates the DAG hierarchy rooted at the current node.)pbdoc" -#define _doc_fullPathName R"pbdoc(fullPathName() -> string - +#define _doc_fullPathName R"pbdoc(fullPathName() -> string\ +\ Returns the full path of the attached object, from the root of the DAG on down.)pbdoc" -#define _doc_getAllPaths R"pbdoc(getAllPaths() -> MDagPathArray - +#define _doc_getAllPaths R"pbdoc(getAllPaths() -> MDagPathArray\ +\ Returns all of the DAG paths which lead to the object to which this function set is attached.)pbdoc" -#define _doc_getPath R"pbdoc(getPath() -> MDagPath - +#define _doc_getPath R"pbdoc(getPath() -> MDagPath\ +\ Returns the DAG path to which this function set is attached, or the first path to the node if the function set is attached to an MObject.)pbdoc" -#define _doc_hasChild R"pbdoc(hasChild(node) -> bool - +#define _doc_hasChild R"pbdoc(hasChild(node) -> bool\ +\ Returns True if the specified node is a child of this one.)pbdoc" -#define _doc_hasParent R"pbdoc(hasParent(node) -> bool - +#define _doc_hasParent R"pbdoc(hasParent(node) -> bool\ +\ Returns True if the specified node is a parent of this one.)pbdoc" -#define _doc_instanceCount R"pbdoc(instanceCount(indirect) -> int - +#define _doc_instanceCount R"pbdoc(instanceCount(indirect) -> int\ +\ Returns the number of instances for this node.)pbdoc" -#define _doc_isChildOf R"pbdoc(isChildOf(node) -> bool - +#define _doc_isChildOf R"pbdoc(isChildOf(node) -> bool\ +\ Returns True if the specified node is a parent of this one.)pbdoc" -#define _doc_isInstanced R"pbdoc(isInstanced(indirect=True) -> bool - +#define _doc_isInstanced R"pbdoc(isInstanced(indirect=True) -> bool\ +\ Returns True if this node is instanced.)pbdoc" -#define _doc_isInstancedAttribute R"pbdoc(isInstancedAttribute(attr) -> bool - +#define _doc_isInstancedAttribute R"pbdoc(isInstancedAttribute(attr) -> bool\ +\ Returns True if the specified attribute is an instanced attribute of this node.)pbdoc" -#define _doc_isParentOf R"pbdoc(isParentOf(node) -> bool - +#define _doc_isParentOf R"pbdoc(isParentOf(node) -> bool\ +\ Returns True if the specified node is a child of this one.)pbdoc" -#define _doc_parent R"pbdoc(parent(index) -> MObject - +#define _doc_parent R"pbdoc(parent(index) -> MObject\ +\ Returns the specified parent of this node.)pbdoc" -#define _doc_parentCount R"pbdoc(parentCount() -> int - +#define _doc_parentCount R"pbdoc(parentCount() -> int\ +\ Returns the number of parents this node has.)pbdoc" -#define _doc_partialPathName R"pbdoc(partialPathName() -> string - +#define _doc_partialPathName R"pbdoc(partialPathName() -> string\ +\ Returns the minimum path string necessary to uniquely identify the attached object.)pbdoc" -#define _doc_removeChild R"pbdoc(removeChild(node) -> self - +#define _doc_removeChild R"pbdoc(removeChild(node) -> self\ +\ Removes the child, specified by MObject, reparenting it under the world.)pbdoc" -#define _doc_removeChildAt R"pbdoc(removeChildAt(index) -> self - +#define _doc_removeChildAt R"pbdoc(removeChildAt(index) -> self\ +\ Removes the child, specified by index, reparenting it under the world.)pbdoc" -#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self - +#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self\ +\ Attaches the function set to the specified node or DAG path.)pbdoc" -#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self - +#define _doc_setObject R"pbdoc(setObject(MObject or MDagPath) -> self\ +\ Attaches the function set to the specified node or DAG path.)pbdoc" -#define _doc_transformationMatrix R"pbdoc(transformationMatrix() -> MMatrix - +#define _doc_transformationMatrix R"pbdoc(transformationMatrix() -> MMatrix\ +\ Returns the object space transformation matrix for this DAG node.)pbdoc" @@ -416,5 +416,4 @@ py::class_(m, "FnDagNode") .def("transformationMatrix", [](MFnDagNode& self) -> MMatrix { return self.transformationMatrix(); - }, _doc_transformationMatrix -); \ No newline at end of file + }, _doc_transformationMatrix); \ No newline at end of file From 51726a1fb46f30b85092a35ac767ed6d5d4cbe22 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 6 Jun 2021 09:51:30 -0700 Subject: [PATCH 31/33] Flatten validate functions namespace --- src/MDGModifier.inl | 96 ++++++++++++++++++++++----------------------- src/util/obj.hpp | 20 +++++----- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 7f50afa..458c8cc 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -2,11 +2,11 @@ py::class_(m, "DGModifier") .def(py::init<>()) .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - validate::object::assert_not_null(node, "Cannot add attribute to a null object."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a(n) '^1s' object."); + validate::is_not_null(node, "Cannot add attribute to a null object."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a(n) '^1s' object."); - validate::object::assert_not_null(attribute, "Cannot add null attribute to a node."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot add null attribute to a node."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.addAttribute(node, attribute); @@ -16,8 +16,8 @@ R"pbdoc(Adds an operation to the modifier to add a new dynamic attribute to the If the attribute is a compound its children will ae added as well, so only the parent needs to be added using this method.)pbdoc") .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - validate::object::assert_not_null(attribute, "Cannot add null extension attribute."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot add extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot add null extension attribute."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot add extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.addExtensionAttribute(nodeClass, attribute); @@ -44,17 +44,17 @@ 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) { - validate::object::assert_not_null(sourceNode, "Cannot connect - sourceNode is null."); - validate::object::assert_has_fn(sourceNode, MFn::kDependencyNode, "Cannot connect - 'sourceNode' must be a 'node' object , not a '^1s' object."); + validate::is_not_null(sourceNode, "Cannot connect - sourceNode is null."); + validate::has_fn(sourceNode, MFn::kDependencyNode, "Cannot connect - 'sourceNode' must be a 'node' object , not a '^1s' object."); - validate::object::assert_not_null(sourceAttr, "Cannot connect - 'sourceAttr' is null."); - validate::object::assert_has_fn(sourceAttr, MFn::kAttribute, "Cannot connect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(sourceAttr, "Cannot connect - 'sourceAttr' is null."); + validate::has_fn(sourceAttr, MFn::kAttribute, "Cannot connect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); - validate::object::assert_not_null(destNode, "Cannot connect - 'destNode' is null."); - validate::object::assert_has_fn(destNode, MFn::kDependencyNode, "Cannot connect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::is_not_null(destNode, "Cannot connect - 'destNode' is null."); + validate::has_fn(destNode, MFn::kDependencyNode, "Cannot connect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); - validate::object::assert_not_null(destAttr, "Cannot connect - 'destAttr' is null."); - validate::object::assert_has_fn(destAttr, MFn::kAttribute, "Cannot connect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(destAttr, "Cannot connect - 'destAttr' is null."); + validate::has_fn(destAttr, MFn::kAttribute, "Cannot connect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); // TODO: Once the MFnAttribute classes are implemented, // add additional validation to ensure that the attributes can be connected @@ -134,8 +134,8 @@ The new node is created and returned but will not be added to the dependency gra Raises TypeError if the named node type does not exist or if it is a DAG node type.)pbdoc") .def("deleteNode", [](MDGModifier & self, MObject node) { - validate::object::assert_not_null(node, "Cannot delete a null object."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot delete a(n) '^1s' object."); + validate::is_not_null(node, "Cannot delete a null object."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot delete a(n) '^1s' object."); if (node.hasFn(MFn::kDagNode)) { MString error_msg("Cannot delete a(n) DAG object - use DAGModifier instead."); @@ -157,17 +157,17 @@ operation is added so that the queue is emptied. Then, deleteNode() can be calle doIt() should be called immediately after to ensure that the queue is emptied before any other operations are added to it.)pbdoc") .def("disconnect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) { - validate::object::assert_not_null(sourceNode, "Cannot disconnect - sourceNode is null."); - validate::object::assert_has_fn(sourceNode, MFn::kDependencyNode, "Cannot disconnect - 'sourceNode' must be a 'node' object , not a '^1s' object."); + validate::is_not_null(sourceNode, "Cannot disconnect - sourceNode is null."); + validate::has_fn(sourceNode, MFn::kDependencyNode, "Cannot disconnect - 'sourceNode' must be a 'node' object , not a '^1s' object."); - validate::object::assert_not_null(sourceAttr, "Cannot disconnect - 'sourceAttr' is null."); - validate::object::assert_has_fn(sourceAttr, MFn::kAttribute, "Cannot disconnect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(sourceAttr, "Cannot disconnect - 'sourceAttr' is null."); + validate::has_fn(sourceAttr, MFn::kAttribute, "Cannot disconnect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); - validate::object::assert_not_null(destNode, "Cannot disconnect - 'destNode' is null."); - validate::object::assert_has_fn(destNode, MFn::kDependencyNode, "Cannot disconnect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::is_not_null(destNode, "Cannot disconnect - 'destNode' is null."); + validate::has_fn(destNode, MFn::kDependencyNode, "Cannot disconnect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); - validate::object::assert_not_null(destAttr, "Cannot disconnect - 'destAttr' is null."); - validate::object::assert_has_fn(destAttr, MFn::kAttribute, "Cannot disconnect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(destAttr, "Cannot disconnect - 'destAttr' is null."); + validate::has_fn(destAttr, MFn::kAttribute, "Cannot disconnect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.disconnect(sourceNode, sourceAttr, destNode, destAttr); @@ -202,11 +202,11 @@ then only the operations which were added since the previous doIt() call will be If undoIt() has been called then the next call to doIt() will do all operations.)pbdoc") .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { - validate::object::assert_not_null(plugin, "Cannot link extension attribute to a null plugin."); - validate::object::assert_has_fn(plugin, MFn::kPlugin, "Cannot link extension attribute to plugin - must specify a 'kPlugin' object, not a '^1s' object."); + validate::is_not_null(plugin, "Cannot link extension attribute to a null plugin."); + validate::has_fn(plugin, MFn::kPlugin, "Cannot link extension attribute to plugin - must specify a 'kPlugin' object, not a '^1s' object."); - validate::object::assert_not_null(attribute, "Cannot link null extension attribute from a plugin."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot link extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot link null extension attribute from a plugin."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot link extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.linkExtensionAttributeToPlugin(plugin, attribute); @@ -329,11 +329,11 @@ 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("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { - validate::object::assert_not_null(node, "Cannot remove an attribute from a null node."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); + validate::is_not_null(node, "Cannot remove an attribute from a null node."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); - validate::object::assert_not_null(attribute, "Cannot remove a null attribute."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot remove a null attribute."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot remove attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeAttribute(node, attribute); @@ -346,8 +346,8 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - validate::object::assert_not_null(attribute, "Cannot remove null extension attribute."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot remove null extension attribute."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); @@ -360,8 +360,8 @@ The attribute MObject passed in will be set to kNullObj. There should be no function sets attached to the attribute at the time of the call as their behaviour may become unpredictable.)pbdoc") .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { - validate::object::assert_not_null(attribute, "Cannot remove null extension attribute (if unset)."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute (if unset) - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot remove null extension attribute (if unset)."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute (if unset) - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); @@ -384,11 +384,11 @@ 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 remove an element of a multi (array) plug.)pbdoc") .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, std::string shortName, std::string longName) { - validate::object::assert_not_null(node, "Cannot rename an attribute from a null node."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); + validate::is_not_null(node, "Cannot rename an attribute from a null node."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); - validate::object::assert_not_null(attribute, "Cannot rename a null attribute."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot rename attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot rename a null attribute."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot rename attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); if (shortName.empty() || longName.empty()) { @@ -406,8 +406,8 @@ There should be no function sets attached to the attribute at the time of the ca }, R"pbdoc(Adds an operation to the modifer that renames a dynamic attribute on the given dependency node.)pbdoc") .def("renameNode", [](MDGModifier & self, MObject node, std::string newName) { - validate::object::assert_not_null(node, "Cannot rename a null node."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::is_not_null(node, "Cannot rename a null node."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); if (newName.empty()) { @@ -420,8 +420,8 @@ There should be no function sets attached to the attribute at the time of the ca }, R"pbdoc(Adds an operation to the modifer to rename a node.)pbdoc") .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { - validate::object::assert_not_null(node, "Cannot un/lock a null node."); - validate::object::assert_has_fn(node, MFn::kDependencyNode, "Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::is_not_null(node, "Cannot un/lock a null node."); + validate::has_fn(node, MFn::kDependencyNode, "Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); MStatus status = self.setNodeLockState(node, newState); @@ -435,11 +435,11 @@ There should be no function sets attached to the attribute at the time of the ca }, 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 plugin, MObject attribute) { - validate::object::assert_not_null(plugin, "Cannot unlink extension attribute from a null plugin."); - validate::object::assert_has_fn(plugin, MFn::kPlugin, "Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); + validate::is_not_null(plugin, "Cannot unlink extension attribute from a null plugin."); + validate::has_fn(plugin, MFn::kPlugin, "Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); - validate::object::assert_not_null(attribute, "Cannot unlink null extension attribute from a plugin."); - validate::object::assert_has_fn(attribute, MFn::kAttribute, "Cannot unlink extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::is_not_null(attribute, "Cannot unlink null extension attribute from a plugin."); + validate::has_fn(attribute, MFn::kAttribute, "Cannot unlink extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); MStatus status = self.unlinkExtensionAttributeFromPlugin(plugin, attribute); diff --git a/src/util/obj.hpp b/src/util/obj.hpp index b7af8f2..553bb5d 100644 --- a/src/util/obj.hpp +++ b/src/util/obj.hpp @@ -1,18 +1,16 @@ namespace validate { - namespace object { - inline void assert_not_null(MObject &o, std::string error_message) { - if (o.isNull()) { - throw std::invalid_argument(error_message.c_str()); - } + inline void is_not_null(MObject &o, std::string error_message) { + if (o.isNull()) { + throw std::invalid_argument(error_message.c_str()); } + } - inline void assert_has_fn(MObject &o, MFn::Type type, std::string error_message) { - if (!o.hasFn(type)) { - MString msg(error_message.c_str()); - msg.format(msg, o.apiTypeStr()); + inline void has_fn(MObject &o, MFn::Type type, std::string error_message) { + if (!o.hasFn(type)) { + MString msg(error_message.c_str()); + msg.format(msg, o.apiTypeStr()); - throw pybind11::type_error(msg.asChar()); - } + throw pybind11::type_error(msg.asChar()); } } } \ No newline at end of file From b4ded3e8d65fd135af46886a4c0acc6485530bb6 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 6 Jun 2021 10:00:10 -0700 Subject: [PATCH 32/33] Make validate function calls multi-line to keep line lengths short --- src/MDGModifier.inl | 116 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/src/MDGModifier.inl b/src/MDGModifier.inl index 458c8cc..26f2521 100644 --- a/src/MDGModifier.inl +++ b/src/MDGModifier.inl @@ -3,10 +3,16 @@ py::class_(m, "DGModifier") .def("addAttribute", [](MDGModifier & self, MObject node, MObject attribute) { validate::is_not_null(node, "Cannot add attribute to a null object."); - validate::has_fn(node, MFn::kDependencyNode, "Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a(n) '^1s' object."); + validate::has_fn( + node, MFn::kDependencyNode, + "Cannot add attribute - 'node' must be a 'kDependencyNode' object, not a(n) '^1s' object." + ); validate::is_not_null(attribute, "Cannot add null attribute to a node."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot add attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.addAttribute(node, attribute); @@ -17,7 +23,10 @@ If the attribute is a compound its children will ae added as well, so only the p .def("addExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { validate::is_not_null(attribute, "Cannot add null extension attribute."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot add extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot add extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.addExtensionAttribute(nodeClass, attribute); @@ -45,16 +54,28 @@ 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) { validate::is_not_null(sourceNode, "Cannot connect - sourceNode is null."); - validate::has_fn(sourceNode, MFn::kDependencyNode, "Cannot connect - 'sourceNode' must be a 'node' object , not a '^1s' object."); + validate::has_fn( + sourceNode, MFn::kDependencyNode, + "Cannot connect - 'sourceNode' must be a 'node' object , not a '^1s' object." + ); validate::is_not_null(sourceAttr, "Cannot connect - 'sourceAttr' is null."); - validate::has_fn(sourceAttr, MFn::kAttribute, "Cannot connect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + sourceAttr, MFn::kAttribute, + "Cannot connect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object." + ); validate::is_not_null(destNode, "Cannot connect - 'destNode' is null."); - validate::has_fn(destNode, MFn::kDependencyNode, "Cannot connect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::has_fn( + destNode, MFn::kDependencyNode, + "Cannot connect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object." + ); validate::is_not_null(destAttr, "Cannot connect - 'destAttr' is null."); - validate::has_fn(destAttr, MFn::kAttribute, "Cannot connect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + destAttr, MFn::kAttribute, + "Cannot connect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object." + ); // TODO: Once the MFnAttribute classes are implemented, // add additional validation to ensure that the attributes can be connected @@ -158,17 +179,28 @@ doIt() should be called immediately after to ensure that the queue is emptied be .def("disconnect", [](MDGModifier & self, MObject sourceNode, MObject sourceAttr, MObject destNode, MObject destAttr) { validate::is_not_null(sourceNode, "Cannot disconnect - sourceNode is null."); - validate::has_fn(sourceNode, MFn::kDependencyNode, "Cannot disconnect - 'sourceNode' must be a 'node' object , not a '^1s' object."); + validate::has_fn( + sourceNode, MFn::kDependencyNode, + "Cannot disconnect - 'sourceNode' must be a 'node' object , not a '^1s' object." + ); validate::is_not_null(sourceAttr, "Cannot disconnect - 'sourceAttr' is null."); - validate::has_fn(sourceAttr, MFn::kAttribute, "Cannot disconnect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + sourceAttr, MFn::kAttribute, + "Cannot disconnect - 'sourceAttr' must be a 'kAttribute' object, not a(n) '^1s' object." + ); validate::is_not_null(destNode, "Cannot disconnect - 'destNode' is null."); - validate::has_fn(destNode, MFn::kDependencyNode, "Cannot disconnect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::has_fn( + destNode, MFn::kDependencyNode, + "Cannot disconnect - 'destNode' must be a 'kDependencyNode' object , not a '^1s' object." + ); validate::is_not_null(destAttr, "Cannot disconnect - 'destAttr' is null."); - validate::has_fn(destAttr, MFn::kAttribute, "Cannot disconnect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object."); - + validate::has_fn( + destAttr, MFn::kAttribute, + "Cannot disconnect - 'destAttr' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.disconnect(sourceNode, sourceAttr, destNode, destAttr); CHECK_STATUS(status) @@ -203,10 +235,16 @@ If undoIt() has been called then the next call to doIt() will do all operations. .def("linkExtensionAttributeToPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { validate::is_not_null(plugin, "Cannot link extension attribute to a null plugin."); - validate::has_fn(plugin, MFn::kPlugin, "Cannot link extension attribute to plugin - must specify a 'kPlugin' object, not a '^1s' object."); + validate::has_fn( + plugin, MFn::kPlugin, + "Cannot link extension attribute to plugin - must specify a 'kPlugin' object, not a '^1s' object." + ); validate::is_not_null(attribute, "Cannot link null extension attribute from a plugin."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot link extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot link extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.linkExtensionAttributeToPlugin(plugin, attribute); @@ -330,10 +368,16 @@ but Maya will better be able to recover if one of the commands fails.)pbdoc") .def("removeAttribute", [](MDGModifier & self, MObject node, MObject attribute) { validate::is_not_null(node, "Cannot remove an attribute from a null node."); - validate::has_fn(node, MFn::kDependencyNode, "Cannot remove attribute - node must be a 'node' object , not a '^1s' object."); + validate::has_fn( + node, MFn::kDependencyNode, + "Cannot remove attribute - node must be a 'node' object , not a '^1s' object." + ); validate::is_not_null(attribute, "Cannot remove a null attribute."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot remove attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot remove attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.removeAttribute(node, attribute); @@ -347,7 +391,10 @@ There should be no function sets attached to the attribute at the time of the ca .def("removeExtensionAttribute", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { validate::is_not_null(attribute, "Cannot remove null extension attribute."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot remove extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); @@ -361,7 +408,10 @@ There should be no function sets attached to the attribute at the time of the ca .def("removeExtensionAttributeIfUnset", [](MDGModifier & self, MNodeClass nodeClass, MObject attribute) { validate::is_not_null(attribute, "Cannot remove null extension attribute (if unset)."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot remove extension attribute (if unset) - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot remove extension attribute (if unset) - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.removeExtensionAttribute(nodeClass, attribute); @@ -385,10 +435,16 @@ There should be no function sets attached to the attribute at the time of the ca .def("renameAttribute", [](MDGModifier & self, MObject node, MObject attribute, std::string shortName, std::string longName) { validate::is_not_null(node, "Cannot rename an attribute from a null node."); - validate::has_fn(node, MFn::kDependencyNode, "Cannot rename attribute - node must be a 'node' object , not a '^1s' object."); + validate::has_fn( + node, MFn::kDependencyNode, + "Cannot rename attribute - node must be a 'node' object , not a '^1s' object." + ); validate::is_not_null(attribute, "Cannot rename a null attribute."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot rename attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot rename attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); if (shortName.empty() || longName.empty()) { @@ -407,7 +463,10 @@ There should be no function sets attached to the attribute at the time of the ca .def("renameNode", [](MDGModifier & self, MObject node, std::string newName) { validate::is_not_null(node, "Cannot rename a null node."); - validate::has_fn(node, MFn::kDependencyNode, "Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::has_fn( + node, MFn::kDependencyNode, + "Cannot rename object - 'node' must be a 'kDependencyNode' object , not a '^1s' object." + ); if (newName.empty()) { @@ -421,7 +480,10 @@ There should be no function sets attached to the attribute at the time of the ca .def("setNodeLockState", [](MDGModifier & self, MObject node, bool newState) { validate::is_not_null(node, "Cannot un/lock a null node."); - validate::has_fn(node, MFn::kDependencyNode, "Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object."); + validate::has_fn( + node, MFn::kDependencyNode, + "Cannot un/lock object - 'node' must be a 'kDependencyNode' object , not a '^1s' object." + ); MStatus status = self.setNodeLockState(node, newState); @@ -436,10 +498,16 @@ There should be no function sets attached to the attribute at the time of the ca .def("unlinkExtensionAttributeFromPlugin", [](MDGModifier & self, MObject plugin, MObject attribute) { validate::is_not_null(plugin, "Cannot unlink extension attribute from a null plugin."); - validate::has_fn(plugin, MFn::kPlugin, "Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object."); + validate::has_fn( + plugin, MFn::kPlugin, + "Cannot unlink extension attribute from plugin - must specify a 'kPlugin' object, not a '^1s' object." + ); validate::is_not_null(attribute, "Cannot unlink null extension attribute from a plugin."); - validate::has_fn(attribute, MFn::kAttribute, "Cannot unlink extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object."); + validate::has_fn( + attribute, MFn::kAttribute, + "Cannot unlink extension attribute - 'attribute' must be a 'kAttribute' object, not a(n) '^1s' object." + ); MStatus status = self.unlinkExtensionAttributeFromPlugin(plugin, attribute); From c4c58bdda17201cef0adc388bf15415019cd59a6 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 6 Jun 2021 12:39:03 -0700 Subject: [PATCH 33/33] Fix test failure on Linux --- .gitignore | 4 +++- Dockerfile.2022 | 36 ++++++++++++++++++++++++++++++++++++ src/main.cpp | 1 - tests/test_MDGModifier.py | 4 +++- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 Dockerfile.2022 diff --git a/.gitignore b/.gitignore index 8249d7c..50592d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ build/** tmp/* *.pyc -MFn.Types.inl \ No newline at end of file +MFn.Types.inl +devkit.tgz +devkitBase \ No newline at end of file diff --git a/Dockerfile.2022 b/Dockerfile.2022 new file mode 100644 index 0000000..58c953a --- /dev/null +++ b/Dockerfile.2022 @@ -0,0 +1,36 @@ +# Build locally for Linux on any platform, e.g. Windows +FROM mottosso/maya:2022 + +RUN yum install centos-release-scl -y && \ + yum install devtoolset-7 bc -y + +# Download devkit +ENV DEVKIT_LOCATION=/root/devkitBase +ENV DEVKIT "https://autodesk-adn-transfer.s3.us-west-2.amazonaws.com/ADN%20Extranet/M%26E/Maya/devkit%202022/Autodesk_Maya_2022_DEVKIT_Linux.tgz" +RUN wget $DEVKIT -O "/root/devkit.tgz" && \ + tar -xvf /root/devkit.tgz $pwd + +# Setup Test Environment +RUN mayapy -m pip install --user \ + nose==1.3.7 \ + nose-exclude==0.5.0 \ + coverage==5.5 \ + flaky==3.7.0 \ + six==1.16.0 \ + sphinx==1.8.5 \ + sphinxcontrib-napoleon==0.7 + +# Since 2019, this sucker throws an +# unnecessary warning if not declared. +RUN mkdir -p /var/tmp/runtime-root +ENV XDG_RUNTIME_DIR /var/tmp/runtime-root +ENV MAYA_DISABLE_ADP 1 + +# The local /build directory +ENV PYTHONPATH=/workspace/build + +WORKDIR /workspace + +COPY docker_entrypoint.sh /usr/bin/entrypoint.sh +RUN chmod +x /usr/bin/entrypoint.sh +ENTRYPOINT [ "/usr/bin/entrypoint.sh" ] \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 026a6fa..d69c878 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,7 +71,6 @@ PYBIND11_MODULE(cmdc, m) { #include "MFn.inl" #include "Types.inl" #include "MObject.inl" - #include "MDagPath.inl" #include "MFnDependencyNode.inl" #include "MFnDagNode.inl" #include "MBoundingBox.inl" diff --git a/tests/test_MDGModifier.py b/tests/test_MDGModifier.py index 88c1bcd..c366d5f 100644 --- a/tests/test_MDGModifier.py +++ b/tests/test_MDGModifier.py @@ -202,10 +202,12 @@ def test_deleteNode_pass(): def test_deleteNode_fail(): + non_node_obj = cmdc.Object() + for exc, name, value in ( [ValueError, 'null object', cmdc.Object()], [TypeError, 'non-node object', as_plug('persp.message').attribute()], - [TypeError, 'DAG object', as_obj(cmds.createNode('transform'))], + [TypeError, 'DAG object', as_obj('persp')], ): test_deleteNode_fail.__doc__ = """Test MDGModifier::deleteNode raises error if called with a(n) {}.""".format(name)