// // Copyright 2016 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. #ifndef PXR_USD_SDF_PY_SPEC_H #define PXR_USD_SDF_PY_SPEC_H /// \file sdf/pySpec.h /// /// SdfSpec Python wrapping utilities. /// /// An SdfSpec subclass is not the representation of scene data. An SdfSpec /// simply provides an interface to data stored in some internal representation. /// SdfSpec subclasses are value types and their lifetimes don't reflect the /// lifetime of the scene data. However, clients still create scene data using /// the New methods on SdfSpec subclasses. /// /// When wrapping to Python we need to wrap the New methods as the constructors. /// This used to look like this: /// /// \code /// class_("MyClass", no_init) /// .def(TfPyRefAndWeakPtr()) /// .def(TfMakePyConstructor(&MyClass::New)) /// ... /// \endcode /// /// But we can't use TfMakePyConstructor() because an SdfSpec handle is /// not a weak pointer. Furthermore, we don't have the problem of needing /// to store a ref pointer in the Python object. But we do still need /// conversion of spec types to yield the most-derived type in python. /// /// This file introduces a few boost::python::class_ def visitors to make /// wrapping specs easy. Spec wrapping should now look like: /// /// \code /// class_, bases, boost::noncopyable> /// ("MyClass", no_init) /// .def(SdfPySpec()) // or SdfPyAbstractSpec() /// .def(SdfMakePySpecConstructor(&MyClass::New)) /// ... /// \endcode /// /// If you need a custom repr you can use SdfPySpecNoRepr() or /// SdfPyAbstractSpecNoRepr() and def("__repr__", ...). #include "pxr/pxr.h" #include "pxr/usd/sdf/api.h" #include #include #include #include #include #include #include #include "pxr/base/tf/pyError.h" #include "pxr/base/tf/pyUtils.h" #include "pxr/usd/sdf/declareHandles.h" #include "pxr/base/tf/tf.h" #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/stringUtils.h" #include "pxr/base/arch/demangle.h" #include #include namespace boost{ namespace python { template struct pointee > { typedef T type; }; } } PXR_NAMESPACE_OPEN_SCOPE class SdfSpec; namespace Sdf_PySpecDetail { namespace bp = boost::python; SDF_API bp::object _DummyInit(bp::tuple const & /* args */, bp::dict const & /* kw */); template struct NewVisitor : bp::def_visitor > { public: NewVisitor(const std::string &doc = std::string()) : _doc(doc) {} template void visit(CLS& c) const { // If there's already a __new__ method, look through the staticmethod to // get the underlying function, replace __new__ with that, then add the // overload, and recreate the staticmethod. This is required because // boost python needs to have all overloads exported before you say // .staticmethod. // Note that it looks like this should do nothing, but it actually does // something! Here's what it does: looking up __new__ on c doesn't // actually produce the staticmethod object -- it does a "descriptor // __get__" which produces the underlying function. Replacing __new__ // with that underlying thing has the effect of unwrapping the // staticmethod, which is exactly what we want. if (PyObject_HasAttrString(c.ptr(), "__new__")) c.attr("__new__") = c.attr("__new__"); c.def("__new__", CTOR::template __new__, _doc.c_str()); c.staticmethod("__new__"); c.def("__init__", bp::raw_function(_DummyInit)); } template void visit(CLS& c, char const* name, Options& options) const { // If there's already a __new__ method, look through the staticmethod to // get the underlying function, replace __new__ with that, then add the // overload, and recreate the staticmethod. This is required because // boost python needs to have all overloads exported before you say // .staticmethod. // Note that it looks like this should do nothing, but it actually does // something! Here's what it does: looking up __new__ on c doesn't // actually produce the staticmethod object -- it does a "descriptor // __get__" which produces the underlying function. Replacing __new__ // with that underlying thing has the effect of unwrapping the // staticmethod, which is exactly what we want. if (PyObject_HasAttrString(c.ptr(), "__new__")) c.attr("__new__") = c.attr("__new__"); c.def("__new__", CTOR::template __new__, // Note: we ignore options.doc() in favor of _doc _doc.c_str(), options.keywords(), options.policies() ); c.staticmethod("__new__"); c.def("__init__", bp::raw_function(_DummyInit)); } private: const std::string _doc; friend class bp::def_visitor_access; }; template struct CtorBase { public: typedef SIG Sig; static Sig *_func; static void SetFunc(Sig *func) { if (! _func) { _func = func; } else { // CODE_COVERAGE_OFF TF_CODING_ERROR("Ctor with signature '%s' is already registered. " "Duplicate will be ignored.", ArchGetDemangled(typeid(Sig)).c_str()); // CODE_COVERAGE_ON } } }; template SIG *CtorBase::_func = 0; template struct NewCtor; } // namespace Sdf_PySpecDetail template Sdf_PySpecDetail::NewVisitor > SdfMakePySpecConstructor(T *func, const std::string &doc = std::string()) { // Instantiate to set static constructor pointer, then return the visitor. Sdf_PySpecDetail::NewCtor Ctor(func); return Sdf_PySpecDetail::NewVisitor >(doc); } namespace Sdf_PySpecDetail { // Create the repr for a spec using Sdf.Find(). SDF_API std::string _SpecRepr(const bp::object&, const SdfSpec*); // Registration for spec types to functions to create a holder with the spec // corresponding to the spec type. typedef PyObject* (*_HolderCreator)(const SdfSpec&); SDF_API void _RegisterHolderCreator(const std::type_info&, _HolderCreator); SDF_API PyObject* _CreateHolder(const std::type_info&, const SdfSpec&); template struct _ConstHandleToPython { typedef _SpecType SpecType; typedef SdfHandle Handle; typedef SdfHandle ConstHandle; _ConstHandleToPython() { bp::to_python_converter >(); } static PyObject *convert(ConstHandle const &p) { return bp::incref(bp::object(TfConst_cast(p)).ptr()); } }; // Register and perform python conversions of SdfHandles to holders. template struct _HandleToPython { public: typedef _SpecType SpecType; typedef _Holder Holder; typedef _Held Handle; typedef _HandleToPython This; static void Register() { _originalConverter = _RegisterConverter(&This::_Convert); _RegisterHolderCreator(typeid(SpecType), &This::_Creator); } static PyObject* convert(const Handle& x) { return _CreateHolder(typeid(SpecType), x.GetSpec()); } private: static PyObject* _Creator(const SdfSpec& spec) { Handle x(Sdf_CastAccess::CastSpec(spec)); return bp::objects::make_ptr_instance::execute(x); } template static bp::converter::to_python_function_t _RegisterConverter(bp::converter::to_python_function_t f) { // Replace the old converter, installed automatically when we // registered the class. WBN if boost python let us do this // without playing games. bp::converter::registration* r = const_cast( bp::converter::registry::query(bp::type_id())); if (r) { bp::converter::to_python_function_t old = r->m_to_python; r->m_to_python = f; return old; } else { // CODE_COVERAGE_OFF Can only happen if there's a bug. TF_CODING_ERROR("No python registration for '%s'!", ArchGetDemangled(typeid(Handle)).c_str()); return 0; // CODE_COVERAGE_ON } } static PyObject* _Convert(const void* p) { const Handle& x = *static_cast(p); return _CreateHolder(typeid(SpecType), x.GetSpec()); } private: static bp::converter::to_python_function_t _originalConverter; }; template bp::converter::to_python_function_t _HandleToPython::_originalConverter = 0; template struct _HandleFromPython { typedef _SpecType SpecType; typedef SdfHandle Handle; _HandleFromPython() { bp::converter::registry::insert(&convertible, &construct, bp::type_id()); } private: static void *convertible(PyObject *p) { if (p == Py_None) return p; void *result = bp::converter::get_lvalue_from_python(p, bp::converter::registered::converters); return result; } static void construct(PyObject* source, bp::converter::rvalue_from_python_stage1_data* data) { void* const storage = ((bp::converter::rvalue_from_python_storage*) data)->storage.bytes; // Deal with the "None" case. if (data->convertible == source) new (storage) Handle(); else { new (storage) Handle(*static_cast(data->convertible)); } data->convertible = storage; } }; // Visitor for def(). template struct SpecVisitor : bp::def_visitor > { template struct _Helper { typedef typename CLS::wrapped_type SpecType; typedef typename CLS::metadata::held_type HeldType; typedef typename CLS::metadata::held_type_arg HeldArgType; typedef typename CLS::metadata::holder HolderType; public: static std::string Repr(const bp::object& self) { const HeldType& held = bp::extract(self); return _SpecRepr(self, get_pointer(held)); } static bool IsExpired(const HeldType& self) { return !self; } static bool NonZero(const HeldType& self) { return self; } static size_t __hash__(const HeldType& self) { return hash_value(self); } static bool __eq__(const HeldType& a, const HeldType& b) { return a == b; } static bool __ne__(const HeldType& a, const HeldType& b) { return a != b; } static bool __lt__(const HeldType& a, const HeldType& b) { return a < b; } static bool __le__(const HeldType& a, const HeldType& b) { return a <= b; } static bool __gt__(const HeldType& a, const HeldType& b) { return a > b; } static bool __ge__(const HeldType& a, const HeldType& b) { return a >= b; } }; public: SpecVisitor(bool addRepr = true) : _addRepr(addRepr) { } template void visit(CLS& c) const { typedef typename CLS::wrapped_type SpecType; typedef typename CLS::metadata::held_type HeldType; typedef typename CLS::metadata::held_type_arg HeldArgType; typedef typename CLS::metadata::holder HolderType; static_assert(std::is_same >::value, "HeldType must be SdfHandle."); // Add methods. c.add_property("expired", &_Helper::IsExpired); c.def(TfPyBoolBuiltinFuncName, &_Helper::NonZero); c.def("__hash__", &_Helper::__hash__); c.def("__eq__", &_Helper::__eq__); c.def("__ne__", &_Helper::__ne__); c.def("__lt__", &_Helper::__lt__); c.def("__le__", &_Helper::__le__); c.def("__gt__", &_Helper::__gt__); c.def("__ge__", &_Helper::__ge__); // Add python conversion to cast away constness. _ConstHandleToPython(); // Add python conversion for SdfHandle. _HandleFromPython(); _HandleFromPython(); _HandleToPython::Register(); // Add __repr__. if (_addRepr) { c.def("__repr__", &_Helper::Repr); } } private: bool _addRepr; }; } // namespace Sdf_PySpecDetail inline Sdf_PySpecDetail::SpecVisitor SdfPySpec() { return Sdf_PySpecDetail::SpecVisitor(); } inline Sdf_PySpecDetail::SpecVisitor SdfPyAbstractSpec() { return Sdf_PySpecDetail::SpecVisitor(); } inline Sdf_PySpecDetail::SpecVisitor SdfPySpecNoRepr() { return Sdf_PySpecDetail::SpecVisitor(false); } inline Sdf_PySpecDetail::SpecVisitor SdfPyAbstractSpecNoRepr() { return Sdf_PySpecDetail::SpecVisitor(false); } namespace Sdf_PySpecDetail { // This generates multi-argument specializations for NewCtor. template struct NewCtor : CtorBase { typedef CtorBase Base; typedef typename Base::Sig Sig; NewCtor(Sig *func) { Base::SetFunc(func); } template static bp::object __new__(bp::object &cls, Args... args) { typedef typename CLS::metadata::held_type HeldType; TfErrorMark m; HeldType specHandle(Base::_func(args...)); if (TfPyConvertTfErrorsToPythonException(m)) bp::throw_error_already_set(); bp::object result = TfPyObject(specHandle); if (TfPyIsNone(result)) TfPyThrowRuntimeError("could not construct " + ArchGetDemangled(typeid(HeldType))); bp::detail::initialize_wrapper(result.ptr(), get_pointer(specHandle)); // make the object have the right class. bp::setattr(result, "__class__", cls); return result; } }; } // namespace Sdf_PySpecDetail PXR_NAMESPACE_CLOSE_SCOPE #endif // PXR_USD_SDF_PY_SPEC_H