Permalink
Fetching contributors…
Cannot retrieve contributors at this time
3783 lines (3023 sloc) 131 KB
"""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."""
import sys
from compass import CompassOverlay
from editortools.thumbview import ThumbView
from pymclevel.infiniteworld import SessionLockLost
"""
leveleditor.py
Viewport objects for Camera and Chunk views, which respond to some keyboard and
mouse input. LevelEditor object responds to some other keyboard and mouse
input, plus handles the undo stack and implements tile entity editors for
chests, signs, and more. Toolbar object which holds instances of EditorTool
imported from editortools/
"""
import gc
import os
import math
import csv
import copy
import time
import numpy
import config
import frustum
import logging
import glutils
import release
import mceutils
import platform
import functools
import editortools
import itertools
import mcplatform
import pymclevel
import renderer
from math import isnan
from os.path import dirname, isdir
from datetime import datetime, timedelta
from collections import defaultdict, deque
from OpenGL import GL
from OpenGL import GLU
from albow import alert, ask, AttrRef, Button, Column, get_font, Grid, input_text, IntField, Menu, root, Row, TableColumn, TableView, TextField, TimeField, Widget, CheckBox
from albow.controls import Label, SmallValueDisplay, ValueDisplay
from albow.dialogs import Dialog, QuickDialog, wrapped_label
from albow.openglwidgets import GLOrtho, GLViewport
from pygame import display, event, key, KMOD_ALT, KMOD_CTRL, KMOD_LALT, KMOD_META, KMOD_RALT, KMOD_SHIFT, mouse, MOUSEMOTION
from depths import DepthOffset
from editortools.operation import Operation
from editortools.chunk import GeneratorPanel
from glbackground import GLBackground, Panel
from glutils import gl, Texture
from mcplatform import askSaveFile
from pymclevel.minecraft_server import alphanum_key #?????
from renderer import MCRenderer
# Label = GLLabel
Settings = config.Settings("Settings")
Settings.flyMode = Settings("Fly Mode", False)
Settings.enableMouseLag = Settings("Enable Mouse Lag", False)
Settings.longDistanceMode = Settings("Long Distance Mode", False)
Settings.shouldResizeAlert = Settings("Window Size Alert", True)
Settings.closeMinecraftWarning = Settings("Close Minecraft Warning", True)
Settings.skin = Settings("MCEdit Skin", "[Current]")
Settings.fov = Settings("Field of View", 70.0)
Settings.spaceHeight = Settings("Space Height", 64)
Settings.blockBuffer = Settings("Block Buffer", 256 * 1048576)
Settings.reportCrashes = Settings("report crashes new", False)
Settings.reportCrashesAsked = Settings("report crashes asked", False)
Settings.viewDistance = Settings("View Distance", 8)
Settings.targetFPS = Settings("Target FPS", 30)
Settings.windowWidth = Settings("window width", 1024)
Settings.windowHeight = Settings("window height", 768)
Settings.windowX = Settings("window x", 0)
Settings.windowY = Settings("window y", 0)
Settings.windowShowCmd = Settings("window showcmd", 1)
Settings.setWindowPlacement = Settings("SetWindowPlacement", True)
Settings.showHiddenOres = Settings("show hidden ores", False)
Settings.fastLeaves = Settings("fast leaves", True)
Settings.roughGraphics = Settings("rough graphics", False)
Settings.showChunkRedraw = Settings("show chunk redraw", True)
Settings.drawSky = Settings("draw sky", True)
Settings.drawFog = Settings("draw fog", True)
Settings.showCeiling = Settings("show ceiling", True)
Settings.drawEntities = Settings("draw entities", True)
Settings.drawMonsters = Settings("draw monsters", True)
Settings.drawItems = Settings("draw items", True)
Settings.drawTileEntities = Settings("draw tile entities", True)
Settings.drawTileTicks = Settings("draw tile ticks", False)
Settings.drawUnpopulatedChunks = Settings("draw unpopulated chunks", True)
Settings.vertexBufferLimit = Settings("vertex buffer limit", 384)
Settings.vsync = Settings("vertical sync", 0)
Settings.visibilityCheck = Settings("visibility check", False)
Settings.viewMode = Settings("View Mode", "Camera")
Settings.undoLimit = Settings("Undo Limit", 20)
ControlSettings = config.Settings("Controls")
ControlSettings.mouseSpeed = ControlSettings("mouse speed", 5.0)
ControlSettings.cameraAccel = ControlSettings("camera acceleration", 125.0)
ControlSettings.cameraDrag = ControlSettings("camera drag", 100.0)
ControlSettings.cameraMaxSpeed = ControlSettings("camera maximum speed", 60.0)
ControlSettings.cameraBrakingSpeed = ControlSettings("camera braking speed", 8.0)
ControlSettings.invertMousePitch = ControlSettings("invert mouse pitch", False)
ControlSettings.autobrake = ControlSettings("autobrake", True)
ControlSettings.swapAxes = ControlSettings("swap axes looking down", False)
arch = platform.architecture()[0]
def remapMouseButton(button):
buttons = [0, 1, 3, 2, 4, 5] # mouse2 is right button, mouse3 is middle
if button < len(buttons):
return buttons[button]
return button
class ControlPanel(Panel):
@classmethod
def getHeader(cls):
header = Label("MCEdit {0} ({1})".format(release.release, arch), font=get_font(18, "VeraBd.ttf"))
return header
def __init__(self, editor):
Panel.__init__(self)
self.editor = editor
self.bg_color = (0, 0, 0, 0.8)
header = self.getHeader()
keysColumn = [Label("")]
buttonsColumn = [header]
cmd = mcplatform.cmd_name
hotkeys = ([(cmd + "-N", "Create New World", editor.mcedit.createNewWorld),
(cmd + "-O", "Open World...", editor.askOpenFile),
(cmd + "-L", "Load World...", editor.askLoadWorld),
(cmd + "-S", "Save", editor.saveFile),
(cmd + "-R", "Reload", editor.reload),
(cmd + "-W", "Close", editor.closeEditor),
("", "", lambda: None),
(cmd + "G", "Goto", editor.showGotoPanel),
(cmd + "-I", "World Info", editor.showWorldInfo),
(cmd + "-Z", "Undo", editor.undo),
(cmd + "-A", "Select All", editor.selectAll),
(cmd + "-D", "Deselect", editor.deselect),
(cmd + "-F", AttrRef(editor, 'viewDistanceLabelText'), editor.swapViewDistance),
("Alt-F4", "Quit", editor.quit),
])
if cmd == "Cmd":
hotkeys[-1] = ("Cmd-Q", hotkeys[-1][1], hotkeys[-1][2])
buttons = mceutils.HotkeyColumn(hotkeys, keysColumn, buttonsColumn)
sideColumn = editor.mcedit.makeSideColumn()
self.add(Row([buttons, sideColumn]))
self.shrink_wrap()
def key_down(self, evt):
if key.name(evt.key) == 'escape':
self.dismiss()
else:
self.editor.key_down(evt)
def mouse_down(self, e):
if e not in self:
self.dismiss()
def unproject(x, y, z):
try:
return GLU.gluUnProject(x, y, z)
except ValueError: # projection failed
return 0, 0, 0
def DebugDisplay(obj, *attrs):
col = []
for attr in attrs:
def _get(attr):
return lambda: str(getattr(obj, attr))
col.append(Row((Label(attr + " = "), ValueDisplay(width=600, get_value=_get(attr)))))
col = Column(col, align="l")
b = GLBackground()
b.add(col)
b.shrink_wrap()
return b
class CameraViewport(GLViewport):
anchor = "tlbr"
oldMousePosition = None
def __init__(self, editor):
self.editor = editor
rect = editor.mcedit.rect
GLViewport.__init__(self, rect)
near = 0.5
far = 4000.0
self.near = near
self.far = far
self.brake = False
self.lastTick = datetime.now()
# self.nearheight = near * tang
self.cameraPosition = (16., 45., 16.)
self.velocity = [0., 0., 0.]
self.yaw = -45. # degrees
self._pitch = 0.1
self.cameraVector = self._cameraVector()
# A state machine to dodge an apparent bug in pygame that generates erroneous mouse move events
# 0 = bad event already happened
# 1 = app just started or regained focus since last bad event
# 2 = mouse cursor was hidden after state 1, next event will be bad
self.avoidMouseJumpBug = 1
Settings.drawSky.addObserver(self)
Settings.drawFog.addObserver(self)
Settings.showCeiling.addObserver(self)
ControlSettings.cameraAccel.addObserver(self, "accelFactor")
ControlSettings.cameraMaxSpeed.addObserver(self, "maxSpeed")
ControlSettings.cameraBrakingSpeed.addObserver(self, "brakeMaxSpeed")
ControlSettings.invertMousePitch.addObserver(self)
ControlSettings.autobrake.addObserver(self)
ControlSettings.swapAxes.addObserver(self)
Settings.visibilityCheck.addObserver(self)
Settings.fov.addObserver(self, "fovSetting", callback=self.updateFov)
self.mouseVector = (0, 0, 0)
# self.add(DebugDisplay(self, "cameraPosition", "blockFaceUnderCursor", "mouseVector", "mouse3dPoint"))
@property
def pitch(self):
return self._pitch
@pitch.setter
def pitch(self, val):
self._pitch = min(89.999, max(-89.999, val))
def updateFov(self, val=None):
hfov = self.fovSetting
fov = numpy.degrees(2.0 * numpy.arctan(self.size[0] / self.size[1] * numpy.tan(numpy.radians(hfov) * 0.5)))
self.fov = fov
self.tang = numpy.tan(numpy.radians(fov))
def stopMoving(self):
self.velocity = [0, 0, 0]
def brakeOn(self):
self.brake = True
def brakeOff(self):
self.brake = False
tickInterval = 1000 / 30
oldPosition = (0, 0, 0)
flyMode = Settings.flyMode.configProperty()
def tickCamera(self, frameStartTime, inputs, inSpace):
if (frameStartTime - self.lastTick).microseconds > self.tickInterval * 1000:
timeDelta = frameStartTime - self.lastTick
self.lastTick = frameStartTime
else:
return
timeDelta = float(timeDelta.microseconds) / 1000000.
timeDelta = min(timeDelta, 0.125) # 8fps lower limit!
drag = ControlSettings.cameraDrag.get()
accel_factor = drag + ControlSettings.cameraAccel.get()
# if we're in space, move faster
drag_epsilon = 10.0 * timeDelta
max_speed = self.maxSpeed
if self.brake:
max_speed = self.brakeMaxSpeed
if inSpace:
accel_factor *= 3.0
max_speed *= 3.0
pi = self.editor.cameraPanKeys
mouseSpeed = ControlSettings.mouseSpeed.get()
self.yaw += pi[0] * mouseSpeed
self.pitch += pi[1] * mouseSpeed
alignMovementToAxes = key.get_mods() & KMOD_SHIFT
if self.flyMode:
(dx, dy, dz) = self._anglesToVector(self.yaw, 0)
elif self.swapAxesLookingDown or alignMovementToAxes:
p = self.pitch
if p > 80 or alignMovementToAxes:
p = 0
(dx, dy, dz) = self._anglesToVector(self.yaw, p)
else:
(dx, dy, dz) = self._cameraVector()
velocity = self.velocity # xxx learn to use matrix/vector libs
i = inputs
yaw = numpy.radians(self.yaw)
cosyaw = -numpy.cos(yaw)
sinyaw = numpy.sin(yaw)
if alignMovementToAxes:
cosyaw = int(cosyaw * 1.4)
sinyaw = int(sinyaw * 1.4)
dx = int(dx * 1.4)
dy = int(dy * 1.6)
dz = int(dz * 1.4)
directedInputs = mceutils.normalize((
i[0] * cosyaw + i[2] * dx,
i[1] + i[2] * dy,
i[2] * dz - i[0] * sinyaw,
))
# give the camera an impulse according to the state of the inputs and in the direction of the camera
cameraAccel = map(lambda x: x * accel_factor * timeDelta, directedInputs)
# cameraImpulse = map(lambda x: x*impulse_factor, directedInputs)
newVelocity = map(lambda a, b: a + b, velocity, cameraAccel)
velocityDir, speed = mceutils.normalize_size(newVelocity)
# apply drag
if speed:
if self.autobrake and not any(inputs):
speed = 0.15 * speed
else:
sign = speed / abs(speed)
speed = abs(speed)
speed = speed - (drag * timeDelta)
if speed < 0.0:
speed = 0.0
speed *= sign
speed = max(-max_speed, min(max_speed, speed))
if abs(speed) < drag_epsilon:
speed = 0
velocity = map(lambda a: a * speed, velocityDir)
# velocity = map(lambda p,d: p + d, velocity, cameraImpulse)
d = map(lambda a, b: abs(a - b), self.cameraPosition, self.oldPosition)
if d[0] + d[2] > 32.0:
self.oldPosition = self.cameraPosition
self.updateFloorQuad()
self.cameraPosition = map(lambda p, d: p + d * timeDelta, self.cameraPosition, velocity)
if self.cameraPosition[1] > 3800.:
self.cameraPosition[1] = 3800.
if self.cameraPosition[1] < -1000.:
self.cameraPosition[1] = -1000.
self.velocity = velocity
self.cameraVector = self._cameraVector()
self.editor.renderer.position = self.cameraPosition
if self.editor.currentTool.previewRenderer:
self.editor.currentTool.previewRenderer.position = self.cameraPosition
def setModelview(self):
pos = self.cameraPosition
look = numpy.array(self.cameraPosition)
look += self.cameraVector
up = (0, 1, 0)
GLU.gluLookAt(pos[0], pos[1], pos[2],
look[0], look[1], look[2],
up[0], up[1], up[2])
def _cameraVector(self):
return self._anglesToVector(self.yaw, self.pitch)
def _anglesToVector(self, yaw, pitch):
def nanzero(x):
if isnan(x):
return 0
else:
return x
dx = -math.sin(math.radians(yaw)) * math.cos(math.radians(pitch))
dy = -math.sin(math.radians(pitch))
dz = math.cos(math.radians(yaw)) * math.cos(math.radians(pitch))
return map(nanzero, [dx, dy, dz])
def updateMouseVector(self):
self.mouseVector = self._mouseVector()
def _mouseVector(self):
"""
returns a vector reflecting a ray cast from the camera
position to the mouse position on the near plane
"""
x, y = mouse.get_pos()
# if (x, y) not in self.rect:
# return (0, 0, 0); # xxx
y = self.get_root().height - y
point1 = unproject(x, y, 0.0)
point2 = unproject(x, y, 1.0)
v = numpy.array(point2) - point1
v = mceutils.normalize(v)
return v
def _blockUnderCursor(self, center=False):
"""
returns a point in 3d space that was determined by
reading the depth buffer value
"""
try:
GL.glReadBuffer(GL.GL_BACK)
except Exception:
logging.exception('Exception during glReadBuffer')
ws = self.get_root().size
if center:
x, y = ws
x //= 2
y //= 2
else:
x, y = mouse.get_pos()
if (x < 0 or y < 0 or x >= ws[0] or
y >= ws[1]):
return 0, 0, 0
y = ws[1] - y
try:
pixel = GL.glReadPixels(x, y, 1, 1, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT)
newpoint = unproject(x, y, pixel[0])
except Exception:
return 0, 0, 0
return newpoint
def updateBlockFaceUnderCursor(self):
focusPair = None
if not self.enableMouseLag or self.editor.frames & 1:
self.updateMouseVector()
if self.editor.mouseEntered:
if not self.mouseMovesCamera:
mouse3dPoint = self._blockUnderCursor()
focusPair = self.findBlockFaceUnderCursor(mouse3dPoint)
elif self.editor.longDistanceMode:
mouse3dPoint = self._blockUnderCursor(True)
focusPair = self.findBlockFaceUnderCursor(mouse3dPoint)
# otherwise, find the block at a controllable distance in front of the camera
if focusPair is None:
focusPair = (self.getCameraPoint(), (0, 0, 0))
self.blockFaceUnderCursor = focusPair
def findBlockFaceUnderCursor(self, projectedPoint):
"""Returns a (pos, Face) pair or None if one couldn't be found"""
d = [0, 0, 0]
try:
intProjectedPoint = map(int, map(numpy.floor, projectedPoint))
except ValueError:
return None # catch NaNs
intProjectedPoint[1] = max(0, intProjectedPoint[1])
# find out which face is under the cursor. xxx do it more precisely
faceVector = ((projectedPoint[0] - (intProjectedPoint[0] + 0.5)),
(projectedPoint[1] - (intProjectedPoint[1] + 0.5)),
(projectedPoint[2] - (intProjectedPoint[2] + 0.5))
)
av = map(abs, faceVector)
i = av.index(max(av))
delta = faceVector[i]
if delta < 0:
d[i] = -1
else:
d[i] = 1
potentialOffsets = []
try:
block = self.editor.level.blockAt(*intProjectedPoint)
except (EnvironmentError, pymclevel.ChunkNotPresent):
return intProjectedPoint, d
if block == pymclevel.alphaMaterials.SnowLayer.ID:
potentialOffsets.append((0, 1, 0))
else:
# discard any faces that aren't likely to be exposed
for face, offsets in pymclevel.faceDirections:
point = map(lambda a, b: a + b, intProjectedPoint, offsets)
try:
neighborBlock = self.editor.level.blockAt(*point)
if block != neighborBlock:
potentialOffsets.append(offsets)
except (EnvironmentError, pymclevel.ChunkNotPresent):
pass
# check each component of the face vector to see if that face is exposed
if tuple(d) not in potentialOffsets:
av[i] = 0
i = av.index(max(av))
d = [0, 0, 0]
delta = faceVector[i]
if delta < 0:
d[i] = -1
else:
d[i] = 1
if tuple(d) not in potentialOffsets:
av[i] = 0
i = av.index(max(av))
d = [0, 0, 0]
delta = faceVector[i]
if delta < 0:
d[i] = -1
else:
d[i] = 1
if tuple(d) not in potentialOffsets:
if len(potentialOffsets):
d = potentialOffsets[0]
else:
# use the top face as a fallback
d = [0, 1, 0]
return intProjectedPoint, d
@property
def ratio(self):
return self.width / float(self.height)
startingMousePosition = None
def mouseLookOn(self):
root.get_root().capture_mouse(self)
self.focus_switch = None
self.startingMousePosition = mouse.get_pos()
if self.avoidMouseJumpBug == 1:
self.avoidMouseJumpBug = 2
def mouseLookOff(self):
root.get_root().capture_mouse(None)
if self.startingMousePosition:
mouse.set_pos(*self.startingMousePosition)
self.startingMousePosition = None
@property
def mouseMovesCamera(self):
return root.get_root().captured_widget is not None
def toggleMouseLook(self):
if not self.mouseMovesCamera:
self.mouseLookOn()
else:
self.mouseLookOff()
mobs = pymclevel.Entity.monsters + ["[Custom]"]
@mceutils.alertException
def editMonsterSpawner(self, point):
mobs = self.mobs
tileEntity = self.editor.level.tileEntityAt(*point)
if not tileEntity:
tileEntity = pymclevel.TAG_Compound()
tileEntity["id"] = pymclevel.TAG_String("MobSpawner")
tileEntity["x"] = pymclevel.TAG_Int(point[0])
tileEntity["y"] = pymclevel.TAG_Int(point[1])
tileEntity["z"] = pymclevel.TAG_Int(point[2])
tileEntity["Delay"] = pymclevel.TAG_Short(120)
tileEntity["EntityId"] = pymclevel.TAG_String(mobs[0])
self.editor.level.addTileEntity(tileEntity)
self.editor.addUnsavedEdit()
panel = Dialog()
def addMob(id):
if id not in mobs:
mobs.insert(0, id)
mobTable.selectedIndex = 0
def selectTableRow(i, evt):
if mobs[i] == "[Custom]":
id = input_text("Type in an EntityID for this spawner. Invalid IDs may crash Minecraft.", 150)
if id:
addMob(id)
else:
return
mobTable.selectedIndex = mobs.index(id)
else:
mobTable.selectedIndex = i
if evt.num_clicks == 2:
panel.dismiss()
mobTable = TableView(columns=(
TableColumn("", 200),
)
)
mobTable.num_rows = lambda: len(mobs)
mobTable.row_data = lambda i: (mobs[i],)
mobTable.row_is_selected = lambda x: x == mobTable.selectedIndex
mobTable.click_row = selectTableRow
mobTable.selectedIndex = 0
def selectedMob():
return mobs[mobTable.selectedIndex]
id = tileEntity["EntityId"].value
addMob(id)
mobTable.selectedIndex = mobs.index(id)
choiceCol = Column((ValueDisplay(width=200, get_value=lambda: selectedMob() + " spawner"), mobTable))
okButton = Button("OK", action=panel.dismiss)
panel.add(Column((choiceCol, okButton)))
panel.shrink_wrap()
panel.present()
tileEntity["EntityId"] = pymclevel.TAG_String(selectedMob())
@mceutils.alertException
def editSign(self, point):
block = self.editor.level.blockAt(*point)
tileEntity = self.editor.level.tileEntityAt(*point)
linekeys = ["Text" + str(i) for i in range(1, 5)]
if not tileEntity:
tileEntity = pymclevel.TAG_Compound()
tileEntity["id"] = pymclevel.TAG_String("Sign")
tileEntity["x"] = pymclevel.TAG_Int(point[0])
tileEntity["y"] = pymclevel.TAG_Int(point[1])
tileEntity["z"] = pymclevel.TAG_Int(point[2])
for l in linekeys:
tileEntity[l] = pymclevel.TAG_String("")
self.editor.level.addTileEntity(tileEntity)
panel = Dialog()
lineFields = [TextField(width=150) for l in linekeys]
for l, f in zip(linekeys, lineFields):
f.value = tileEntity[l].value
colors = [
"Black",
"Blue",
"Green",
"Cyan",
"Red",
"Purple",
"Yellow",
"Light Gray",
"Dark Gray",
"Light Blue",
"Bright Green",
"Bright Blue",
"Bright Red",
"Bright Purple",
"Bright Yellow",
"White",
]
def menu_picked(index):
c = u'\xa7' + hex(index)[-1]
currentField = panel.focus_switch.focus_switch
currentField.text += c # xxx view hierarchy
currentField.insertion_point = len(currentField.text)
colorMenu = mceutils.MenuButton("Color Code...", colors, menu_picked=menu_picked)
column = [Label("Edit Sign")] + lineFields + [colorMenu, Button("OK", action=panel.dismiss)]
panel.add(Column(column))
panel.shrink_wrap()
if panel.present():
self.editor.addUnsavedEdit()
for l, f in zip(linekeys, lineFields):
tileEntity[l].value = f.value[:15]
@mceutils.alertException
def editContainer(self, point, containerID):
tileEntityTag = self.editor.level.tileEntityAt(*point)
if tileEntityTag is None:
tileEntityTag = pymclevel.TileEntity.Create(containerID)
pymclevel.TileEntity.setpos(tileEntityTag, point)
self.editor.level.addTileEntity(tileEntityTag)
if tileEntityTag["id"].value != containerID:
return
backupEntityTag = copy.deepcopy(tileEntityTag)
def itemProp(key):
# xxx do validation here
def getter(self):
if 0 == len(tileEntityTag["Items"]):
return "N/A"
return tileEntityTag["Items"][self.selectedItemIndex][key].value
def setter(self, val):
if 0 == len(tileEntityTag["Items"]):
return
self.dirty = True
tileEntityTag["Items"][self.selectedItemIndex][key].value = val
return property(getter, setter)
class ChestWidget(Widget):
dirty = False
Slot = itemProp("Slot")
id = itemProp("id")
Damage = itemProp("Damage")
Count = itemProp("Count")
itemLimit = pymclevel.TileEntity.maxItems.get(containerID, 255)
def slotFormat(slot):
slotNames = pymclevel.TileEntity.slotNames.get(containerID)
if slotNames:
return slotNames.get(slot, slot)
return slot
chestWidget = ChestWidget()
chestItemTable = TableView(columns=[
TableColumn("Slot", 80, "l", fmt=slotFormat),
TableColumn("ID", 50, "l"),
TableColumn("DMG", 50, "l"),
TableColumn("Count", 65, "l"),
TableColumn("Name", 200, "l"),
])
def itemName(id, damage):
try:
return pymclevel.items.items.findItem(id, damage).name
except pymclevel.items.ItemNotFound:
return "Unknown Item"
def getRowData(i):
item = tileEntityTag["Items"][i]
slot, id, damage, count = item["Slot"].value, item["id"].value, item["Damage"].value, item["Count"].value
return slot, id, damage, count, itemName(id, damage)
chestWidget.selectedItemIndex = 0
def selectTableRow(i, evt):
chestWidget.selectedItemIndex = i
chestItemTable.num_rows = lambda: len(tileEntityTag["Items"])
chestItemTable.row_data = getRowData
chestItemTable.row_is_selected = lambda x: x == chestWidget.selectedItemIndex
chestItemTable.click_row = selectTableRow
fieldRow = (
# mceutils.IntInputRow("Slot: ", ref=AttrRef(chestWidget, 'Slot'), min= -128, max=127),
mceutils.IntInputRow("ID: ", ref=AttrRef(chestWidget, 'id'), min=0, max=32767),
mceutils.IntInputRow("DMG: ", ref=AttrRef(chestWidget, 'Damage'), min=-32768, max=32767),
mceutils.IntInputRow("Count: ", ref=AttrRef(chestWidget, 'Count'), min=-128, max=127),
)
def deleteFromWorld():
i = chestWidget.selectedItemIndex
item = tileEntityTag["Items"][i]
id = item["id"].value
Damage = item["Damage"].value
deleteSameDamage = mceutils.CheckBoxLabel("Only delete items with the same damage value")
deleteBlocksToo = mceutils.CheckBoxLabel("Also delete blocks placed in the world")
if id not in (8, 9, 10, 11): # fluid blocks
deleteBlocksToo.value = True
w = wrapped_label("WARNING: You are about to modify the entire world. This cannot be undone. Really delete all copies of this item from all land, chests, furnaces, dispensers, dropped items, item-containing tiles, and player inventories in this world?", 60)
col = (w, deleteSameDamage)
if id < 256:
col += (deleteBlocksToo,)
d = Dialog(Column(col), ["OK", "Cancel"])
if d.present() == "OK":
def deleteItemsIter():
i = 0
if deleteSameDamage.value:
def matches(t):
return t["id"].value == id and t["Damage"].value == Damage
else:
def matches(t):
return t["id"].value == id
def matches_itementity(e):
if e["id"].value != "Item":
return False
if "Item" not in e:
return False
t = e["Item"]
return matches(t)
for player in self.editor.level.players:
tag = self.editor.level.getPlayerTag(player)
l = len(tag["Inventory"])
tag["Inventory"].value = [t for t in tag["Inventory"].value if not matches(t)]
for chunk in self.editor.level.getChunks():
if id < 256 and deleteBlocksToo.value:
matchingBlocks = chunk.Blocks == id
if deleteSameDamage.value:
matchingBlocks &= chunk.Data == Damage
if any(matchingBlocks):
chunk.Blocks[matchingBlocks] = 0
chunk.Data[matchingBlocks] = 0
chunk.chunkChanged()
self.editor.invalidateChunks([chunk.chunkPosition])
for te in chunk.TileEntities:
if "Items" in te:
l = len(te["Items"])
te["Items"].value = [t for t in te["Items"].value if not matches(t)]
if l != len(te["Items"]):
chunk.dirty = True
entities = [e for e in chunk.Entities if matches_itementity(e)]
if len(entities) != len(chunk.Entities):
chunk.Entities.value = entities
chunk.dirty = True
yield (i, self.editor.level.chunkCount)
i += 1
progressInfo = "Deleting the item {0} from the entire world ({1} chunks)".format(itemName(chestWidget.id, 0), self.editor.level.chunkCount)
mceutils.showProgress(progressInfo, deleteItemsIter(), cancel=True)
self.editor.addUnsavedEdit()
chestWidget.selectedItemIndex = min(chestWidget.selectedItemIndex, len(tileEntityTag["Items"]) - 1)
def deleteItem():
i = chestWidget.selectedItemIndex
item = tileEntityTag["Items"][i]
tileEntityTag["Items"].value = [t for t in tileEntityTag["Items"].value if t is not item]
chestWidget.selectedItemIndex = min(chestWidget.selectedItemIndex, len(tileEntityTag["Items"]) - 1)
def deleteEnable():
return len(tileEntityTag["Items"]) and chestWidget.selectedItemIndex != -1
def addEnable():
return len(tileEntityTag["Items"]) < chestWidget.itemLimit
def addItem():
slot = 0
for item in tileEntityTag["Items"]:
if slot == item["Slot"].value:
slot += 1
if slot >= chestWidget.itemLimit:
return
item = pymclevel.TAG_Compound()
item["id"] = pymclevel.TAG_Short(0)
item["Damage"] = pymclevel.TAG_Short(0)
item["Slot"] = pymclevel.TAG_Byte(slot)
item["Count"] = pymclevel.TAG_Byte(0)
tileEntityTag["Items"].append(item)
addItemButton = Button("Add Item", action=addItem, enable=addEnable)
deleteItemButton = Button("Delete This Item", action=deleteItem, enable=deleteEnable)
deleteFromWorldButton = Button("Delete Item ID From Entire World", action=deleteFromWorld, enable=deleteEnable)
deleteCol = Column((addItemButton, deleteItemButton, deleteFromWorldButton), align="l")
fieldRow = Row(fieldRow)
col = Column((chestItemTable, fieldRow, deleteCol))
chestWidget.add(col)
chestWidget.shrink_wrap()
Dialog(client=chestWidget, responses=["Done"]).present()
level = self.editor.level
class ChestEditOperation(Operation):
def perform(self, recordUndo=True):
level.addTileEntity(tileEntityTag)
def undo(self):
level.addTileEntity(backupEntityTag)
return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntityTag), (1, 1, 1))
if chestWidget.dirty:
op = ChestEditOperation(self.editor, self.editor.level)
op.perform()
self.editor.addOperation(op)
self.editor.addUnsavedEdit()
rightMouseDragStart = None
def rightClickDown(self, evt):
self.rightMouseDragStart = datetime.now()
self.toggleMouseLook()
def rightClickUp(self, evt):
x, y = evt.pos
if self.rightMouseDragStart is None:
return
td = datetime.now() - self.rightMouseDragStart
# except AttributeError:
# return
# print "RightClickUp: ", td
if td.seconds > 0 or td.microseconds > 280000:
self.mouseLookOff()
def leftClickDown(self, evt):
self.editor.toolMouseDown(evt, self.blockFaceUnderCursor)
if evt.num_clicks == 2:
def distance2(p1, p2):
return numpy.sum(map(lambda a, b: (a - b) ** 2, p1, p2))
point, face = self.blockFaceUnderCursor
if point is not None:
point = map(lambda x: int(numpy.floor(x)), point)
if self.editor.currentTool is self.editor.selectionTool:
try:
block = self.editor.level.blockAt(*point)
if distance2(point, self.cameraPosition) > 4:
blockEditors = {
pymclevel.alphaMaterials.MonsterSpawner.ID: self.editMonsterSpawner,
pymclevel.alphaMaterials.Sign.ID: self.editSign,
pymclevel.alphaMaterials.WallSign.ID: self.editSign,
}
edit = blockEditors.get(block)
if edit:
self.editor.endSelection()
edit(point)
else:
# detect "container" tiles
te = self.editor.level.tileEntityAt(*point)
if te and "Items" in te and "id" in te:
self.editor.endSelection()
self.editContainer(point, te["id"].value)
except (EnvironmentError, pymclevel.ChunkNotPresent):
pass
def leftClickUp(self, evt):
self.editor.toolMouseUp(evt, self.blockFaceUnderCursor)
# --- Event handlers ---
def mouse_down(self, evt):
button = remapMouseButton(evt.button)
logging.debug("Mouse down %d @ %s", button, evt.pos)
if button == 1:
if sys.platform == "darwin" and evt.ctrl:
self.rightClickDown(evt)
else:
self.leftClickDown(evt)
elif button == 2:
self.rightClickDown(evt)
else:
evt.dict['keyname'] = "mouse{0}".format(button)
self.editor.key_down(evt)
self.editor.focus_on(None)
# self.focus_switch = None
def mouse_up(self, evt):
button = remapMouseButton(evt.button)
logging.debug("Mouse up %d @ %s", button, evt.pos)
if button == 1:
if sys.platform == "darwin" and evt.ctrl:
self.rightClickUp(evt)
else:
self.leftClickUp(evt)
elif button == 2:
self.rightClickUp(evt)
else:
evt.dict['keyname'] = "mouse{0}".format(button)
self.editor.key_up(evt)
def mouse_drag(self, evt):
self.mouse_move(evt)
self.editor.mouse_drag(evt)
lastRendererUpdate = datetime.now()
def mouse_move(self, evt):
if self.avoidMouseJumpBug == 2:
self.avoidMouseJumpBug = 0
return
def sensitivityAdjust(d):
return d * ControlSettings.mouseSpeed.get() / 10.0
self.editor.mouseEntered = True
if self.mouseMovesCamera:
pitchAdjust = sensitivityAdjust(evt.rel[1])
if self.invertMousePitch:
pitchAdjust = -pitchAdjust
self.yaw += sensitivityAdjust(evt.rel[0])
self.pitch += pitchAdjust
if datetime.now() - self.lastRendererUpdate > timedelta(0, 0, 500000):
self.editor.renderer.loadNearbyChunks()
self.lastRendererUpdate = datetime.now()
# adjustLimit = 2
# self.oldMousePosition = (x, y)
# if (self.startingMousePosition[0] - x > adjustLimit or self.startingMousePosition[1] - y > adjustLimit or
# self.startingMousePosition[0] - x < -adjustLimit or self.startingMousePosition[1] - y < -adjustLimit):
# mouse.set_pos(*self.startingMousePosition)
# event.get(MOUSEMOTION)
# self.oldMousePosition = (self.startingMousePosition)
def activeevent(self, evt):
if evt.state & 0x2 and evt.gain != 0:
self.avoidMouseJumpBug = 1
@property
def tooltipText(self):
return self.editor.currentTool.worldTooltipText
floorQuad = numpy.array(((-4000.0, 0.0, -4000.0),
(-4000.0, 0.0, 4000.0),
(4000.0, 0.0, 4000.0),
(4000.0, 0.0, -4000.0),
), dtype='float32')
def updateFloorQuad(self):
floorQuad = ((-4000.0, 0.0, -4000.0),
(-4000.0, 0.0, 4000.0),
(4000.0, 0.0, 4000.0),
(4000.0, 0.0, -4000.0),
)
floorQuad = numpy.array(floorQuad, dtype='float32')
if self.editor.renderer.inSpace():
floorQuad *= 8.0
floorQuad += (self.cameraPosition[0], 0.0, self.cameraPosition[2])
self.floorQuad = floorQuad
self.floorQuadList.invalidate()
def drawFloorQuad(self):
self.floorQuadList.call(self._drawFloorQuad)
def _drawCeiling(self):
lines = []
minz = minx = -256
maxz = maxx = 256
for x in range(minx, maxx + 1, 16):
lines.append((x, 0, minz))
lines.append((x, 0, maxz))
for z in range(minz, maxz + 1, 16):
lines.append((minx, 0, z))
lines.append((maxx, 0, z))
GL.glColor(0.3, 0.7, 0.9)
GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array(lines, dtype='float32'))
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glDepthMask(False)
GL.glDrawArrays(GL.GL_LINES, 0, len(lines))
GL.glDisable(GL.GL_DEPTH_TEST)
GL.glDepthMask(True)
def drawCeiling(self):
GL.glMatrixMode(GL.GL_MODELVIEW)
# GL.glPushMatrix()
x, y, z = self.cameraPosition
x -= x % 16
z -= z % 16
y = self.editor.level.Height
GL.glTranslate(x, y, z)
self.ceilingList.call(self._drawCeiling)
GL.glTranslate(-x, -y, -z)
_floorQuadList = None
@property
def floorQuadList(self):
if not self._floorQuadList:
self._floorQuadList = glutils.DisplayList()
return self._floorQuadList
_ceilingList = None
@property
def ceilingList(self):
if not self._ceilingList:
self._ceilingList = glutils.DisplayList()
return self._ceilingList
@property
def floorColor(self):
if self.drawSky:
return 0.0, 0.0, 1.0, 0.3
else:
return 0.0, 1.0, 0.0, 0.15
# floorColor = (0.0, 0.0, 1.0, 0.1)
def _drawFloorQuad(self):
GL.glDepthMask(True)
GL.glPolygonOffset(DepthOffset.ChunkMarkers + 2, DepthOffset.ChunkMarkers + 2)
GL.glVertexPointer(3, GL.GL_FLOAT, 0, self.floorQuad)
GL.glColor(*self.floorColor)
with gl.glEnable(GL.GL_BLEND, GL.GL_DEPTH_TEST, GL.GL_POLYGON_OFFSET_FILL):
GL.glDrawArrays(GL.GL_QUADS, 0, 4)
@property
def drawSky(self):
return self._drawSky
@drawSky.setter
def drawSky(self, val):
self._drawSky = val
if self.skyList:
self.skyList.invalidate()
if self._floorQuadList:
self._floorQuadList.invalidate()
skyList = None
def drawSkyBackground(self):
if self.skyList is None:
self.skyList = glutils.DisplayList()
self.skyList.call(self._drawSkyBackground)
def _drawSkyBackground(self):
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glPushMatrix()
GL.glLoadIdentity()
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glPushMatrix()
GL.glLoadIdentity()
GL.glEnableClientState(GL.GL_COLOR_ARRAY)
quad = numpy.array([-1, -1, -1, 1, 1, 1, 1, -1], dtype='float32')
colors = numpy.array([0x48, 0x49, 0xBA, 0xff,
0x8a, 0xaf, 0xff, 0xff,
0x8a, 0xaf, 0xff, 0xff,
0x48, 0x49, 0xBA, 0xff, ], dtype='uint8')
alpha = 1.0
if alpha > 0.0:
if alpha < 1.0:
GL.glEnable(GL.GL_BLEND)
GL.glVertexPointer(2, GL.GL_FLOAT, 0, quad)
GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, colors)
GL.glDrawArrays(GL.GL_QUADS, 0, 4)
if alpha < 1.0:
GL.glDisable(GL.GL_BLEND)
GL.glDisableClientState(GL.GL_COLOR_ARRAY)
GL.glMatrixMode(GL.GL_PROJECTION)
GL.glPopMatrix()
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glPopMatrix()
enableMouseLag = Settings.enableMouseLag.configProperty()
@property
def drawFog(self):
return self._drawFog and not self.editor.renderer.inSpace()
@drawFog.setter
def drawFog(self, val):
self._drawFog = val
fogColor = numpy.array([0.6, 0.8, 1.0, 1.0], dtype='float32')
fogColorBlack = numpy.array([0.0, 0.0, 0.0, 1.0], dtype='float32')
def enableFog(self):
GL.glEnable(GL.GL_FOG)
if self.drawSky:
GL.glFogfv(GL.GL_FOG_COLOR, self.fogColor)
else:
GL.glFogfv(GL.GL_FOG_COLOR, self.fogColorBlack)
GL.glFogf(GL.GL_FOG_DENSITY, 0.002)
def disableFog(self):
GL.glDisable(GL.GL_FOG)
def getCameraPoint(self):
distance = self.editor.currentTool.cameraDistance
return [i for i in itertools.imap(lambda p, d: int(numpy.floor(p + d * distance)),
self.cameraPosition,
self.cameraVector)]
blockFaceUnderCursor = (0, 0, 0), (0, 0, 0)
viewingFrustum = None
def setup_projection(self):
distance = 1.0
if self.editor.renderer.inSpace():
distance = 8.0
GLU.gluPerspective(max(self.fov, 25.0), self.ratio, self.near * distance, self.far * distance)
def setup_modelview(self):
self.setModelview()
def gl_draw(self):
self.tickCamera(self.editor.frameStartTime, self.editor.cameraInputs, self.editor.renderer.inSpace())
self.render()
def render(self):
# if self.visibilityCheck:
if True:
self.viewingFrustum = frustum.Frustum.fromViewingMatrix()
else:
self.viewingFrustum = None
# self.editor.drawStars()
if self.drawSky:
self.drawSkyBackground()
if self.drawFog:
self.enableFog()
self.drawFloorQuad()
self.editor.renderer.viewingFrustum = self.viewingFrustum
self.editor.renderer.draw()
focusPair = None
if self.showCeiling and not self.editor.renderer.inSpace():
self.drawCeiling()
if self.editor.level:
try:
self.updateBlockFaceUnderCursor()
except (EnvironmentError, pymclevel.ChunkNotPresent) as e:
logging.debug("Updating cursor block: %s", e)
self.blockFaceUnderCursor = (None, None)
root.get_root().update_tooltip()
focusPair = self.blockFaceUnderCursor
(blockPosition, faceDirection) = focusPair
if None != blockPosition:
self.editor.updateInspectionString(blockPosition)
# for t in self.toolbar.tools:
if self.find_widget(mouse.get_pos()) == self:
ct = self.editor.currentTool
if ct:
ct.drawTerrainReticle()
ct.drawToolReticle()
else:
self.editor.drawWireCubeReticle()
for t in self.editor.toolbar.tools:
t.drawTerrainMarkers()
for t in self.editor.toolbar.tools:
t.drawToolMarkers()
if self.drawFog:
self.disableFog()
if self._compass is None:
self._compass = CompassOverlay()
self._compass.yawPitch = self.yaw, 0
with gl.glPushMatrix(GL.GL_PROJECTION):
GL.glLoadIdentity()
GL.glOrtho(0., 1., float(self.height) / self.width, 0, -200, 200)
self._compass.draw()
_compass = None
class ChunkViewport(CameraViewport):
defaultScale = 1.0 # pixels per block
def __init__(self, *a, **kw):
CameraViewport.__init__(self, *a, **kw)
def setup_projection(self):
w, h = (0.5 * s / self.defaultScale
for s in self.size)
minx, maxx = - w, w
miny, maxy = - h, h
minz, maxz = -4000, 4000
GL.glOrtho(minx, maxx, miny, maxy, minz, maxz)
def setup_modelview(self):
x, y, z = self.cameraPosition
GL.glRotate(90.0, 1.0, 0.0, 0.0)
GL.glTranslate(-x, 0, -z)
def zoom(self, f):
x, y, z = self.cameraPosition
mx, my, mz = self.blockFaceUnderCursor[0]
dx, dz = mx - x, mz - z
s = min(4.0, max(1 / 16., self.defaultScale / f))
if s != self.defaultScale:
self.defaultScale = s
f = 1.0 - f
self.cameraPosition = x + dx * f, self.editor.level.Height, z + dz * f
self.editor.renderer.loadNearbyChunks()
incrementFactor = 1.4
def zoomIn(self):
self.zoom(1.0 / self.incrementFactor)
def zoomOut(self):
self.zoom(self.incrementFactor)
def mouse_down(self, evt):
if evt.button == 4: # wheel up - zoom in
# if self.defaultScale == 4.0:
# self.editor.swapViewports()
# else:
self.zoomIn()
elif evt.button == 5: # wheel down - zoom out
self.zoomOut()
else:
super(ChunkViewport, self).mouse_down(evt)
def rightClickDown(self, evt):
pass
def rightClickUp(self, evt):
pass
def mouse_move(self, evt):
pass
@mceutils.alertException
def mouse_drag(self, evt):
if evt.buttons[2]:
x, y, z = self.cameraPosition
dx, dz = evt.rel
self.cameraPosition = (
x - dx / self.defaultScale,
y,
z - dz / self.defaultScale)
else:
super(ChunkViewport, self).mouse_drag(evt)
def render(self):
super(ChunkViewport, self).render()
@property
def tooltipText(self):
text = super(ChunkViewport, self).tooltipText
if text == "1 W x 1 L x 1 H":
return None
return text
def drawCeiling(self):
pass
# if self.defaultScale >= 0.5:
# return super(ChunkViewport, self).drawCeiling()
class LevelEditor(GLViewport):
anchor = "tlbr"
def __init__(self, mcedit):
self.mcedit = mcedit
rect = mcedit.rect
GLViewport.__init__(self, rect)
self.frames = 0
self.frameStartTime = datetime.now()
self.oldFrameStartTime = self.frameStartTime
self.dragInProgress = False
self.debug = 0
self.debugString = ""
self.perfSamples = 5
self.frameSamples = [timedelta(0, 0, 0)] * 5
self.unsavedEdits = 0
self.undoStack = []
self.copyStack = []
self.level = None
self.cameraInputs = [0., 0., 0.]
self.cameraPanKeys = [0., 0.]
self.cameraToolDistance = self.defaultCameraToolDistance
self.createRenderers()
self.sixteenBlockTex = self.genSixteenBlockTexture()
# self.Font = Font("Verdana, Arial", 18)
self.generateStars()
self.optionsBar = Widget()
mcEditButton = Button("MCEdit", action=self.showControls)
viewDistanceDown = Button("<", action=self.decreaseViewDistance)
viewDistanceUp = Button(">", action=self.increaseViewDistance)
viewDistanceReadout = ValueDisplay(width=40, ref=AttrRef(self.renderer, "viewDistance"))
chunksReadout = SmallValueDisplay(width=140,
get_value=lambda: "Chunks: %d" % len(self.renderer.chunkRenderers),
tooltipText="Number of chunks loaded into the renderer.")
fpsReadout = SmallValueDisplay(width=80,
get_value=lambda: "fps: %0.1f" % self.averageFPS,
tooltipText="Frames per second.")
cpsReadout = SmallValueDisplay(width=100,
get_value=lambda: "cps: %0.1f" % self.averageCPS,
tooltipText="Chunks per second.")
mbReadout = SmallValueDisplay(width=60,
get_value=lambda: "MBv: %0.1f" % (self.renderer.bufferUsage / 1000000.),
tooltipText="Memory used for vertexes")
def showViewOptions():
col = []
col.append(mceutils.CheckBoxLabel("Entities", fg_color=(0xff, 0x22, 0x22), ref=Settings.drawEntities.propertyRef()))
col.append(mceutils.CheckBoxLabel("Items", fg_color=(0x22, 0xff, 0x22), ref=Settings.drawItems.propertyRef()))
col.append(mceutils.CheckBoxLabel("TileEntities", fg_color=(0xff, 0xff, 0x22), ref=Settings.drawTileEntities.propertyRef()))
col.append(mceutils.CheckBoxLabel("TileTicks", ref=Settings.drawTileTicks.propertyRef()))
col.append(mceutils.CheckBoxLabel("Unpopulated Chunks", fg_color=renderer.TerrainPopulatedRenderer.color,
ref=Settings.drawUnpopulatedChunks.propertyRef()))
col.append(mceutils.CheckBoxLabel("Sky", ref=Settings.drawSky.propertyRef()))
col.append(mceutils.CheckBoxLabel("Fog", ref=Settings.drawFog.propertyRef()))
col.append(mceutils.CheckBoxLabel("Ceiling",
ref=Settings.showCeiling.propertyRef()))
col.append(mceutils.CheckBoxLabel("Hidden Ores",
ref=Settings.showHiddenOres.propertyRef()))
col.append(mceutils.CheckBoxLabel("Chunk Redraw", fg_color=(0xff, 0x99, 0x99),
ref=Settings.showChunkRedraw.propertyRef()))
col = Column(col, align="r")
d = QuickDialog()
d.add(col)
d.shrink_wrap()
d.topleft = viewButton.bottomleft
d.present(centered=False)
viewButton = Button("Show...", action=showViewOptions)
mbReadoutRow = Row((mbReadout, Label("")))
readoutGrid = Grid(((chunksReadout, fpsReadout), (mbReadoutRow, cpsReadout), ), 0, 0)
self.viewportButton = Button("Camera View", action=self.swapViewports,
tooltipText="Shortcut: TAB")
self.recordUndoButton = mceutils.CheckBoxLabel("Record Undo", ref=AttrRef(self, 'recordUndo'))
row = (mcEditButton, viewDistanceDown, Label("View Distance:"), viewDistanceReadout, viewDistanceUp,
readoutGrid, viewButton, self.viewportButton, self.recordUndoButton)
# row += (Button("CR Info", action=self.showChunkRendererInfo), )
row = Row(row)
self.add(row)
self.statusLabel = ValueDisplay(width=self.width, ref=AttrRef(self, "statusText"))
self.mainViewport = CameraViewport(self)
self.chunkViewport = ChunkViewport(self)
self.mainViewport.height -= row.height
self.mainViewport.height -= self.statusLabel.height
self.statusLabel.bottom = self.bottom
self.statusLabel.anchor = "blrh"
self.add(self.statusLabel)
self.viewportContainer = Widget(is_gl_container=True, anchor="tlbr")
self.viewportContainer.top = row.bottom
self.viewportContainer.size = self.mainViewport.size
self.add(self.viewportContainer)
Settings.viewMode.addObserver(self)
Settings.undoLimit.addObserver(self)
self.reloadToolbar()
self.currentTool = None
self.toolbar.selectTool(0)
self.controlPanel = ControlPanel(self)
self.controlPanel.topleft = mcEditButton.bottomleft
def __del__(self):
self.deleteAllCopiedSchematics()
_viewMode = None
@property
def viewMode(self):
return self._viewMode
@viewMode.setter
def viewMode(self, val):
if val == self._viewMode:
return
ports = {"Chunk": self.chunkViewport, "Camera": self.mainViewport}
for p in ports.values():
p.set_parent(None)
port = ports.get(val, self.mainViewport)
self.mainViewport.mouseLookOff()
self._viewMode = val
if val == "Camera":
x, y, z = self.chunkViewport.cameraPosition
try:
h = self.level.heightMapAt(int(x), int(z))
except:
h = 0
y = max(self.mainViewport.cameraPosition[1], h + 2)
self.mainViewport.cameraPosition = x, y, z
# self.mainViewport.yaw = 180.0
# self.mainViewport.pitch = 90.0
self.mainViewport.cameraVector = self.mainViewport._cameraVector()
self.renderer.overheadMode = False
self.viewportButton.text = "Chunk View"
else:
x, y, z = self.mainViewport.cameraPosition
self.chunkViewport.cameraPosition = x, y, z
self.renderer.overheadMode = True
self.viewportButton.text = "Camera View"
self.viewportContainer.add(port)
self.currentViewport = port
self.chunkViewport.size = self.mainViewport.size = self.viewportContainer.size
self.renderer.loadNearbyChunks()
def swapViewports(self):
if Settings.viewMode.get() == "Chunk":
Settings.viewMode = "Camera"
else:
Settings.viewMode = "Chunk"
maxCopies = 10
def addCopiedSchematic(self, sch):
self.copyStack.insert(0, sch)
if len(self.copyStack) > self.maxCopies:
self.deleteCopiedSchematic(self.copyStack[-1])
self.updateCopyPanel()
def _deleteSchematic(self, sch):
if hasattr(sch, 'close'):
sch.close()
if sch.filename and os.path.exists(sch.filename):
os.remove(sch.filename)
def deleteCopiedSchematic(self, sch):
self._deleteSchematic(sch)
self.copyStack = [s for s in self.copyStack if s is not sch]
self.updateCopyPanel()
def deleteAllCopiedSchematics(self):
for s in self.copyStack:
self._deleteSchematic(s)
copyPanel = None
def updateCopyPanel(self):
if self.copyPanel:
self.copyPanel.set_parent(None)
if 0 == len(self.copyStack):
return
self.copyPanel = self.createCopyPanel()
self.copyPanel.topright = self.topright
self.add(self.copyPanel)
thumbCache = None
fboCache = None
def createCopyPanel(self):
panel = GLBackground()
panel.bg_color = (0.0, 0.0, 0.0, 0.5)
self.thumbCache = thumbCache = self.thumbCache or {}
self.fboCache = self.fboCache or {}
for k in self.thumbCache.keys():
if k not in self.copyStack:
del self.thumbCache[k]
def createOneCopyPanel(sch):
p = GLBackground()
p.bg_color = (0.0, 0.0, 0.0, 0.4)
thumb = thumbCache.get(sch)
if thumb is None:
thumb = ThumbView(sch)
thumb.mouse_down = lambda e: self.pasteSchematic(sch)
thumb.tooltipText = "Click to import this item."
thumbCache[sch] = thumb
self.addWorker(thumb.renderer)
deleteButton = Button("Delete", action=lambda: (self.deleteCopiedSchematic(sch)))
saveButton = Button("Save", action=lambda: (self.exportSchematic(sch)))
sizeLabel = Label("{0} x {1} x {2}".format(sch.Length, sch.Width, sch.Height))
p.add(Row((thumb, Column((sizeLabel, Row((deleteButton, saveButton))), spacing=5))))
p.shrink_wrap()
return p
copies = [createOneCopyPanel(sch) for sch in self.copyStack]
panel.add(Column(copies, align="l"))
panel.shrink_wrap()
panel.anchor = "whrt"
return panel
@mceutils.alertException
def showAnalysis(self, schematic):
self.analyzeBox(schematic, schematic.bounds)
def analyzeBox(self, level, box):
entityCounts = defaultdict(int)
tileEntityCounts = defaultdict(int)
types = numpy.zeros(65536, dtype='uint32')
def _analyzeBox():
i = 0
for (chunk, slices, point) in level.getChunkSlices(box):
i += 1
yield i, box.chunkCount
blocks = numpy.array(chunk.Blocks[slices], dtype='uint16')
blocks |= (numpy.array(chunk.Data[slices], dtype='uint16') << 12)
b = numpy.bincount(blocks.ravel())
types[:b.shape[0]] += b
for ent in chunk.getEntitiesInBox(box):
if ent["id"].value == "Item":
v = pymclevel.items.items.findItem(ent["Item"]["id"].value,
ent["Item"]["Damage"].value).name
else:
v = ent["id"].value
entityCounts[(ent["id"].value, v)] += 1
for ent in chunk.getTileEntitiesInBox(box):
tileEntityCounts[ent["id"].value] += 1
with mceutils.setWindowCaption("ANALYZING - "):
mceutils.showProgress("Analyzing {0} blocks...".format(box.volume), _analyzeBox(), cancel=True)
entitySum = numpy.sum(entityCounts.values())
tileEntitySum = numpy.sum(tileEntityCounts.values())
presentTypes = types.nonzero()
blockCounts = sorted([(level.materials[t & 0xfff, t >> 12], types[t]) for t in presentTypes[0]])
counts = []
c = 0
b = level.materials.Air
for block, count in blockCounts:
# Collapse waters and lavas together so different fluid levels are counted together
# xxx optional
if block.idStr not in ("water", "lava") or b.idStr != block.idStr:
counts.append((b, c))
b = block
c = 0
c += count
counts.append((b, c))
blockRows = [("({0}:{1})".format(block.ID, block.blockData), block.name, count) for block, count in counts]
# blockRows.sort(key=lambda x: alphanum_key(x[2]), reverse=True)
rows = list(blockRows)
def extendEntities():
if entitySum:
rows.extend([("", "", ""), ("", "<Entities>", entitySum)])
rows.extend([(id[0], id[1], count) for (id, count) in sorted(entityCounts.iteritems())])
if tileEntitySum:
rows.extend([("", "", ""), ("", "<TileEntities>", tileEntitySum)])
rows.extend([(id, id, count) for (id, count) in sorted(tileEntityCounts.iteritems())])
extendEntities()
columns = [
TableColumn("ID", 120),
TableColumn("Name", 250),
TableColumn("Count", 100),
]
table = TableView(columns=columns)
table.sortColumn = columns[2]
table.reverseSort = True
def sortColumn(col):
if table.sortColumn == col:
table.reverseSort = not table.reverseSort
else:
table.reverseSort = (col.title == "Count")
colnum = columns.index(col)
def sortKey(x):
val = x[colnum]
if isinstance(val, basestring):
alphanum_key(val)
return val
blockRows.sort(key=sortKey,
reverse=table.reverseSort)
table.sortColumn = col
rows[:] = blockRows
extendEntities()
table.num_rows = lambda: len(rows)
table.row_data = lambda i: rows[i]
table.row_is_selected = lambda x: False
table.click_column_header = sortColumn
tableBacking = Widget()
tableBacking.add(table)
tableBacking.shrink_wrap()
def saveToFile():
filename = askSaveFile(mcplatform.docsFolder,
title='Save analysis...',
defaultName=self.level.displayName + "_analysis",
filetype='Comma Separated Values\0*.txt\0\0',
suffix="",
)
if filename:
try:
csvfile = csv.writer(open(filename, "wb"))
except Exception, e:
alert(str(e))
else:
csvfile.writerows(rows)
saveButton = Button("Save to file...", action=saveToFile)
col = Column((Label("Volume: {0} blocks.".format(box.volume)), tableBacking, saveButton))
Dialog(client=col, responses=["OK"]).present()
def exportSchematic(self, schematic):
filename = mcplatform.askSaveSchematic(
mcplatform.schematicsDir, self.level.displayName, "schematic")
if filename:
schematic.saveToFile(filename)
def getLastCopiedSchematic(self):
if len(self.copyStack) == 0:
return None
return self.copyStack[0]
toolbar = None
def reloadToolbar(self):
self.toolbar = EditorToolbar(self, tools=[editortools.SelectionTool(self),
editortools.BrushTool(self),
editortools.CloneTool(self),
editortools.FillTool(self),
editortools.FilterTool(self),
editortools.ConstructionTool(self),
editortools.PlayerPositionTool(self),
editortools.PlayerSpawnPositionTool(self),
editortools.ChunkTool(self),
])
self.toolbar.anchor = 'bwh'
self.add(self.toolbar)
self.toolbar.bottom = self.viewportContainer.bottom # bottoms are touching
self.toolbar.centerx = self.centerx
is_gl_container = True
maxDebug = 1
allBlend = False
onscreen = True
mouseEntered = True
defaultCameraToolDistance = 10
mouseSensitivity = 5.0
longDistanceMode = Settings.longDistanceMode.configProperty()
def genSixteenBlockTexture(self):
has12 = GL.glGetString(GL.GL_VERSION) >= "1.2"
if has12:
maxLevel = 2
mode = GL.GL_LINEAR_MIPMAP_NEAREST
else:
maxLevel = 1
mode = GL.GL_LINEAR
def makeSixteenBlockTex():
darkColor = (0x30, 0x30, 0x30, 0xff)
lightColor = (0x80, 0x80, 0x80, 0xff)
w, h, = 256, 256
teximage = numpy.zeros((w, h, 4), dtype='uint8')
teximage[:] = 0xff
teximage[:, ::16] = lightColor
teximage[::16, :] = lightColor
teximage[:2] = darkColor
teximage[-1:] = darkColor
teximage[:, -1:] = darkColor
teximage[:, :2] = darkColor
# GL.glTexParameter(GL.GL_TEXTURE_2D,
# GL.GL_TEXTURE_MIN_FILTER,
# GL.GL_NEAREST_MIPMAP_NEAREST),
GL.glTexParameter(GL.GL_TEXTURE_2D,
GL.GL_TEXTURE_MAX_LEVEL,
maxLevel - 1)
for lev in range(maxLevel):
step = 1 << lev
if lev:
teximage[::16] = 0xff
teximage[:, ::16] = 0xff
teximage[:2] = darkColor
teximage[-1:] = darkColor
teximage[:, -1:] = darkColor
teximage[:, :2] = darkColor
GL.glTexImage2D(GL.GL_TEXTURE_2D, lev, GL.GL_RGBA8,
w / step, h / step, 0,
GL.GL_RGBA, GL.GL_UNSIGNED_BYTE,
teximage[::step, ::step].ravel())
return Texture(makeSixteenBlockTex, mode)
def showProgress(self, *a, **kw):
return mceutils.showProgress(*a, **kw)
def drawConstructionCube(self, box, color, texture=None):
if texture == None:
texture = self.sixteenBlockTex
# textured cube faces
GL.glEnable(GL.GL_BLEND)
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glDepthMask(False)
# edges within terrain
GL.glDepthFunc(GL.GL_GREATER)
try:
GL.glColor(color[0], color[1], color[2], max(color[3], 0.35))
except IndexError:
raise
GL.glLineWidth(1.0)
mceutils.drawCube(box, cubeType=GL.GL_LINE_STRIP)
# edges on or outside terrain
GL.glDepthFunc(GL.GL_LEQUAL)
GL.glColor(color[0], color[1], color[2], max(color[3] * 2, 0.75))
GL.glLineWidth(2.0)
mceutils.drawCube(box, cubeType=GL.GL_LINE_STRIP)
GL.glDepthFunc(GL.GL_LESS)
GL.glColor(color[0], color[1], color[2], color[3])
GL.glDepthFunc(GL.GL_LEQUAL)
mceutils.drawCube(box, texture=texture, selectionBox=True)
GL.glDepthMask(True)
GL.glDisable(GL.GL_BLEND)
GL.glDisable(GL.GL_DEPTH_TEST)
def loadFile(self, filename):
"""
Called when the user picks a level using Load World or Open File.
"""
if self.level and self.unsavedEdits > 0:
resp = ask("Save unsaved edits before loading?", ["Cancel", "Don't Save", "Save"], default=2, cancel=0)
if resp == "Cancel":
return
if resp == "Save":
self.saveFile()
self.freezeStatus("Loading " + filename)
if self.level:
self.level.close()
try:
level = pymclevel.fromFile(filename)
except Exception, e:
logging.exception(
'Wasn\'t able to open a file {file => %s}' % filename
)
alert(u"I don't know how to open {0}:\n\n{1!r}".format(filename, e))
return
assert level
self.mcedit.addRecentWorld(filename)
try:
self.currentViewport.cameraPosition = level.getPlayerPosition()
y, p = level.getPlayerOrientation()
if p == -90.0:
p += 0.000000001
if p == 90.0:
p -= 0.000000001
self.mainViewport.yaw, self.mainViewport.pitch = y, p
pdim = level.getPlayerDimension()
if pdim and pdim in level.dimensions:
level = level.dimensions[pdim]
except (KeyError, pymclevel.PlayerNotFound): # TagNotFound
# player tag not found, maybe
try:
self.currentViewport.cameraPosition = level.playerSpawnPosition()
except KeyError: # TagNotFound
self.currentViewport.cameraPosition = numpy.array((0, level.Height * 0.75, 0))
self.mainViewport.yaw = -45.
self.mainViewport.pitch = 0.0
self.removeNetherPanel()
self.undoStack = []
self.loadLevel(level)
self.recordUndo = True
self.clearUnsavedEdits()
self.renderer.position = self.currentViewport.cameraPosition
self.renderer.loadNearbyChunks()
def loadLevel(self, level):
"""
Called to load a level, world, or dimension into the editor and display it in the viewport.
"""
self.level = level
self.toolbar.selectTool(-1)
self.toolbar.removeToolPanels()
self.selectedChunks = set()
self.mainViewport.stopMoving()
self.renderer.level = level
self.addWorker(self.renderer)
self.initWindowCaption()
self.selectionTool.selectNone()
[t.levelChanged() for t in self.toolbar.tools]
if isinstance(self.level, pymclevel.MCInfdevOldLevel):
if self.level.parentWorld:
dimensions = self.level.parentWorld.dimensions
else:
dimensions = self.level.dimensions
dimensionsMenu = [("Earth", "0")]
dimensionsMenu += [(pymclevel.MCAlphaDimension.dimensionNames.get(dimNo, "Dimension {0}".format(dimNo)), str(dimNo)) for dimNo in dimensions]
for dim, name in pymclevel.MCAlphaDimension.dimensionNames.iteritems():
if dim not in dimensions:
dimensionsMenu.append((name, str(dim)))
menu = Menu("", dimensionsMenu)
def presentMenu():
x, y = self.netherPanel.topleft
dimIdx = menu.present(self, (x, y - menu.height))
if dimIdx == -1:
return
dimNo = int(dimensionsMenu[dimIdx][1])
self.gotoDimension(dimNo)
self.netherPanel = Panel()
self.netherButton = Button("Goto Dimension", action=presentMenu)
self.netherPanel.add(self.netherButton)
self.netherPanel.shrink_wrap()
self.netherPanel.bottomright = self.viewportContainer.bottomright
self.netherPanel.anchor = "brwh"
self.add(self.netherPanel)
if len(list(self.level.allChunks)) == 0:
resp = ask("It looks like this level is completely empty! You'll have to create some chunks before you can get started.", responses=["Create Chunks", "Cancel"])
if resp == "Create Chunks":
x, y, z = self.mainViewport.cameraPosition
box = pymclevel.BoundingBox((x - 128, 0, z - 128), (256, self.level.Height, 256))
self.selectionTool.setSelection(box)
self.toolbar.selectTool(8)
self.toolbar.tools[8].createChunks()
self.mainViewport.cameraPosition = (x, self.level.Height, z)
def removeNetherPanel(self):
if self.netherPanel:
self.remove(self.netherPanel)
self.netherPanel = None
@mceutils.alertException
def gotoEarth(self):
assert self.level.parentWorld
self.removeNetherPanel()
self.loadLevel(self.level.parentWorld)
x, y, z = self.mainViewport.cameraPosition
self.mainViewport.cameraPosition = [x * 8, y, z * 8]
@mceutils.alertException
def gotoNether(self):
self.removeNetherPanel()
x, y, z = self.mainViewport.cameraPosition
self.mainViewport.cameraPosition = [x / 8, y, z / 8]
self.loadLevel(self.level.getDimension(-1))
def gotoDimension(self, dimNo):
if dimNo == self.level.dimNo:
return
elif dimNo == -1 and self.level.dimNo == 0:
self.gotoNether()
elif dimNo == 0 and self.level.dimNo == -1:
self.gotoEarth()
else:
self.removeNetherPanel()
if dimNo:
if dimNo == 1:
self.mainViewport.cameraPosition = (0, 96, 0)
self.loadLevel(self.level.getDimension(dimNo))
else:
self.loadLevel(self.level.parentWorld)
netherPanel = None
def initWindowCaption(self):
filename = self.level.filename
s = os.path.split(filename)
title = os.path.split(s[0])[1] + os.sep + s[1] + u" - MCEdit " + release.release
title = title.encode('ascii', 'replace')
display.set_caption(title)
@mceutils.alertException
def reload(self):
filename = self.level.filename
# self.discardAllChunks()
self.loadFile(filename)
@mceutils.alertException
def saveFile(self):
with mceutils.setWindowCaption("SAVING - "):
if isinstance(self.level, pymclevel.ChunkedLevelMixin): # xxx relight indev levels?
level = self.level
if level.parentWorld:
level = level.parentWorld
if hasattr(level, 'checkSessionLock'):
try:
level.checkSessionLock()
except SessionLockLost, e:
alert(e.message + "\n\nYour changes cannot be saved.")
return
for level in itertools.chain(level.dimensions.itervalues(), [level]):
if "Canceled" == mceutils.showProgress("Lighting chunks", level.generateLightsIter(), cancel=True):
return
if self.level == level:
if isinstance(level, pymclevel.MCInfdevOldLevel):
needsRefresh = [c.chunkPosition for c in level._loadedChunkData.itervalues() if c.dirty]
needsRefresh.extend(level.unsavedWorkFolder.listChunks())
else:
needsRefresh = [c for c in level.allChunks if level.getChunk(*c).dirty]
#xxx change MCInfdevOldLevel to monitor changes since last call
self.invalidateChunks(needsRefresh)
self.freezeStatus("Saving...")
self.level.saveInPlace()
self.recordUndo = True
self.clearUnsavedEdits()
def addUnsavedEdit(self):
if self.unsavedEdits:
self.remove(self.saveInfoBackground)
self.unsavedEdits += 1
saveInfoBackground = GLBackground()
saveInfoBackground.bg_color = (0.0, 0.0, 0.0, 0.6)
saveInfoLabel = Label(self.saveInfoLabelText)
saveInfoLabel.anchor = "blwh"
# saveInfoLabel.width = 500
saveInfoBackground.add(saveInfoLabel)
saveInfoBackground.shrink_wrap()
saveInfoBackground.left = 50
saveInfoBackground.bottom = self.toolbar.toolbarRectInWindowCoords()[1]
self.add(saveInfoBackground)
self.saveInfoBackground = saveInfoBackground
def clearUnsavedEdits(self):
if self.unsavedEdits:
self.unsavedEdits = 0
self.remove(self.saveInfoBackground)
@property
def saveInfoLabelText(self):
if self.unsavedEdits == 0:
return ""
return "{0} unsaved edits. CTRL-S to save. {1}".format(self.unsavedEdits, "" if self.recordUndo else "(UNDO DISABLED)")
@property
def viewDistanceLabelText(self):
return "View Distance ({0})".format(self.renderer.viewDistance)
def createRenderers(self):
self.renderer = MCRenderer()
self.workers = deque()
if self.level:
self.renderer.level = self.level
self.addWorker(self.renderer)
self.renderer.viewDistance = int(Settings.viewDistance.get())
def addWorker(self, chunkWorker):
if chunkWorker not in self.workers:
self.workers.appendleft(chunkWorker)
def removeWorker(self, chunkWorker):
if chunkWorker in self.workers:
self.workers.remove(chunkWorker)
def getFrameDuration(self):
frameDuration = timedelta(0, 1, 0) / self.renderer.targetFPS
return frameDuration
lastRendererDraw = datetime.now()
def idleevent(self, e):
if any(self.cameraInputs) or any(self.cameraPanKeys):
self.postMouseMoved()
if(self.renderer.needsImmediateRedraw or
(self.renderer.needsRedraw and datetime.now() - self.lastRendererDraw > timedelta(0, 1, 0) / 3)):
self.invalidate()
self.lastRendererDraw = datetime.now()
if self.renderer.needsImmediateRedraw:
self.invalidate()
if self.get_root().do_draw:
frameDuration = self.getFrameDuration()
while frameDuration > (datetime.now() - self.frameStartTime):
self.doWorkUnit()
else:
return
self.doWorkUnit()
def activeevent(self, evt):
self.mainViewport.activeevent(evt)
if evt.state & 0x4: # minimized
if evt.gain == 0:
logging.debug("Offscreen")
self.onscreen = False
self.mouseLookOff()
else:
logging.debug("Onscreen")
self.onscreen = True
self.invalidate()
if evt.state & 0x1: # mouse enter/leave
if evt.gain == 0:
logging.debug("Mouse left")
self.mouseEntered = False
self.mouseLookOff()
else:
logging.debug("Mouse entered")
self.mouseEntered = True
def swapDebugLevels(self):
self.debug += 1
if self.debug > self.maxDebug:
self.debug = 0
if self.debug:
self.showDebugPanel()
else:
self.hideDebugPanel()
def showDebugPanel(self):
dp = GLBackground()
debugLabel = ValueDisplay(width=1100, ref=AttrRef(self, "debugString"))
inspectLabel = ValueDisplay(width=1100, ref=AttrRef(self, "inspectionString"))
dp.add(Column((debugLabel, inspectLabel)))
dp.shrink_wrap()
dp.bg_color = (0, 0, 0, 0.6)
self.add(dp)
dp.top = 40
self.debugPanel = dp
def hideDebugPanel(self):
self.remove(self.debugPanel)
@property
def statusText(self):
try:
return self.currentTool.statusText
except Exception, e:
return repr(e)
def toolMouseDown(self, evt, f): # xxx f is a tuple
if self.level:
if None != f:
(focusPoint, direction) = f
self.currentTool.mouseDown(evt, focusPoint, direction)
def toolMouseUp(self, evt, f): # xxx f is a tuple
if self.level:
if None != f:
(focusPoint, direction) = f
self.currentTool.mouseUp(evt, focusPoint, direction)
def mouse_up(self, evt):
button = remapMouseButton(evt.button)
evt.dict['keyname'] = "mouse{0}".format(button)
self.key_up(evt)
def mouse_drag(self, evt):
# if 'button' not in evt.dict or evt.button != 1:
# return
if self.level:
f = self.blockFaceUnderCursor
if None != f:
(focusPoint, direction) = f
self.currentTool.mouseDrag(evt, focusPoint, direction)
def mouse_down(self, evt):
button = remapMouseButton(evt.button)
evt.dict['keyname'] = "mouse{0}".format(button)
self.mcedit.focus_switch = self
self.focus_switch = None
self.key_down(evt)
'''
def mouseDragOn(self):
x,y = mouse.get_pos(0)
if None != self.currentOperation:
self.dragInProgress = True
self.dragStartPoint = (x,y)
self.currentOperation.dragStart(x,y)
def mouseDragOff(self):
if self.dragInProgress:
self.dragInProgress = False
'''
def mouseLookOff(self):
self.mouseWasCaptured = False
self.mainViewport.mouseLookOff()
def mouseLookOn(self):
self.mainViewport.mouseLookOn()
@property
def blockFaceUnderCursor(self):
return self.currentViewport.blockFaceUnderCursor
# @property
# def worldTooltipText(self):
# try:
# if self.blockFaceUnderCursor:
# pos = self.blockFaceUnderCursor[0]
# blockID = self.level.blockAt(*pos)
# blockData = self.level.blockDataAt(*pos)
#
# return "{name} ({bid})\n{pos}".format(name=self.level.materials.names[blockID][blockData], bid=blockID,pos=pos)
#
# except Exception, e:
# return None
#
def generateStars(self):
starDistance = 999.0
starCount = 2000
r = starDistance
randPoints = (numpy.random.random(size=starCount * 3)) * 2.0 * r
randPoints.shape = (starCount, 3)
nearbyPoints = (randPoints[:, 0] < r) & (randPoints[:, 1] < r) & (randPoints[:, 2] < r)
randPoints[nearbyPoints] += r
randPoints[:starCount / 2, 0] = -randPoints[:starCount / 2, 0]
randPoints[::2, 1] = -randPoints[::2, 1]
randPoints[::4, 2] = -randPoints[::4, 2]
randPoints[1::4, 2] = -randPoints[1::4, 2]
randsizes = numpy.random.random(size=starCount) * 6 + 0.8
vertsPerStar = 4
vertexBuffer = numpy.zeros((starCount, vertsPerStar, 3), dtype='float32')
def normvector(x):
return x / numpy.sqrt(numpy.sum(x * x, 1))[:, numpy.newaxis]
viewVector = normvector(randPoints)
rmod = numpy.random.random(size=starCount * 3) * 2.0 - 1.0
rmod.shape = (starCount, 3)
referenceVector = viewVector + rmod
rightVector = normvector(numpy.cross(referenceVector, viewVector)) * randsizes[:, numpy.newaxis] # vector perpendicular to viewing line
upVector = normvector(numpy.cross(rightVector, viewVector)) * randsizes[:, numpy.newaxis] # vector perpendicular previous vector and viewing line
p = randPoints
p1 = p + (- upVector - rightVector)
p2 = p + (upVector - rightVector)
p3 = p + (upVector + rightVector)
p4 = p + (- upVector + rightVector)
vertexBuffer[:, 0, :] = p1
vertexBuffer[:, 1, :] = p2
vertexBuffer[:, 2, :] = p3
vertexBuffer[:, 3, :] = p4
self.starVertices = vertexBuffer.ravel()
starColor = None
def drawStars(self):
pos = self.mainViewport.cameraPosition
self.mainViewport.cameraPosition = map(lambda x: x / 128.0, pos)
self.mainViewport.setModelview()
GL.glColor(.5, .5, .5, 1.)
GL.glVertexPointer(3, GL.GL_FLOAT, 0, self.starVertices)
GL.glDrawArrays(GL.GL_QUADS, 0, len(self.starVertices) / 3)
self.mainViewport.cameraPosition = pos
self.mainViewport.setModelview()
fractionalReachAdjustment = True
def postMouseMoved(self):
evt = event.Event(MOUSEMOTION, rel=(0, 0), pos=mouse.get_pos(), buttons=mouse.get_pressed())
event.post(evt)
def resetReach(self):
self.postMouseMoved()
if self.currentTool.resetToolReach():
return
self.cameraToolDistance = self.defaultCameraToolDistance
def increaseReach(self):
self.postMouseMoved()
if self.currentTool.increaseToolReach():
return
self.cameraToolDistance = self._incrementReach(self.cameraToolDistance)
def decreaseReach(self):
self.postMouseMoved()
if self.currentTool.decreaseToolReach():
return
self.cameraToolDistance = self._decrementReach(self.cameraToolDistance)
def _incrementReach(self, reach):
reach += 1
if reach > 30 and self.fractionalReachAdjustment:
reach *= 1.05
return reach
def _decrementReach(self, reach):
reach -= 1
if reach > 30 and self.fractionalReachAdjustment:
reach *= 0.95
return reach
def key_up(self, evt):
d = self.cameraInputs
keyname = getattr(evt, 'keyname', None) or key.name(evt.key)
if keyname == config.config.get('Keys', 'Brake'):
self.mainViewport.brakeOff()
# if keyname == 'left ctrl' or keyname == 'right ctrl':
# self.hideControls()
if keyname == config.config.get('Keys', 'Left'):
d[0] = 0.
if keyname == config.config.get('Keys', 'Right'):
d[0] = 0.
if keyname == config.config.get('Keys', 'Forward'):
d[2] = 0.
if keyname == config.config.get('Keys', 'Back'):
d[2] = 0.
if keyname == config.config.get('Keys', 'Up'):
d[1] = 0.
if keyname == config.config.get('Keys', 'Down'):
d[1] = 0.
cp = self.cameraPanKeys
if keyname == config.config.get('Keys', 'Pan Left'):
cp[0] = 0.
if keyname == config.config.get('Keys', 'Pan Right'):
cp[0] = 0.
if keyname == config.config.get('Keys', 'Pan Up'):
cp[1] = 0.
if keyname == config.config.get('Keys', 'Pan Down'):
cp[1] = 0.
# if keyname in ("left alt", "right alt"):
def key_down(self, evt):
keyname = evt.dict.get('keyname', None) or key.name(evt.key)
if keyname == 'enter':
keyname = 'return'
d = self.cameraInputs
im = [0., 0., 0.]
mods = evt.dict.get('mod', 0)
if keyname == 'f4' and (mods & (KMOD_ALT | KMOD_LALT | KMOD_RALT)):
self.quit()
return
if mods & KMOD_ALT:
if keyname == "z":
self.longDistanceMode = not self.longDistanceMode
if keyname in "12345":
name = "option" + keyname
if hasattr(self.currentTool, name):
getattr(self.currentTool, name)()
elif hasattr(evt, 'cmd') and evt.cmd:
if keyname == 'q' and mcplatform.cmd_name == "Cmd":
self.quit()
return
if keyname == 'f':
self.swapViewDistance()
if keyname == 'a':
self.selectAll()
if keyname == 'd':
self.deselect()
if keyname == 'x':
self.cutSelection()
if keyname == 'c':
self.copySelection()
if keyname == 'v':
self.pasteSelection()
if keyname == 'r':
self.reload()
if keyname == 'o':
self.askOpenFile()
if keyname == 'l':
self.askLoadWorld()
if keyname == 'z':
self.undo()
if keyname == 's':
self.saveFile()
if keyname == 'n':
self.createNewLevel()
if keyname == 'w':
self.closeEditor()
if keyname == 'i':
self.showWorldInfo()
if keyname == 'g':
self.showGotoPanel()
if keyname == 'e':
self.selectionTool.exportSelection()
if keyname == 'f9':
if mods & KMOD_ALT:
self.parent.reloadEditor()
# ===========================================================
# debugPanel = Panel()
# buttonColumn = [
# Button("Reload Editor", action=self.parent.reloadEditor),
# ]
# debugPanel.add(Column(buttonColumn))
# debugPanel.shrink_wrap()
# self.add_centered(debugPanel)
# ===========================================================
elif mods & KMOD_SHIFT:
raise GL.GLError(err=1285, description="User pressed CONTROL-SHIFT-F9, requesting a GL Memory Error")
else:
try:
expr = input_text(">>> ", 600)
expr = compile(expr, 'eval', 'single')
alert("Result: {0!r}".format(eval(expr, globals(), locals())))
except Exception, e:
alert("Exception: {0!r}".format(e))
if keyname == 'f10':
def causeError():
raise ValueError("User pressed CONTROL-F10, requesting a program error.")
if mods & KMOD_ALT:
alert("MCEdit, a Minecraft World Editor\n\nCopyright 2010 David Rio Vierra")
elif mods & KMOD_SHIFT:
mceutils.alertException(causeError)()
else:
causeError()
else:
if keyname == 'tab':
self.swapViewports()
if keyname == config.config.get('Keys', 'Brake'):
self.mainViewport.brakeOn()
if keyname == config.config.get('Keys', 'Reset Reach'):
self.resetReach()
if keyname == config.config.get('Keys', 'Increase Reach'):
self.increaseReach()
if keyname == config.config.get('Keys', 'Decrease Reach'):
self.decreaseReach()
if keyname == config.config.get('Keys', 'Flip'):
self.currentTool.flip()
if keyname == config.config.get('Keys', 'Roll'):
self.currentTool.roll()
if keyname == config.config.get('Keys', 'Rotate'):
self.currentTool.rotate()
if keyname == config.config.get('Keys', 'Mirror'):
self.currentTool.mirror()
if keyname == config.config.get('Keys', 'Swap'):
self.currentTool.swap()
if keyname == 'escape':
self.toolbar.tools[0].endSelection()
self.mouseLookOff()
self.showControls()
# movement
if keyname == config.config.get('Keys', 'Left'):
d[0] = -1.
im[0] = -1.
if keyname == config.config.get('Keys', 'Right'):
d[0] = 1.
im[0] = 1.
if keyname == config.config.get('Keys', 'Forward'):
d[2] = 1.
im[2] = 1.
if keyname == config.config.get('Keys', 'Back'):
d[2] = -1.
im[2] = -1.
if keyname == config.config.get('Keys', 'Up'):
d[1] = 1.
im[1] = 1.
if keyname == config.config.get('Keys', 'Down'):
d[1] = -1.
im[1] = -1.
cp = self.cameraPanKeys
if keyname == config.config.get('Keys', 'Pan Left'):
cp[0] = -1.
if keyname == config.config.get('Keys', 'Pan Right'):
cp[0] = 1.
if keyname == config.config.get('Keys', 'Pan Up'):
cp[1] = -1.
if keyname == config.config.get('Keys', 'Pan Down'):
cp[1] = 1.
if keyname == config.config.get('Keys', 'Confirm Construction'):
self.confirmConstruction()
# =======================================================================
# if keyname == config.config.get('Keys','Toggle Flat Shading'):
# self.renderer.swapMipmapping()
# if keyname == config.config.get('Keys','Toggle Lighting'):
# self.renderer.toggleLighting()
# =======================================================================
if keyname == config.config.get('Keys', 'Toggle FPS Counter'):
self.swapDebugLevels()
if keyname == config.config.get('Keys', 'Toggle Renderer'):
self.renderer.render = not self.renderer.render
if keyname == config.config.get('Keys', 'Delete Blocks'):
self.deleteSelectedBlocks()
if keyname in "123456789":
self.toolbar.selectTool(int(keyname) - 1)
if keyname in ('f1', 'f2', 'f3', 'f4', 'f5'):
self.mcedit.loadRecentWorldNumber(int(keyname[1]))
def showGotoPanel(self):
gotoPanel = Widget()
gotoPanel.X, gotoPanel.Y, gotoPanel.Z = map(int, self.mainViewport.cameraPosition)
inputRow = (
Label("X: "), IntField(ref=AttrRef(gotoPanel, "X")),
Label("Y: "), IntField(ref=AttrRef(gotoPanel, "Y")),
Label("Z: "), IntField(ref=AttrRef(gotoPanel, "Z")),
)
inputRow = Row(inputRow)
column = (
Label("Goto Position:"),
Label("(click anywhere to teleport)"),
inputRow,
# Row( (Button("Cancel"), Button("Goto")), align="r" )
)
column = Column(column)
gotoPanel.add(column)
gotoPanel.shrink_wrap()
d = Dialog(client=gotoPanel, responses=["Goto", "Cancel"])
def click_outside(event):
if event not in d:
x, y, z = self.blockFaceUnderCursor[0]
if y == 0:
y = 64
y += 3
gotoPanel.X, gotoPanel.Y, gotoPanel.Z = x, y, z
if event.num_clicks == 2:
d.dismiss("Goto")
d.mouse_down = click_outside
d.top = self.viewportContainer.top + 10
d.centerx = self.viewportContainer.centerx
if d.present(centered=False) == "Goto":
destPoint = gotoPanel.X, gotoPanel.Y, gotoPanel.Z
if self.currentViewport is self.chunkViewport:
self.swapViewports()
self.mainViewport.cameraPosition = destPoint
def closeEditor(self):
if self.unsavedEdits:
answer = ask("Save unsaved edits before closing?", ["Cancel", "Don't Save", "Save"], default=-1, cancel=0)
if answer == "Save":
self.saveFile()
if answer == "Cancel":
return
self.unsavedEdits = 0
self.mainViewport.mouseLookOff()
self.level = None
self.renderer.stopWork()
self.removeWorker(self.renderer)
self.renderer.level = None
self.mcedit.removeEditor()
def repairRegions(self):
worldFolder = self.level.worldFolder
for filename in worldFolder.findRegionFiles():
rf = worldFolder.tryLoadRegionFile(filename)
if rf:
rf.repair()
alert("Repairs complete. See the console window for details.")
@mceutils.alertException
def showWorldInfo(self):
ticksPerDay = 24000
ticksPerHour = ticksPerDay / 24
ticksPerMinute = ticksPerDay / (24 * 60)
def decomposeMCTime(time):
day = time / ticksPerDay
tick = time % ticksPerDay
hour = tick / ticksPerHour
tick %= ticksPerHour
minute = tick / ticksPerMinute
tick %= ticksPerMinute
return day, hour, minute, tick
def composeMCTime(d, h, m, t):
time = d * ticksPerDay + h * ticksPerHour + m * ticksPerMinute + t
return time
worldInfoPanel = Dialog()
items = []
t = functools.partial(isinstance, self.level)
if t(pymclevel.MCInfdevOldLevel):
if self.level.version == pymclevel.MCInfdevOldLevel.VERSION_ANVIL:
levelFormat = "Minecraft Infinite World (Anvil Format)"
elif self.level.version == pymclevel.MCInfdevOldLevel.VERSION_MCR:
levelFormat = "Minecraft Infinite World (Region Format)"
else:
levelFormat = "Minecraft Infinite World (Old Chunk Format)"
elif t(pymclevel.MCIndevLevel):
levelFormat = "Minecraft Indev (.mclevel format)"
elif t(pymclevel.MCSchematic):
levelFormat = "MCEdit Schematic"
elif t(pymclevel.ZipSchematic):
levelFormat = "MCEdit Schematic (Zipped Format)"
elif t(pymclevel.MCJavaLevel):
levelFormat = "Minecraft Classic or raw block array"
else:
levelFormat = "Unknown"
formatLabel = Label(levelFormat)
items.append(Label("Format:"))
items.append(formatLabel)
if hasattr(self.level, 'Time'):
time = self.level.Time
# timezone adjust -
# minecraft time shows 0:00 on day 0 at the first sunrise
# I want that to be 6:00 on day 1, so I add 30 hours
timezoneAdjust = ticksPerHour * 30
time += timezoneAdjust
d, h, m, tick = decomposeMCTime(time)
dayInput = IntField(value=d, min=1) # ref=AttrRef(self, "Day"))
items.append(Row((Label("Day: "), dayInput)))
timeInput = TimeField(value=(h, m))
timeInputRow = Row((Label("Time of day:"), timeInput))
items.append(timeInputRow)
if hasattr(self.level, 'RandomSeed'):
seed = self.level.RandomSeed
seedInputRow = mceutils.IntInputRow("RandomSeed: ", width=250, ref=AttrRef(self.level, "RandomSeed"))
items.append(seedInputRow)
if hasattr(self.level, 'GameType'):
t = self.level.GameType
types = ["Survival", "Creative"]
def gametype(t):
if t < len(types):
return types[t]
return "Unknown"
def action():
if b.gametype < 2:
b.gametype = 1 - b.gametype
b.text = gametype(b.gametype)
self.level.GameType = b.gametype
self.addUnsavedEdit()
b = Button(gametype(t), action=action)
b.gametype = t
gametypeRow = Row((Label("Game Type: "), b))
items.append(gametypeRow)
if isinstance(self.level, pymclevel.MCInfdevOldLevel):
chunkCount = self.level.chunkCount
chunkCountLabel = Label("Number of chunks: {0}".format(chunkCount))
items.append(chunkCountLabel)
if hasattr(self.level, 'worldFolder'):
if hasattr(self.level.worldFolder, 'regionFiles'):
worldFolder = self.level.worldFolder
regionCount = len(worldFolder.regionFiles)
regionCountLabel = Label("Number of regions: {0}".format(regionCount))
items.append(regionCountLabel)
button = Button("Repair regions", action=self.repairRegions)
items.append(button)
def openFolder():
filename = self.level.filename
if not isdir(filename):
filename = dirname(filename)
mcplatform.platform_open(filename)
revealButton = Button("Open Folder", action=openFolder)
items.append(revealButton)
# if all(hasattr(self.level, i) for i in ("Length", "Width", "Height")):
size = self.level.size
sizelabel = Label("{L}L x {W}W x {H}H".format(L=size[2], H=size[1], W=size[0]))
items.append(sizelabel)
if hasattr(self.level, "Entities"):
label = Label("{0} Entities".format(len(self.level.Entities)))
items.append(label)
if hasattr(self.level, "TileEntities"):
label = Label("{0} TileEntities".format(len(self.level.TileEntities)))
items.append(label)
col = Column(items)
col = Column((col, Button("OK", action=worldInfoPanel.dismiss)))
worldInfoPanel.add(col)
worldInfoPanel.shrink_wrap()
worldInfoPanel.present()
if hasattr(self.level, 'Time'):
h, m = timeInput.value
time = composeMCTime(dayInput.value, h, m, tick)
time -= timezoneAdjust
if self.level.Time != time:
self.level.Time = time
# xxx TimeChangeOperation
self.addUnsavedEdit()
if hasattr(self.level, 'RandomSeed'):
if seed != self.level.RandomSeed:
self.addUnsavedEdit()
def swapViewDistance(self):
if self.renderer.viewDistance >= self.renderer.maxViewDistance:
self.renderer.viewDistance = self.renderer.minViewDistance
else:
self.renderer.viewDistance += 2
self.addWorker(self.renderer)
Settings.viewDistance.set(self.renderer.viewDistance)
def increaseViewDistance(self):
self.renderer.viewDistance = min(self.renderer.maxViewDistance, self.renderer.viewDistance + 2)
self.addWorker(self.renderer)
Settings.viewDistance.set(self.renderer.viewDistance)
def decreaseViewDistance(self):
self.renderer.viewDistance = max(self.renderer.minViewDistance, self.renderer.viewDistance - 2)
self.addWorker(self.renderer)
Settings.viewDistance.set(self.renderer.viewDistance)
@mceutils.alertException
def askLoadWorld(self):
if not os.path.isdir(pymclevel.saveFileDir):
alert(u"Could not find the Minecraft saves directory!\n\n({0} was not found or is not a directory)".format(pymclevel.saveFileDir))
return
worldPanel = Widget()
potentialWorlds = os.listdir(pymclevel.saveFileDir)
potentialWorlds = [os.path.join(pymclevel.saveFileDir, p) for p in potentialWorlds]
worldFiles = [p for p in potentialWorlds if pymclevel.MCInfdevOldLevel.isLevel(p)]
worlds = []
for f in worldFiles:
try:
lev = pymclevel.MCInfdevOldLevel(f, readonly=True)
except Exception:
continue
else:
worlds.append(lev)
if len(worlds) == 0:
alert("No worlds found! You should probably play Minecraft to create your first world.")
return
def loadWorld():
self.mcedit.loadFile(worldData[worldTable.selectedWorldIndex][3].filename)
def click_row(i, evt):
worldTable.selectedWorldIndex = i
if evt.num_clicks == 2:
loadWorld()
d.dismiss("Cancel")
worldTable = TableView(columns=[
TableColumn("Last Played", 250, "l"),
TableColumn("Level Name (filename)", 400, "l"),
TableColumn("Dims", 100, "r"),
])
def dateobj(lp):
try:
return datetime.utcfromtimestamp(lp / 1000.0)
except:
return datetime.utcfromtimestamp(0.0)
def dateFormat(lp):
try:
return lp.strftime("%x %X").decode('utf-8')
except:
return u"{0} seconds since the epoch.".format(lp)
def nameFormat(w):
if w.LevelName == w.displayName:
return w.LevelName
return u"{0} ({1})".format(w.LevelName, w.displayName)
worldData = [[dateFormat(d), nameFormat(w), str(w.dimensions.keys())[1:-1], w, d]
for w, d in ((w, dateobj(w.LastPlayed)) for w in worlds)]
worldData.sort(key=lambda (a, b, dim, w, d): d, reverse=True)
# worlds = [w[2] for w in worldData]
worldTable.selectedWorldIndex = 0
worldTable.num_rows = lambda: len(worldData)
worldTable.row_data = lambda i: worldData[i]
worldTable.row_is_selected = lambda x: x == worldTable.selectedWorldIndex
worldTable.click_row = click_row
worldPanel.add(worldTable)
worldPanel.shrink_wrap()
d = Dialog(worldPanel, ["Load", "Cancel"])
if d.present() == "Load":
loadWorld()
def askOpenFile(self):
self.mouseLookOff()
try:
filename = mcplatform.askOpenFile()
if filename:
self.parent.loadFile(filename)
except Exception:
logging.exception('Error while asking user for filename')
return
def createNewLevel(self):
self.mouseLookOff()
newWorldPanel = Widget()
newWorldPanel.w = newWorldPanel.h = 16
newWorldPanel.x = newWorldPanel.z = newWorldPanel.f = 0
newWorldPanel.y = 64
newWorldPanel.seed = 0
label = Label("Creating a new world.")
generatorPanel = GeneratorPanel()
xinput = mceutils.IntInputRow("X: ", ref=AttrRef(newWorldPanel, "x"))
yinput = mceutils.IntInputRow("Y: ", ref=AttrRef(newWorldPanel, "y"))
zinput = mceutils.IntInputRow("Z: ", ref=AttrRef(newWorldPanel, "z"))
finput = mceutils.IntInputRow("f: ", ref=AttrRef(newWorldPanel, "f"), min=0, max=3)
xyzrow = Row([xinput, yinput, zinput, finput])
seedinput = mceutils.IntInputRow("Seed: ", width=250, ref=AttrRef(newWorldPanel, "seed"))
winput = mceutils.IntInputRow("East-West Chunks: ", ref=AttrRef(newWorldPanel, "w"), min=0)
hinput = mceutils.IntInputRow("North-South Chunks: ", ref=AttrRef(newWorldPanel, "h"), min=0)
# grassinputrow = Row( (Label("Grass: ")
# from editortools import BlockButton
# blockInput = BlockButton(pymclevel.alphaMaterials, pymclevel.alphaMaterials.Grass)
# blockInputRow = Row( (Label("Surface: "), blockInput) )
types = ["Survival", "Creative"]
def gametype(t):
if t < len(types):
return types[t]
return "Unknown"
def action():
if gametypeButton.gametype < 2:
gametypeButton.gametype = 1 - gametypeButton.gametype
gametypeButton.text = gametype(gametypeButton.gametype)
gametypeButton = Button(gametype(0), action=action)
gametypeButton.gametype = 0
gametypeRow = Row((Label("Game Type:"), gametypeButton))
newWorldPanel.add(Column((label, Row([winput, hinput]), xyzrow, seedinput, gametypeRow, generatorPanel), align="l"))
newWorldPanel.shrink_wrap()
result = Dialog(client=newWorldPanel, responses=["Create", "Cancel"]).present()
if result == "Cancel":
return
filename = mcplatform.askCreateWorld(pymclevel.saveFileDir)
if not filename:
return
w = newWorldPanel.w
h = newWorldPanel.h
x = newWorldPanel.x
y = newWorldPanel.y
z = newWorldPanel.z
f = newWorldPanel.f
seed = newWorldPanel.seed or None
self.freezeStatus("Creating world...")
try:
newlevel = pymclevel.MCInfdevOldLevel(filename=filename, create=True, random_seed=seed)
# chunks = list(itertools.product(xrange(w / 2 - w + cx, w / 2 + cx), xrange(h / 2 - h + cz, h / 2 + cz)))
if generatorPanel.generatorChoice.selectedChoice == "Flatland":
y = generatorPanel.chunkHeight
newlevel.setPlayerPosition((x + 0.5, y + 2.8, z + 0.5))
newlevel.setPlayerOrientation((f * 90.0, 0.0))
newlevel.setPlayerSpawnPosition((x, y + 1, z))
newlevel.GameType = gametypeButton.gametype
newlevel.saveInPlace()
worker = generatorPanel.generate(newlevel, pymclevel.BoundingBox((x - w * 8, 0, z - h * 8), (w * 16, newlevel.Height, h * 16)))
if "Canceled" == mceutils.showProgress("Generating chunks...", worker, cancel=True):
raise RuntimeError("Canceled.")
if y < 64:
y = 64
newlevel.setBlockAt(x, y, z, pymclevel.alphaMaterials.Sponge.ID)
newlevel.saveInPlace()
self.loadFile(filename)
except Exception:
logging.exception(
'Error while creating world. {world => %s}' % filename
)
return
return newlevel
def confirmConstruction(self):
self.currentTool.confirm()
def selectionToChunks(self, remove=False, add=False):
box = self.selectionBox()
if box:
if box == self.level.bounds:
self.selectedChunks = set(self.level.allChunks)
return
selectedChunks = self.selectedChunks
boxedChunks = set(box.chunkPositions)
if boxedChunks.issubset(selectedChunks):
remove = True
if remove and not add:
selectedChunks.difference_update(boxedChunks)
else:
selectedChunks.update(boxedChunks)
self.selectionTool.selectNone()
def selectAll(self):
if self.currentViewport is self.chunkViewport:
self.selectedChunks = set(self.level.allChunks)
else:
self.selectionTool.selectAll()
def deselect(self):
self.selectionTool.deselect()
self.selectedChunks.clear()
def endSelection(self):
self.selectionTool.endSelection()
def cutSelection(self):
self.selectionTool.cutSelection()
def copySelection(self):
self.selectionTool.copySelection()
def pasteSelection(self):
schematic = self.getLastCopiedSchematic()
self.pasteSchematic(schematic)
def pasteSchematic(self, schematic):
if schematic == None:
return
self.currentTool.cancel()
craneTool = self.toolbar.tools[5] # xxx
self.currentTool = craneTool
craneTool.loadLevel(schematic)
def deleteSelectedBlocks(self):
self.selectionTool.deleteBlocks()
@mceutils.alertException
def undo(self):
if len(self.undoStack) == 0:
return
with mceutils.setWindowCaption("UNDOING - "):
self.freezeStatus("Undoing the previous operation...")
op = self.undoStack.pop()
op.undo()
changedBox = op.dirtyBox()
if changedBox is not None:
self.invalidateBox(changedBox)
if op.changedLevel:
self.addUnsavedEdit()
def invalidateBox(self, box):
self.renderer.invalidateChunksInBox(box)
def invalidateChunks(self, c):
self.renderer.invalidateChunks(c)
def invalidateAllChunks(self):
self.renderer.invalidateAllChunks()
def discardAllChunks(self):
self.renderer.discardAllChunks()
def addDebugString(self, string):
if self.debug:
self.debugString += string
averageFPS = 0.0
averageCPS = 0.0
shouldLoadAndRender = True
showWorkInfo = False
def gl_draw(self):
self.debugString = ""
self.inspectionString = ""
if not self.level:
return
if not self.shouldLoadAndRender:
return
self.renderer.loadVisibleChunks()
self.addWorker(self.renderer)
if self.currentTool.previewRenderer:
self.currentTool.previewRenderer.loadVisibleChunks()
self.addWorker(self.currentTool.previewRenderer)
self.frames += 1
frameDuration = self.getFrameDuration()
while frameDuration > (datetime.now() - self.frameStartTime): # if it's less than 0ms until the next frame, go draw. otherwise, go work.
self.doWorkUnit()
if self.showWorkInfo:
self.updateWorkInfoPanel()
frameStartTime = datetime.now()
timeDelta = frameStartTime - self.frameStartTime
# self.addDebugString("FrameStart: {0} CameraTick: {1}".format(frameStartTime, self.mainViewport.lastTick))
# self.addDebugString("D: %d, " % () )
self.currentFrameDelta = timeDelta
self.frameSamples.pop(0)
self.frameSamples.append(timeDelta)
frameTotal = numpy.sum(self.frameSamples)
self.averageFPS = 1000000. / ((frameTotal.microseconds + 1000000 * frameTotal.seconds) / float(len(self.frameSamples)) + 0.00001)
r = self.renderer
chunkTotal = numpy.sum(r.chunkSamples)
cps = 1000000. / ((chunkTotal.microseconds + 1000000 * chunkTotal.seconds) / float(len(r.chunkSamples)) + 0.00001)
self.averageCPS = cps
self.oldFrameStartTime = self.frameStartTime
self.frameStartTime = frameStartTime
if self.debug > 0:
self.debugString = "FPS: %0.1f/%0.1f, CPS: %0.1f, VD: %d, W: %d, WF: %d, " % (1000000. / (float(timeDelta.microseconds) + 0.000001),
self.averageFPS,
cps,
self.renderer.viewDistance,
len(self.workers),
self.renderer.workFactor)
if True: # xxx
self.debugString += "DL: {dl} ({dlcount}), Tx: {t}, gc: {g}, ".format(
dl=len(glutils.DisplayList.allLists), dlcount=glutils.gl.listCount,
t=len(glutils.Texture.allTextures), g=len(gc.garbage))
if self.renderer:
self.renderer.addDebugInfo(self.addDebugString)
# if self.onscreen:
def createWorkInfoPanel(self):
infos = []
for w in sorted(self.workers):
if isinstance(w, MCRenderer):
label = Label("Rendering chunks" + ((datetime.now().second / 3) % 3) * ".")
progress = Label("{0} chunks ({1} pending updates)".format(len(w.chunkRenderers), len(w.invalidChunkQueue)))
col = Column((label, progress), align="l", width=200)
infos.append(col)
elif isinstance(w, RunningOperation): # **FIXME** Where is RunningOperation supposed to come from? -David Sowder 20120311
label = Label(w.description)
progress = Label(w.progress)
col = Column((label, progress), align="l", width=200)
infos.append(col)
panel = Panel()
if len(infos):
panel.add(Column(infos))
panel.shrink_wrap()
return panel
workInfoPanel = None
def updateWorkInfoPanel(self):
if self.workInfoPanel:
self.workInfoPanel.set_parent(None)
self.workInfoPanel = self.createWorkInfoPanel()
if self.workInfoPanel:
self.workInfoPanel.topright = self.topright
self.add(self.workInfoPanel)
def doWorkUnit(self):
if len(self.workers):
try:
w = self.workers.popleft()
w.next()
self.workers.append(w)
except StopIteration:
if hasattr(w, "needsRedraw") and w.needsRedraw:
self.invalidate()
else:
time.sleep(0.001)
def updateInspectionString(self, blockPosition):
self.inspectionString += str(blockPosition) + ": "
x, y, z = blockPosition
cx, cz = x // 16, z // 16
try:
if self.debug:
if isinstance(self.level, pymclevel.MCIndevLevel):
bl = self.level.blockLightAt(*blockPosition)
blockID = self.level.blockAt(*blockPosition)
bdata = self.level.blockDataAt(*blockPosition)
self.inspectionString += "ID: %d:%d (%s), " % (
blockID, bdata, self.level.materials.names[blockID][bdata])
self.inspectionString += "Data: %d, Light: %d, " % (bdata, bl)
elif isinstance(self.level, pymclevel.ChunkedLevelMixin):
sl = self.level.skylightAt(*blockPosition)
bl = self.level.blockLightAt(*blockPosition)
bdata = self.level.blockDataAt(*blockPosition)
blockID = self.level.blockAt(*blockPosition)
self.inspectionString += "ID: %d:%d (%s), " % (
blockID, bdata, self.level.materials.names[blockID][bdata])
try:
path = self.level.getChunk(cx, cz).filename
except:
path = "chunks.dat"
self.inspectionString += "Data: %d, L: %d, SL: %d" % (
bdata, bl, sl)
try:
hm = self.level.heightMapAt(x, z)
self.inspectionString += ", H: %d" % hm
except:
pass
try:
tp = self.level.getChunk(cx, cz).TerrainPopulated
self.inspectionString += ", TP: %d" % tp
except:
pass
self.inspectionString += ", D: %d" % self.level.getChunk(cx, cz).dirty
self.inspectionString += ", NL: %d" % self.level.getChunk(cx, cz).needsLighting
try:
biome = self.level.getChunk(cx, cz).Biomes[x & 15, z & 15]
from pymclevel import biome_types
self.inspectionString += ", Bio: %s" % biome_types.biome_types[biome]
except AttributeError:
pass
if isinstance(self.level, pymclevel.pocket.PocketWorld):
ch = self.level.getChunk(cx, cz)
self.inspectionString += ", DC: %s" % ch.DirtyColumns[z & 15, x & 15]
self.inspectionString += ", Ch(%d, %d): %s" % (cx, cz, path)
else: # classic
blockID = self.level.blockAt(*blockPosition)
self.inspectionString += "ID: %d (%s), " % (
blockID, self.level.materials.names[blockID][0])
except Exception, e:
self.inspectionString += "Chunk {0} had an error: {1!r}".format((int(numpy.floor(blockPosition[0])) >> 4, int(numpy.floor(blockPosition[2])) >> 4), e)
pass
def drawWireCubeReticle(self, color=(1.0, 1.0, 1.0, 1.0), position=None):
GL.glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire)
GL.glEnable(GL.GL_POLYGON_OFFSET_FILL)
blockPosition, faceDirection = self.blockFaceUnderCursor
blockPosition = position or blockPosition
mceutils.drawTerrainCuttingWire(pymclevel.BoundingBox(blockPosition, (1, 1, 1)), c1=color)
GL.glDisable(GL.GL_POLYGON_OFFSET_FILL)
def drawString(self, x, y, color, string):
return
def freezeStatus(self, string):
return
# GL.glColor(1.0, 0., 0., 1.0)
#
# # glDrawBuffer(GL.GL_FRONT)
# GL.glMatrixMode(GL.GL_PROJECTION)
# GL.glPushMatrix()
# glRasterPos(50, 100)
# for i in string:
# glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, ord(i))
#
# # glDrawBuffer(GL.GL_BACK)
# GL.glMatrixMode(GL.GL_PROJECTION)
# GL.glPopMatrix()
# glFlush()
# display.flip()
# # while(True): pass
def selectionSize(self):
return self.selectionTool.selectionSize()
def selectionBox(self):
return self.selectionTool.selectionBox()
def selectionChanged(self):
if not self.currentTool.toolEnabled():
self.toolbar.selectTool(-1)
self.currentTool.selectionChanged()
def addOperation(self, op):
if self.recordUndo:
self.undoStack.append(op)
if len(self.undoStack) > self.undoLimit:
self.undoStack.pop(0)
self.performWithRetry(op)
recordUndo = True
def performWithRetry(self, op):
try:
op.perform(self.recordUndo)
except MemoryError:
self.invalidateAllChunks()
op.perform(self.recordUndo)
def quit(self):
self.mouseLookOff()
self.mcedit.confirm_quit()
mouseWasCaptured = False
def showControls(self):
self.controlPanel.present(False)
infoPanel = None
def showChunkRendererInfo(self):
if self.infoPanel:
self.infoPanel.set_parent(None)
return
self.infoPanel = infoPanel = Widget(bg_color=(0, 0, 0, 80))
infoPanel.add(Label(""))
def idleHandler(evt):
x, y, z = self.blockFaceUnderCursor[0]
cx, cz = x // 16, z // 16
cr = self.renderer.chunkRenderers.get((cx, cz))
if None is cr:
return
crNames = ["%s - %0.1fkb" % (type(br).__name__, br.bufferSize() / 1000.0) for br in cr.blockRenderers]
infoLabel = Label("\n".join(crNames))
infoPanel.remove(infoPanel.subwidgets[0])
infoPanel.add(infoLabel)
infoPanel.shrink_wrap()
self.invalidate()
infoPanel.idleevent = idleHandler
infoPanel.topleft = self.viewportContainer.topleft
self.add(infoPanel)
infoPanel.click_outside_response = -1
# infoPanel.present()
## def testGLSL(self):
## print "Hello"
## level = MCLevel.fromFile("mrchunk.schematic")
## blocks = level.Blocks
## blockCount = level.Width * level.Length * level.Height,
## fbo = glGenFramebuffersEXT(1)
## glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)
##
## print blockCount, fbo
##
## destBlocks = numpy.zeros(blockCount, 'uint8')
## (sourceTex, destTex) = glGenTextures(2)
##
## glBindTexture(GL_TEXTURE_3D, sourceTex)
## glTexImage3D(GL_TEXTURE_3D, 0, 1,
## level.Width, level.Length, level.Height,
## 0, GL_RED, GL.GL_UNSIGNED_BYTE,
## blocks)
##
## # return
##
## glBindTexture(GL.GL_TEXTURE_2D, destTex)
## glTexImage2D(GL.GL_TEXTURE_2D, 0, 1,
## level.Width, level.Length,
## 0, GL_RED, GL.GL_UNSIGNED_BYTE, destBlocks)
## glTexParameter(GL.GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
## glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL.GL_TEXTURE_2D, destTex, 0)
##
## vertShader = glCreateShader(GL_VERTEX_SHADER)
##
## vertShaderSource = """
## void main()
## {
## gl_Position = gl_Vertex
## }
## """
##
## glShaderSource(vertShader, vertShaderSource);
## glCompileShader(vertShader);
##
## fragShader = glCreateShader(GL_FRAGMENT_SHADER)
##
## fragShaderSource = """