From 06152fd6a3bebdf12ebe2cbb00af608b611b47a2 Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 9 May 2021 12:04:46 -0700 Subject: [PATCH 1/3] Implement MPlug::as methods that return POD --- src/MPlug.inl | 50 +++++++++++++++++++------- test.ps1 | 2 +- tests/test_MPlug.py | 85 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/MPlug.inl b/src/MPlug.inl index 80bbba1..14d5239 100644 --- a/src/MPlug.inl +++ b/src/MPlug.inl @@ -23,51 +23,75 @@ plug.def(py::init<>()) }, R"pbdoc(Returns a plug for the array of plugs of which this plug is an element.)pbdoc") .def("asBool", [](MPlug & self) -> bool { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asBool(); }, R"pbdoc(Retrieves the plug's value, as a boolean.)pbdoc") .def("asChar", [](MPlug & self) -> char { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asChar(); }, R"pbdoc(Retrieves the plug's value, as a single-byte integer.)pbdoc") .def("asDouble", [](MPlug & self) -> double { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asDouble(); }, R"pbdoc(Retrieves the plug's value, as a double-precision float.)pbdoc") .def("asFloat", [](MPlug & self) -> float { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asFloat(); }, R"pbdoc(Retrieves the plug's value, as a single-precision float.)pbdoc") .def("asInt", [](MPlug & self) -> int { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asInt(); }, R"pbdoc(Retrieves the plug's value, as a regular integer.)pbdoc") .def("asMAngle", [](MPlug & self) -> MAngle { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asMAngle(); }, R"pbdoc(Retrieves the plug's value, as an MAngle.)pbdoc") .def("asMDataHandle", [](MPlug & self) -> MDataHandle { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asMDataHandle(); }, R"pbdoc(Retrieve the current value of the attribute this plug references.)pbdoc") .def("asMDistance", [](MPlug & self) -> MDistance { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asMDistance(); }, R"pbdoc(Retrieves the plug's value, as an MDistance.)pbdoc") .def("asMObject", [](MPlug & self) -> MObject { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asMObject(); }, R"pbdoc(Retrieves the plug's value, as as an MObject containing a direct reference to the plug's data.)pbdoc") .def("asMTime", [](MPlug & self) -> MTime { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asMTime(); }, R"pbdoc(Retrieves the plug's value, as an MTime.)pbdoc") .def("asShort", [](MPlug & self) -> short { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + return self.asShort(); }, R"pbdoc(Retrieves the plug's value, as a short integer.)pbdoc") - .def("asString", [](MPlug & self) -> MString { - throw std::logic_error{"Function not yet implemented."}; + .def("asString", [](MPlug & self) -> std::string { + plug::assert_not_null(self); + + return std::string(self.asString().asChar()); }, R"pbdoc(Retrieves the plug's value, as a string.)pbdoc") .def("attribute", [](MPlug & self) -> MObject { diff --git a/test.ps1 b/test.ps1 index fe5c372..ab1199a 100644 --- a/test.ps1 +++ b/test.ps1 @@ -6,4 +6,4 @@ if (-not $args.count) { $directory = $args[0] } -."$env:MAYA_LOCATION/bin/mayapy.exe" -m nose -xv $directory \ No newline at end of file +."$env:MAYA_LOCATION/bin/mayapy.exe" -B -m nose -xv $directory \ No newline at end of file diff --git a/tests/test_MPlug.py b/tests/test_MPlug.py index 21adcab..61717d4 100644 --- a/tests/test_MPlug.py +++ b/tests/test_MPlug.py @@ -1,5 +1,6 @@ """Test suite for MPlug bindings.""" +import copy import nose.tools import six import unittest @@ -25,7 +26,7 @@ def p(base, *args): return '.'.join(parts) - +@unittest.skip('') class TestCommonMethods(unittest.TestCase): """Tests for common MPlug methods bindings.""" @@ -53,8 +54,8 @@ def test_info(self): # You would think it would return the full plug path, but it doesn't... assert plug.info() == p(self.node, 'branch', 0, 'leaf') - +@unittest.skip('') class TestArrayMethods(unittest.TestCase): """Tests for MPlug methods bindings for array/element plugs.""" @@ -190,7 +191,7 @@ def test_numElements(self): nose.tools.assert_raises(TypeError, non_array_root.numElements) nose.tools.assert_raises(ValueError, cmdc.Plug().numElements) - +@unittest.skip('') class TestCompoundPlugMethods(unittest.TestCase): """Tests for MPlug methods bindings for compound plugs.""" @@ -261,7 +262,7 @@ def test_numChildren(self): nose.tools.assert_raises(TypeError, non_parent.numChildren) nose.tools.assert_raises(ValueError, cmdc.Plug().numChildren) - +@unittest.skip('') class TestConnectionMethods(unittest.TestCase): """Tests for MPlug methods bindings for connections.""" @@ -378,3 +379,79 @@ def test_sourceWithConversion(self): assert tgt_plug.sourceWithConversion().node().hasFn(cmdc.Fn.kUnitConversion), 'Plug.sourceWithConversion skipped over conversion node' nose.tools.assert_raises(ValueError, cmdc.Plug().sourceWithConversion) + + +def test_asType(): + """Test for MPlug::as* bindings.""" + + for (method_name, value, add_attr_kwargs, set_attr_kwargs) in [ + ('asBool', True, {'at': 'bool'}, {}), + ('asChar', (65, 'A'), {'at': 'char'}, {}), + ('asDouble', 1.0, {'at': 'double'}, {}), + ('asFloat', 1.0, {'at': 'float'}, {}), + ('asInt', 5, {'at': 'long'}, {}), + # asMAngle - not yet implemented + # asMDataHandle - not yet implemented + # asMDistance - not yet implemented + # asMObject - custom test (see below) + # asMTime - not yet implemented + ('asShort', 3, {'at': 'enum', 'enumName': 'a:b:c:d'}, {}), + ('asString', 'hello', {'dt': 'string'}, {'type': 'string'}), + ]: + # Somehow, this works + test_asType.__doc__ = """Test for MPlug::{} bindings.""".format(method_name) + + yield check_asType, method_name, value, add_attr_kwargs, set_attr_kwargs + + +def check_asType(method_name, value, add_attr_kwargs, set_attr_kwargs): + """Test for MPlug::as* bindings.""" + + # 'asChar' expects an int but returns a char in Python + if isinstance(value, tuple): + in_value, out_value = value + else: + in_value = value + out_value = value + + node = cmds.createNode('network') + + attr = p(node, 'attr') + + cmds.addAttr(node, ln='attr', **add_attr_kwargs) + cmds.setAttr(attr, in_value, **set_attr_kwargs) + + plug = cmdc.SelectionList().add(attr).getPlug(0) + + method = getattr(plug, method_name) + + expected = out_value + actual = method() + + error_message = ( + 'Plug method {} returned the wrong value - expected: {}, actual: {}' + .format( + method_name, expected, actual + ) + ) + + if isinstance(expected, float): + assert abs(expected - actual) <= 1e-5, error_message + else: + assert expected == actual, error_message + + +def test_asMObject(): + """Test for MPlug::asMObject bindings.""" + + cube, = cmds.polyCube(constructionHistory=False) + mesh, = cmds.listRelatives(cube, children=True, type='mesh') + + plug = cmdc.SelectionList().add(p(mesh, 'worldMesh')).getPlug(0) + + value = plug.asMObject() + + assert value is not None, 'Plug.asMObject returned a null' + assert not value.isNull(), 'Plug.asMObject returned a null MObject' + assert value.hasFn(cmdc.Fn.kMesh), \ + 'Plug.asMObject returned an object of type {} instead of kMesh'.format(value.apiTypeStr()) \ No newline at end of file From 9a6c7fd5630a02adca0b2f105fedf2e636794dff Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 9 May 2021 12:16:09 -0700 Subject: [PATCH 2/3] Implement MPlug::set methods that accept POD --- src/MPlug.inl | 56 ++++++++++++++++++------ tests/test_MPlug.py | 103 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 137 insertions(+), 22 deletions(-) diff --git a/src/MPlug.inl b/src/MPlug.inl index 14d5239..fcd331d 100644 --- a/src/MPlug.inl +++ b/src/MPlug.inl @@ -442,47 +442,69 @@ Note that the behavior of connectedTo() is identical to destinationsWithConversi }, R"pbdoc(Switches the plug to reference the given attribute of the same node as the previously referenced attribute.)pbdoc") .def("setBool", [](MPlug & self, bool value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setBool(value); }, R"pbdoc(Sets the plug's value as a boolean.)pbdoc") .def("setChar", [](MPlug & self, char value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setChar(value); }, R"pbdoc(Sets the plug's value as a single-byte integer.)pbdoc") .def("setDouble", [](MPlug & self, double value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setDouble(value); }, R"pbdoc(Sets the plug's value as a double-precision float.)pbdoc") .def("setFloat", [](MPlug & self, float value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setFloat(value); }, R"pbdoc(Sets the plug's value as a single-precision float.)pbdoc") .def("setInt", [](MPlug & self, int value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setInt(value); }, R"pbdoc(Sets the plug's value as a regular integer.)pbdoc") .def("setMAngle", [](MPlug & self, MAngle angle) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMAngle(angle); }, R"pbdoc(Sets the plug's value as an MAngle.)pbdoc") .def("setMDataHandle", [](MPlug & self, MDataHandle dataHandle) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMDataHandle(dataHandle); }, R"pbdoc(Sets the plug's value as a data handle.)pbdoc") .def("setMDistance", [](MPlug & self, MDistance distance) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMDistance(distance); }, R"pbdoc(Sets the plug's value as an MDistance.)pbdoc") .def("setMObject", [](MPlug & self, MObject object) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMObject(object); }, R"pbdoc(Sets the plug's value as an MObject.)pbdoc") .def("setMPxData", [](MPlug & self, MPxData *data) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMPxData(data); }, R"pbdoc(Sets the plug's value using custom plug-in data.)pbdoc") .def("setMTime", [](MPlug & self, MTime time) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setMTime(time); }, R"pbdoc(Sets the plug's value as an MTime.)pbdoc") .def("setNumElements", [](MPlug & self, unsigned int num_elements) { @@ -490,11 +512,17 @@ Note that the behavior of connectedTo() is identical to destinationsWithConversi }, R"pbdoc(Pre-allocates space for count elements in an array of plugs.)pbdoc") .def("setShort", [](MPlug & self, short value) { - throw std::logic_error{"Function not yet implemented."}; + plug::assert_not_null(self); + + self.setShort(value); }, R"pbdoc(Sets the plug's value as a short integer.)pbdoc") - .def("setString", [](MPlug & self, MString value) { - throw std::logic_error{"Function not yet implemented."}; + .def("setString", [](MPlug & self, std::string value) { + plug::assert_not_null(self); + + MString string(value.c_str()); + + self.setString(string); }, R"pbdoc(Sets the plug's value as a string.)pbdoc") .def("source", [](MPlug & self) -> MPlug { diff --git a/tests/test_MPlug.py b/tests/test_MPlug.py index 61717d4..f0fd77b 100644 --- a/tests/test_MPlug.py +++ b/tests/test_MPlug.py @@ -10,6 +10,7 @@ import cmdc + def p(base, *args): """Construct a plug string from the given attributes.""" @@ -26,7 +27,7 @@ def p(base, *args): return '.'.join(parts) -@unittest.skip('') + class TestCommonMethods(unittest.TestCase): """Tests for common MPlug methods bindings.""" @@ -55,7 +56,7 @@ def test_info(self): # You would think it would return the full plug path, but it doesn't... assert plug.info() == p(self.node, 'branch', 0, 'leaf') -@unittest.skip('') + class TestArrayMethods(unittest.TestCase): """Tests for MPlug methods bindings for array/element plugs.""" @@ -191,7 +192,7 @@ def test_numElements(self): nose.tools.assert_raises(TypeError, non_array_root.numElements) nose.tools.assert_raises(ValueError, cmdc.Plug().numElements) -@unittest.skip('') + class TestCompoundPlugMethods(unittest.TestCase): """Tests for MPlug methods bindings for compound plugs.""" @@ -262,7 +263,7 @@ def test_numChildren(self): nose.tools.assert_raises(TypeError, non_parent.numChildren) nose.tools.assert_raises(ValueError, cmdc.Plug().numChildren) -@unittest.skip('') + class TestConnectionMethods(unittest.TestCase): """Tests for MPlug methods bindings for connections.""" @@ -381,7 +382,7 @@ def test_sourceWithConversion(self): nose.tools.assert_raises(ValueError, cmdc.Plug().sourceWithConversion) -def test_asType(): +def test_asType_methods(): """Test for MPlug::as* bindings.""" for (method_name, value, add_attr_kwargs, set_attr_kwargs) in [ @@ -399,12 +400,12 @@ def test_asType(): ('asString', 'hello', {'dt': 'string'}, {'type': 'string'}), ]: # Somehow, this works - test_asType.__doc__ = """Test for MPlug::{} bindings.""".format(method_name) + test_asType_methods.__doc__ = """Test for MPlug::{} bindings.""".format(method_name) - yield check_asType, method_name, value, add_attr_kwargs, set_attr_kwargs + yield check_asType_method, method_name, value, add_attr_kwargs, set_attr_kwargs -def check_asType(method_name, value, add_attr_kwargs, set_attr_kwargs): +def check_asType_method(method_name, value, add_attr_kwargs, set_attr_kwargs): """Test for MPlug::as* bindings.""" # 'asChar' expects an int but returns a char in Python @@ -454,4 +455,90 @@ def test_asMObject(): assert value is not None, 'Plug.asMObject returned a null' assert not value.isNull(), 'Plug.asMObject returned a null MObject' assert value.hasFn(cmdc.Fn.kMesh), \ + 'Plug.asMObject returned an object of type {} instead of kMesh'.format(value.apiTypeStr()) + + +def test_setType_methods(): + """Test for MPlug::as* bindings.""" + + for (method_name, value, add_attr_kwargs) in [ + ('setBool', True, {'at': 'bool'}), + ('setChar', ('A', 65), {'at': 'char'}), + ('setDouble', 1.0, {'at': 'double'}), + ('setFloat', 1.0, {'at': 'float'}), + ('setInt', 5, {'at': 'long'}), + # setMAngle - not yet implemented + # setMDataHandle - not yet implemented + # setMDistance - not yet implemented + # setMObject - custom test (see below) + # setMPxData - not yet implemented + # setMTime - not yet implemented + ('setShort', 3, {'at': 'enum', 'enumName': 'a:b:c:d'}), + ('setString', 'hello', {'dt': 'string'}), + ]: + # Somehow, this works + test_setType_methods.__doc__ = """Test for MPlug::{} bindings.""".format(method_name) + + yield check_setType_method, method_name, value, add_attr_kwargs + + +def check_setType_method(method_name, value, add_attr_kwargs): + """Test for MPlug::set* bindings.""" + + # 'asChar' expects an int but returns a char in Python + if isinstance(value, tuple): + in_value, out_value = value + else: + in_value = value + out_value = value + + node = cmds.createNode('network') + + attr = p(node, 'attr') + + cmds.addAttr(node, ln='attr', **add_attr_kwargs) + + plug = cmdc.SelectionList().add(attr).getPlug(0) + + method = getattr(plug, method_name) + method(in_value) + + expected = out_value + actual = cmds.getAttr(attr) + + error_message = ( + 'Plug method {} set the wrong value - expected: {}, actual: {}' + .format( + method_name, expected, actual + ) + ) + + if isinstance(expected, float): + assert abs(expected - actual) <= 1e-5, error_message + else: + assert expected == actual, error_message + + +def test_setMObject(): + """Test for MPlug::setMObject bindings.""" + + node = cmds.createNode('network') + + cmds.addAttr(node, ln='attr', dt='mesh') + + cube, = cmds.polyCube(constructionHistory=False) + mesh, = cmds.listRelatives(cube, children=True, type='mesh') + + src_plug = cmdc.SelectionList().add(p(mesh, 'worldMesh')).getPlug(0) + dst_plug = cmdc.SelectionList().add(p(node, 'attr')).getPlug(0) + + src_value = src_plug.asMObject() + + dst_plug.setMObject(src_value) + + dst_value = dst_plug.asMObject() + + assert dst_value is not None, 'Plug.asMObject returned a null' + assert not dst_value.isNull(), 'Plug.asMObject returned a null MObject' + assert dst_value.hasFn(cmdc.Fn.kMesh), \ 'Plug.asMObject returned an object of type {} instead of kMesh'.format(value.apiTypeStr()) \ No newline at end of file From 26fde24a41319f1e59cddec2caee3636e8dd422d Mon Sep 17 00:00:00 2001 From: Ryan Porter Date: Sun, 9 May 2021 17:20:30 -0700 Subject: [PATCH 3/3] Make Plug.asChar and Plug.setChar expect ordinal ints --- src/MPlug.inl | 8 ++++---- tests/test_MPlug.py | 26 ++++++-------------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/MPlug.inl b/src/MPlug.inl index fcd331d..7474a8f 100644 --- a/src/MPlug.inl +++ b/src/MPlug.inl @@ -28,10 +28,10 @@ plug.def(py::init<>()) return self.asBool(); }, R"pbdoc(Retrieves the plug's value, as a boolean.)pbdoc") - .def("asChar", [](MPlug & self) -> char { + .def("asChar", [](MPlug & self) -> int { plug::assert_not_null(self); - return self.asChar(); + return (self.asChar()); }, R"pbdoc(Retrieves the plug's value, as a single-byte integer.)pbdoc") .def("asDouble", [](MPlug & self) -> double { @@ -447,10 +447,10 @@ Note that the behavior of connectedTo() is identical to destinationsWithConversi self.setBool(value); }, R"pbdoc(Sets the plug's value as a boolean.)pbdoc") - .def("setChar", [](MPlug & self, char value) { + .def("setChar", [](MPlug & self, int value) { plug::assert_not_null(self); - self.setChar(value); + self.setChar(char(value)); }, R"pbdoc(Sets the plug's value as a single-byte integer.)pbdoc") .def("setDouble", [](MPlug & self, double value) { diff --git a/tests/test_MPlug.py b/tests/test_MPlug.py index f0fd77b..bb1a408 100644 --- a/tests/test_MPlug.py +++ b/tests/test_MPlug.py @@ -387,7 +387,7 @@ def test_asType_methods(): for (method_name, value, add_attr_kwargs, set_attr_kwargs) in [ ('asBool', True, {'at': 'bool'}, {}), - ('asChar', (65, 'A'), {'at': 'char'}, {}), + ('asChar', ord('A'), {'at': 'char'}, {}), ('asDouble', 1.0, {'at': 'double'}, {}), ('asFloat', 1.0, {'at': 'float'}, {}), ('asInt', 5, {'at': 'long'}, {}), @@ -407,26 +407,19 @@ def test_asType_methods(): def check_asType_method(method_name, value, add_attr_kwargs, set_attr_kwargs): """Test for MPlug::as* bindings.""" - - # 'asChar' expects an int but returns a char in Python - if isinstance(value, tuple): - in_value, out_value = value - else: - in_value = value - out_value = value node = cmds.createNode('network') attr = p(node, 'attr') cmds.addAttr(node, ln='attr', **add_attr_kwargs) - cmds.setAttr(attr, in_value, **set_attr_kwargs) + cmds.setAttr(attr, value, **set_attr_kwargs) plug = cmdc.SelectionList().add(attr).getPlug(0) method = getattr(plug, method_name) - expected = out_value + expected = value actual = method() error_message = ( @@ -463,7 +456,7 @@ def test_setType_methods(): for (method_name, value, add_attr_kwargs) in [ ('setBool', True, {'at': 'bool'}), - ('setChar', ('A', 65), {'at': 'char'}), + ('setChar', ord('A'), {'at': 'char'}), ('setDouble', 1.0, {'at': 'double'}), ('setFloat', 1.0, {'at': 'float'}), ('setInt', 5, {'at': 'long'}), @@ -485,13 +478,6 @@ def test_setType_methods(): def check_setType_method(method_name, value, add_attr_kwargs): """Test for MPlug::set* bindings.""" - # 'asChar' expects an int but returns a char in Python - if isinstance(value, tuple): - in_value, out_value = value - else: - in_value = value - out_value = value - node = cmds.createNode('network') attr = p(node, 'attr') @@ -501,9 +487,9 @@ def check_setType_method(method_name, value, add_attr_kwargs): plug = cmdc.SelectionList().add(attr).getPlug(0) method = getattr(plug, method_name) - method(in_value) + method(value) - expected = out_value + expected = value actual = cmds.getAttr(attr) error_message = (