diff --git a/src/ForwardDeclarations.inl b/src/ForwardDeclarations.inl index 08f719c..ddc5144 100644 --- a/src/ForwardDeclarations.inl +++ b/src/ForwardDeclarations.inl @@ -15,6 +15,7 @@ py::class_ Plug(m, "Plug"); py::class_ Point(m, "Point"); py::class_ PxData(m, "PxData"); py::class_ Quaternion(m, "Quaternion"); +py::class_ EulerRotation(m, "EulerRotation"); py::class_ SelectionList(m, "SelectionList"); py::class_ Status(m, "Status"); py::class_ String(m, "String"); @@ -25,6 +26,9 @@ py::enum_ fn_type(Fn, "Type"); py::class_ Uuid(m, "Uuid"); py::class_ NodeClass(m, "NodeClass"); py::class_ DGModifier(m, "DGModifier"); -py::class_ FnDagNode(m, "FnDagNode"); +py::class_ FnDagNode(m, "FnDagNode"); py::class_ BoundingBox(m, "BoundingBox"); -py::class_ Color(m, "Color"); \ No newline at end of file +py::class_ Color(m, "Color"); +py::class_ Space(m, "Space"); +py::class_ TransformationMatrix(m, "TransformationMatrix"); +py::class_ FnTransform(m, "FnTransform"); \ No newline at end of file diff --git a/src/MEulerRotation.inl b/src/MEulerRotation.inl new file mode 100644 index 0000000..6605273 --- /dev/null +++ b/src/MEulerRotation.inl @@ -0,0 +1,202 @@ +#define _doc_EulerRotation_setValue \ + "Set the rotation." + +#define _doc_EulerRotation_asQuaternion \ + "Returns the rotation as an equivalent quaternion." + +#define _doc_EulerRotation_asMatrix \ + "Returns the rotation as an equivalent matrix." + +#define _doc_EulerRotation_asVector \ + "Returns the X, Y and Z rotations as a vector." + +#define _doc_EulerRotation_isEquivalent \ + "Returns true if this rotation has the same order as another and their X, Y and Z components are within a tolerance of each other." + +#define _doc_EulerRotation_isZero \ + "Returns true if the X, Y and Z components are each within a tolerance of 0.0." + +#define _doc_EulerRotation_incrementalRotateBy \ + "Increase this rotation by a given angle around the specified axis. The update is done in series of small increments to avoid flipping." + +#define _doc_EulerRotation_inverse \ + "Returns a new MEulerRotation containing the inverse rotation of this one and reversed rotation order." + +#define _doc_EulerRotation_invertIt \ + "In-place inversion of the rotation. Rotation order is also reversed." + +#define _doc_EulerRotation_reorder \ + "Returns a new MEulerRotation having this rotation, reordered to use the given rotation order." + +#define _doc_EulerRotation_reorderIt \ + "In-place reordering to use the given rotation order." + +#define _doc_EulerRotation_bound \ + "Returns a new MEulerRotation having this rotation, but with each rotation component bound within +/- PI." + +#define _doc_EulerRotation_boundIt \ + "In-place bounding of each rotation component to lie wthin +/- PI." + +#define _doc_EulerRotation_alternateSolution \ + "Returns an equivalent rotation which is not simply a multiple." + +#define _doc_EulerRotation_setToAlternateSolution \ + "Replace this rotation with an alternate solution." + +#define _doc_EulerRotation_closestSolution \ + "Returns the equivalent rotation which comes closest to a target." + +#define _doc_EulerRotation_setToClosestSolution \ + "Replace this rotation with the closest solution to a target." + +#define _doc_EulerRotation_closestCut \ + "Returns the rotation which is full spin multiples of this one and comes closest to target." + +#define _doc_EulerRotation_setToClosestCut \ + "Replace this rotation with the closest cut to a target." + +#define _doc_EulerRotation_decompose \ + "Extracts a rotation from a matrix." + +py::enum_(EulerRotation, "RotationOrder") + .value("kXYZ", MEulerRotation::RotationOrder::kXYZ) + .value("kYZX", MEulerRotation::RotationOrder::kYZX) + .value("kZXY", MEulerRotation::RotationOrder::kZXY) + .value("kXZY", MEulerRotation::RotationOrder::kXZY) + .value("kYXZ", MEulerRotation::RotationOrder::kYXZ) + .value("kZYX", MEulerRotation::RotationOrder::kZYX) + .export_values(); + +EulerRotation + .def(py::init<>()) + .def(py::init(), py::arg("src")) + .def(py::init(), + py::arg("vec"), py::arg("order") = MEulerRotation::kXYZ) + .def(py::init(), + py::arg("x"), py::arg("y"), py::arg("x"), py::arg("order") = MEulerRotation::kXYZ) + .def(py::init([](std::array seq, MEulerRotation::RotationOrder order) + { + return std::unique_ptr(new MEulerRotation(seq[0], seq[1], seq[2], order)); + } + ), py::arg("seq"), py::arg("order") = MEulerRotation::kXYZ) + + .def_readwrite("x", &MEulerRotation::x) + .def_readwrite("y", &MEulerRotation::y) + .def_readwrite("z", &MEulerRotation::z) + .def_readwrite("order", &MEulerRotation::order) + + .def(py::self + MEulerRotation(), py::arg("other")) + .def(py::self - MEulerRotation(), py::arg("other")) + .def(py::self * MEulerRotation(), py::arg("other")) + .def(py::self *= MEulerRotation(), py::arg("other")) + .def(py::self * MQuaternion(), py::arg("other")) + .def(py::self *= MQuaternion(), py::arg("other")) + .def(py::self * double(), py::arg("other")) + .def(py::self *= double(), py::arg("other")) + .def(py::self == MEulerRotation(), py::arg("other")) + .def(py::self != MEulerRotation(), py::arg("other")) + .def(-py::self) + + .def("__repr__", [](const MEulerRotation &a) { + return ""; + } + ) + + .def("alternateSolution", [](const MEulerRotation& self) { + return self.alternateSolution(); + }, _doc_EulerRotation_alternateSolution) + + .def("asMatrix", &MEulerRotation::asMatrix, _doc_EulerRotation_asMatrix) + + .def("asQuaternion", &MEulerRotation::asQuaternion, _doc_EulerRotation_asQuaternion) + + .def("asVector", &MEulerRotation::asVector, _doc_EulerRotation_asVector) + + .def("bound", [](const MEulerRotation& self) { + return self.bound(); + }, _doc_EulerRotation_bound) + + .def("boundIt", [](MEulerRotation & self) -> MEulerRotation& { + return self.boundIt(); + }, _doc_EulerRotation_boundIt) + + .def("boundIt", [](MEulerRotation & self, MEulerRotation src) -> MEulerRotation& { + return self.boundIt(src); + }, py::arg("src"), _doc_EulerRotation_boundIt) + + .def("closestCut", [](MEulerRotation & self, MEulerRotation target) -> MEulerRotation { + return self.closestCut(target); + }, py::arg("target"), _doc_EulerRotation_closestCut) + + .def("closestSolution", [](MEulerRotation & self, MEulerRotation target) -> MEulerRotation { + return self.closestSolution(target); + }, py::arg("dst"), _doc_EulerRotation_closestSolution) + + .def_static("decompose", &MEulerRotation::decompose, + py::arg("matrix"), py::arg("ord"), _doc_EulerRotation_decompose) + + .def("incrementalRotateBy", &MEulerRotation::incrementalRotateBy, + py::arg("axis"), py::arg("angle"), _doc_EulerRotation_incrementalRotateBy) + + .def("inverse", [](MEulerRotation & self) -> MEulerRotation { + return self.inverse(); + }, _doc_EulerRotation_inverse) + + .def("invertIt", &MEulerRotation::invertIt, _doc_EulerRotation_invertIt) + + .def("isEquivalent", [](MEulerRotation & self, MEulerRotation other, double tolerance = kEulerRotationEpsilon) -> bool { + return self.isEquivalent(other, tolerance); + }, py::arg("other"), py::arg("tolerance") = kEulerRotationEpsilon, _doc_EulerRotation_isEquivalent) + + .def("isZero", [](MEulerRotation & self, double tolerance = kEulerRotationEpsilon) -> bool { + return self.isZero(tolerance); + }, py::arg("tolerance") = kEulerRotationEpsilon, _doc_EulerRotation_isZero) + + .def("reorder", [](MEulerRotation & self, MEulerRotation::RotationOrder order) -> MEulerRotation { + return self.reorder(order); + }, py::arg("order"), _doc_EulerRotation_reorder) + + .def("reorderIt", [](MEulerRotation & self, MEulerRotation::RotationOrder order) -> MEulerRotation& { + return self.reorderIt(order); + }, py::arg("order"), _doc_EulerRotation_reorderIt) + + .def("setToAlternateSolution", [](MEulerRotation & self) -> MEulerRotation& { + return self.setToAlternateSolution(); + }, _doc_EulerRotation_setToAlternateSolution) + + .def("setToAlternateSolution", [](MEulerRotation & self, MEulerRotation rot) -> MEulerRotation& { + return self.setToAlternateSolution(rot); + }, py::arg("rot"), _doc_EulerRotation_setToAlternateSolution) + + .def("setToClosestCut", [](MEulerRotation & self, MEulerRotation target) -> MEulerRotation& { + return self.setToClosestCut(target); + }, py::arg("target"), _doc_EulerRotation_setToClosestCut) + + .def("setToClosestCut", [](MEulerRotation & self, MEulerRotation src, MEulerRotation target) -> MEulerRotation& { + return self.setToClosestCut(src, target); + }, py::arg("src"), py::arg("target"), _doc_EulerRotation_setToClosestCut) + + .def("setToClosestSolution", [](MEulerRotation & self, MEulerRotation target) -> MEulerRotation& { + return self.setToClosestSolution(target); + }, py::arg("target"), _doc_EulerRotation_setToClosestSolution) + + .def("setToClosestSolution", [](MEulerRotation & self, MEulerRotation src, MEulerRotation target) -> MEulerRotation& { + return self.setToClosestSolution(src, target); + }, py::arg("src"), py::arg("target"), _doc_EulerRotation_setToClosestSolution) + + .def("setValue", [](MEulerRotation & self, const MEulerRotation & rot) -> MEulerRotation& { + return self.setValue(rot.x, rot.y, rot.z, rot.order); + }, py::arg("rot"), _doc_EulerRotation_setValue) + + .def("setValue", [](MEulerRotation & self, MVector vec, MEulerRotation::RotationOrder order = MEulerRotation::kXYZ) -> MEulerRotation& { + return self.setValue(vec, order); + }, py::arg("v"), py::arg("order") = MEulerRotation::kXYZ, _doc_EulerRotation_setValue) + + .def("setValue", [](MEulerRotation & self, double xx, double yy, double zz, MEulerRotation::RotationOrder order = MEulerRotation::kXYZ) -> MEulerRotation& { + return self.setValue(xx, yy, zz, order); + }, py::arg("xx"), py::arg("yy"), py::arg("zz"), py::arg("order") = MEulerRotation::kXYZ, _doc_EulerRotation_setValue); + ; \ No newline at end of file diff --git a/src/MFn.Types.inl b/src/MFn.Types.inl index bd29607..9d88683 100644 --- a/src/MFn.Types.inl +++ b/src/MFn.Types.inl @@ -1 +1 @@ -// Auto-generated by /Scripts/mfn.py at build time +// Auto-generated by /Scripts/mfn.py at build time diff --git a/src/MFnTransform.inl b/src/MFnTransform.inl new file mode 100644 index 0000000..a455cd3 --- /dev/null +++ b/src/MFnTransform.inl @@ -0,0 +1,507 @@ +#define _doc_FnTransform_create \ + "Creates a new transform node and attaches it to the function set." + +#define _doc_FnTransform_transformation \ + "Returns the transformation matrix represented by this transform." + +#define _doc_FnTransform_setTranslation \ + "Sets the transform's translation." + +#define _doc_FnTransform_translateBy \ + "Adds an MVector to the transform's translation." + +#define _doc_FnTransform_setScale \ + "Sets the transform's scale components." + +#define _doc_FnTransform_scaleBy \ + "Multiplies the transform's XYZ scale components by a sequence of three floats." + +#define _doc_FnTransform_scalePivot \ + "Returns the transform's scale pivot." + +#define _doc_FnTransform_setScalePivot \ + "Sets the transform's scale pivot." + +#define _doc_FnTransform_scalePivotTranslation \ + "Returns the transform's scale pivot translation." + +#define _doc_FnTransform_setScalePivotTranslation \ + "Sets the transform's scale pivot translation." + +#define _doc_FnTransform_setShear \ + "Sets the transform's shear." + +#define _doc_FnTransform_shearBy \ + "Multiplies the transform's shear components by a sequence of three floats." + +#define _doc_FnTransform_setRotation \ + "Sets the transform's rotation using an MEulerRotation or MQuaternion." + +#define _doc_FnTransform_rotateBy \ + "Adds an MEulerRotation or MQuaternion to the transform's rotation." + +#define _doc_FnTransform_rotatePivot \ + "Returns the transform's rotate pivot." + +#define _doc_FnTransform_setRotatePivot \ + "Sets the transform's rotate pivot." + +#define _doc_FnTransform_rotatePivotTranslation \ + "Returns the transform's rotate pivot translation." + +#define _doc_FnTransform_setRotatePivotTranslation \ + "Sets the transform's rotate pivot translation." + +#define _doc_FnTransform_rotateOrientation \ + "Returns the MQuaternion which orients the local rotation space." + +#define _doc_FnTransform_setRotateOrientation \ + "Sets the MQuaternion which orients the local rotation space." + +#define _doc_FnTransform_rotationOrder \ + "Returns the order of rotations when the transform's rotation is expressed as an MEulerRotation." + +#define _doc_FnTransform_setRotationOrder \ + "Sets the transform's rotation order." + +#define _doc_FnTransform_restPosition \ + "Returns the transform's rest position matrix." + +#define _doc_FnTransform_setRestPosition \ + "Sets the transform's rest position matrix." + +#define _doc_FnTransform_resetFromRestPosition \ + "Resets the transform from its rest position matrix." + +#define _doc_FnTransform_clearRestPosition \ + "Clears the transform's rest position matrix." + +#define _doc_FnTransform_isLimited \ + "Returns True if the specified limit type is enabled." + +#define _doc_FnTransform_limitValue \ + "Returns the value of the specified limit." + +#define _doc_FnTransform_setLimit \ + "Sets the value of the specified limit." + +#define _doc_FnTransform_enableLimit \ + "Enables or disables a specified limit type." + +py::enum_(FnTransform, "LimitType") + .value("kScaleMinX", MFnTransform::kScaleMinX) + .value("kScaleMaxX", MFnTransform::kScaleMaxX) + .value("kScaleMinY", MFnTransform::kScaleMinY) + .value("kScaleMaxY", MFnTransform::kScaleMaxY) + .value("kScaleMinZ", MFnTransform::kScaleMinZ) + .value("kScaleMaxZ", MFnTransform::kScaleMaxZ) + + .value("kShearMinXY", MFnTransform::kShearMinXY) + .value("kShearMaxXY", MFnTransform::kShearMaxXY) + .value("kShearMinXZ", MFnTransform::kShearMinXZ) + .value("kShearMaxXZ", MFnTransform::kShearMaxXZ) + .value("kShearMinYZ", MFnTransform::kShearMinYZ) + .value("kShearMaxYZ", MFnTransform::kShearMaxYZ) + + .value("kRotateMinX", MFnTransform::kRotateMinX) + .value("kRotateMaxX", MFnTransform::kRotateMaxX) + .value("kRotateMinY", MFnTransform::kRotateMinY) + .value("kRotateMaxY", MFnTransform::kRotateMaxY) + .value("kRotateMinZ", MFnTransform::kRotateMinZ) + .value("kRotateMaxZ", MFnTransform::kRotateMaxZ) + + .value("kTranslateMinX", MFnTransform::kTranslateMinX) + .value("kTranslateMaxX", MFnTransform::kTranslateMaxX) + .value("kTranslateMinY", MFnTransform::kTranslateMinY) + .value("kTranslateMaxY", MFnTransform::kTranslateMaxY) + .value("kTranslateMinZ", MFnTransform::kTranslateMinZ) + .value("kTranslateMaxZ", MFnTransform::kTranslateMaxZ) + .export_values(); + +FnTransform + .def(py::init<>()) + + .def(py::init([](MObject& object) { + MStatus status; + auto result = std::unique_ptr(new MFnTransform(object, &status)); + + if (!status) { + throw std::runtime_error( + "Invalid parameter passed for object - " + "not a Transform Node, " + "Node does not exist or " + "no valid pointer to Node" + ); + } + + return result; + }), py::arg("object")) + + .def(py::init([](MDagPath& dagPath) { + MStatus status; + auto result = std::unique_ptr(new MFnTransform(dagPath, &status)); + + if (!status) { + throw std::runtime_error( + "Invalid parameter passed for object - " + "not a Transform Node, " + "Node does not exist or " + "no valid pointer to Node" + ); + } + + return result; + }), py::arg("dagPath")) + + .def("clearRestPosition", [](MFnTransform & self) { + MStatus status = self.clearRestPosition(); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, _doc_FnTransform_clearRestPosition) + + .def("create", [](MFnTransform & self, MObject parent = MObject::kNullObj) -> MObject { + MStatus status; + MObject transform = self.create(parent, &status); + + if (!status) { + throw std::runtime_error(status.errorString().asChar()); + } + + return transform; + }, py::arg_v("parent", MObject::kNullObj, "Object.kNullObj"), _doc_FnTransform_create) + + .def("enableLimit", [](MFnTransform & self, MFnTransform::LimitType limitType, bool enable) { + MStatus status = self.enableLimit(limitType, enable); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("limitType"), py::arg("enable"), _doc_FnTransform_enableLimit) + + .def("isLimited", [](MFnTransform & self, MFnTransform::LimitType limitType) -> bool { + MStatus status; + bool result = self.isLimited(limitType, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, py::arg("limitType"), _doc_FnTransform_isLimited) + + .def("limitValue", [](MFnTransform & self, MFnTransform::LimitType limitType) -> double { + MStatus status; + double limit = self.limitValue(limitType, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return limit; + }, py::arg("limitType"), _doc_FnTransform_limitValue) + + .def("resetFromRestPosition", [](MFnTransform & self) { + MStatus status = self.resetFromRestPosition(); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, _doc_FnTransform_resetFromRestPosition) + + .def("restPosition", [](MFnTransform & self) -> MTransformationMatrix { + MStatus status; + MTransformationMatrix matrix = self.restPosition(&status); + + if (! status) { + throw std::logic_error(status.errorString().asChar()); + } + + return matrix; + }, _doc_FnTransform_restPosition) + + .def("rotateBy", [](MFnTransform & self, MEulerRotation rotation, MSpace::Space space = MSpace::kTransform) { + MStatus status = self.rotateBy(rotation, space); + + if (! status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotation"), py::arg("space") = MSpace::kTransform, _doc_FnTransform_rotateBy) + + .def("rotateBy", [](MFnTransform & self, MQuaternion quaternion, MSpace::Space space = MSpace::kTransform) { + MStatus status = self.rotateBy(quaternion, space); + + if (! status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("quaternion"), py::arg("space") = MSpace::kTransform, _doc_FnTransform_rotateBy) + + .def("rotateBy", [](MFnTransform & self, const py::list & rotation, MTransformationMatrix::RotationOrder order, MSpace::Space space = MSpace::kTransform) { + if (rotation.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for rotate."); + } + double tmp[3]; + std::transform(std::begin(rotation), std::end(rotation), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.rotateBy(tmp, order, space); + + if (! status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotation"), py::arg("order"), py::arg("space") = MSpace::kTransform, _doc_FnTransform_rotateBy) + + .def("rotateOrientation", [](MFnTransform & self, MSpace::Space space) -> MQuaternion { + MStatus status; + MQuaternion orient = self.rotateOrientation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return orient; + }, py::arg("space"), _doc_FnTransform_rotateOrientation) + + .def("rotatePivot", [](MFnTransform & self, MSpace::Space space) -> MPoint { + MStatus status; + MPoint pivot = self.rotatePivot(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return pivot; + }, py::arg("space"), _doc_FnTransform_rotatePivot) + + .def("rotatePivotTranslation", [](MFnTransform & self, MSpace::Space space) -> MVector { + MStatus status; + MVector pivot = self.rotatePivotTranslation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return pivot; + }, py::arg("space"), _doc_FnTransform_rotatePivotTranslation) + + .def_property_readonly("rotationOrder", [](MFnTransform & self) -> MTransformationMatrix::RotationOrder { + MStatus status; + auto result = self.rotationOrder(&status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, _doc_FnTransform_rotationOrder) + + .def("scaleBy", [](MFnTransform & self, const py::list & scale) { + if (scale.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for scale."); + } + double tmp[3]; + std::transform(std::begin(scale), std::end(scale), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.scaleBy(tmp); + + if (! status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("scale"), _doc_FnTransform_scaleBy) + + .def("scalePivot", [](MFnTransform & self, MSpace::Space space) -> MPoint { + MStatus status; + MPoint pivot = self.scalePivot(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return pivot; + }, py::arg("space"), _doc_FnTransform_scalePivot) + + .def("scalePivotTranslation", [](MFnTransform & self, MSpace::Space space) -> MVector { + MStatus status; + MVector pivot = self.scalePivotTranslation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return pivot; + }, py::arg("space"), _doc_FnTransform_scalePivotTranslation) + + .def("setLimit", [](MFnTransform & self, MFnTransform::LimitType limitType, double value) { + MStatus status = self.setLimit(limitType, value); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("limitType"), py::arg("value"), _doc_FnTransform_setLimit) + + .def("setRestPosition", [](MFnTransform & self, const MTransformationMatrix & matrix) { + MStatus status = self.setRestPosition(matrix); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("matrix"), _doc_FnTransform_setRestPosition) + + .def("setRotateOrientation", [](MFnTransform & self, const MQuaternion& quat, MSpace::Space space, bool balance) { + MStatus status = self.setRotateOrientation(quat, space, balance); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("quat"), py::arg("space"), py::arg("balance"), _doc_FnTransform_setRotateOrientation) + + .def("setRotatePivot", [](MFnTransform & self, const MPoint& point, MSpace::Space space, bool balance) { + MStatus status = self.setRotatePivot(point, space, balance); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("point"), py::arg("space"), py::arg("balance"), _doc_FnTransform_setRotatePivot) + + .def("setRotatePivotTranslation", [](MFnTransform & self, const MVector & vec, MSpace::Space space) { + MStatus status = self.setRotatePivotTranslation(vec, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vec"), py::arg("space"), _doc_FnTransform_setRotatePivotTranslation) + + .def("setRotation", [](MFnTransform & self, MEulerRotation rotation) { + MStatus status = self.setRotation(rotation); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotation"), _doc_FnTransform_setRotation) + + .def("setRotation", [](MFnTransform & self, MQuaternion quaternion, MSpace::Space space = MSpace::kTransform) { + MStatus status = self.setRotation(quaternion, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("quaternion"), py::arg("space") = MSpace::kTransform, _doc_FnTransform_setRotation) + + .def("setRotation", [](MFnTransform & self, const py::list & rotation, MTransformationMatrix::RotationOrder order) { + if (rotation.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for rotation."); + } + double tmp[3]; + std::transform(std::begin(rotation), std::end(rotation), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.setRotation(tmp); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotation"), py::arg("order"), _doc_FnTransform_setRotation) + + .def("setRotationOrder", [](MFnTransform & self, MTransformationMatrix::RotationOrder order, bool reorder) { + MStatus status = self.setRotationOrder(order, reorder); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("order"), py::arg("reorder"), _doc_FnTransform_setRotationOrder) + + .def("setScale", [](MFnTransform & self, const py::list & scale) { + if (scale.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for scale."); + } + double tmp[3]; + std::transform(std::begin(scale), std::end(scale), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.setScale(tmp); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("scale"), _doc_FnTransform_setScale) + + .def("setScalePivot", [](MFnTransform & self, const MPoint & point, MSpace::Space space, bool balance) { + MStatus status = self.setScalePivot(point, space, balance); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("point"), py::arg("space"), py::arg("balance"), _doc_FnTransform_setScalePivot) + + .def("setScalePivotTranslation", [](MFnTransform & self, const MVector & vec, MSpace::Space space) { + MStatus status = self.setScalePivotTranslation(vec, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vec"), py::arg("space"), _doc_FnTransform_setScalePivotTranslation) + + .def("setShear", [](MFnTransform & self, const py::list & shear) { + if (shear.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for shear."); + } + double tmp[3]; + std::transform(std::begin(shear), std::end(shear), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.setShear(tmp); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("shear"), _doc_FnTransform_setShear) + + .def("setTranslation", [](MFnTransform & self, const MVector & vec, MSpace::Space space) { + MStatus status = self.setTranslation(vec, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vec"), py::arg("space"), _doc_FnTransform_setTranslation) + + .def("shearBy", [](MFnTransform & self, const py::list & shear) { + if (shear.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for shear."); + } + double tmp[3]; + std::transform(std::begin(shear), std::end(shear), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.shearBy(tmp); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("shear"), _doc_FnTransform_shearBy) + + .def_property_readonly("transformation", [](MFnTransform & self) -> MTransformationMatrix { + MStatus status; + MTransformationMatrix matrix = self.transformation(&status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return matrix; + }, _doc_FnTransform_transformation) + + .def("translateBy", [](MFnTransform & self, const MVector & vec, MSpace::Space space) { + MStatus status = self.translateBy(vec, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vec"), py::arg("space"), _doc_FnTransform_translateBy); \ No newline at end of file diff --git a/src/MTransformationMatrix.inl b/src/MTransformationMatrix.inl new file mode 100644 index 0000000..45b265c --- /dev/null +++ b/src/MTransformationMatrix.inl @@ -0,0 +1,322 @@ +#define _doc_TransformationMatrix_asMatrix \ + "Interpolates between the identity transformation and that currently in the object, returning the result as an MMatrix." + +#define _doc_TransformationMatrix_asMatrixInverse \ + "Returns the inverse of the matrix representing the transformation." + +#define _doc_TransformationMatrix_asScaleMatrix \ + "Returns the matrix which takes points from object space to the space immediately following scale and shear transformations." + +#define _doc_TransformationMatrix_asRotateMatrix \ + "Returns the matrix which takes points from object space to the space immediately following the scale/shear/rotation transformations." + +#define _doc_TransformationMatrix_setScale \ + "Sets the transformation's scale components to the three floats in the provided sequence." + +#define _doc_TransformationMatrix_setRotation \ + "Sets the transformation's rotation component." + +#define _doc_TransformationMatrix_rotationOrder \ + "Returns the order of rotations when the transformation's rotate component is expressed as an euler rotation." + +#define _doc_TransformationMatrix_reorderRotation \ + "Reorders the transformation's rotate component to give the same overall rotation but using a new order or rotations." + +#define _doc_TransformationMatrix_setToRotationAxis \ + "Sets the transformation's rotate component to be a given axis vector and angle in radians." + +#define _doc_TransformationMatrix_rotationOrientation \ + "Returns a quaternion which orients the local rotation space." + +#define _doc_TransformationMatrix_setRotationOrientation \ + "Sets a quaternion which orients the local rotation space." + +#define _doc_TransformationMatrix_setTranslation \ + "Sets the transformation's translation component." + +#define _doc_TransformationMatrix_setShear \ + "Sets the transformation's shear component." + +#define _doc_TransformationMatrix_scalePivot \ + "Returns the transformation's scale pivot component." + +#define _doc_TransformationMatrix_setScalePivot \ + "Sets the transformation's scale pivot component." + +#define _doc_TransformationMatrix_scalePivotTranslation \ + "Returns the transformation's scale pivot translation component." + +#define _doc_TransformationMatrix_setScalePivotTranslation \ + "Sets the transformation's scale pivot translation component." + +#define _doc_TransformationMatrix_rotatePivot \ + "Returns the transformation's rotate pivot component." + +#define _doc_TransformationMatrix_setRotatePivot \ + "Sets the transformation's rotate pivot component." + +#define _doc_TransformationMatrix_rotatePivotTranslation \ + "Returns the transformation's rotate pivot translation component." + +#define _doc_TransformationMatrix_setRotatePivotTranslation \ + "Sets the transformation's rotate pivot translation component." + +#define _doc_TransformationMatrix_isEquivalent \ + "Returns true if this transformation's matrix is within tolerance of another's matrix." + +py::enum_(TransformationMatrix, "RotationOrder") + .value("kInvalid", MTransformationMatrix::RotationOrder::kInvalid) + .value("kXYZ", MTransformationMatrix::RotationOrder::kXYZ) + .value("kYZX", MTransformationMatrix::RotationOrder::kYZX) + .value("kZXY", MTransformationMatrix::RotationOrder::kZXY) + .value("kXZY", MTransformationMatrix::RotationOrder::kXZY) + .value("kYXZ", MTransformationMatrix::RotationOrder::kYXZ) + .value("kZYX", MTransformationMatrix::RotationOrder::kZYX) + .value("kLast", MTransformationMatrix::RotationOrder::kLast) + .export_values(); + +TransformationMatrix + .def(py::init<>()) + .def(py::init(), py::arg("src")) + .def(py::init(), py::arg("src")) + + .def("asMatrix", [](MTransformationMatrix & self, double interp = 1.0) -> MMatrix { + return self.asMatrix(interp); + }, py::arg("interp") = 1.0, _doc_TransformationMatrix_asMatrix) + + .def("asMatrixInverse", &MTransformationMatrix::asMatrixInverse, _doc_TransformationMatrix_asMatrixInverse) + + .def("asRotateMatrix", &MTransformationMatrix::asRotateMatrix, _doc_TransformationMatrix_asRotateMatrix) + + .def("asScaleMatrix", &MTransformationMatrix::asScaleMatrix, _doc_TransformationMatrix_asScaleMatrix) + + .def("isEquivalent", &MTransformationMatrix::isEquivalent, py::arg("other"), py::arg("tolerance") = MTransformationMatrix_kTol, _doc_TransformationMatrix_isEquivalent) + + .def("reorderRotation", [](MTransformationMatrix & self, MTransformationMatrix::RotationOrder order) { + MStatus status = self.reorderRotation(order); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("order"), _doc_TransformationMatrix_reorderRotation) + + .def("rotatePivot", [](MTransformationMatrix & self, MSpace::Space space) -> MPoint { + MStatus status; + MPoint result = self.rotatePivot(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, py::arg("space"), _doc_TransformationMatrix_rotatePivot) + + .def("rotatePivotTranslation", [](MTransformationMatrix & self, MSpace::Space space) -> MVector { + MStatus status; + MVector result = self.rotatePivotTranslation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, py::arg("space"), _doc_TransformationMatrix_rotatePivotTranslation) + + .def_property_readonly("quaternionRotation", &MTransformationMatrix::rotation, + "Returns the rotation component of the transformation matrix as a quaternion.") + + .def_property_readonly("eulerRotation", &MTransformationMatrix::eulerRotation, + "Returns the rotation component of the transformation matrix as an euler rotation.") + + .def("setRotation", [](MTransformationMatrix & self, const MQuaternion& rotation) { + self.rotateTo(rotation); + }, py::arg("rotation"), "Set the rotation component of the transformation matrix using a quaternion.") + + .def("setRotation", [](MTransformationMatrix & self, const MEulerRotation& rotation) { + self.rotateTo(rotation); + }, py::arg("rotation"), "Set the rotation component of the transformation matrix using an euler rotation.") + + .def("rotateBy", [](MTransformationMatrix & self, const MQuaternion& rotate, MSpace::Space space) { + MStatus status; + self.rotateBy(rotate, space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotate"), py::arg("space"), "Adds to the rotation component of the rotation matrix by rotating relative to the existing transformation using a quaternion.") + + .def("rotateBy", [](MTransformationMatrix & self, const MEulerRotation& rotate, MSpace::Space space) { + MStatus status; + self.rotateBy(rotate, space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("rotate"), py::arg("space"), "Adds to the rotation component of the rotation matrix by rotating relative to the existing transformation using an euler rotation.") + + .def_property_readonly("rotationOrder", [](MTransformationMatrix & self) -> MTransformationMatrix::RotationOrder { + MStatus status; + auto result = self.rotationOrder(&status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, _doc_TransformationMatrix_rotationOrder) + + .def("rotationOrientation", &MTransformationMatrix::rotationOrientation, _doc_TransformationMatrix_rotationOrientation) + + .def("setRotationOrientation", &MTransformationMatrix::setRotationOrientation, py::arg("q"), _doc_TransformationMatrix_setRotationOrientation) + + .def("scalePivot", [](MTransformationMatrix & self, MSpace::Space space) -> MPoint { + MStatus status; + MPoint result = self.scalePivot(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, py::arg("space"), _doc_TransformationMatrix_scalePivot) + + .def("scalePivotTranslation", [](MTransformationMatrix & self, MSpace::Space space) -> MVector { + MStatus status; + MVector result = self.scalePivotTranslation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + return result; + }, py::arg("space"), _doc_TransformationMatrix_scalePivotTranslation) + + .def("setRotatePivot", [](MTransformationMatrix & self, MPoint point, MSpace::Space space, bool balance) { + MStatus status = self.setRotatePivot(point, space, balance); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("point"), py::arg("space"), py::arg("balance"), _doc_TransformationMatrix_setRotatePivot) + + .def("setRotatePivotTranslation", [](MTransformationMatrix & self, MVector vector, MSpace::Space space) { + MStatus status = self.setRotatePivotTranslation(vector, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vector"), py::arg("space"), _doc_TransformationMatrix_setRotatePivotTranslation) + + .def("scale", [](MTransformationMatrix & self, MSpace::Space space) -> py::list { + py::list result; + double scale[3]; + MStatus status = self.getScale(scale, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + + result.append(scale[0]); + result.append(scale[1]); + result.append(scale[2]); + + return result; + }, py::arg("space"), "Get the scale component of the transformation matrix and retun it as a list of 3 floats.") + + .def("setScale", [](MTransformationMatrix & self, const py::list & scale, MSpace::Space space) { + if (scale.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for scale."); + } + double tmp[3]; + std::transform(std::begin(scale), std::end(scale), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.setScale(tmp, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("scale"), py::arg("space"), _doc_TransformationMatrix_setScale) + + .def("scaleBy", [](MTransformationMatrix & self, const py::list & scale, MSpace::Space space) { + if (scale.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for scale."); + } + double tmp[3]; + std::transform(std::begin(scale), std::end(scale), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.addScale(tmp, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("scale"), py::arg("space"), "Multiplies the transformation's scale components by the three floats in the provided sequence.") + + .def("setScalePivot", [](MTransformationMatrix & self, MPoint point, MSpace::Space space, bool balance) { + MStatus status = self.setScalePivot(point, space, balance); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("point"), py::arg("space"), py::arg("balance"), _doc_TransformationMatrix_setScalePivot) + + .def("setScalePivotTranslation", [](MTransformationMatrix & self, MVector vector, MSpace::Space space) { + MStatus status = self.setScalePivotTranslation(vector, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vector"), py::arg("space"), _doc_TransformationMatrix_setScalePivotTranslation) + + .def("setShear", [](MTransformationMatrix & self, const py::list & shear, MSpace::Space space) { + if (shear.size() != 3) + { + throw std::invalid_argument("You must provide a list of 3 floats for shear."); + } + double tmp[3]; + std::transform(std::begin(shear), std::end(shear), std::begin(tmp), + [](pybind11::handle handle) -> double { return handle.cast(); }); + + MStatus status = self.setShear(tmp, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("shear"), py::arg("space"), _doc_TransformationMatrix_setShear) + + .def("setToRotationAxis", [](MTransformationMatrix & self, MVector axis, double rotation) { + MStatus status = self.setToRotationAxis(axis, rotation); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("axis"), py::arg("rotation"), _doc_TransformationMatrix_setToRotationAxis) + + .def("translation", [](MTransformationMatrix & self, MSpace::Space space) -> MVector { + MStatus status; + MVector translation = self.getTranslation(space, &status); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + return translation; + }, py::arg("space"), "Returns the transformation's translation component as a vector.") + + .def("setTranslation", [](MTransformationMatrix & self, MVector vector, MSpace::Space space) { + MStatus status = self.setTranslation(vector, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vector"), py::arg("space"), _doc_TransformationMatrix_setTranslation) + + .def("translateBy", [](MTransformationMatrix & self, MVector vector, MSpace::Space space) { + MStatus status = self.addTranslation(vector, space); + + if (!status) { + throw std::logic_error(status.errorString().asChar()); + } + }, py::arg("vector"), py::arg("space"), "Adds a vector to the transformation's translate component."); \ No newline at end of file diff --git a/src/Math.inl b/src/Math.inl index 1e165f0..0fb2438 100644 --- a/src/Math.inl +++ b/src/Math.inl @@ -5,8 +5,16 @@ Vector const double>(), py::arg("x"), py::arg("y"), - py::arg("z")) + py::arg("z") = 0.0) .def(py::init(), py::arg("src")) + .def(py::init(), py::arg("src")) + .def(py::init([](std::array seq) { + return std::unique_ptr(new MVector(seq[0], seq[1], seq[2])); + }), py::arg("seq"), "Create a new vector from a seqence of 3 floats") + .def(py::init([](std::array seq) { + return std::unique_ptr(new MVector(seq[0], seq[1])); + }), py::arg("seq"), "Create a new vector from a seqence of 2 floats") + .def_readwrite("x", &MVector::x) .def_readwrite("y", &MVector::y) .def_readwrite("z", &MVector::z) @@ -16,12 +24,78 @@ Vector .def(py::self += py::self, py::arg("other")) .def(py::self -= py::self, py::arg("other")) .def(py::self *= double(), py::arg("other")) + .def(py::self *= MMatrix(), py::arg("other")) .def(py::self /= double(), py::arg("other")) .def(py::self * double(), py::arg("other")) + .def(py::self * MMatrix(), py::arg("other")) + .def(py::self * py::self, py::arg("other")) .def(py::self / double(), py::arg("other")) + .def(py::self == py::self, py::arg("other")) + .def(py::self != py::self, py::arg("other")) + .def(py::self ^ py::self, py::arg("other")) + .def(-py::self) + + .def("angle", &MVector::angle, py::arg("other"), + "Returns the angle, in radians, between this vector and another.") + + .def("isEquivalent", &MVector::isEquivalent, py::arg("other"), py::arg("tolerance"), + "Returns True if this vector and another are within a given tolerance of being equal.") + + .def("isParallel", &MVector::isParallel, + py::arg("other"), py::arg("tolerance"), + "Returns True if this vector and another are within the given tolerance of being parallel.") + + .def("length", &MVector::length, + "Returns the magnitude of this vector.") + + .def("normal", &MVector::normal, + "Returns a new vector containing the normalized version of this one.") + + .def("normalize", &MVector::normalize, + "Normalizes this vector in-place and returns a new reference to it.") + + .def("rotateBy", [](const MVector &self, const MQuaternion& rot){ + return self.rotateBy(rot); + }, py::arg("rot"), "Returns the vector resulting from rotating this one by the given amount.") + + .def("rotateBy", [](const MVector &self, const MEulerRotation& rot){ + return self.rotateBy(rot); + }, py::arg("rot"), "Returns the vector resulting from rotating this one by the given amount.") + + .def("rotateTo", &MVector::rotateTo, py::arg("target"), + "Returns the quaternion which will rotate this vector into another.") + + .def("transformAsNormal", &MVector::transformAsNormal, py::arg("matrix"), + "Returns a new vector which is calculated by postmultiplying this vector by the transpose of the given matrix's inverse and then normalizing the result.") + + .def_property_readonly_static("oneVector", [](py::object) { return MVector::one; }, + "The vector <1,1,1>") - .def("__neg__", [](MVector v) { return -v; }, py::is_operator()) + .def_property_readonly_static("zeroVector", [](py::object) { return MVector::zero; }, + "The vector <0,0,0>") + .def_property_readonly_static("xAxisVector", [](py::object) { return MVector::xAxis; }, + "The vector <1,0,0>") + + .def_property_readonly_static("xNegAxisVector", [](py::object) { return MVector::xNegAxis; }, + "The vector <-1,0,0>") + + .def_property_readonly_static("yAxisVector", [](py::object) { return MVector::yAxis; }, + "The vector <0,1,0>") + + .def_property_readonly_static("yNegAxisVector", [](py::object) { return MVector::yNegAxis; }, + "The vector <0,-1,0>") + + .def_property_readonly_static("zAxisVector", [](py::object) { return MVector::zAxis; }, + "The vector <0,0,1>") + + .def_property_readonly_static("zNegAxisVector", [](py::object) { return MVector::zNegAxis; }, + "The vector <0,0,-1>") + + .def("__len__", [](const MVector &self) -> int { + return 3; + }, "Returns length of the vector, which is always 3.") + // Support print() .def("__repr__", [](const MVector &a) { return "()) - .def(py::init(), - py::arg("x"), - py::arg("y"), - py::arg("z"), - py::arg("w")) + .def(py::init(), + py::arg("x"), py::arg("y"), py::arg("z") = 0.0, py::arg("w") = 1.0) .def(py::init(), py::arg("src")) + .def(py::init(), py::arg("src")) + .def(py::init([](std::array seq) { + return std::unique_ptr(new MPoint(seq[0], seq[1], seq[2], seq[3])); + }), py::arg("seq"), "Create a new point from a seqence of 4 floats.") + .def(py::init([](std::array seq) { + return std::unique_ptr(new MPoint(seq[0], seq[1], seq[2])); + }), py::arg("seq"), "Create a new point from a seqence of 3 floats.`w` will be set to 1.0") + .def(py::init([](std::array seq) { + return std::unique_ptr(new MPoint(seq[0], seq[1])); + }), py::arg("seq"), "Create a new point from a seqence of 2 floats. `z` will be set to 0.0 and `w` will be set to 1.0") + .def_readwrite("x", &MPoint::x) .def_readwrite("y", &MPoint::y) .def_readwrite("z", &MPoint::z) .def_readwrite("w", &MPoint::w) - .def(py::self + py::self, py::arg("other")) + .def(py::self + MVector(), py::arg("other")) + .def(py::self - MVector(), py::arg("other")) .def(py::self - py::self, py::arg("other")) - .def(py::self += py::self, py::arg("other")) - .def(py::self -= py::self, py::arg("other")) + .def(py::self += MVector(), py::arg("other")) + .def(py::self -= MVector(), py::arg("other")) .def(py::self *= double(), py::arg("other")) .def(py::self * double(), py::arg("other")) + .def(py::self * MMatrix(), py::arg("other")) + .def(py::self *= MMatrix(), py::arg("other")) .def(py::self / double(), py::arg("other")) + .def(py::self == py::self, py::arg("other")) + .def(py::self != py::self, py::arg("other")) + + .def("cartesianize", &MPoint::cartesianize) + .def("rationalize", &MPoint::rationalize) + .def("homogenize", &MPoint::homogenize) + .def("distanceTo", &MPoint::distanceTo, py::arg("other")) + .def("isEquivalent", &MPoint::isEquivalent, py::arg("other"), py::arg("tol") = MPoint_kTol) + + .def_property_readonly_static("origin", [](py::object) { return MPoint::origin; }, + "The point <0,0,0,1>") + + .def("__len__", [](const MPoint &self) -> int { + return 4; + }, "Returns length of the point, which is always 4.") // Support print() .def("__repr__", [](const MPoint &a) { @@ -68,6 +165,24 @@ Point Matrix .def(py::init<>()) .def(py::init(), py::arg("src")) + .def(py::init([](std::array seq) { + double tmp[4][4] = {{seq[0], seq[1], seq[2], seq[3]}, + {seq[4], seq[5], seq[6], seq[7]}, + {seq[8], seq[9], seq[10], seq[11]}, + {seq[12], seq[13], seq[14], seq[15]} + }; + + return std::unique_ptr(new MMatrix(tmp)); + }), py::arg("seq"), "Create a new matrix from a sequence of 16 float values.") + .def(py::init([](std::array, 4> seq) { + double tmp[4][4] = {{seq[0][0], seq[0][1], seq[0][2], seq[0][3]}, + {seq[1][0], seq[1][1], seq[1][2], seq[1][3]}, + {seq[2][0], seq[2][1], seq[2][2], seq[2][3]}, + {seq[3][0], seq[3][1], seq[3][2], seq[3][3]}, + }; + + return std::unique_ptr(new MMatrix(tmp)); + }), py::arg("seq"), "Create a new matrix from a sequence of 4 tuples of 4 float values each.") .def(py::self += MMatrix(), py::arg("other")) .def(py::self + MMatrix(), py::arg("other")) @@ -86,6 +201,16 @@ Matrix .def("inverse", &MMatrix::inverse) .def("adjoint", &MMatrix::adjoint) .def("homogenize", &MMatrix::homogenize) + .def("transpose", &MMatrix::transpose) + .def("det3x3", &MMatrix::det3x3) + .def("det4x4", &MMatrix::det4x4) + .def("setToIdentity", &MMatrix::setToIdentity) + + .def_property_readonly("isSingular", &MMatrix::isSingular) + + .def("__len__", [](const MMatrix &self) -> int { + return 16; + }, "Returns length of the matrix, which is always 16.") // Support print() .def("__repr__", [](const MMatrix &a) { @@ -99,6 +224,7 @@ Matrix ); Quaternion + .def(py::init<>()) .def(py::init(), py::arg("src")) - .def(py::init(), + .def(py::init(), py::arg("a"), - py::arg("b")) + py::arg("b"), + py::arg("factor") = 1.0) .def(py::init(), py::arg("angle"), py::arg("axis")) + .def(py::init([](std::array seq) { + return std::unique_ptr(new MQuaternion(seq[0], seq[1], seq[2], seq[3])); + }), py::arg("seq"), "Create a new quaternion from a seqence of 4 floats") + + .def(py::self + MQuaternion(), py::arg("other")) + .def(py::self - MQuaternion(), py::arg("other")) + .def(py::self * MQuaternion(), py::arg("other")) + .def(double() * py::self, py::arg("scale")) + .def(py::self *= MQuaternion(), py::arg("other")) + .def(py::self == MQuaternion(), py::arg("other")) + .def(py::self != MQuaternion(), py::arg("other")) + .def(-py::self) + .def_readwrite("x", &MQuaternion::x) .def_readwrite("y", &MQuaternion::y) .def_readwrite("z", &MQuaternion::z) .def_readwrite("w", &MQuaternion::w) + .def("asAxisAngle", [](const MQuaternion& self) -> std::pair { + MVector axis; + double theta; + self.getAxisAngle(axis, theta); + + return std::make_pair(axis, theta); + }, "Returns the rotation as a tuple containing an axis vector and an angle in radians about that axis.") + + .def("asEulerRotation", &MQuaternion::asEulerRotation) + .def("asMatrix", &MQuaternion::asMatrix) + .def("conjugate", &MQuaternion::conjugate) + .def("conjugateIt", &MQuaternion::conjugateIt) + .def("exp", &MQuaternion::exp) + .def("inverse", &MQuaternion::inverse) + .def("invertIt", &MQuaternion::invertIt) + .def("log", &MQuaternion::log) + .def("isEquivalent", &MQuaternion::isEquivalent, py::arg("other"), py::arg("tol") = kQuaternionEpsilon) + .def("negateIt", &MQuaternion::negateIt) + .def("normal", &MQuaternion::normal) + .def("normalizeIt", &MQuaternion::normalizeIt) + .def("setToXAxis", &MQuaternion::setToXAxis, py::arg("angle")) + .def("setToYAxis", &MQuaternion::setToYAxis, py::arg("angle")) + .def("setToZAxis", &MQuaternion::setToZAxis, py::arg("angle")) + .def("setValue", [](MQuaternion& self, const MQuaternion& quat) { + self = quat; + }, py::arg("quat")) + + .def("setValue", [](MQuaternion& self, const MEulerRotation& rot) { + self = rot; + }, py::arg("rot")) + + .def("setValue", [](MQuaternion& self, const MMatrix& matrix) { + self = matrix; + }, py::arg("matrix")) + + .def("setValue", [](MQuaternion& self, const MVector& axis, double angle) { + self.setAxisAngle(axis, angle); + }, py::arg("axis"), py::arg("angle")) + + .def_static("slerp", [](const MQuaternion& p, const MQuaternion& q, double t, short spin) { + return slerp(p, q, t, spin); + }, py::arg("p"), py::arg("q"), py::arg("t"), py::arg("spin")) + + .def_static("squad", [](const MQuaternion& p, const MQuaternion& a, const MQuaternion& b, + const MQuaternion& q, double t, short spin) { + return squad(p, a, b, q, t, spin); + + }, py::arg("p"), py::arg("a"), py::arg("b"), py::arg("q"), py::arg("t"), py::arg("spin")) + + .def_static("squadPt", [](const MQuaternion& q0, const MQuaternion& q1, const MQuaternion& q2) { + return squadPt(q0, q1, q2); + }, py::arg("q0"), py::arg("q1"), py::arg("q2")) + + .def("__len__", [](const MQuaternion &self) -> int { + return 4; + }, "Returns length of the quaternion, which is always 4.") + .def("__repr__", [](const MQuaternion& q) { return ""; } ); + + +py::enum_(Space, "Space") + .value("kInvalid", MSpace::Space::kInvalid) + .value("kTransform", MSpace::Space::kTransform) + .value("kPreTransform", MSpace::Space::kPreTransform) + .value("kPostTransform", MSpace::Space::kPostTransform) + .value("kWorld", MSpace::Space::kWorld) + .value("kObject", MSpace::Space::kObject) + .value("kLast", MSpace::Space::kLast) + .export_values(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 89c967b..740cfa6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include // Types #include @@ -43,6 +45,7 @@ // Function sets #include #include +#include #include "util/atov.hpp" #include "util/plug.hpp" @@ -69,6 +72,7 @@ PYBIND11_MODULE(cmdc, m) { #include "ForwardDeclarations.inl" #include "Math.inl" + #include "MEulerRotation.inl" #include "MDagModifier.inl" #include "MDagPath.inl" #include "MDGModifier.inl" @@ -80,6 +84,8 @@ PYBIND11_MODULE(cmdc, m) { #include "MBoundingBox.inl" #include "MPlug.inl" #include "MSelectionList.inl" + #include "MTransformationMatrix.inl" + #include "MFnTransform.inl" #ifdef VERSION_INFO m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); diff --git a/tests/test_MFnTransform.py b/tests/test_MFnTransform.py new file mode 100644 index 0000000..e364adc --- /dev/null +++ b/tests/test_MFnTransform.py @@ -0,0 +1,24 @@ +import nose.tools + +import cmdc + + +def test_init_transform(): + transform = cmdc.FnTransform() + + +def test_create_transform(): + transform = cmdc.FnTransform() + + transformObj = transform.create() + assert not transformObj.isNull() + + +def test_limits(): + transform = cmdc.FnTransform() + transform.create() + + assert not transform.isLimited(cmdc.FnTransform.kScaleMinX) + + transform.enableLimit(cmdc.FnTransform.kScaleMinX, True) + assert transform.isLimited(cmdc.FnTransform.kScaleMinX) \ No newline at end of file diff --git a/tests/test_MMatrix.py b/tests/test_MMatrix.py new file mode 100644 index 0000000..0eceea3 --- /dev/null +++ b/tests/test_MMatrix.py @@ -0,0 +1,38 @@ +import nose.tools + +import cmdc + + +def test_new_matrix(): + default = cmdc.Matrix() + + seq16 = [1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1] + seq16Matrix = cmdc.Matrix(seq16) + + seq4 = [[1,0,0,0], + [0,1,0,0], + [0,0,1,0], + [0,0,0,1]] + seq4Matrix = cmdc.Matrix(seq4) + + nose.tools.assert_raises( + TypeError, + cmdc.Matrix, + [1,0,0] + ) + + nose.tools.assert_raises( + TypeError, + cmdc.Matrix, + [[1,0,0], []] + ) + + +def test_properties(): + matrix = cmdc.Matrix() + assert not matrix.isSingular + + assert len(matrix) == 16 \ No newline at end of file diff --git a/tests/test_MPoint.py b/tests/test_MPoint.py new file mode 100644 index 0000000..d1692fb --- /dev/null +++ b/tests/test_MPoint.py @@ -0,0 +1,92 @@ +import nose.tools + +import cmdc + + +def test_new_point(): + point = cmdc.Point() + assert point.x == 0 and point.y == 0 and point.z == 0 and point.w == 1 + + point.x = 4 + point.y = 3 + point.z = 2 + point.w = 0 + assert point.x == 4 and point.y == 3 and point.z == 2 and point.w == 0 + + point = cmdc.Point(1,2,3,4) + assert point.x == 1 and point.y == 2 and point.z == 3 and point.w == 4 + + point = cmdc.Point(1,2,3) + assert point.x == 1 and point.y == 2 and point.z == 3 and point.w == 1 + + point = cmdc.Point(1,2) + assert point.x == 1 and point.y == 2 and point.z == 0 and point.w == 1 + + point = cmdc.Point([5,6,7,8]) + assert point.x == 5 and point.y == 6 and point.z == 7 and point.w == 8 + + point = cmdc.Point([5,6,7]) + assert point.x == 5 and point.y == 6 and point.z == 7 and point.w == 1 + + point = cmdc.Point([5,6]) + assert point.x == 5 and point.y == 6 and point.z == 0 and point.w == 1 + + origin = cmdc.Point.origin + assert origin.x == 0 and origin.y == 0 and origin.z == 0 and origin.w == 1 + + copy = cmdc.Point(point) + assert copy is not point + assert copy.x == 5 and copy.y == 6 and copy.z == 0 and copy.w == 1 + + copy = cmdc.Point(cmdc.Vector(1,2,3)) + assert copy.x == 1 and copy.y == 2 and copy.z == 3 and copy.w == 1 + + assert len(cmdc.Point.origin) == 4 + +def test_multiply(): + point = cmdc.Point(1,2,3) + scaled = point * 2 + assert scaled.x == 2 and scaled.y == 4 and scaled.z == 6 and scaled.w == 1 + + scaled *= 3 + assert scaled.x == 6 and scaled.y == 12 and scaled.z == 18 and scaled.w == 1 + + matrix = cmdc.Matrix([1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 4,2,3,1]) + translated = point * matrix + assert translated.x == 5 and translated.y == 4 and translated.z == 6 and translated.w == 1 + + point *= matrix + assert point.x == 5 and point.y == 4 and point.z == 6 and point.w == 1 + +def test_divide(): + point = cmdc.Point(4,8,16) + scaled = point / 2 + assert scaled.x == 2 and scaled.y == 4 and scaled.z == 8 and scaled.w == 1 + +def test_add(): + point = cmdc.Point(2,3,4) + result = point + cmdc.Vector.oneVector + assert result.x == 3 and result.y == 4 and result.z == 5 + + point += cmdc.Vector(6,7,8) + assert point.x == 8 and point.y == 10 and point.z == 12 + +def test_subtract(): + point = cmdc.Point(2,3,4) + result = point - cmdc.Vector.oneVector + assert result.x == 1 and result.y == 2 and result.z == 3 + + point -= cmdc.Vector.oneVector + assert point.x == 1 and point.y == 2 and point.z == 3 + +def test_extra(): + pointA = cmdc.Point(1,0,0) + pointB = cmdc.Point(-1,0,0) + + assert pointA.distanceTo(pointB) == 2 + + assert not pointA.isEquivalent(pointB) + assert pointA.isEquivalent(pointA) diff --git a/tests/test_MQuaternion.py b/tests/test_MQuaternion.py new file mode 100644 index 0000000..175a8d4 --- /dev/null +++ b/tests/test_MQuaternion.py @@ -0,0 +1,56 @@ +import nose.tools +import math +import cmdc + + +def test_new_quaternion(): + quat = cmdc.Quaternion() + assert quat.x == 0 and quat.y == 0 and quat.z == 0 and quat.w == 1 + + quat = cmdc.Quaternion([.5,.6,.7,1]) + assert quat.x == 0.5 and quat.y == 0.6 and quat.z == 0.7 and quat.w == 1 + + copy = cmdc.Quaternion(quat) + assert copy is not quat + assert copy.x == 0.5 and copy.y == 0.6 and copy.z == 0.7 and copy.w == 1 + + quat = cmdc.Quaternion(math.pi, cmdc.Vector.xAxisVector) + assert quat.x == 1.0 and quat.y == 0.0 and quat.z == 0.0 and quat.w <= 0.1e-4 + + quat = cmdc.Quaternion(cmdc.Vector.xAxisVector, cmdc.Vector.yAxisVector, 0.0) + assert quat.x == 0.0 and quat.y == 0.0 and quat.z == 0.0 and quat.w == 1.0 + + assert len(quat) == 4 + +def test_multiply(): + quat = cmdc.Quaternion() + other = quat * cmdc.Quaternion() + assert other == quat + + quat *= other + assert other == quat + + quat = 2.0 * other + assert quat.w == 2.0 + + +def test_set_value(): + quat = cmdc.Quaternion() + + quat.setValue(cmdc.Quaternion()) + + quat.setValue(cmdc.EulerRotation()) + + quat.setValue(cmdc.Matrix()) + + quat.setValue(cmdc.Vector.zAxisVector, 0.0) + + +def test_extra(): + quat = cmdc.Quaternion() + + axisAngle = quat.asAxisAngle() + assert isinstance(axisAngle, tuple) + assert len(axisAngle) == 2 + assert isinstance(axisAngle[0], cmdc.Vector) + assert isinstance(axisAngle[1], float) \ No newline at end of file diff --git a/tests/test_MTransformationMatrix.py b/tests/test_MTransformationMatrix.py new file mode 100644 index 0000000..eb6cb0c --- /dev/null +++ b/tests/test_MTransformationMatrix.py @@ -0,0 +1,54 @@ +import nose.tools + +import cmdc + +def test_new_transformation_matrix(): + origMatrix = cmdc.TransformationMatrix() + copyMatrix = cmdc.TransformationMatrix(origMatrix) + assert copyMatrix is not origMatrix + + otherCopy = cmdc.TransformationMatrix(cmdc.Matrix()) + +def test_as_matrix(): + mat = cmdc.TransformationMatrix() + + invMatrix = mat.asMatrixInverse() + rotMatrix = mat.asRotateMatrix() + scaleMatrix = mat.asScaleMatrix() + +def test_translation(): + mat = cmdc.TransformationMatrix() + + translation = cmdc.Vector(1.0, 2.0, 3.0) + mat.setTranslation(translation, cmdc.Space.kTransform) + assert mat.translation(cmdc.Space.kTransform) == translation + + mat.translateBy(translation, cmdc.Space.kTransform) + assert mat.translation(cmdc.Space.kTransform) == cmdc.Vector(2.0, 4.0, 6.0) + +def test_rotation(): + mat = cmdc.TransformationMatrix() + + mat.reorderRotation(cmdc.TransformationMatrix.kZYX) + assert mat.rotationOrder == cmdc.TransformationMatrix.kZYX + + mat.rotateBy(cmdc.Quaternion(), cmdc.Space.kTransform) + mat.rotateBy(cmdc.EulerRotation(), cmdc.Space.kTransform) + + rotation = cmdc.Quaternion() + mat.setRotation(rotation) + assert rotation == mat.quaternionRotation + + rotation = cmdc.EulerRotation() + mat.setRotation(rotation) + assert rotation == mat.eulerRotation + +def test_scale(): + mat = cmdc.TransformationMatrix() + + scale = [2.0, 3.0, 4.0] + mat.setScale(scale, cmdc.Space.kTransform) + assert mat.scale(cmdc.Space.kTransform) == scale + + mat.scaleBy(scale, cmdc.Space.kTransform) + assert mat.scale(cmdc.Space.kTransform) == [4.0, 9.0, 16.0] diff --git a/tests/test_MVector.py b/tests/test_MVector.py new file mode 100644 index 0000000..16dd00e --- /dev/null +++ b/tests/test_MVector.py @@ -0,0 +1,138 @@ +import nose.tools + +import cmdc + + +def test_new_vector(): + default = cmdc.Vector() + assert default.x == 0.0 and default.y == 0.0 and default.z == 0.0 + + default.x = 1 + default.y = 2 + default.z = 3 + assert default.x == 1.0 and default.y == 2.0 and default.z == 3.0 + + a = cmdc.Vector(1, 2, 3) + assert a.x == 1.0 and a.y == 2.0 and a.z == 3.0 + + a = cmdc.Vector(1, 2) + assert a.x == 1.0 and a.y == 2.0 and a.z == 0.0 + + b = cmdc.Vector([4, 5, 6]) + assert b.x == 4.0 and b.y == 5.0 and b.z == 6.0 + + c = cmdc.Vector([7, 8]) + assert c.x == 7.0 and c.y == 8.0 and c.z == 0.0 + + d = cmdc.Vector((1, 2, 3)) + assert d.x == 1.0 and d.y == 2.0 and d.z == 3.0 + + nose.tools.assert_raises( + TypeError, + cmdc.Vector, + [9] + ) + + nose.tools.assert_raises( + TypeError, + cmdc.Vector, + [9, 10, 11, 12] + ) + + copy = cmdc.Vector(a) + assert a is not copy + + copy = cmdc.Vector(cmdc.Point(1,2,3)) + assert copy.x == 1.0 and copy.y == 2.0 and copy.z == 3.0 + + one = cmdc.Vector.oneVector + assert one.x == 1.0 and one.y == 1.0 and one.z == 1.0 + + zero = cmdc.Vector.zeroVector + assert zero.x == 0.0 and zero.y == 0.0 and zero.z == 0.0 + + xAxis = cmdc.Vector.xAxisVector + assert xAxis.x == 1.0 and xAxis.y == 0.0 and xAxis.z == 0.0 + + xNegAxis = cmdc.Vector.xNegAxisVector + assert xNegAxis.x == -1.0 and xNegAxis.y == 0.0 and xNegAxis.z == 0.0 + + yAxis = cmdc.Vector.yAxisVector + assert yAxis.x == 0.0 and yAxis.y == 1.0 and yAxis.z == 0.0 + + yNegAxis = cmdc.Vector.yNegAxisVector + assert yNegAxis.x == 0.0 and yNegAxis.y == -1.0 and yNegAxis.z == 0.0 + + zAxis = cmdc.Vector.zAxisVector + assert zAxis.x == 0.0 and zAxis.y == 0.0 and zAxis.z == 1.0 + + zNegAxis = cmdc.Vector.zNegAxisVector + assert zNegAxis.x == 0.0 and zNegAxis.y == 0.0 and zNegAxis.z == -1.0 + + assert len(cmdc.Vector.zeroVector) == 3 + +def test_add(): + xy = cmdc.Vector.xAxisVector + cmdc.Vector.yAxisVector + assert xy.x == 1.0 and xy.y == 1.0 and xy.z == 0 + + xy += cmdc.Vector.zAxisVector + assert xy.x == 1.0 and xy.y == 1.0 and xy.z == 1.0 + +def test_multiply(): + result = cmdc.Vector.oneVector * 2.0 + assert result.x == 2.0 and result.y == 2.0 and result.z == 2.0 + + result *= 3.0 + assert result.x == 6.0 and result.y == 6.0 and result.z == 6.0 + + result = cmdc.Vector.xAxisVector * cmdc.Matrix() + assert result == cmdc.Vector.xAxisVector + +def test_subtract(): + xy = cmdc.Vector.xAxisVector - cmdc.Vector.yAxisVector + assert xy.x == 1.0 and xy.y == -1.0 and xy.z == 0 + + xy -= cmdc.Vector.zAxisVector + assert xy.x == 1.0 and xy.y == -1.0 and xy.z == -1.0 + + invVector = -xy + assert invVector.x == -1.0 and invVector.y == 1.0 and invVector.z == 1.0 + +def test_divide(): + result = cmdc.Vector.oneVector / 2.0 + assert result.x == 0.5 and result.y == 0.5 and result.z == 0.5 + + result /= 0.5 + assert result.x == 1.0 and result.y == 1.0 and result.z == 1.0 + +def test_dot(): + assert cmdc.Vector.xAxisVector * cmdc.Vector.xAxisVector == 1.0 + assert cmdc.Vector.xAxisVector * cmdc.Vector.yAxisVector == 0.0 + +def test_cross(): + xAxis = cmdc.Vector.yAxisVector ^ cmdc.Vector.zAxisVector + assert xAxis.x == 1.0 and xAxis.y == 0.0 and xAxis.z == 0.0 + +def test_normal(): + vector = cmdc.Vector.yAxisVector * 2 + assert vector.length() == 2.0 + + normalized = vector.normal() + assert normalized.length() == 1.0 + assert vector.length() == 2.0 + + vector.normalize() + assert vector.length() == 1.0 + +def test_extra(): + angle = cmdc.Vector.xAxisVector.angle(cmdc.Vector.yAxisVector) + assert (angle - 1.570) < 0.001 + + assert cmdc.Vector.xAxisVector.isParallel(cmdc.Vector.xAxisVector, 0.001) + assert not cmdc.Vector.xAxisVector.isParallel(cmdc.Vector.yAxisVector, 0.001) + + assert cmdc.Vector.zAxisVector.isEquivalent(cmdc.Vector.zAxisVector, 0.001) + assert not cmdc.Vector.zAxisVector.isEquivalent(cmdc.Vector.zeroVector, 0.001) + + assert cmdc.Vector.oneVector == cmdc.Vector.oneVector + assert cmdc.Vector.oneVector != cmdc.Vector.zeroVector \ No newline at end of file