This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
mcedit/mceutils.py
2013-10-06 16:34:17 -10:00

614 lines
17 KiB
Python

"""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