All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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 TF_PYENUM_H
25 #define TF_PYENUM_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 
37 #include "pxr/base/arch/demangle.h"
38 #include "pxr/base/tf/enum.h"
39 #include "pxr/base/tf/hash.h"
40 #include "pxr/base/tf/hashmap.h"
41 #include "pxr/base/tf/iterator.h"
42 #include "pxr/base/tf/singleton.h"
43 #include "pxr/base/tf/stringUtils.h"
44 
45 #include <boost/preprocessor/stringize.hpp>
46 #include <boost/python/class.hpp>
47 #include <boost/python/converter/from_python.hpp>
48 #include <boost/python/converter/registered.hpp>
49 #include <boost/python/converter/rvalue_from_python_data.hpp>
50 #include <boost/python/list.hpp>
51 #include <boost/python/object.hpp>
52 #include <boost/python/operators.hpp>
53 #include <boost/python/refcount.hpp>
54 #include <boost/python/scope.hpp>
55 #include <boost/python/to_python_converter.hpp>
56 #include <boost/python/tuple.hpp>
57 
58 #include <string>
59 
60 PXR_NAMESPACE_OPEN_SCOPE
61 
65 class Tf_PyEnum { };
66 
71 class Tf_PyEnumRegistry {
72 
73  public:
74  typedef Tf_PyEnumRegistry This;
75 
76  private:
77  Tf_PyEnumRegistry();
78  virtual ~Tf_PyEnumRegistry();
79  friend class TfSingleton<This>;
80 
81  public:
82 
83  TF_API static This &GetInstance() {
85  }
86 
87  TF_API
88  void RegisterValue(TfEnum const &e, boost::python::object const &obj);
89 
90  template <typename T>
91  void RegisterEnumConversions() {
92  // Register conversions to and from python.
93  boost::python::to_python_converter<T, _EnumToPython<T> >();
94  _EnumFromPython<T>();
95  }
96 
97  private:
98 
99  template <typename T>
100  struct _EnumFromPython {
101  _EnumFromPython() {
102  boost::python::converter::registry::insert
103  (&convertible, &construct, boost::python::type_id<T>());
104  }
105  static void *convertible(PyObject *obj) {
106  TfHashMap<PyObject *, TfEnum, _ObjectHash> const &o2e =
107  Tf_PyEnumRegistry::GetInstance()._objectsToEnums;
108  TfHashMap<PyObject *, TfEnum, _ObjectHash>::const_iterator
109  i = o2e.find(obj);
110  // In the case of producing a TfEnum or an integer, any
111  // registered enum type is fine. In all other cases, the
112  // enum types must match.
113  if (boost::is_same<T, TfEnum>::value ||
114  (boost::is_integral<T>::value && !boost::is_enum<T>::value))
115  return i != o2e.end() ? obj : 0;
116  else
117  return (i != o2e.end() && i->second.IsA<T>()) ? obj : 0;
118  }
119  static void construct(PyObject *src, boost::python::converter::
120  rvalue_from_python_stage1_data *data) {
121  void *storage =
122  ((boost::python::converter::
123  rvalue_from_python_storage<T> *)data)->storage.bytes;
124  new (storage) T(_GetEnumValue(src, (T *)0));
125  data->convertible = storage;
126  }
127  private:
128  // Overloads to explicitly allow conversion of the TfEnum integer
129  // value to other enum/integral types.
130  template <typename U>
131  static U _GetEnumValue(PyObject *src, U *) {
132  return U(Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src].
133  GetValueAsInt());
134  }
135  static TfEnum _GetEnumValue(PyObject *src, TfEnum *) {
136  return Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src];
137  }
138  };
139 
140  template <typename T>
141  struct _EnumToPython {
142  static PyObject *convert(T const &t);
143  };
144 
145  // Since our enum objects live as long as the registry does, we can use the
146  // pointer values for a hash.
147  struct _ObjectHash {
148  size_t operator()(PyObject *o) const {
149  return reinterpret_cast<size_t>(o);
150  }
151  };
152 
153  TfHashMap<TfEnum, PyObject *, TfHash> _enumsToObjects;
154  TfHashMap<PyObject *, TfEnum, _ObjectHash> _objectsToEnums;
155 
156 };
157 
158 TF_API_TEMPLATE_CLASS(TfSingleton<Tf_PyEnumRegistry>);
159 
160 // Private function used for __repr__ of wrapped enum types.
161 TF_API
162 std::string Tf_PyEnumRepr(boost::python::object const &self);
163 
164 // Private base class for types which are instantiated and exposed to python
165 // for each registered enum type.
166 struct Tf_PyEnumWrapper
167  : public Tf_PyEnum, boost::totally_ordered<Tf_PyEnumWrapper>
168 {
169  typedef Tf_PyEnumWrapper This;
170 
171  Tf_PyEnumWrapper(std::string const &n, TfEnum const &val) :
172  name(n), value(val) {}
173  long GetValue() const {
174  return value.GetValueAsInt();
175  }
176  std::string GetName() const{
177  return name;
178  }
179  std::string GetDisplayName() const {
180  return TfEnum::GetDisplayName(value);
181  }
182  std::string GetFullName() const {
183  return TfEnum::GetFullName(value);
184  }
185  friend bool operator ==(Tf_PyEnumWrapper const &self,
186  long other) {
187  return self.value.GetValueAsInt() == other;
188  }
189 
190  friend bool operator ==(Tf_PyEnumWrapper const &lhs,
191  Tf_PyEnumWrapper const &rhs) {
192  return lhs.value == rhs.value;
193  }
194 
195  friend bool operator <(Tf_PyEnumWrapper const &lhs,
196  Tf_PyEnumWrapper const &rhs)
197  {
198  // If same, not less.
199  if (lhs == rhs)
200  return false;
201  // If types don't match, string compare names.
202  if (!lhs.value.IsA(rhs.value.GetType()))
203  return TfEnum::GetFullName(lhs.value) <
204  TfEnum::GetFullName(rhs.value);
205  // If types do match, numerically compare values.
206  return lhs.GetValue() < rhs.GetValue();
207  }
208 
209  //
210  // XXX Bitwise operators for Enums are a temporary measure to support the
211  // use of Enums as Bitmasks in libSd. It should be noted that Enums are
212  // NOT closed under these operators. The proper place for such operators
213  // is in a yet-nonexistent Bitmask type.
214  //
215 
216  friend TfEnum operator |(Tf_PyEnumWrapper const &lhs,
217  Tf_PyEnumWrapper const &rhs) {
218  if (lhs.value.IsA(rhs.value.GetType())) {
219  return TfEnum(lhs.value.GetType(),
220  lhs.value.GetValueAsInt() |
221  rhs.value.GetValueAsInt());
222  }
223  TfPyThrowTypeError("Enum type mismatch");
224  return TfEnum();
225  }
226  friend TfEnum operator |(Tf_PyEnumWrapper const &lhs, long rhs) {
227  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() | rhs);
228  }
229  friend TfEnum operator |(long lhs, Tf_PyEnumWrapper const &rhs) {
230  return TfEnum(rhs.value.GetType(), lhs | rhs.value.GetValueAsInt());
231  }
232 
233  friend TfEnum operator &(Tf_PyEnumWrapper const &lhs,
234  Tf_PyEnumWrapper const &rhs) {
235  if (lhs.value.IsA(rhs.value.GetType())) {
236  return TfEnum(lhs.value.GetType(),
237  lhs.value.GetValueAsInt() &
238  rhs.value.GetValueAsInt());
239  }
240  TfPyThrowTypeError("Enum type mismatch");
241  return TfEnum();
242  }
243  friend TfEnum operator &(Tf_PyEnumWrapper const &lhs, long rhs) {
244  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() & rhs);
245  }
246  friend TfEnum operator &(long lhs, Tf_PyEnumWrapper const &rhs) {
247  return TfEnum(rhs.value.GetType(), lhs & rhs.value.GetValueAsInt());
248  }
249 
250  friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs,
251  Tf_PyEnumWrapper const &rhs) {
252  if (lhs.value.IsA(rhs.value.GetType())) {
253  return TfEnum(lhs.value.GetType(),
254  lhs.value.GetValueAsInt() ^
255  rhs.value.GetValueAsInt());
256  }
257  TfPyThrowTypeError("Enum type mismatch");
258  return TfEnum();
259  }
260  friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs, long rhs) {
261  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() ^ rhs);
262  }
263  friend TfEnum operator ^(long lhs, Tf_PyEnumWrapper const &rhs) {
264  return TfEnum(rhs.value.GetType(), lhs ^ rhs.value.GetValueAsInt());
265  }
266 
267  friend TfEnum operator ~(Tf_PyEnumWrapper const &rhs) {
268  return TfEnum(rhs.value.GetType(), ~rhs.value.GetValueAsInt());
269  }
270  std::string name;
271  TfEnum value;
272 };
273 
274 template <typename T>
275 PyObject *
276 Tf_PyEnumRegistry::_EnumToPython<T>::convert(T const &t)
277 {
278  TfEnum e(t);
279 
280  // If there is no registered enum object, create a new one of
281  // the appropriate type.
282  if (!Tf_PyEnumRegistry::GetInstance()._enumsToObjects.count(e)) {
283  std::string name = ArchGetDemangled(e.GetType());
284  name = TfStringReplace(name, " ", "_");
285  name = TfStringReplace(name, "::", "_");
286  name = TfStringReplace(name, "<", "_");
287  name = TfStringReplace(name, ">", "_");
288  name = "AutoGenerated_" + name + "_" +
289  TfStringify(e.GetValueAsInt());
290 
291  boost::python::object wrappedVal =
292  boost::python::object(Tf_PyEnumWrapper(name, e));
293 
294  wrappedVal.attr("_baseName") = std::string();
295 
296  Tf_PyEnumRegistry::GetInstance().RegisterValue(e, wrappedVal);
297  }
298 
299  return boost::python::
300  incref(Tf_PyEnumRegistry::GetInstance()._enumsToObjects[e]);
301 }
302 
303 // Private template class which is instantiated and exposed to python for each
304 // registered enum type.
305 template <typename T>
306 struct Tf_TypedPyEnumWrapper : Tf_PyEnumWrapper
307 {
308  Tf_TypedPyEnumWrapper(std::string const &n, TfEnum const &val) :
309  Tf_PyEnumWrapper(n, val) {}
310 };
311 
312 // Removes the MFB package prefix from name if it starts with it, and replaces
313 // spaces with underscores.
314 TF_API
315 std::string Tf_PyCleanEnumName(std::string name);
316 
317 // Adds attribute of given name with given value to given scope.
318 // Issues a coding error if attribute by that name already existed.
319 TF_API
320 void Tf_PyEnumAddAttribute(boost::python::scope &s,
321  const std::string &name,
322  const boost::python::object &value);
323 
365 
366 // Detect scoped enums by using that the C++ standard does not allow them to
367 // be converted to int implicitly.
368 template <typename T, bool IsScopedEnum = !std::is_convertible<T, int>::value>
369 struct TfPyWrapEnum {
370 
371 private:
372  typedef boost::python::class_<
373  Tf_TypedPyEnumWrapper<T>, boost::python::bases<Tf_PyEnumWrapper> >
374  _EnumPyClassType;
375 
376 public:
377 
382  explicit TfPyWrapEnum( std::string const &name = std::string())
383  {
384  using namespace boost::python;
385 
386  const bool explicitName = !name.empty();
387 
388  // First, take either the given name, or the demangled type name.
389  std::string enumName = explicitName ? name :
390  TfStringReplace(ArchGetDemangled(typeid(T)), "::", ".");
391 
392  // If the name is dotted, take everything before the dot as the base
393  // name. This is used in repr.
394  std::string baseName = TfStringGetBeforeSuffix(enumName);
395  if (baseName == enumName)
396  baseName = std::string();
397 
398  // If the name is dotted, take the last element as the enum name.
399  if (!TfStringGetSuffix(enumName).empty())
400  enumName = TfStringGetSuffix(enumName);
401 
402  // If the name was not explicitly given, then clean it up by removing
403  // the mfb package name prefix if it exists.
404  if (!explicitName) {
405  if (!baseName.empty())
406  baseName = Tf_PyCleanEnumName(baseName);
407  else
408  enumName = Tf_PyCleanEnumName(enumName);
409  }
410 
411  if (IsScopedEnum) {
412  // Make the enumName appear in python representation
413  // for scoped enums.
414  if (!baseName.empty()) {
415  baseName += ".";
416  }
417  baseName += enumName;
418  }
419 
420  // Make a python type for T.
421  _EnumPyClassType enumClass(enumName.c_str(), no_init);
422  enumClass.setattr("_baseName", baseName);
423 
424  // Register conversions for it.
425  Tf_PyEnumRegistry::GetInstance().RegisterEnumConversions<T>();
426 
427  // Export values. Only clean names if basename is empty (i.e. the enum
428  // is top-level).
429  _ExportValues(baseName.empty(), 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 cleanNames, _EnumPyClassType &enumClass) {
445  boost::python::list valueList;
446 
447  std::vector<std::string> names = TfEnum::GetAllNames<T>();
448  TF_FOR_ALL(name, names) {
449  bool success = false;
450  TfEnum enumValue = TfEnum::GetValueFromName<T>(*name, &success);
451  if (!success) {
452  continue;
453  }
454 
455  std::string cleanedName = cleanNames ?
456  Tf_PyCleanEnumName(*name) : *name;
457 
458  // convert value to python.
459  Tf_TypedPyEnumWrapper<T> wrappedValue(cleanedName, enumValue);
460  boost::python::object pyValue(wrappedValue);
461 
462  // register it as the python object for this value.
463  Tf_PyEnumRegistry::GetInstance().RegisterValue(enumValue, pyValue);
464 
465  // Take all the values and export them into the current scope.
466  std::string valueName = wrappedValue.GetName();
467  if (IsScopedEnum) {
468  // If scoped enum, enum values appear on the enumClass ...
469  boost::python::scope s(enumClass);
470  Tf_PyEnumAddAttribute(s, valueName, pyValue);
471  } else {
472  // ... otherwise, enum values appear on the enclosing scope.
473  boost::python::scope s;
474  Tf_PyEnumAddAttribute(s, valueName, pyValue);
475  }
476 
477  valueList.append(pyValue);
478  }
479 
480  // Add a tuple of all the values to the enum class.
481  enumClass.setattr("allValues", boost::python::tuple(valueList));
482  }
483 
484 };
485 
486 PXR_NAMESPACE_CLOSE_SCOPE
487 
488 #endif // TF_PYENUM_H
Manage a single instance of an object (see.
Definition: singleton.h:122
GfVec3d operator^(GfVec3d const &v1, GfVec3d const &v2)
Returns the cross product of v1 and v2.
Definition: vec3d.h:457
TF_API std::string TfStringGetSuffix(const std::string &name, char delimiter= '.')
Returns the suffix of a string.
ARCH_API std::string ArchGetDemangled(const std::string &typeName)
Return demangled RTTI-generated type name.
An enum class that records both enum type and enum value.
Definition: enum.h:140
static TF_API std::string GetDisplayName(TfEnum val)
Returns the display name for an enumerated value.
#define TF_FOR_ALL(iter, c)
Macro for iterating over a container.
Definition: iterator.h:390
TF_API void TfPyThrowTypeError(std::string const &msg)
Raises a python TypeError and throws a C++ exception.
static T & GetInstance()
Return a reference to an object of type T, creating it if necessary.
Definition: singleton.h:137
static TF_API std::string GetFullName(TfEnum val)
Returns the fully-qualified name for an enumerated value.
TfPyWrapEnum(std::string const &name=std::string())
Construct an enum wrapper object.
Definition: pyEnum.h:382
TfType represents a dynamic runtime type.
Definition: type.h:70
VT_API bool operator==(VtDictionary const &, VtDictionary const &)
Equality comparison.
TF_API std::string TfStringGetBeforeSuffix(const std::string &name, char delimiter= '.')
Returns everything up to the suffix of a string.
std::enable_if<!std::is_enum< T >::value, std::string >::type TfStringify(const T &v)
Convert an arbitrary type into a string.
Definition: stringUtils.h:523
Used to wrap enum types for script.
Definition: pyEnum.h:369
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.