"""Copyright (c) 2010-2012 David Rio Vierra Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""" """ mceutils.py Exception catching, some basic box drawing, texture pack loading, oddball UI elements """ from albow.controls import ValueDisplay from albow import alert, ask, Button, Column, Label, root, Row, ValueButton, Widget import config from cStringIO import StringIO from datetime import datetime import directories import httplib import mcplatform import numpy from OpenGL import GL, GLU import os import platform import png from pygame import display, image, Surface import pymclevel import release import sys import traceback import zipfile import logging def alertException(func): def _alertException(*args, **kw): try: return func(*args, **kw) except root.Cancel: alert("Canceled.") except pymclevel.infiniteworld.SessionLockLost as e: alert(e.message + "\n\nYour changes cannot be saved.") except Exception, e: logging.exception("Exception:") if ask("Error during {0}: {1!r}".format(func, e)[:1000], ["Report Error", "Okay"], default=1, cancel=0) == "Report Error": try: import squash_python squash_python.get_client().recordException(*sys.exc_info()) except ImportError: pass except Exception: logging.exception("Error while recording exception data:") return _alertException def drawFace(box, face, type=GL.GL_QUADS): x, y, z, = box.origin x2, y2, z2 = box.maximum if face == pymclevel.faces.FaceXDecreasing: faceVertices = numpy.array( (x, y2, z2, x, y2, z, x, y, z, x, y, z2, ), dtype='f4') elif face == pymclevel.faces.FaceXIncreasing: faceVertices = numpy.array( (x2, y, z2, x2, y, z, x2, y2, z, x2, y2, z2, ), dtype='f4') elif face == pymclevel.faces.FaceYDecreasing: faceVertices = numpy.array( (x2, y, z2, x, y, z2, x, y, z, x2, y, z, ), dtype='f4') elif face == pymclevel.faces.FaceYIncreasing: faceVertices = numpy.array( (x2, y2, z, x, y2, z, x, y2, z2, x2, y2, z2, ), dtype='f4') elif face == pymclevel.faces.FaceZDecreasing: faceVertices = numpy.array( (x, y, z, x, y2, z, x2, y2, z, x2, y, z, ), dtype='f4') elif face == pymclevel.faces.FaceZIncreasing: faceVertices = numpy.array( (x2, y, z2, x2, y2, z2, x, y2, z2, x, y, z2, ), dtype='f4') faceVertices.shape = (4, 3) dim = face >> 1 dims = [0, 1, 2] dims.remove(dim) texVertices = numpy.array( faceVertices[:, dims], dtype='f4' ).flatten() faceVertices.shape = (12,) texVertices *= 16 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glVertexPointer(3, GL.GL_FLOAT, 0, faceVertices) GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, texVertices) GL.glEnable(GL.GL_POLYGON_OFFSET_FILL) GL.glEnable(GL.GL_POLYGON_OFFSET_LINE) if type is GL.GL_LINE_STRIP: indexes = numpy.array((0, 1, 2, 3, 0), dtype='uint32') GL.glDrawElements(type, 5, GL.GL_UNSIGNED_INT, indexes) else: GL.glDrawArrays(type, 0, 4) GL.glDisable(GL.GL_POLYGON_OFFSET_FILL) GL.glDisable(GL.GL_POLYGON_OFFSET_LINE) GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) def drawCube(box, cubeType=GL.GL_QUADS, blockType=0, texture=None, textureVertices=None, selectionBox=False): """ pass a different cubeType e.g. GL_LINE_STRIP for wireframes """ x, y, z, = box.origin x2, y2, z2 = box.maximum dx, dy, dz = x2 - x, y2 - y, z2 - z cubeVertices = numpy.array( ( x, y, z, x, y2, z, x2, y2, z, x2, y, z, x2, y, z2, x2, y2, z2, x, y2, z2, x, y, z2, x2, y, z2, x, y, z2, x, y, z, x2, y, z, x2, y2, z, x, y2, z, x, y2, z2, x2, y2, z2, x, y2, z2, x, y2, z, x, y, z, x, y, z2, x2, y, z2, x2, y, z, x2, y2, z, x2, y2, z2, ), dtype='f4') if textureVertices == None: textureVertices = numpy.array( ( 0, -dy * 16, 0, 0, dx * 16, 0, dx * 16, -dy * 16, dx * 16, -dy * 16, dx * 16, 0, 0, 0, 0, -dy * 16, dx * 16, -dz * 16, 0, -dz * 16, 0, 0, dx * 16, 0, dx * 16, 0, 0, 0, 0, -dz * 16, dx * 16, -dz * 16, dz * 16, 0, 0, 0, 0, -dy * 16, dz * 16, -dy * 16, dz * 16, -dy * 16, 0, -dy * 16, 0, 0, dz * 16, 0, ), dtype='f4') textureVertices.shape = (6, 4, 2) if selectionBox: textureVertices[0:2] += (16 * (x & 15), 16 * (y2 & 15)) textureVertices[2:4] += (16 * (x & 15), -16 * (z & 15)) textureVertices[4:6] += (16 * (z & 15), 16 * (y2 & 15)) textureVertices[:] += 0.5 GL.glVertexPointer(3, GL.GL_FLOAT, 0, cubeVertices) if texture != None: GL.glEnable(GL.GL_TEXTURE_2D) GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) texture.bind() GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, textureVertices), GL.glEnable(GL.GL_POLYGON_OFFSET_FILL) GL.glEnable(GL.GL_POLYGON_OFFSET_LINE) GL.glDrawArrays(cubeType, 0, 24) GL.glDisable(GL.GL_POLYGON_OFFSET_FILL) GL.glDisable(GL.GL_POLYGON_OFFSET_LINE) if texture != None: GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glDisable(GL.GL_TEXTURE_2D) def drawTerrainCuttingWire(box, c0=(0.75, 0.75, 0.75, 0.4), c1=(1.0, 1.0, 1.0, 1.0)): # glDepthMask(False) GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthFunc(GL.GL_LEQUAL) GL.glColor(*c1) GL.glLineWidth(2.0) drawCube(box, cubeType=GL.GL_LINE_STRIP) GL.glDepthFunc(GL.GL_GREATER) GL.glColor(*c0) GL.glLineWidth(1.0) drawCube(box, cubeType=GL.GL_LINE_STRIP) GL.glDepthFunc(GL.GL_LEQUAL) GL.glDisable(GL.GL_DEPTH_TEST) # glDepthMask(True) # texturePacksDir = os.path.join(pymclevel.minecraftDir, "texturepacks") def loadAlphaTerrainTexture(): pngFile = None texW, texH, terraindata = loadPNGFile(os.path.join(directories.dataDir, "terrain.png")) def _loadFunc(): loadTextureFunc(texW, texH, terraindata) tex = glutils.Texture(_loadFunc) tex.data = terraindata return tex def loadPNGData(filename_or_data): reader = png.Reader(filename_or_data) (w, h, data, metadata) = reader.read_flat() data = numpy.array(data, dtype='uint8') data.shape = (h, w, metadata['planes']) if data.shape[2] == 1: # indexed color. remarkably straightforward. data.shape = data.shape[:2] data = numpy.array(reader.palette(), dtype='uint8')[data] if data.shape[2] < 4: data = numpy.insert(data, 3, 255, 2) return w, h, data def loadPNGFile(filename): (w, h, data) = loadPNGData(filename) powers = (16, 32, 64, 128, 256, 512, 1024, 2048, 4096) assert (w in powers) and (h in powers) # how crude return w, h, data def loadTextureFunc(w, h, ndata): GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, w, h, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, ndata) return w, h def loadPNGTexture(filename, *a, **kw): filename = os.path.join(directories.dataDir, filename) try: w, h, ndata = loadPNGFile(filename) tex = glutils.Texture(functools.partial(loadTextureFunc, w, h, ndata), *a, **kw) tex.data = ndata return tex except Exception, e: print "Exception loading ", filename, ": ", repr(e) return glutils.Texture() import glutils def normalize(x): l = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] if l <= 0.0: return [0, 0, 0] size = numpy.sqrt(l) if size <= 0.0: return [0, 0, 0] return map(lambda a: a / size, x) def normalize_size(x): l = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] if l <= 0.0: return [0., 0., 0.], 0. size = numpy.sqrt(l) if size <= 0.0: return [0, 0, 0], 0 return (x / size), size # Label = GLLabel class HotkeyColumn(Widget): is_gl_container = True def __init__(self, items, keysColumn=None, buttonsColumn=None): if keysColumn is None: keysColumn = [] if buttonsColumn is None: buttonsColumn = [] Widget.__init__(self) for (hotkey, title, action) in items: if isinstance(title, (str, unicode)): button = Button(title, action=action) else: button = ValueButton(ref=title, action=action, width=200) button.anchor = self.anchor label = Label(hotkey, width=75, margin=button.margin) label.anchor = "wh" label.height = button.height keysColumn.append(label) buttonsColumn.append(button) self.buttons = list(buttonsColumn) buttonsColumn = Column(buttonsColumn) buttonsColumn.anchor = self.anchor keysColumn = Column(keysColumn) commandRow = Row((keysColumn, buttonsColumn)) self.add(commandRow) self.shrink_wrap() from albow import CheckBox, AttrRef, Menu class MenuButton(Button): def __init__(self, title, choices, **kw): Button.__init__(self, title, **kw) self.choices = choices self.menu = Menu(title, ((c, c) for c in choices)) def action(self): index = self.menu.present(self, (0, 0)) if index == -1: return self.menu_picked(index) def menu_picked(self, index): pass class ChoiceButton(ValueButton): align = "c" choose = None def __init__(self, choices, scrolling=True, scroll_items=30, **kw): # passing an empty list of choices is ill-advised if 'choose' in kw: self.choose = kw.pop('choose') ValueButton.__init__(self, action=self.showMenu, **kw) self.scrolling = scrolling self.scroll_items = scroll_items self.choices = choices or ["[UNDEFINED]"] widths = [self.font.size(c)[0] for c in choices] + [self.width] if len(widths): self.width = max(widths) + self.margin * 2 self.choiceIndex = 0 def showMenu(self): choiceIndex = self.menu.present(self, (0, 0)) if choiceIndex != -1: self.choiceIndex = choiceIndex if self.choose: self.choose() def get_value(self): return self.selectedChoice @property def selectedChoice(self): if self.choiceIndex >= len(self.choices) or self.choiceIndex < 0: return "" return self.choices[self.choiceIndex] @selectedChoice.setter def selectedChoice(self, val): idx = self.choices.index(val) if idx != -1: self.choiceIndex = idx @property def choices(self): return self._choices @choices.setter def choices(self, ch): self._choices = ch self.menu = Menu("", ((name, "pickMenu") for name in self._choices), self.scrolling, self.scroll_items) def CheckBoxLabel(title, *args, **kw): tooltipText = kw.pop('tooltipText', None) cb = CheckBox(*args, **kw) lab = Label(title, fg_color=cb.fg_color) lab.mouse_down = cb.mouse_down if tooltipText: cb.tooltipText = tooltipText lab.tooltipText = tooltipText class CBRow(Row): margin = 0 @property def value(self): return self.checkbox.value @value.setter def value(self, val): self.checkbox.value = val row = CBRow((lab, cb)) row.checkbox = cb return row from albow import FloatField, IntField def FloatInputRow(title, *args, **kw): return Row((Label(title, tooltipText=kw.get('tooltipText')), FloatField(*args, **kw))) def IntInputRow(title, *args, **kw): return Row((Label(title, tooltipText=kw.get('tooltipText')), IntField(*args, **kw))) from albow.dialogs import Dialog from datetime import timedelta def setWindowCaption(prefix): caption = display.get_caption()[0] class ctx: def __enter__(self): display.set_caption(prefix + caption) def __exit__(self, *args): display.set_caption(caption) return ctx() def showProgress(progressText, progressIterator, cancel=False): """Show the progress for a long-running synchronous operation. progressIterator should be a generator-like object that can return either None, for an indeterminate indicator, A float value between 0.0 and 1.0 for a determinate indicator, A string, to update the progress info label or a tuple of (float value, string) to set the progress and update the label""" class ProgressWidget(Dialog): progressFraction = 0.0 firstDraw = False def draw(self, surface): Widget.draw(self, surface) frameStart = datetime.now() frameInterval = timedelta(0, 1, 0) / 2 amount = None try: while datetime.now() < frameStart + frameInterval: amount = progressIterator.next() if self.firstDraw is False: self.firstDraw = True break except StopIteration: self.dismiss() infoText = "" if amount is not None: if isinstance(amount, tuple): if len(amount) > 2: infoText = ": " + amount[2] amount, max = amount[:2] else: max = amount maxwidth = (self.width - self.margin * 2) if amount is None: self.progressBar.width = maxwidth self.progressBar.bg_color = (255, 255, 25, 255) elif isinstance(amount, basestring): self.statusText = amount else: self.progressAmount = amount if isinstance(amount, (int, float)): self.progressFraction = float(amount) / (float(max) or 1) self.progressBar.width = maxwidth * self.progressFraction self.statusText = str("{0} / {1}".format(amount, max)) else: self.statusText = str(amount) if infoText: self.statusText += infoText @property def estimateText(self): delta = ((datetime.now() - self.startTime)) progressPercent = (int(self.progressFraction * 10000)) left = delta * (10000 - progressPercent) / (progressPercent or 1) return "Time left: {0}".format(left) def cancel(self): if cancel: self.dismiss(False) def idleevent(self, evt): self.invalidate() widget = ProgressWidget() widget.progressText = progressText widget.statusText = "" widget.progressAmount = 0.0 progressLabel = ValueDisplay(ref=AttrRef(widget, 'progressText'), width=550) statusLabel = ValueDisplay(ref=AttrRef(widget, 'statusText'), width=550) estimateLabel = ValueDisplay(ref=AttrRef(widget, 'estimateText'), width=550) progressBar = Widget(size=(550, 20), bg_color=(150, 150, 150, 255)) widget.progressBar = progressBar col = (progressLabel, statusLabel, estimateLabel, progressBar) if cancel: cancelButton = Button("Cancel", action=widget.cancel, fg_color=(255, 0, 0, 255)) col += (Column((cancelButton,), align="r"),) widget.add(Column(col)) widget.shrink_wrap() widget.startTime = datetime.now() if widget.present(): return widget.progressAmount else: return "Canceled" from glutils import DisplayList import functools