1"""Numpy (new version) module implementation of the OpenGL-ctypes array interfaces
2
3XXX Need to register handlers for all of the scalar types that numpy returns,
4would like to have all return values be int/float if they are of  compatible
5type as well.
6"""
7REGISTRY_NAME = 'numpy'
8import operator,logging
9from OpenGL import _configflags
10_log = logging.getLogger( __name__ )
11try:
12    import numpy
13except ImportError as err:
14    raise ImportError( """No numpy module present: %s"""%(err))
15import OpenGL
16import ctypes
17from OpenGL._bytes import long, integer_types
18from OpenGL.raw.GL import _types
19from OpenGL.raw.GL.VERSION import GL_1_1
20from OpenGL import constant, error
21from OpenGL.arrays import formathandler
22c_void_p = ctypes.c_void_p
23from OpenGL import acceleratesupport
24NumpyHandler = None
25if acceleratesupport.ACCELERATE_AVAILABLE:
26    try:
27        from OpenGL_accelerate.numpy_formathandler import NumpyHandler
28    except ImportError as err:
29        _log.warn(
30            "Unable to load numpy_formathandler accelerator from OpenGL_accelerate"
31        )
32if NumpyHandler is None:
33    # numpy's array interface has changed over time :(
34    testArray = numpy.array( [1,2,3,4],'i' )
35    # Numpy's "ctypes" interface actually creates a new ctypes object
36    # in python for every access of the .ctypes attribute... which can take
37    # ridiculously large periods when you multiply it by millions of iterations
38    if hasattr(testArray,'__array_interface__'):
39        def dataPointer( cls, instance ):
40            """Convert given instance to a data-pointer value (integer)"""
41            try:
42                return long(instance.__array_interface__['data'][0])
43            except AttributeError as err:
44                instance = cls.asArray( instance )
45                try:
46                    return long(instance.__array_interface__['data'][0])
47                except AttributeError as err:
48                    return long(instance.__array_data__[0],0)
49    else:
50        def dataPointer( cls, instance ):
51            """Convert given instance to a data-pointer value (integer)"""
52            try:
53                return long(instance.__array_data__[0],0)
54            except AttributeError as err:
55                instance = cls.asArray( instance )
56                try:
57                    return long(instance.__array_interface__['data'][0])
58                except AttributeError as err:
59                    return long(instance.__array_data__[0],0)
60    try:
61        del testArray
62    except NameError as err:
63        pass
64    dataPointer = classmethod( dataPointer )
65
66
67    class NumpyHandler( formathandler.FormatHandler ):
68        """Numpy-specific data-type handler for OpenGL
69
70        Attributes:
71
72            ERROR_ON_COPY -- if True, will raise errors
73                if we have to copy an array object in order to produce
74                a contiguous array of the correct type.
75        """
76        HANDLED_TYPES = (numpy.ndarray,)# list, tuple )
77        dataPointer = dataPointer
78        isOutput = True
79        ERROR_ON_COPY = _configflags.ERROR_ON_COPY
80        @classmethod
81        def zeros( cls, dims, typeCode ):
82            """Return Numpy array of zeros in given size"""
83            return numpy.zeros( dims, GL_TYPE_TO_ARRAY_MAPPING[typeCode])
84        @classmethod
85        def arrayToGLType( cls, value ):
86            """Given a value, guess OpenGL type of the corresponding pointer"""
87            typeCode = value.dtype
88            constant = ARRAY_TO_GL_TYPE_MAPPING.get( typeCode )
89            if constant is None:
90                raise TypeError(
91                    """Don't know GL type for array of type %r, known types: %s\nvalue:%s"""%(
92                        typeCode, list(ARRAY_TO_GL_TYPE_MAPPING.keys()), value,
93                    )
94                )
95            return constant
96
97        @classmethod
98        def arraySize( cls, value, typeCode = None ):
99            """Given a data-value, calculate dimensions for the array"""
100            return value.size
101        @classmethod
102        def arrayByteCount( cls, value, typeCode = None ):
103            """Given a data-value, calculate number of bytes required to represent"""
104            try:
105                return value.nbytes
106            except AttributeError as err:
107                if cls.ERROR_ON_COPY:
108                    raise error.CopyError(
109                        """Non-numpy array passed to numpy arrayByteCount: %s""",
110                        type(value),
111                    )
112                value = cls.asArray( value, typeCode )
113                return value.nbytes
114        @classmethod
115        def asArray( cls, value, typeCode=None ):
116            """Convert given value to an array value of given typeCode"""
117            if value is None:
118                return value
119            else:
120                return cls.contiguous( value, typeCode )
121
122        @classmethod
123        def contiguous( cls, source, typeCode=None ):
124            """Get contiguous array from source
125
126            source -- numpy Python array (or compatible object)
127                for use as the data source.  If this is not a contiguous
128                array of the given typeCode, a copy will be made,
129                otherwise will just be returned unchanged.
130            typeCode -- optional 1-character typeCode specifier for
131                the numpy.array function.
132
133            All gl*Pointer calls should use contiguous arrays, as non-
134            contiguous arrays will be re-copied on every rendering pass.
135            Although this doesn't raise an error, it does tend to slow
136            down rendering.
137            """
138            typeCode = GL_TYPE_TO_ARRAY_MAPPING[ typeCode ]
139            try:
140                contiguous = source.flags.contiguous
141            except AttributeError as err:
142                if typeCode:
143                    return numpy.ascontiguousarray( source, typeCode )
144                else:
145                    return numpy.ascontiguousarray( source )
146            else:
147                if contiguous and (typeCode is None or typeCode==source.dtype.char):
148                    return source
149                elif (contiguous and cls.ERROR_ON_COPY):
150                    from OpenGL import error
151                    raise error.CopyError(
152                        """Array of type %r passed, required array of type %r""",
153                        source.dtype.char, typeCode,
154                    )
155                else:
156                    # We have to do astype to avoid errors about unsafe conversions
157                    # XXX Confirm that this will *always* create a new contiguous array
158                    # XXX Guard against wacky conversion types like uint to float, where
159                    # we really don't want to have the C-level conversion occur.
160                    # XXX ascontiguousarray is apparently now available in numpy!
161                    if cls.ERROR_ON_COPY:
162                        from OpenGL import error
163                        raise error.CopyError(
164                            """Non-contiguous array passed""",
165                            source,
166                        )
167                    if typeCode is None:
168                        typeCode = source.dtype.char
169                    return numpy.ascontiguousarray( source, typeCode )
170        @classmethod
171        def unitSize( cls, value, typeCode=None ):
172            """Determine unit size of an array (if possible)"""
173            return value.shape[-1]
174        @classmethod
175        def dimensions( cls, value, typeCode=None ):
176            """Determine dimensions of the passed array value (if possible)"""
177            return value.shape
178        @classmethod
179        def from_param( cls, instance, typeCode=None ):
180            try:
181                pointer = cls.dataPointer( instance )
182            except TypeError as err:
183                array = cls.asArray( instance, typeCode )
184                pp = cls.dataPointer( array )
185                pp._temporary_array_ = (array,)
186                return pp
187            else:
188                if typeCode and instance.dtype != GL_TYPE_TO_ARRAY_MAPPING[ typeCode ]:
189                    raise error.CopyError(
190                        """Array of type %r passed, required array of type %r""",
191                        instance.dtype.char, typeCode,
192                    )
193                return c_void_p( pointer )
194
195try:
196    numpy.array( [1], 's' )
197    SHORT_TYPE = 's'
198except TypeError as err:
199    SHORT_TYPE = 'h'
200    USHORT_TYPE = 'H'
201
202def lookupDtype( char ):
203    return numpy.zeros( (1,), dtype=char ).dtype
204
205ARRAY_TO_GL_TYPE_MAPPING = {
206    lookupDtype('d'): GL_1_1.GL_DOUBLE,
207    lookupDtype('f'): GL_1_1.GL_FLOAT,
208    lookupDtype('i'): GL_1_1.GL_INT,
209    lookupDtype(SHORT_TYPE): GL_1_1.GL_SHORT,
210    lookupDtype(USHORT_TYPE): GL_1_1.GL_UNSIGNED_SHORT,
211    lookupDtype('B'): GL_1_1.GL_UNSIGNED_BYTE,
212    lookupDtype('c'): GL_1_1.GL_UNSIGNED_BYTE,
213    lookupDtype('b'): GL_1_1.GL_BYTE,
214    lookupDtype('I'): GL_1_1.GL_UNSIGNED_INT,
215    #lookupDtype('P'), _types.GL_VOID_P, # normally duplicates another type (e.g. 'I')
216    None: None,
217}
218GL_TYPE_TO_ARRAY_MAPPING = {
219    GL_1_1.GL_DOUBLE: lookupDtype('d'),
220    GL_1_1.GL_FLOAT:lookupDtype('f'),
221    GL_1_1.GL_INT: lookupDtype('i'),
222    GL_1_1.GL_BYTE: lookupDtype('b'),
223    GL_1_1.GL_SHORT: lookupDtype(SHORT_TYPE),
224    GL_1_1.GL_UNSIGNED_INT: lookupDtype('I'),
225    GL_1_1.GL_UNSIGNED_BYTE: lookupDtype('B'),
226    GL_1_1.GL_UNSIGNED_SHORT: lookupDtype(USHORT_TYPE),
227    _types.GL_VOID_P: lookupDtype('P'),
228    None: None,
229}
230