Loading...
Searching...
No Matches
pyEnum.h
Go to the documentation of this file.
1//
2// Copyright 2016 Pixar
3//
4// Licensed under the Apache License, Version 2.0 (the "Apache License")
5// with the following modification; you may not use this file except in
6// compliance with the Apache License and the following modification to it:
7// Section 6. Trademarks. is deleted and replaced with:
8//
9// 6. Trademarks. This License does not grant permission to use the trade
10// names, trademarks, service marks, or product names of the Licensor
11// and its affiliates, except as required to comply with Section 4(c) of
12// the License and to reproduce the content of the NOTICE file.
13//
14// You may obtain a copy of the Apache License at
15//
16// http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software
19// distributed under the Apache License with the above modification is
20// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21// KIND, either express or implied. See the Apache License for the specific
22// language governing permissions and limitations under the Apache License.
23//
24#ifndef PXR_BASE_TF_PY_ENUM_H
25#define PXR_BASE_TF_PY_ENUM_H
26
29
30#include "pxr/pxr.h"
31
32#include "pxr/base/tf/api.h"
33#include "pxr/base/tf/pyObjWrapper.h"
34#include "pxr/base/tf/pyUtils.h"
35#include "pxr/base/tf/type.h"
36
38#include "pxr/base/tf/enum.h"
39#include "pxr/base/tf/hash.h"
40#include "pxr/base/tf/hashmap.h"
44
45#include <boost/python/class.hpp>
46#include <boost/python/converter/from_python.hpp>
47#include <boost/python/converter/registered.hpp>
48#include <boost/python/converter/rvalue_from_python_data.hpp>
49#include <boost/python/list.hpp>
50#include <boost/python/object.hpp>
51#include <boost/python/operators.hpp>
52#include <boost/python/refcount.hpp>
53#include <boost/python/scope.hpp>
54#include <boost/python/to_python_converter.hpp>
55#include <boost/python/tuple.hpp>
56
57#include <string>
58
59PXR_NAMESPACE_OPEN_SCOPE
60
64class Tf_PyEnum { };
65
70class Tf_PyEnumRegistry {
71
72 public:
73 typedef Tf_PyEnumRegistry This;
74
75 private:
76 Tf_PyEnumRegistry();
77 virtual ~Tf_PyEnumRegistry();
78 friend class TfSingleton<This>;
79
80 public:
81
82 TF_API static This &GetInstance() {
84 }
85
86 TF_API
87 void RegisterValue(TfEnum const &e, boost::python::object const &obj);
88
89 template <typename T>
90 void RegisterEnumConversions() {
91 // Register conversions to and from python.
92 boost::python::to_python_converter<T, _EnumToPython<T> >();
93 _EnumFromPython<T>();
94 }
95
96 private:
97
98 TF_API
99 PyObject *_ConvertEnumToPython(TfEnum const &e);
100
101 template <typename T>
102 struct _EnumFromPython {
103 _EnumFromPython() {
104 boost::python::converter::registry::insert
105 (&convertible, &construct, boost::python::type_id<T>());
106 }
107 static void *convertible(PyObject *obj) {
108 TfHashMap<PyObject *, TfEnum, _ObjectHash> const &o2e =
109 Tf_PyEnumRegistry::GetInstance()._objectsToEnums;
110 TfHashMap<PyObject *, TfEnum, _ObjectHash>::const_iterator
111 i = o2e.find(obj);
112 // In the case of producing a TfEnum or an integer, any
113 // registered enum type is fine. In all other cases, the
114 // enum types must match.
115 if (std::is_same<T, TfEnum>::value ||
116 (std::is_integral<T>::value && !std::is_enum<T>::value))
117 return i != o2e.end() ? obj : 0;
118 else
119 return (i != o2e.end() && i->second.IsA<T>()) ? obj : 0;
120 }
121 static void construct(PyObject *src, boost::python::converter::
122 rvalue_from_python_stage1_data *data) {
123 void *storage =
124 ((boost::python::converter::
125 rvalue_from_python_storage<T> *)data)->storage.bytes;
126 new (storage) T(_GetEnumValue(src, (T *)0));
127 data->convertible = storage;
128 }
129 private:
130 // Overloads to explicitly allow conversion of the TfEnum integer
131 // value to other enum/integral types.
132 template <typename U>
133 static U _GetEnumValue(PyObject *src, U *) {
134 return U(Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src].
135 GetValueAsInt());
136 }
137 static TfEnum _GetEnumValue(PyObject *src, TfEnum *) {
138 return Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src];
139 }
140 };
141
142 template <class T>
143 struct _EnumToPython {
144 static PyObject *convert(T t) {
145 return Tf_PyEnumRegistry
146 ::GetInstance()._ConvertEnumToPython(TfEnum(t));
147 }
148 };
149
150 // Since our enum objects live as long as the registry does, we can use the
151 // pointer values for a hash.
152 struct _ObjectHash {
153 size_t operator()(PyObject *o) const {
154 return reinterpret_cast<size_t>(o);
155 }
156 };
157
158 TfHashMap<TfEnum, PyObject *, TfHash> _enumsToObjects;
159 TfHashMap<PyObject *, TfEnum, _ObjectHash> _objectsToEnums;
160
161};
162
163TF_API_TEMPLATE_CLASS(TfSingleton<Tf_PyEnumRegistry>);
164
165// Private function used for __repr__ of wrapped enum types.
166TF_API
167std::string Tf_PyEnumRepr(boost::python::object const &self);
168
169// Private base class for types which are instantiated and exposed to python
170// for each registered enum type.
171struct Tf_PyEnumWrapper : public Tf_PyEnum
172{
173 typedef Tf_PyEnumWrapper This;
174
175 Tf_PyEnumWrapper(std::string const &n, TfEnum const &val) :
176 name(n), value(val) {}
177 long GetValue() const {
178 return value.GetValueAsInt();
179 }
180 std::string GetName() const{
181 return name;
182 }
183 std::string GetDisplayName() const {
184 return TfEnum::GetDisplayName(value);
185 }
186 std::string GetFullName() const {
187 return TfEnum::GetFullName(value);
188 }
189 friend bool operator ==(Tf_PyEnumWrapper const &self,
190 long other) {
191 return self.value.GetValueAsInt() == other;
192 }
193
194 friend bool operator ==(Tf_PyEnumWrapper const &lhs,
195 Tf_PyEnumWrapper const &rhs) {
196 return lhs.value == rhs.value;
197 }
198
199 friend bool operator !=(Tf_PyEnumWrapper const &lhs,
200 Tf_PyEnumWrapper const &rhs) {
201 return !(lhs == rhs);
202 }
203
204 friend bool operator <(Tf_PyEnumWrapper const &lhs,
205 Tf_PyEnumWrapper const &rhs)
206 {
207 // If same, not less.
208 if (lhs == rhs)
209 return false;
210 // If types don't match, string compare names.
211 if (!lhs.value.IsA(rhs.value.GetType()))
212 return TfEnum::GetFullName(lhs.value) <
213 TfEnum::GetFullName(rhs.value);
214 // If types do match, numerically compare values.
215 return lhs.GetValue() < rhs.GetValue();
216 }
217
218 friend bool operator >(Tf_PyEnumWrapper const& lhs,
219 Tf_PyEnumWrapper const& rhs)
220 {
221 return rhs < lhs;
222 }
223
224 friend bool operator <=(Tf_PyEnumWrapper const& lhs,
225 Tf_PyEnumWrapper const& rhs)
226 {
227 return !(lhs > rhs);
228 }
229
230 friend bool operator >=(Tf_PyEnumWrapper const& lhs,
231 Tf_PyEnumWrapper const& rhs)
232 {
233 return !(lhs < rhs);
234 }
235
236 //
237 // XXX Bitwise operators for Enums are a temporary measure to support the
238 // use of Enums as Bitmasks in libSd. It should be noted that Enums are
239 // NOT closed under these operators. The proper place for such operators
240 // is in a yet-nonexistent Bitmask type.
241 //
242
243 friend TfEnum operator |(Tf_PyEnumWrapper const &lhs,
244 Tf_PyEnumWrapper const &rhs) {
245 if (lhs.value.IsA(rhs.value.GetType())) {
246 return TfEnum(lhs.value.GetType(),
247 lhs.value.GetValueAsInt() |
248 rhs.value.GetValueAsInt());
249 }
250 TfPyThrowTypeError("Enum type mismatch");
251 return TfEnum();
252 }
253 friend TfEnum operator |(Tf_PyEnumWrapper const &lhs, long rhs) {
254 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() | rhs);
255 }
256 friend TfEnum operator |(long lhs, Tf_PyEnumWrapper const &rhs) {
257 return TfEnum(rhs.value.GetType(), lhs | rhs.value.GetValueAsInt());
258 }
259
260 friend TfEnum operator &(Tf_PyEnumWrapper const &lhs,
261 Tf_PyEnumWrapper const &rhs) {
262 if (lhs.value.IsA(rhs.value.GetType())) {
263 return TfEnum(lhs.value.GetType(),
264 lhs.value.GetValueAsInt() &
265 rhs.value.GetValueAsInt());
266 }
267 TfPyThrowTypeError("Enum type mismatch");
268 return TfEnum();
269 }
270 friend TfEnum operator &(Tf_PyEnumWrapper const &lhs, long rhs) {
271 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() & rhs);
272 }
273 friend TfEnum operator &(long lhs, Tf_PyEnumWrapper const &rhs) {
274 return TfEnum(rhs.value.GetType(), lhs & rhs.value.GetValueAsInt());
275 }
276
277 friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs,
278 Tf_PyEnumWrapper const &rhs) {
279 if (lhs.value.IsA(rhs.value.GetType())) {
280 return TfEnum(lhs.value.GetType(),
281 lhs.value.GetValueAsInt() ^
282 rhs.value.GetValueAsInt());
283 }
284 TfPyThrowTypeError("Enum type mismatch");
285 return TfEnum();
286 }
287 friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs, long rhs) {
288 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() ^ rhs);
289 }
290 friend TfEnum operator ^(long lhs, Tf_PyEnumWrapper const &rhs) {
291 return TfEnum(rhs.value.GetType(), lhs ^ rhs.value.GetValueAsInt());
292 }
293
294 friend TfEnum operator ~(Tf_PyEnumWrapper const &rhs) {
295 return TfEnum(rhs.value.GetType(), ~rhs.value.GetValueAsInt());
296 }
297 std::string name;
298 TfEnum value;
299};
300
301// Private template class which is instantiated and exposed to python for each
302// registered enum type.
303template <typename T>
304struct Tf_TypedPyEnumWrapper : Tf_PyEnumWrapper
305{
306 Tf_TypedPyEnumWrapper(std::string const &n, TfEnum const &val) :
307 Tf_PyEnumWrapper(n, val) {}
308
309 static boost::python::object GetValueFromName(const std::string& name) {
310 bool found = false;
311 const TfEnum value = TfEnum::GetValueFromName<T>(name, &found);
312 return found
313 ? boost::python::object(value)
314 : boost::python::object();
315 }
316};
317
318// Sanitizes the given \p name for use as a Python identifier. This includes
319// replacing spaces with '_' and appending '_' to names matching Python
320// keywords.
321//
322// If \p stripPackageName is true and \p name begins with the package name,
323// it will be stripped off.
324TF_API
325std::string Tf_PyCleanEnumName(std::string name,
326 bool stripPackageName = false);
327
328// Adds attribute of given name with given value to given scope.
329// Issues a coding error if attribute by that name already existed.
330TF_API
331void Tf_PyEnumAddAttribute(boost::python::scope &s,
332 const std::string &name,
333 const boost::python::object &value);
334
376
377// Detect scoped enums by using that the C++ standard does not allow them to
378// be converted to int implicitly.
379template <typename T, bool IsScopedEnum = !std::is_convertible<T, int>::value>
381
382private:
383 typedef boost::python::class_<
384 Tf_TypedPyEnumWrapper<T>, boost::python::bases<Tf_PyEnumWrapper> >
385 _EnumPyClassType;
386
387public:
388
393 explicit TfPyWrapEnum( std::string const &name = std::string())
394 {
395 using namespace boost::python;
396
397 const bool explicitName = !name.empty();
398
399 // First, take either the given name, or the demangled type name.
400 std::string enumName = explicitName ? name :
401 TfStringReplace(ArchGetDemangled(typeid(T)), "::", ".");
402
403 // If the name is dotted, take everything before the dot as the base
404 // name. This is used in repr.
405 std::string baseName = TfStringGetBeforeSuffix(enumName);
406 if (baseName == enumName)
407 baseName = std::string();
408
409 // If the name is dotted, take the last element as the enum name.
410 if (!TfStringGetSuffix(enumName).empty())
411 enumName = TfStringGetSuffix(enumName);
412
413 // If the name was not explicitly given, then clean it up by removing
414 // the package name prefix if it exists.
415 if (!explicitName) {
416 if (!baseName.empty()) {
417 baseName = Tf_PyCleanEnumName(
418 baseName, /* stripPackageName = */ true);
419 }
420 else {
421 enumName = Tf_PyCleanEnumName(
422 enumName, /* stripPackageName = */ true);
423 }
424 }
425
426 if (IsScopedEnum) {
427 // Make the enumName appear in python representation
428 // for scoped enums.
429 if (!baseName.empty()) {
430 baseName += ".";
431 }
432 baseName += enumName;
433 }
434
435 // Make a python type for T.
436 _EnumPyClassType enumClass(enumName.c_str(), no_init);
437 enumClass.def("GetValueFromName", &Tf_TypedPyEnumWrapper<T>::GetValueFromName, arg("name"));
438 enumClass.staticmethod("GetValueFromName");
439 enumClass.setattr("_baseName", baseName);
440
441 // Register conversions for it.
442 Tf_PyEnumRegistry::GetInstance().RegisterEnumConversions<T>();
443
444 // Export values.
445 //
446 // Only strip the package name from top-level enum values.
447 // For example, if an enum named "Foo" is declared at top-level
448 // scope in Tf with values "TfBar" and "TfBaz", we want to strip
449 // off Tf so that the values in Python will be Tf.Bar and Tf.Baz.
450 const bool stripPackageName = baseName.empty();
451 _ExportValues(stripPackageName, enumClass);
452
453 // Register with Tf so that python clients of a TfType
454 // that represents an enum are able to get to the equivalent
455 // python class with .pythonclass
456 const TfType &type = TfType::Find<T>();
457 if (!type.IsUnknown())
458 type.DefinePythonClass(enumClass);
459 }
460
461 private:
462
466 void _ExportValues(bool stripPackageName, _EnumPyClassType &enumClass) {
467 boost::python::list valueList;
468
469 for (const std::string& name : TfEnum::GetAllNames<T>()) {
470 bool success = false;
471 TfEnum enumValue = TfEnum::GetValueFromName<T>(name, &success);
472 if (!success) {
473 continue;
474 }
475
476 const std::string cleanedName =
477 Tf_PyCleanEnumName(name, stripPackageName);
478
479 // convert value to python.
480 Tf_TypedPyEnumWrapper<T> wrappedValue(cleanedName, enumValue);
481 boost::python::object pyValue(wrappedValue);
482
483 // register it as the python object for this value.
484 Tf_PyEnumRegistry::GetInstance().RegisterValue(enumValue, pyValue);
485
486 // Take all the values and export them into the current scope.
487 std::string valueName = wrappedValue.GetName();
488 if (IsScopedEnum) {
489 // If scoped enum, enum values appear on the enumClass ...
490 boost::python::scope s(enumClass);
491 Tf_PyEnumAddAttribute(s, valueName, pyValue);
492 } else {
493 // ... otherwise, enum values appear on the enclosing scope.
494 boost::python::scope s;
495 Tf_PyEnumAddAttribute(s, valueName, pyValue);
496 }
497
498 valueList.append(pyValue);
499 }
500
501 // Add a tuple of all the values to the enum class.
502 enumClass.setattr("allValues", boost::python::tuple(valueList));
503 }
504
505};
506
507PXR_NAMESPACE_CLOSE_SCOPE
508
509#endif // PXR_BASE_TF_PY_ENUM_H
A simple iterator adapter for STL containers.
Miscellaneous Utilities for dealing with script.
TF_API void TfPyThrowTypeError(const char *msg)
Raises a Python TypeError with the given error msg and throws a boost::python::error_already_set exce...
An enum class that records both enum type and enum value.
Definition: enum.h:137
static TF_API std::string GetFullName(TfEnum val)
Returns the fully-qualified name for an enumerated value.
static TF_API std::string GetDisplayName(TfEnum val)
Returns the display name for an enumerated value.
Manage a single instance of an object (see.
Definition: singleton.h:122
static T & GetInstance()
Return a reference to an object of type T, creating it if necessary.
Definition: singleton.h:137
TfType represents a dynamic runtime type.
Definition: type.h:65
TF_API void DefinePythonClass(const TfPyObjWrapper &classObj) const
Define the Python class object corresponding to this TfType.
bool IsUnknown() const
Return true if this is the unknown type, representing a type unknown to the TfType system.
Definition: type.h:388
Demangle C++ typenames generated by the typeid() facility.
std::string ArchGetDemangled()
Return demangled RTTI generated-type name.
Definition: demangle.h:103
TF_API std::string TfStringReplace(const std::string &source, const std::string &from, const std::string &to)
Replaces all occurrences of string from with to in source.
TF_API std::string TfStringGetBeforeSuffix(const std::string &name, char delimiter='.')
Returns everything up to the suffix of a string.
TF_API std::string TfStringGetSuffix(const std::string &name, char delimiter='.')
Returns the suffix of a string.
Manage a single instance of an object.
Definitions of basic string utilities in tf.
Used to wrap enum types for script.
Definition: pyEnum.h:380
TfPyWrapEnum(std::string const &name=std::string())
Construct an enum wrapper object.
Definition: pyEnum.h:393