diff --git a/ReadMe.rst b/ReadMe.rst index 7d7fa301..eceb9050 100644 --- a/ReadMe.rst +++ b/ReadMe.rst @@ -161,13 +161,18 @@ Setup on the Raspberry Pi type:: sudo pip install pi3d - - Otherwise you can download from https://pypi.python.org/pypi/pi3d - and extract the package then in a terminal:: + or for python3 + sudo pip3 install pi3d + + (or pip-3.2 or whatever see below*) Otherwise you can download from + https://pypi.python.org/pypi/pi3d and extract the package then in a + terminal:: sudo python setup.py install + or for python3 + sudo python3 setup.py install - (or you may need to use python3) this will put the package into the + This will put the package into the relevant location on your device (for instance /usr/local/lib/python2.7/dist-packages/) allowing it to be imported by your applications. @@ -193,7 +198,8 @@ Setup on the Raspberry Pi Imaging Library as this is needed for importing any graphics used by Pi3D. The original Imaging library is no longer really maintained and doesn't run on python_3. The better equivalent replacement is Pillow. - To install Pillow you can get it from the raspbian jessie repository. + In the near future Pillow will be the default imaging library but at the + time of writing you have to use the raspbian jessie repository. This is the 'trial' version of raspbian and to install packages from there you need to add an additional line to /etc/apt/sources.list:: @@ -222,10 +228,11 @@ Setup on the Raspberry Pi and ``python3-setuptools`` also pip is different:: sudo apt-get install python3-pip - sudo pip-3.2 install Pillow + sudo pip3 install Pillow - If you do not intend to run python_3 you can install the old PIL: in the - terminal, type:: + (*used to be ``pip-3.2``, google for the latest botch!) If you do not + intend to run python_3 you can install the old PIL: in the terminal, + type:: sudo apt-get install python-imaging @@ -414,7 +421,8 @@ Pi3D started with code based on Peter de Rivaz 'pyopengles' code (jonmacey.blogspot.co.uk/2012/06/). Many Thanks, especially to Peter de Rivaz, Jon Macey, Richar Urwin, Peter Hess, -David Wallin and others who have contributed to Pi3D - keep up the good work! +David Wallin, Avishay Orpaz (avishorp) and others who have contributed to Pi3D +- keep up the good work! **PLEASE READ LICENSING AND COPYRIGHT NOTICES ESPECIALLY IF USING FOR COMMERCIAL PURPOSES** diff --git a/pi3d/Buffer.py b/pi3d/Buffer.py index 4caa0309..227be768 100644 --- a/pi3d/Buffer.py +++ b/pi3d/Buffer.py @@ -92,24 +92,26 @@ def __init__(self, shape, pts, texcoords, faces, normals=None, smooth=True): self.tex_coords = texcoords self.indices = faces self.material = (0.5, 0.5, 0.5, 1.0) + self.__pack_data() + def __pack_data(self): # Pack points,normals and texcoords into tuples and convert to ctype floats. - n_verts = len(pts) - if len(texcoords) != n_verts: - if len(normals) != n_verts: + n_verts = len(self.vertices) + if len(self.tex_coords) != n_verts: + if len(self.normals) != n_verts: self.N_BYTES = 12 # only use pts - self.array_buffer = c_floats(pts.reshape(-1).tolist()) + self.array_buffer = c_floats(self.vertices.reshape(-1).tolist()) else: self.N_BYTES = 24 # use pts and normals - self.array_buffer = c_floats(np.concatenate((pts, normals), + self.array_buffer = c_floats(np.concatenate((self.vertices, self.normals), axis=1).reshape(-1).tolist()) else: self.N_BYTES = 32 # use all three NB doesn't check that normals are there - self.array_buffer = c_floats(np.concatenate((pts, normals, texcoords), + self.array_buffer = c_floats(np.concatenate((self.vertices, self.normals, self.tex_coords), axis=1).reshape(-1).tolist()) - self.ntris = len(faces) - self.element_array_buffer = c_shorts(faces.reshape(-1)) + self.ntris = len(self.indices) + self.element_array_buffer = c_shorts(self.indices.reshape(-1)) from pi3d.Display import Display self.disp = Display.INSTANCE # rely on there always being one! @@ -269,3 +271,27 @@ def draw(self, shape=None, shader=None, textures=None, ntl=None, shny=None, full else: opengles.glDrawElements(GL_POINTS, self.ntris * 3, GL_UNSIGNED_SHORT, 0) + # Implement pickle/unpickle support + def __getstate__(self): + return { + 'unib': list(self.unib), + 'vertices': self.vertices, + 'normals': self.normals, + 'tex_coords': self.tex_coords, + 'indices': self.indices, + 'material': self.material, + 'textures': self.textures + } + + def __setstate__(self, state): + unib_tuple = tuple(state['unib']) + self.unib = (ctypes.c_float * 12)(*unib_tuple) + self.vertices = state['vertices'] + self.normals = state['normals'] + self.tex_coords = state['tex_coords'] + self.indices = state['indices'] + self.material = state['material'] + self.textures = state['textures'] + self.opengl_loaded = False + self.disk_loaded = True + self.__pack_data() diff --git a/pi3d/Shape.py b/pi3d/Shape.py index 9c9bbcb1..3f85a3e8 100644 --- a/pi3d/Shape.py +++ b/pi3d/Shape.py @@ -2,7 +2,7 @@ import ctypes -from numpy import array, dot +from numpy import array, dot, savez, load from math import radians, pi, sin, cos from pi3d.constants import * @@ -72,7 +72,23 @@ def __init__(self, camera, light, name, x, y, z, 18 custom data space 54 56 19 custom data space 57 59 ===== ========================================== ==== == + """ + self.shader = None + self.textures = [] + + self.buf = [] + """self.buf contains a buffer for each part of this shape that needs + rendering with a different Shader/Texture. self.draw() relies on objects + inheriting from this filling buf with at least one element. + """ + + self.children = [] + self._camera = camera + + self.__init_matrices() + def __init_matrices(self): + """ Shape holds matrices that are updated each time it is moved or rotated this saves time recalculating them each frame as the Shape is drawn """ @@ -115,17 +131,6 @@ def __init__(self, camera, light, name, x, y, z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) - self._camera = camera - self.shader = None - self.textures = [] - - self.buf = [] - """self.buf contains a buffer for each part of this shape that needs - rendering with a different Shader/Texture. self.draw() relies on objects - inheriting from this filling buf with at least one element. - """ - - self.children = [] def draw(self, shader=None, txtrs=None, ntl=None, shny=None, camera=None, mlist=[]): """If called without parameters, there has to have been a previous call to @@ -683,3 +688,28 @@ def _lathe(self, path, sides=12, rise=0.0, loops=1.0): opy = py return Buffer(self, verts, tex_coords, idx, norms) + + def __getstate__(self): + return { + 'unif': list(self.unif), + 'childModel': self.childModel, + 'children': self.children, + 'name': self.name, + 'buf': self.buf, + 'textures': self.textures + } + + def __setstate__(self, state): + unif_tuple = tuple(state['unif']) + self.unif = (ctypes.c_float * 60)(*unif_tuple) + self.childModel = state['childModel'] + self.name = state['name'] + self.children = state['children'] + self.buf = state['buf'] + self.textures = state['textures'] + self.opengl_loaded = False + self.disk_loaded = True + self._camera = None + self.__init_matrices() + + diff --git a/pi3d/Texture.py b/pi3d/Texture.py index 1dfe4315..0dfcce7f 100644 --- a/pi3d/Texture.py +++ b/pi3d/Texture.py @@ -71,6 +71,7 @@ def __init__(self, file_string, blend=False, flip=False, size=0, self.size = size self.mipmap = mipmap self.byte_size = 0 + self._loaded = False if defer: self.load_disk() else: @@ -96,6 +97,11 @@ def _load_disk(self): Pngfont, Font, Defocus and ShadowCaster inherit from Texture but don't do all this so have to override this """ + + # If already loaded, abort + if self._loaded: + return + s = self.file_string + ' ' im = Image.open(self.file_string) @@ -133,6 +139,8 @@ def _load_disk(self): self._tex = ctypes.c_int() if 'fonts/' in self.file_string: self.im = im + + self._loaded = True def _load_opengl(self): """overrides method of Loadable""" @@ -168,6 +176,28 @@ def _unload_opengl(self): """clear it out""" opengles.glDeleteTextures(1, ctypes.byref(self._tex)) + + # Implement pickle/unpickle support + def __getstate__(self): + # Make sure the image is actually loaded + if not self._loaded: + self._load_disk() + + return { + 'blend': self.blend, + 'flip': self.flip, + 'size': self.size, + 'mipmap': self.mipmap, + 'file_string': self.file_string, + 'ix': self.ix, + 'iy': self.iy, + 'alpha': self.alpha, + 'image': self.image, + '_tex': self._tex, + '_loaded': self._loaded, + 'opengl_loaded': False, + 'disk_loaded': self.disk_loaded + } class TextureCache(object): def __init__(self, max_size=None): #TODO use max_size in some way diff --git a/pi3d/shape/ElevationMap.py b/pi3d/shape/ElevationMap.py index cdb5915b..89381d35 100644 --- a/pi3d/shape/ElevationMap.py +++ b/pi3d/shape/ElevationMap.py @@ -47,16 +47,21 @@ def __init__(self, mapfile, camera=None, light=None, print("... Map size can't be bigger than 200x200 divisions") divx = 200 divy = 200 - if issubclass(type(mapfile), type("")): #HORRIBLE. Only way to cope with python2v3 - if mapfile[0] != '/': - mapfile = sys.path[0] + '/' + mapfile - if VERBOSE: - print("Loading height map ...", mapfile) - - im = Image.open(mapfile) - im = ImageOps.invert(im) - else: - im = mapfile #allow image files to be passed as mapfile + print(type(mapfile), type("")) + + try: + if '' + mapfile == mapfile: #HORRIBLE. Only way to cope with python2v3 + if mapfile[0] != '/': + mapfile = sys.path[0] + '/' + mapfile + if VERBOSE: + print("Loading height map ...", mapfile) + + im = Image.open(mapfile) + im = ImageOps.invert(im) + else: + im = mapfile #allow image files to be passed as mapfile + except: + im = mapfile ix, iy = im.size if (ix > 200 and divx == 0) or (divx > 0): if divx == 0: diff --git a/pi3d/shape/Sprite.py b/pi3d/shape/Sprite.py index 16206c23..fa23a5d9 100644 --- a/pi3d/shape/Sprite.py +++ b/pi3d/shape/Sprite.py @@ -9,13 +9,15 @@ def __init__(self, camera=None, light=None, w=1.0, h=1.0, name="", x=0.0, y=0.0, z=20.0, rx=0.0, ry=0.0, rz=0.0, sx=1.0, sy=1.0, sz=1.0, - cx=0.0, cy=0.0, cz=0.0): + cx=0.0, cy=0.0, cz=0.0, flip=False): """Uses standard constructor for Shape. Extra Keyword arguments: *w* Width. *h* Height. + *flip* + If set to True then the Sprite is flipped vertically (top to bottom) """ super(Sprite, self).__init__(camera, light, name, x, y, z, rx, ry, rz, sx, sy, sz, cx, cy, cz) @@ -28,13 +30,13 @@ def __init__(self, camera=None, light=None, w=1.0, h=1.0, name="", self.inds = [] ww = w / 2.0 - hh = h / 2.0 + hh = h / 2.0 if not flip else -h / 2.0 self.verts = ((-ww, hh, 0.0), (ww, hh, 0.0), (ww, -hh, 0.0), (-ww, -hh, 0.0)) self.norms = ((0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1)) self.texcoords = ((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0 , 1.0)) - self.inds = ((0, 1, 3), (1, 2, 3)) + self.inds = ((0, 1, 3), (1, 2, 3)) if not flip else ((3, 2, 0), (2, 1, 0)) self.buf = [] self.buf.append(Buffer(self, self.verts, self.texcoords, self.inds, self.norms)) diff --git a/pi3d/util/OffScreenTexture.py b/pi3d/util/OffScreenTexture.py index c0a7ff5b..8945d4ad 100644 --- a/pi3d/util/OffScreenTexture.py +++ b/pi3d/util/OffScreenTexture.py @@ -19,6 +19,7 @@ def __init__(self, name): self.image = self.im.convert("RGBA").tostring('raw', "RGBA") self.alpha = True self.blend = False + self.mipmap = False self._tex = ctypes.c_int() self.framebuffer = (ctypes.c_int * 1)() diff --git a/pi3d/util/String.py b/pi3d/util/String.py index 0ffa3a9e..355aa427 100644 --- a/pi3d/util/String.py +++ b/pi3d/util/String.py @@ -112,3 +112,39 @@ def make_verts(): #local function to justify each line self.buf.append(Buffer(self, self.verts, self.texcoords, self.inds, self.norms)) self.buf[0].textures = [font] self.buf[0].unib[1] = -1.0 + + self.string = string #for later use in quick_change() method + self.maxlen = len(string) + self.font = font + + def quick_change(self, new_string): + """Method for quickly changing some characters within a previously + generated String. i.e. for changing digits in a readout etc. + + NB: 1. if you use a variable width font there will be some distortion + as characters are stretched or squashed to the original character's + dimensions. 2. there is no account made of new line characters (TODO) + 3. you must make the original string long enough to fit any additional + characters you add to new_string 4. you must make sure the Font as + used for the String.__init__ contains all the glyphs you may need for + subsequent changes. + """ + import ctypes + if new_string != self.string: + new_string = new_string + ' ' * (len(self.string) - len(new_string)) + trunc_string = new_string[:self.maxlen] #chop to length + for i, c in enumerate(trunc_string): + if c != self.string[i]: + stride = 8 + offset = 6 + texc = self.font.glyph_table[c][2] + for j, tc in enumerate(texc): #patch values directly into array_buffer + for k in [0, 1]: + self.buf[0].array_buffer[(i * 4 + j) * stride + offset + k] = tc[k] + uvmod = True + self.buf[0]._select() #then just call glBufferData + opengles.glBufferData(GL_ARRAY_BUFFER, + ctypes.sizeof(self.buf[0].array_buffer), + ctypes.byref(self.buf[0].array_buffer), + GL_STATIC_DRAW) + self.string = new_string