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
172 : public Tf_PyEnum, boost::totally_ordered<Tf_PyEnumWrapper>
173{
174 typedef Tf_PyEnumWrapper This;
175
176 Tf_PyEnumWrapper(std::string const &n, TfEnum const &val) :
177 name(n), value(val) {}
178 long GetValue() const {
179 return value.GetValueAsInt();
180 }
181 std::string GetName() const{
182 return name;
183 }
184 std::string GetDisplayName() const {
185 return TfEnum::GetDisplayName(value);
186 }
187 std::string GetFullName() const {
188 return TfEnum::GetFullName(value);
189 }
190 friend bool operator ==(Tf_PyEnumWrapper const &self,
191 long other) {
192 return self.value.GetValueAsInt() == other;
193 }
194
195 friend bool operator ==(Tf_PyEnumWrapper const &lhs,
196 Tf_PyEnumWrapper const &rhs) {
197 return lhs.value == rhs.value;
198 }
199
200 friend bool operator <(Tf_PyEnumWrapper const &lhs,
201 Tf_PyEnumWrapper const &rhs)
202 {
203 // If same, not less.
204 if (lhs == rhs)
205 return false;
206 // If types don't match, string compare names.
207 if (!lhs.value.IsA(rhs.value.GetType()))
208 return TfEnum::GetFullName(lhs.value) <
209 TfEnum::GetFullName(rhs.value);
210 // If types do match, numerically compare values.
211 return lhs.GetValue() < rhs.GetValue();
212 }
213
214 //
215 // XXX Bitwise operators for Enums are a temporary measure to support the
216 // use of Enums as Bitmasks in libSd. It should be noted that Enums are
217 // NOT closed under these operators. The proper place for such operators
218 // is in a yet-nonexistent Bitmask type.
219 //
220
221 friend TfEnum operator |(Tf_PyEnumWrapper const &lhs,
222 Tf_PyEnumWrapper const &rhs) {
223 if (lhs.value.IsA(rhs.value.GetType())) {
224 return TfEnum(lhs.value.GetType(),
225 lhs.value.GetValueAsInt() |
226 rhs.value.GetValueAsInt());
227 }
228 TfPyThrowTypeError("Enum type mismatch");
229 return TfEnum();
230 }
231 friend TfEnum operator |(Tf_PyEnumWrapper const &lhs, long rhs) {
232 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() | rhs);
233 }
234 friend TfEnum operator |(long lhs, Tf_PyEnumWrapper const &rhs) {
235 return TfEnum(rhs.value.GetType(), lhs | rhs.value.GetValueAsInt());
236 }
237
238 friend TfEnum operator &(Tf_PyEnumWrapper const &lhs,
239 Tf_PyEnumWrapper const &rhs) {
240 if (lhs.value.IsA(rhs.value.GetType())) {
241 return TfEnum(lhs.value.GetType(),
242 lhs.value.GetValueAsInt() &
243 rhs.value.GetValueAsInt());
244 }
245 TfPyThrowTypeError("Enum type mismatch");
246 return TfEnum();
247 }
248 friend TfEnum operator &(Tf_PyEnumWrapper const &lhs, long rhs) {
249 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() & rhs);
250 }
251 friend TfEnum operator &(long lhs, Tf_PyEnumWrapper const &rhs) {
252 return TfEnum(rhs.value.GetType(), lhs & rhs.value.GetValueAsInt());
253 }
254
255 friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs,
256 Tf_PyEnumWrapper const &rhs) {
257 if (lhs.value.IsA(rhs.value.GetType())) {
258 return TfEnum(lhs.value.GetType(),
259 lhs.value.GetValueAsInt() ^
260 rhs.value.GetValueAsInt());
261 }
262 TfPyThrowTypeError("Enum type mismatch");
263 return TfEnum();
264 }
265 friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs, long rhs) {
266 return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() ^ rhs);
267 }
268 friend TfEnum operator ^(long lhs, Tf_PyEnumWrapper const &rhs) {
269 return TfEnum(rhs.value.GetType(), lhs ^ rhs.value.GetValueAsInt());
270 }
271
272 friend TfEnum operator ~(Tf_PyEnumWrapper const &rhs) {
273 return TfEnum(rhs.value.GetType(), ~rhs.value.GetValueAsInt());
274 }
275 std::string name;
276 TfEnum value;
277};
278
279// Private template class which is instantiated and exposed to python for each
280// registered enum type.
281template <typename T>
282struct Tf_TypedPyEnumWrapper : Tf_PyEnumWrapper
283{
284 Tf_TypedPyEnumWrapper(std::string const &n, TfEnum const &val) :
285 Tf_PyEnumWrapper(n, val) {}
286
287 static boost::python::object GetValueFromName(const std::string& name) {
288 bool found = false;
289 const TfEnum value = TfEnum::GetValueFromName<T>(name, &found);
290 return found
291 ? boost::python::object(value)
292 : boost::python::object();
293 }
294};
295
296// Sanitizes the given \p name for use as a Python identifier. This includes
297// replacing spaces with '_' and appending '_' to names matching Python
298// keywords.
299//
300// If \p stripPackageName is true and \p name begins with the package name,
301// it will be stripped off.
302TF_API
303std::string Tf_PyCleanEnumName(std::string name,
304 bool stripPackageName = false);
305
306// Adds attribute of given name with given value to given scope.
307// Issues a coding error if attribute by that name already existed.
308TF_API
309void Tf_PyEnumAddAttribute(boost::python::scope &s,
310 const std::string &name,
311 const boost::python::object &value);
312
354
355// Detect scoped enums by using that the C++ standard does not allow them to
356// be converted to int implicitly.
357template <typename T, bool IsScopedEnum = !std::is_convertible<T, int>::value>
359
360private:
361 typedef boost::python::class_<
362 Tf_TypedPyEnumWrapper<T>, boost::python::bases<Tf_PyEnumWrapper> >
363 _EnumPyClassType;
364
365public:
366
371 explicit TfPyWrapEnum( std::string const &name = std::string())
372 {
373 using namespace boost::python;
374
375 const bool explicitName = !name.empty();
376
377 // First, take either the given name, or the demangled type name.
378 std::string enumName = explicitName ? name :
379 TfStringReplace(ArchGetDemangled(typeid(T)), "::", ".");
380
381 // If the name is dotted, take everything before the dot as the base
382 // name. This is used in repr.
383 std::string baseName = TfStringGetBeforeSuffix(enumName);
384 if (baseName == enumName)
385 baseName = std::string();
386
387 // If the name is dotted, take the last element as the enum name.
388 if (!TfStringGetSuffix(enumName).empty())
389 enumName = TfStringGetSuffix(enumName);
390
391 // If the name was not explicitly given, then clean it up by removing
392 // the package name prefix if it exists.
393 if (!explicitName) {
394 if (!baseName.empty()) {
395 baseName = Tf_PyCleanEnumName(
396 baseName, /* stripPackageName = */ true);
397 }
398 else {
399 enumName = Tf_PyCleanEnumName(
400 enumName, /* stripPackageName = */ true);
401 }
402 }
403
404 if (IsScopedEnum) {
405 // Make the enumName appear in python representation
406 // for scoped enums.
407 if (!baseName.empty()) {
408 baseName += ".";
409 }
410 baseName += enumName;
411 }
412
413 // Make a python type for T.
414 _EnumPyClassType enumClass(enumName.c_str(), no_init);
415 enumClass.def("GetValueFromName", &Tf_TypedPyEnumWrapper<T>::GetValueFromName, arg("name"));
416 enumClass.staticmethod("GetValueFromName");
417 enumClass.setattr("_baseName", baseName);
418
419 // Register conversions for it.
420 Tf_PyEnumRegistry::GetInstance().RegisterEnumConversions<T>();
421
422 // Export values.
423 //
424 // Only strip the package name from top-level enum values.
425 // For example, if an enum named "Foo" is declared at top-level
426 // scope in Tf with values "TfBar" and "TfBaz", we want to strip
427 // off Tf so that the values in Python will be Tf.Bar and Tf.Baz.
428 const bool stripPackageName = baseName.empty();
429 _ExportValues(stripPackageName, enumClass);
430
431 // Register with Tf so that python clients of a TfType
432 // that represents an enum are able to get to the equivalent
433 // python class with .pythonclass
434 const TfType &type = TfType::Find<T>();
435 if (!type.IsUnknown())
436 type.DefinePythonClass(enumClass);
437 }
438
439 private:
440
444 void _ExportValues(bool stripPackageName, _EnumPyClassType &enumClass) {
445 boost::python::list valueList;
446
447 for (const std::string& name : TfEnum::GetAllNames<T>()) {
448 bool success = false;
449 TfEnum enumValue = TfEnum::GetValueFromName<T>(name, &success);
450 if (!success) {
451 continue;
452 }
453
454 const std::string cleanedName =
455 Tf_PyCleanEnumName(name, stripPackageName);
456
457 // convert value to python.
458 Tf_TypedPyEnumWrapper<T> wrappedValue(cleanedName, enumValue);
459 boost::python::object pyValue(wrappedValue);
460
461 // register it as the python object for this value.
462 Tf_PyEnumRegistry::GetInstance().RegisterValue(enumValue, pyValue);
463
464 // Take all the values and export them into the current scope.
465 std::string valueName = wrappedValue.GetName();
466 if (IsScopedEnum) {
467 // If scoped enum, enum values appear on the enumClass ...
468 boost::python::scope s(enumClass);
469 Tf_PyEnumAddAttribute(s, valueName, pyValue);
470 } else {
471 // ... otherwise, enum values appear on the enclosing scope.
472 boost::python::scope s;
473 Tf_PyEnumAddAttribute(s, valueName, pyValue);
474 }
475
476 valueList.append(pyValue);
477 }
478
479 // Add a tuple of all the values to the enum class.
480 enumClass.setattr("allValues", boost::python::tuple(valueList));
481 }
482
483};
484
485PXR_NAMESPACE_CLOSE_SCOPE
486
487#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:358
TfPyWrapEnum(std::string const &name=std::string())
Construct an enum wrapper object.
Definition: pyEnum.h:371