"""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([("", "", ""), ("", "", entitySum)]) rows.extend([(id[0], id[1], count) for (id, count) in sorted(entityCounts.iteritems())]) if tileEntitySum: rows.extend([("", "", ""), ("", "", 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 = """ ## void main() ## { ## gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75); ## } ## """ ## ## glShaderSource(fragShader, fragShaderSource); ## glCompileShader(fragShader); ## ## ## ## prog = glCreateProgram() ## ## glAttachShader(prog, vertShader) ## glAttachShader(prog, fragShader) ## glLinkProgram(prog) ## ## glUseProgram(prog); ## # return ## GL.glDisable(GL.GL_DEPTH_TEST); ## GL.glVertexPointer(2, GL.GL_FLOAT, 0, [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]); ## GL.glDrawArrays(GL.GL_QUADS, 0, 4); ## GL.glEnable(GL.GL_DEPTH_TEST); ## ## glFlush(); ## destBlocks = glGetTexImage(GL.GL_TEXTURE_2D, 0, GL_RED, GL.GL_UNSIGNED_BYTE); ## print destBlocks, destBlocks[0:8]; ## raise SystemExit; def handleMemoryError(self): if self.renderer.viewDistance <= 2: raise MemoryError("Out of memory. Please restart MCEdit.") if hasattr(self.level, 'compressAllChunks'): self.level.compressAllChunks() self.toolbar.selectTool(-1) self.renderer.viewDistance = self.renderer.viewDistance - 4 self.renderer.discardAllChunks() logging.warning( 'Out of memory, decreasing view distance. {view => %s}' % ( self.renderer.viewDistance ) ) Settings.viewDistance.set(self.renderer.viewDistance) config.saveConfig() class EditorToolbar(GLOrtho): # is_gl_container = True toolbarSize = (184, 24) tooltipsUp = True toolbarTextureSize = (182., 22.) currentToolTextureRect = (0., 22., 24., 24.) toolbarWidthRatio = 0.5 # toolbar's width as fraction of screen width. def toolbarSizeForScreenWidth(self, width): f = max(1, int(width + 398) / 400) return map(lambda x: x * f, self.toolbarSize) # return ( self.toolbarWidthRatio * width, # self.toolbarWidthRatio * width * self.toolbarTextureSize[1] / self.toolbarTextureSize[0] ) def __init__(self, rect, tools, *args, **kw): GLOrtho.__init__(self, xmin=0, ymin=0, xmax=self.toolbarSize[0], ymax=self.toolbarSize[1], near=-4.0, far=4.0) self.size = self.toolbarTextureSize self.tools = tools for i, t in enumerate(tools): t.toolNumber = i t.hotkey = i + 1 self.toolTextures = {} self.toolbarDisplayList = glutils.DisplayList() self.reloadTextures() def set_parent(self, parent): GLOrtho.set_parent(self, parent) self.parent_resized(0, 0) def parent_resized(self, dw, dh): self.size = self.toolbarSizeForScreenWidth(self.parent.width) self.centerx = self.parent.centerx self.bottom = self.parent.viewportContainer.bottom # xxx resize children when get def getTooltipText(self): toolNumber = self.toolNumberUnderMouse(mouse.get_pos()) return self.tools[toolNumber].tooltipText tooltipText = property(getTooltipText) def toolNumberUnderMouse(self, pos): x, y = self.global_to_local(pos) (tx, ty, tw, th) = self.toolbarRectInWindowCoords() toolNumber = 9. * x / tw return min(int(toolNumber), 8) def mouse_down(self, evt): if self.parent.level: toolNo = self.toolNumberUnderMouse(evt.pos) if toolNo < 0 or toolNo > 8: return if evt.button == 1: self.selectTool(toolNo) if evt.button == 3: self.showToolOptions(toolNo) def showToolOptions(self, toolNumber): if toolNumber < len(self.tools) and toolNumber >= 0: t = self.tools[toolNumber] # if not t.toolEnabled(): # return if t.optionsPanel: t.optionsPanel.present() def selectTool(self, toolNumber): ''' pass a number outside the bounds to pick the selection tool''' if toolNumber >= len(self.tools) or toolNumber < 0: toolNumber = 0 t = self.tools[toolNumber] if not t.toolEnabled(): return if self.parent.currentTool == t: self.parent.currentTool.toolReselected() else: self.parent.selectionTool.hidePanel() if self.parent.currentTool != None: self.parent.currentTool.cancel() self.parent.currentTool = t self.parent.currentTool.toolSelected() def removeToolPanels(self): for tool in self.tools: tool.hidePanel() def toolbarRectInWindowCoords(self): """returns a rectangle (x, y, w, h) representing the toolbar's location in the window. use for hit testing.""" (pw, ph) = self.parent.size pw = float(pw) ph = float(ph) x, y = self.toolbarSizeForScreenWidth(pw) tw = x * 180. / 182. th = y * 20. / 22. tx = (pw - tw) / 2 ty = ph - th * 22. / 20. return tx, ty, tw, th def toolTextureChanged(self): self.toolbarDisplayList.invalidate() def reloadTextures(self): self.toolTextureChanged() self.guiTexture = mceutils.loadPNGTexture('gui.png') self.toolTextures = {} for tool in self.tools: if hasattr(tool, 'reloadTextures'): tool.reloadTextures() if hasattr(tool, 'markerList'): tool.markerList.invalidate() def drawToolbar(self): GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glColor(1., 1., 1., 1.) w, h = self.toolbarTextureSize self.guiTexture.bind() GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array(( 1, h + 1, 0.5, w + 1, h + 1, 0.5, w + 1, 1, 0.5, 1, 1, 0.5, ), dtype="f4")) GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array(( 0, 0, w, 0, w, h, 0, h, ), dtype="f4")) GL.glDrawArrays(GL.GL_QUADS, 0, 4) for i in range(len(self.tools)): tool = self.tools[i] if tool.toolIconName is None: continue try: if not tool.toolIconName in self.toolTextures: filename = "toolicons" + os.sep + "{0}.png".format(tool.toolIconName) self.toolTextures[tool.toolIconName] = mceutils.loadPNGTexture(filename) x = 20 * i + 4 y = 4 w = 16 h = 16 self.toolTextures[tool.toolIconName].bind() GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array(( x, y + h, 1, x + w, y + h, 1, x + w, y, 1, x, y, 1, ), dtype="f4")) GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array(( 0, 0, w * 16, 0, w * 16, h * 16, 0, h * 16, ), dtype="f4")) GL.glDrawArrays(GL.GL_QUADS, 0, 4) except Exception: logging.exception('Error while drawing toolbar.') GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) gfont = None def gl_draw(self): GL.glEnable(GL.GL_TEXTURE_2D) GL.glEnable(GL.GL_BLEND) self.toolbarDisplayList.call(self.drawToolbar) GL.glColor(1.0, 1.0, 0.0) # GL.glEnable(GL.GL_BLEND) # with gl.glPushMatrix(GL_TEXTURE): # GL.glLoadIdentity() # self.gfont.flatPrint("ADLADLADLADLADL") try: currentToolNumber = self.tools.index(self.parent.currentTool) except ValueError: pass else: # draw a bright rectangle around the current tool (texx, texy, texw, texh) = self.currentToolTextureRect # =================================================================== # tx = tx + tw * float(currentToolNumber) / 9. # tx = tx - (2./20.)*float(tw) / 9 # ty = ty - (2./20.)*th # # tw = th # tw = (24./20.)* th # th = tw # # =================================================================== tx = 20. * float(currentToolNumber) ty = 0. tw = 24. th = 24. GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) self.guiTexture.bind() GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array(( tx, ty, 2, tx + tw, ty, 2, tx + tw, ty + th, 2, tx, ty + th, 2, ), dtype="f4")) GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array(( texx, texy + texh, texx + texw, texy + texh, texx + texw, texy, texx, texy, ), dtype="f4")) GL.glDrawArrays(GL.GL_QUADS, 0, 4) GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glDisable(GL.GL_TEXTURE_2D) redOutBoxes = numpy.zeros(9 * 4 * 2, dtype='float32') cursor = 0 for i in range(len(self.tools)): t = self.tools[i] if t.toolEnabled(): continue redOutBoxes[cursor:cursor + 8] = [ 4 + i * 20, 4, 4 + i * 20, 20, 20 + i * 20, 20, 20 + i * 20, 4, ] cursor += 8 if cursor: GL.glColor(1.0, 0.0, 0.0, 0.3) GL.glVertexPointer(2, GL.GL_FLOAT, 0, redOutBoxes) GL.glDrawArrays(GL.GL_QUADS, 0, cursor / 2) GL.glDisable(GL.GL_BLEND)