All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
wrapArray.h
1//
2// Copyright 2016 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_BASE_VT_WRAP_ARRAY_H
8#define PXR_BASE_VT_WRAP_ARRAY_H
9
10#include "pxr/pxr.h"
11#include "pxr/base/vt/api.h"
12#include "pxr/base/vt/array.h"
13#include "pxr/base/vt/types.h"
14#include "pxr/base/vt/value.h"
15#include "pxr/base/vt/pyOperators.h"
16
17#include "pxr/base/arch/math.h"
20#include "pxr/base/gf/half.h"
21#include "pxr/base/gf/traits.h"
23#include "pxr/base/tf/pyFunction.h"
24#include "pxr/base/tf/pyLock.h"
25#include "pxr/base/tf/pyObjWrapper.h"
26#include "pxr/base/tf/pyResultConversions.h"
27#include "pxr/base/tf/pyUtils.h"
29#include "pxr/base/tf/meta.h"
30#include "pxr/base/tf/span.h"
32#include "pxr/base/tf/tf.h"
33#include "pxr/base/tf/wrapTypeHelpers.h"
34
35#include "pxr/external/boost/python/class.hpp"
36#include "pxr/external/boost/python/copy_const_reference.hpp"
37#include "pxr/external/boost/python/def.hpp"
38#include "pxr/external/boost/python/detail/api_placeholder.hpp"
39#include "pxr/external/boost/python/extract.hpp"
40#include "pxr/external/boost/python/implicit.hpp"
41#include "pxr/external/boost/python/iterator.hpp"
42#include "pxr/external/boost/python/make_constructor.hpp"
43#include "pxr/external/boost/python/object.hpp"
44#include "pxr/external/boost/python/operators.hpp"
45#include "pxr/external/boost/python/return_arg.hpp"
46#include "pxr/external/boost/python/slice.hpp"
47#include "pxr/external/boost/python/type_id.hpp"
48#include "pxr/external/boost/python/overloads.hpp"
49
50#include <algorithm>
51#include <numeric>
52#include <ostream>
53#include <string>
54#include <memory>
55#include <vector>
56
57PXR_NAMESPACE_OPEN_SCOPE
58
59namespace Vt_WrapArray {
60
61using namespace pxr_boost::python;
62
63using std::unique_ptr;
64using std::vector;
65using std::string;
66
67template <typename T>
68object
69getitem_ellipsis(VtArray<T> const &self, object idx)
70{
71 object ellipsis = object(handle<>(borrowed(Py_Ellipsis)));
72 if (idx != ellipsis) {
73 PyErr_SetString(PyExc_TypeError, "unsupported index type");
74 throw_error_already_set();
75 }
76 return object(self);
77}
78
79template <typename T>
80object
81getitem_index(VtArray<T> const &self, int64_t idx)
82{
83 static const bool throwError = true;
84 idx = TfPyNormalizeIndex(idx, self.size(), throwError);
85 return object(self[idx]);
86}
87
88template <typename T>
89object
90getitem_slice(VtArray<T> const &self, slice idx)
91{
92 try {
93 slice::range<typename VtArray<T>::const_iterator> range =
94 idx.get_indices(self.begin(), self.end());
95 const size_t setSize = 1 + (range.stop - range.start) / range.step;
96 VtArray<T> result(setSize);
97 size_t i = 0;
98 for (; range.start != range.stop; range.start += range.step, ++i) {
99 result[i] = *range.start;
100 }
101 result[i] = *range.start;
102 return object(result);
103 }
104 catch (std::invalid_argument const &) {
105 return object();
106 }
107}
108
109template <typename T, typename S>
110void
111setArraySlice(VtArray<T> &self, S value,
112 slice::range<T*>& range, size_t setSize, bool tile = false)
113{
114 // Check size.
115 const size_t length = len(value);
116 if (length == 0)
117 TfPyThrowValueError("No values with which to set array slice.");
118 if (!tile && length < setSize) {
119 string msg = TfStringPrintf
120 ("Not enough values to set slice. Expected %zu, got %zu.",
121 setSize, length);
123 }
124
125 // Extract the values before setting any. If we can extract the
126 // whole vector at once then do that since it should be faster.
127 std::vector<T> extracted;
128 extract<std::vector<T> > vectorExtraction(value);
129 if (vectorExtraction.check()) {
130 std::vector<T> tmp = vectorExtraction();
131 extracted.swap(tmp);
132 }
133 else {
134 extracted.reserve(length);
135 for (size_t i = 0; i != length; ++i) {
136 extracted.push_back(extract<T>(value[i]));
137 }
138 }
139
140 // We're fine, go through and set them. Handle common case as a fast
141 // path.
142 if (range.step == 1 && length >= setSize) {
143 std::copy(extracted.begin(), extracted.begin() + setSize, range.start);
144 }
145 else {
146 for (size_t i = 0; i != setSize; range.start += range.step, ++i) {
147 *range.start = extracted[i % length];
148 }
149 }
150}
151
152template <typename T>
153void
154setArraySlice(VtArray<T> &self, slice idx, object value, bool tile = false)
155{
156 // Get the range.
157 slice::range<T*> range;
158 try {
159 T* data = self.data();
160 range = idx.get_indices(data, data + self.size());
161 }
162 catch (std::invalid_argument const &) {
163 // Do nothing
164 return;
165 }
166
167 // Get the number of items to be set.
168 const size_t setSize = 1 + (range.stop - range.start) / range.step;
169
170 // Copy from VtArray. We only want to take this path if the passed value is
171 // *exactly* a VtArray. That is, we don't want to take this path if it can
172 // merely *convert* to a VtArray, so we check that we can extract a mutable
173 // lvalue reference from the python object, which requires that there be a
174 // real VtArray there.
175 if (extract< VtArray<T> &>(value).check()) {
176 const VtArray<T> val = extract< VtArray<T> >(value);
177 const size_t length = val.size();
178 if (length == 0)
179 TfPyThrowValueError("No values with which to set array slice.");
180 if (!tile && length < setSize) {
181 string msg = TfStringPrintf
182 ("Not enough values to set slice. Expected %zu, got %zu.",
183 setSize, length);
185 }
186
187 // We're fine, go through and set them.
188 for (size_t i = 0; i != setSize; range.start += range.step, ++i) {
189 *range.start = val[i % length];
190 }
191 }
192
193 // Copy from scalar.
194 else if (extract<T>(value).check()) {
195 if (!tile) {
196 // XXX -- We're allowing implicit tiling; do we want to?
197 //TfPyThrowValueError("can only assign an iterable.");
198 }
199
200 // Use scalar to fill entire slice.
201 const T val = extract<T>(value);
202 for (size_t i = 0; i != setSize; range.start += range.step, ++i) {
203 *range.start = val;
204 }
205 }
206
207 // Copy from list.
208 else if (extract<list>(value).check()) {
209 setArraySlice(self, extract<list>(value)(), range, setSize, tile);
210 }
211
212 // Copy from tuple.
213 else if (extract<tuple>(value).check()) {
214 setArraySlice(self, extract<tuple>(value)(), range, setSize, tile);
215 }
216
217 // Copy from iterable.
218 else {
219 setArraySlice(self, list(value), range, setSize, tile);
220 }
221}
222
223
224template <typename T>
225void
226setitem_ellipsis(VtArray<T> &self, object idx, object value)
227{
228 object ellipsis = object(handle<>(borrowed(Py_Ellipsis)));
229 if (idx != ellipsis) {
230 PyErr_SetString(PyExc_TypeError, "unsupported index type");
231 throw_error_already_set();
232 }
233 setArraySlice(self, slice(0, self.size()), value);
234}
235
236template <typename T>
237void
238setitem_index(VtArray<T> &self, int64_t idx, object value)
239{
240 idx = TfPyNormalizeIndex(idx, self.size(), /*throwError=*/true);
241 setArraySlice(self, slice(idx, idx+1), value, /*tile=*/true);
242}
243
244template <typename T>
245void
246setitem_slice(VtArray<T> &self, slice idx, object value)
247{
248 setArraySlice(self, idx, value);
249}
250
251
252template <class T>
253VT_API string GetVtArrayName();
254
255template <class T, class... Ts>
256constexpr bool Vt_IsAnySameImpl(TfMetaList<Ts...>) {
257 return (std::is_same_v<T, Ts> || ...);
258}
259
260template <class T, class TypeList>
261constexpr bool Vt_IsAnySame() {
262 return Vt_IsAnySameImpl<T>(TypeList{});
263}
264
265// This is the same types as in VT_INTEGRAL_BUILTIN_VALUE_TYPES with char
266// and bool types removed.
267using Vt_OptimizedStreamIntegralTypes =
268 TfMetaList<short, unsigned short,
269 int, unsigned int,
270 long, unsigned long,
271 long long, unsigned long long>;
272
273// Explicitly convert half to float here instead of relying on implicit
274// conversion to float to work around the fact that libc++ only provides
275// implementations of std::isfinite for types where std::is_arithmetic
276// is true.
277template <typename T>
278inline bool _IsFinite(T const &value) {
279 return std::isfinite(value);
280}
281inline bool _IsFinite(GfHalf const &value) {
282 return std::isfinite(static_cast<float>(value));
283}
284
285template <typename T>
286static void streamValue(std::ostringstream &stream, T const &value) {
287 // To avoid overhead we stream out certain builtin types directly
288 // without calling TfPyRepr().
289 if constexpr(Vt_IsAnySame<T, Vt_OptimizedStreamIntegralTypes>()) {
290 stream << value;
291 }
292 // For float types we need to be make sure to represent infs and nans correctly.
293 else if constexpr(GfIsFloatingPoint<T>::value) {
294 if (_IsFinite(value)) {
295 stream << value;
296 }
297 else {
298 stream << TfPyRepr(value);
299 }
300 }
301 else {
302 stream << TfPyRepr(value);
303 }
304}
305
306static unsigned int
307Vt_ComputeEffectiveRankAndLastDimSize(
308 Vt_ShapeData const *sd, size_t *lastDimSize)
309{
310 unsigned int rank = sd->GetRank();
311 if (rank == 1)
312 return rank;
313
314 size_t divisor = std::accumulate(
315 sd->otherDims, sd->otherDims + rank-1,
316 1, [](size_t x, size_t y) { return x * y; });
317
318 size_t remainder = divisor ? sd->totalSize % divisor : 0;
319 *lastDimSize = divisor ? sd->totalSize / divisor : 0;
320
321 if (remainder)
322 rank = 1;
323
324 return rank;
325}
326
327template <typename T>
328string __repr__(VtArray<T> const &self)
329{
330 if (self.empty())
331 return TF_PY_REPR_PREFIX +
332 TfStringPrintf("%s()", GetVtArrayName<VtArray<T> >().c_str());
333
334 std::ostringstream stream;
335 stream.precision(17);
336 stream << "(";
337 for (size_t i = 0; i < self.size(); ++i) {
338 stream << (i ? ", " : "");
339 streamValue(stream, self[i]);
340 }
341 stream << (self.size() == 1 ? ",)" : ")");
342
343 const std::string repr = TF_PY_REPR_PREFIX +
344 TfStringPrintf("%s(%zd, %s)",
345 GetVtArrayName<VtArray<T> >().c_str(),
346 self.size(), stream.str().c_str());
347
348 // XXX: This is to deal with legacy shaped arrays and should be removed
349 // once all shaped arrays have been eliminated.
350 // There is no nice way to make an eval()able __repr__ for shaped arrays
351 // that preserves the shape information, so put it in <> to make it
352 // clearly not eval()able. That has the advantage that, if somebody passes
353 // the repr into eval(), it'll raise a SyntaxError that clearly points to
354 // the beginning of the __repr__.
355 Vt_ShapeData const *shapeData = self._GetShapeData();
356 size_t lastDimSize = 0;
357 unsigned int rank =
358 Vt_ComputeEffectiveRankAndLastDimSize(shapeData, &lastDimSize);
359 if (rank > 1) {
360 std::string shapeStr = "(";
361 for (size_t i = 0; i != rank-1; ++i) {
362 shapeStr += TfStringPrintf(
363 i ? ", %d" : "%d", shapeData->otherDims[i]);
364 }
365 shapeStr += TfStringPrintf(", %zu)", lastDimSize);
366 return TfStringPrintf("<%s with shape %s>",
367 repr.c_str(), shapeStr.c_str());
368 }
369
370 return repr;
371}
372
373template <typename T>
374VtArray<T> *VtArray__init__(object const &values)
375{
376 // Make an array.
377 unique_ptr<VtArray<T> > ret(new VtArray<T>(len(values)));
378
379 // Set the values. This is equivalent to saying 'ret[...] = values'
380 // in python, except that we allow tiling here.
381 static const bool tile = true;
382 setArraySlice(*ret, slice(0, ret->size()), values, tile);
383 return ret.release();
384}
385template <typename T>
386VtArray<T> *VtArray__init__2(size_t size, object const &values)
387{
388 // Make the array.
389 unique_ptr<VtArray<T> > ret(new VtArray<T>(size));
390
391 // Set the values. This is equivalent to saying 'ret[...] = values'
392 // in python, except that we allow tiling here.
393 static const bool tile = true;
394 setArraySlice(*ret, slice(0, ret->size()), values, tile);
395
396 return ret.release();
397}
398
399// overloading for operator special methods, to allow tuple / list & array
400// combinations
401ARCH_PRAGMA_PUSH
402ARCH_PRAGMA_UNSAFE_USE_OF_BOOL
403ARCH_PRAGMA_UNARY_MINUS_ON_UNSIGNED
404
405VTOPERATOR_WRAP(__add__,__radd__)
406VTOPERATOR_WRAP_NONCOMM(__sub__,__rsub__)
407VTOPERATOR_WRAP(__mul__,__rmul__)
408VTOPERATOR_WRAP_NONCOMM(__div__,__rdiv__)
409VTOPERATOR_WRAP_NONCOMM(__mod__,__rmod__)
410
411ARCH_PRAGMA_POP
412}
413
414template <typename T>
415static std::string _VtStr(T const &self)
416{
417 return TfStringify(self);
418}
419
420template <typename T>
421void VtWrapArray()
422{
423 using namespace Vt_WrapArray;
424
425 typedef T This;
426 typedef typename This::ElementType Type;
427
428 string name = GetVtArrayName<This>();
429 string typeStr = ArchGetDemangled(typeid(Type));
430 string docStr = TfStringPrintf("An array of type %s.", typeStr.c_str());
431
432 auto selfCls = class_<This>(name.c_str(), docStr.c_str(), no_init)
433 .setattr("_isVtArray", true)
434 .def(TfTypePythonClass())
435 .def(init<>())
436 .def("__init__", make_constructor(VtArray__init__<Type>),
437 (const char *)
438 "__init__(values)\n\n"
439 "values: a sequence (tuple, list, or another VtArray with "
440 "element type convertible to the new array's element type)\n\n"
441 )
442 .def("__init__", make_constructor(VtArray__init__2<Type>))
443 .def(init<unsigned int>())
444
445 .def("__getitem__", getitem_ellipsis<Type>)
446 .def("__getitem__", getitem_slice<Type>)
447 .def("__getitem__", getitem_index<Type>)
448 .def("__setitem__", setitem_ellipsis<Type>)
449 .def("__setitem__", setitem_slice<Type>)
450 .def("__setitem__", setitem_index<Type>)
451
452 .def("__len__", &This::size)
453 .def("__iter__", iterator<This>())
454
455 .def("__repr__", __repr__<Type>)
456
457// .def(str(self))
458 .def("__str__", _VtStr<T>)
459 .def(self == self)
460 .def(self != self)
461
462#ifdef NUMERIC_OPERATORS
463#define ADDITION_OPERATOR
464#define SUBTRACTION_OPERATOR
465#define MULTIPLICATION_OPERATOR
466#define DIVISION_OPERATOR
467#define UNARY_NEG_OPERATOR
468#endif
469
470#ifdef ADDITION_OPERATOR
471 VTOPERATOR_WRAPDECLARE(+,__add__,__radd__)
472#endif
473#ifdef SUBTRACTION_OPERATOR
474 VTOPERATOR_WRAPDECLARE(-,__sub__,__rsub__)
475#endif
476#ifdef MULTIPLICATION_OPERATOR
477 VTOPERATOR_WRAPDECLARE(*,__mul__,__rmul__)
478#endif
479#ifdef DIVISION_OPERATOR
480 VTOPERATOR_WRAPDECLARE(/,__div__,__rdiv__)
481#endif
482#ifdef MOD_OPERATOR
483 VTOPERATOR_WRAPDECLARE(%,__mod__,__rmod__)
484#endif
485#ifdef DOUBLE_MULT_OPERATOR
486 .def(self * double())
487 .def(double() * self)
488#endif
489#ifdef DOUBLE_DIV_OPERATOR
490 .def(self / double())
491#endif
492#ifdef UNARY_NEG_OPERATOR
493 .def(- self)
494#endif
495
496 ;
497
498 // Wrap conversions from python sequences.
499 TfPyContainerConversions::from_python_sequence<
500 This,
501 TfPyContainerConversions::
502 variable_capacity_all_items_convertible_policy>();
503
504 // Wrap implicit conversions from VtArray to TfSpan.
505 implicitly_convertible<This, TfSpan<Type> >();
506 implicitly_convertible<This, TfSpan<const Type> >();
507}
508
509template <class Array>
511Vt_ConvertFromPySequenceOrIter(TfPyObjWrapper const &obj)
512{
513 typedef typename Array::ElementType ElemType;
514 TfPyLock lock;
515 if (PySequence_Check(obj.ptr())) {
516 Py_ssize_t len = PySequence_Length(obj.ptr());
517 Array result(len);
518 ElemType *elem = result.data();
519 for (Py_ssize_t i = 0; i != len; ++i) {
520 pxr_boost::python::handle<> h(PySequence_ITEM(obj.ptr(), i));
521 if (!h) {
522 if (PyErr_Occurred())
523 PyErr_Clear();
524 return VtValue();
525 }
526 pxr_boost::python::extract<ElemType> e(h.get());
527 if (!e.check())
528 return VtValue();
529 *elem++ = e();
530 }
531 return VtValue(result);
532 } else if (PyIter_Check(obj.ptr())) {
533 Array result;
534 while (PyObject *item = PyIter_Next(obj.ptr())) {
535 pxr_boost::python::handle<> h(item);
536 if (!h) {
537 if (PyErr_Occurred())
538 PyErr_Clear();
539 return VtValue();
540 }
541 pxr_boost::python::extract<ElemType> e(h.get());
542 if (!e.check())
543 return VtValue();
544 result.push_back(e());
545 }
546 return VtValue(result);
547 }
548 return VtValue();
549}
550
551template <class Array, class Iter>
553Vt_ConvertFromRange(Iter begin, Iter end)
554{
555 typedef typename Array::ElementType ElemType;
556 Array result(distance(begin, end));
557 for (ElemType *e = result.data(); begin != end; ++begin) {
558 VtValue cast = VtValue::Cast<ElemType>(*begin);
559 if (cast.IsEmpty())
560 return cast;
561 cast.Swap(*e++);
562 }
563 return VtValue(result);
564}
565
566template <class T>
568Vt_CastToArray(VtValue const &v) {
569 VtValue ret;
570 TfPyObjWrapper obj;
571 // Attempt to convert from either python sequence or vector<VtValue>.
572 if (v.IsHolding<TfPyObjWrapper>()) {
573 ret = Vt_ConvertFromPySequenceOrIter<T>(v.UncheckedGet<TfPyObjWrapper>());
574 } else if (v.IsHolding<std::vector<VtValue> >()) {
575 std::vector<VtValue> const &vec = v.UncheckedGet<std::vector<VtValue> >();
576 ret = Vt_ConvertFromRange<T>(vec.begin(), vec.end());
577 }
578 return ret;
579}
580
582template <class Elem>
583void VtRegisterValueCastsFromPythonSequencesToArray()
584{
585 typedef VtArray<Elem> Array;
586 VtValue::RegisterCast<TfPyObjWrapper, Array>(Vt_CastToArray<Array>);
587 VtValue::RegisterCast<std::vector<VtValue>, Array>(Vt_CastToArray<Array>);
588}
589
590#define VT_WRAP_ARRAY(unused, elem) \
591 VtWrapArray< VtArray< VT_TYPE(elem) > >();
592
593PXR_NAMESPACE_CLOSE_SCOPE
594
595#endif // PXR_BASE_VT_WRAP_ARRAY_H
Architecture-specific math function calls.
A simple iterator adapter for STL containers.
Miscellaneous Utilities for dealing with script.
#define TF_PY_REPR_PREFIX
A macro which expands to the proper repr prefix for a library.
Definition: pyUtils.h:42
TF_API void TfPyThrowValueError(const char *msg)
Raises a Python ValueError with the given error msg and throws a pxr_boost::python::error_already_set...
TF_API int64_t TfPyNormalizeIndex(int64_t index, uint64_t size, bool throwError=false)
Return a positive index in the range [0,size).
std::string TfPyRepr(T const &t)
Return repr(t).
Definition: pyUtils.h:163
Defines all the types "TYPED" for which Vt creates a VtTYPEDArray typedef.
Convenience class for accessing the Python Global Interpreter Lock.
Definition: pyLock.h:105
Boost Python object wrapper.
Definition: pyObjWrapper.h:79
TF_API PyObject * ptr() const
Underlying PyObject* access.
Represents an arbitrary dimensional rectangular container class.
Definition: array.h:211
Provides a container which may hold any type, and provides introspection and iteration over array typ...
Definition: value.h:147
bool IsEmpty() const
Returns true iff this value is empty.
Definition: value.h:1285
VtValue & Swap(VtValue &rhs) noexcept
Swap this with rhs.
Definition: value.h:955
bool IsHolding() const
Return true if this value is holding an object of type T, false otherwise.
Definition: value.h:1064
T const & UncheckedGet() const &
Returns a const reference to the held object if the held object is of type T.
Definition: value.h:1104
size_t size() const
Return the total number of elements in this array.
Definition: array.h:472
pointer data()
Return a non-const pointer to this array's data.
Definition: array.h:401
bool empty() const
Return true if this array contains no elements, false otherwise.
Definition: array.h:498
iterator end()
Returns a non-const iterator to the end of the array.
Definition: array.h:366
iterator begin()
Return a non-const iterator to the start of the array.
Definition: array.h:363
std::string ArchGetDemangled()
Return demangled RTTI generated-type name.
Definition: demangle.h:86
std::string TfStringify(const T &v)
Convert an arbitrary type into a string.
Definition: stringUtils.h:555
TF_API std::string TfStringPrintf(const char *fmt,...)
Returns a string formed by a printf()-like specification.
This header serves to simply bring in the half float datatype and provide a hash_value function.
pxr_half::half GfHalf
A 16-bit floating point data type.
Definition: half.h:24
Define integral types.
Pragmas for controlling compiler-specific behaviors.
Utilities for providing C++ <-> Python container support.
Definitions of basic string utilities in tf.
A metafunction which is equivalent to std::is_floating_point but allows for additional specialization...
Definition: traits.h:45
A boost.python visitor that associates the Python class object created by the wrapping with the TfTyp...
A file containing basic constants and definitions.