class EditorSession(QtCore.QObject): def __init__(self, filename, configuredBlocks, readonly=False, progressCallback=None): """ :param filename: :type filename: str :param configuredBlocks: :type configuredBlocks: dict??? :param readonly: :type readonly: bool :param progressCallback: :type progressCallback: callable :return: :rtype: """ from mcedit2 import __version__ as v progressMax = 8 # fixme if progressCallback is None: def progress(status): pass else: def progress(status): progressCallback(progress.progressCount, progressMax, status) progress.progressCount += 1 progress.progressCount = 0 QtCore.QObject.__init__(self) self.undoStack = MCEUndoStack() self.resourceLoader = minecraftinstall.getResourceLoaderForFilename(filename) self.loader = None self.blockModels = None self.textureAtlas = None self.editorTab = None self.filename = filename self.dockWidgets = [] self.undoBlock = None self.currentTool = None self.dirty = False self.configuredBlocks = None self.copiedSchematic = None # xxx should be app global!! """:type : WorldEditor""" # --- Open world editor --- try: progress("Creating WorldEditor...") self.worldEditor = WorldEditor(filename, readonly=readonly) except UndoFolderExists: msgBox = QtGui.QMessageBox() msgBox.setIcon(QtGui.QMessageBox.Warning) msgBox.setWindowTitle(self.tr("MCEdit %(version)s") % {"version": v}) msgBox.setText(self.tr("This world was not properly closed by MCEdit.")) msgBox.setInformativeText(self.tr( "MCEdit may have crashed. An undo history was found for this world. You may try " "to resume editing with the saved undo history, or start over with the current " "state of the world.")) resumeBtn = msgBox.addButton("Resume Editing", QtGui.QMessageBox.ApplyRole) msgBox.addButton("Discard History", QtGui.QMessageBox.DestructiveRole) # msgBox.exec_() # clicked = msgBox.clickedButton() # xxxxx resume editing not implemented in session - need to restore undo history! clicked = None resume = clicked is resumeBtn try: self.worldEditor = WorldEditor(filename, readonly=readonly, resume=resume) except NotImplementedError: NotImplementedYet() raise IOError("Uh-oh") self.worldEditor.requireRevisions() self.currentDimension = None progress("Creating menus...") # --- Menus --- self.menus = [] # - Edit - self.menuEdit = QtGui.QMenu(self.tr("Edit")) self.menuEdit.setObjectName("menuEdit") self.actionCut = QtGui.QAction(self.tr("Cut"), self, triggered=self.cut, enabled=False) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionCut.setObjectName("actionCut") self.actionCopy = QtGui.QAction(self.tr("Copy"), self, triggered=self.copy, enabled=False) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionCopy.setObjectName("actionCopy") self.actionPaste = QtGui.QAction(self.tr("Paste"), self, triggered=self.paste, enabled=False) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.actionPaste.setObjectName("actionPaste") self.actionPaste_Blocks = QtGui.QAction(self.tr("Paste Blocks"), self, triggered=self.pasteBlocks, enabled=False) self.actionPaste_Blocks.setShortcut(QtGui.QKeySequence("Ctrl+Shift+V")) self.actionPaste_Blocks.setObjectName("actionPaste_Blocks") self.actionPaste_Entities = QtGui.QAction(self.tr("Paste Entities"), self, triggered=self.pasteEntities, enabled=False) self.actionPaste_Entities.setShortcut(QtGui.QKeySequence("Ctrl+Alt+V")) self.actionPaste_Entities.setObjectName("actionPaste_Entities") self.actionClear = QtGui.QAction(self.tr("Delete"), self, triggered=self.deleteSelection, enabled=False) self.actionClear.setShortcut(QtGui.QKeySequence.Delete) self.actionClear.setObjectName("actionClear") self.actionDeleteBlocks = QtGui.QAction(self.tr("Delete Blocks"), self, triggered=self.deleteBlocks, enabled=False) self.actionDeleteBlocks.setShortcut(QtGui.QKeySequence("Shift+Del")) self.actionDeleteBlocks.setObjectName("actionDeleteBlocks") self.actionDeleteEntities = QtGui.QAction(self.tr("Delete Entities"), self, triggered=self.deleteEntities, enabled=False) self.actionDeleteEntities.setShortcut(QtGui.QKeySequence("Shift+Alt+Del")) self.actionDeleteEntities.setObjectName("actionDeleteEntities") self.actionFill = QtGui.QAction(self.tr("Fill"), self, triggered=self.fill, enabled=False) self.actionFill.setShortcut(QtGui.QKeySequence("Shift+Ctrl+F")) self.actionFill.setObjectName("actionFill") self.actionFindReplace = QtGui.QAction(self.tr("Find/Replace"), self, triggered=self.findReplace, enabled=True) self.actionFindReplace.setShortcut(QtGui.QKeySequence.Find) self.actionFindReplace.setObjectName("actionFindReplace") self.actionAnalyze = QtGui.QAction(self.tr("Analyze"), self, triggered=self.analyze, enabled=True) # self.actionAnalyze.setShortcut(QtGui.QKeySequence.Analyze) self.actionAnalyze.setObjectName("actionAnalyze") undoAction = self.undoStack.createUndoAction(self.menuEdit) undoAction.setShortcut(QtGui.QKeySequence.Undo) redoAction = self.undoStack.createRedoAction(self.menuEdit) redoAction.setShortcut(QtGui.QKeySequence.Redo) self.menuEdit.addAction(undoAction) self.menuEdit.addAction(redoAction) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionCut) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionPaste) self.menuEdit.addAction(self.actionPaste_Blocks) self.menuEdit.addAction(self.actionPaste_Entities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionClear) self.menuEdit.addAction(self.actionDeleteBlocks) self.menuEdit.addAction(self.actionDeleteEntities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFill) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFindReplace) self.menuEdit.addAction(self.actionAnalyze) self.menus.append(self.menuEdit) # - Select - self.menuSelect = QtGui.QMenu(self.tr("Select")) self.actionSelectAll = QtGui.QAction(self.tr("Select All"), self, triggered=self.selectAll) self.actionSelectAll.setShortcut(QtGui.QKeySequence.SelectAll) self.menuSelect.addAction(self.actionSelectAll) self.actionDeselect = QtGui.QAction(self.tr("Deselect"), self, triggered=self.deselect) self.actionDeselect.setShortcut(QtGui.QKeySequence("Ctrl+D")) self.menuSelect.addAction(self.actionDeselect) self.menus.append(self.menuSelect) # - Import/Export - self.menuImportExport = QtGui.QMenu(self.tr("Import/Export")) self.actionExport = QtGui.QAction(self.tr("Export"), self, triggered=self.export) self.actionExport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+E")) self.menuImportExport.addAction(self.actionExport) self.actionImport = QtGui.QAction(self.tr("Import"), self, triggered=self.import_) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+D")) self.menuImportExport.addAction(self.actionImport) self.actionImport = QtGui.QAction(self.tr("Show Exports Library"), self, triggered=QtGui.qApp.libraryDockWidget.toggleViewAction().trigger) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+L")) self.menuImportExport.addAction(self.actionImport) self.menus.append(self.menuImportExport) # - Chunk - self.menuChunk = QtGui.QMenu(self.tr("Chunk")) self.actionDeleteChunks = QtGui.QAction(self.tr("Delete Chunks"), self, triggered=self.deleteChunks) self.actionCreateChunks = QtGui.QAction(self.tr("Create Chunks"), self, triggered=self.createChunks) self.actionRepopChunks = QtGui.QAction(self.tr("Mark Chunks For Repopulation"), self, triggered=self.repopChunks) self.menuChunk.addAction(self.actionDeleteChunks) self.menuChunk.addAction(self.actionCreateChunks) self.menuChunk.addAction(self.actionRepopChunks) self.menus.append(self.menuChunk) # --- Resources --- self.geometryCache = GeometryCache() progress("Loading textures and models...") self.setConfiguredBlocks(configuredBlocks) # Must be called after resourceLoader is in place self.editorOverlay = scenenode.Node() self.biomeTypes = BiomeTypes() # --- Panels --- progress("Loading panels...") self.playerPanel = PlayerPanel(self) self.mapPanel = MapPanel(self) self.worldInfoPanel = WorldInfoPanel(self) self.panels = [self.playerPanel, self.worldInfoPanel, self.mapPanel] self.panelActions = [] # --- Tools --- progress("Loading tools...") self.toolClasses = list(editortools.ToolClasses()) self.toolActionGroup = QtGui.QActionGroup(self) self.tools = [cls(self) for cls in self.toolClasses] self.toolActions = [tool.pickToolAction() for tool in self.tools] self.actionsByName = {action.toolName: action for action in self.toolActions} for tool in self.tools: tool.toolPicked.connect(self.chooseTool) for action in self.toolActions: self.toolActionGroup.addAction(action) self.selectionTool = self.getTool("Select") self.moveTool = self.getTool("Move") # --- Dimensions --- def _dimChanged(f): def _changed(): self.gotoDimension(f) return _changed dimButton = self.changeDimensionButton = QtGui.QToolButton() dimButton.setText(self.dimensionMenuLabel("")) dimAction = self.changeDimensionAction = QtGui.QWidgetAction(self) dimAction.setDefaultWidget(dimButton) dimMenu = self.dimensionsMenu = QtGui.QMenu() for dimName in self.worldEditor.listDimensions(): displayName = self.dimensionDisplayName(dimName) action = dimMenu.addAction(displayName) action._changed = _dimChanged(dimName) action.triggered.connect(action._changed) dimButton.setMenu(dimMenu) dimButton.setPopupMode(QtGui.QToolButton.InstantPopup) self.panelActions.append(dimAction) mcVersionButton = self.changeMCVersionButton = QtGui.QToolButton() mcVersionButton.setText(self.minecraftVersionLabel()) mcVersionAction = self.changeMCVersionAction = QtGui.QWidgetAction(self) mcVersionAction.setDefaultWidget(mcVersionButton) self.mcVersionMenu = QtGui.QMenu() mcVersionButton.setMenu(self.mcVersionMenu) mcVersionButton.setPopupMode(QtGui.QToolButton.InstantPopup) self.panelActions.append(mcVersionAction) resourcePackButton = self.changeResourcePackButton = QtGui.QToolButton() resourcePackButton.setText(self.resourcePackLabel()) resourcePackAction = self.changeResourcePackAction = QtGui.QWidgetAction(self) resourcePackAction.setDefaultWidget(resourcePackButton) self.resourcePackMenu = QtGui.QMenu() resourcePackButton.setMenu(self.resourcePackMenu) resourcePackButton.setPopupMode(QtGui.QToolButton.InstantPopup) self.panelActions.append(resourcePackAction) self._updateVersionsAndResourcePacks() progress("Loading overworld dimension") self.gotoDimension("") # --- Editor stuff --- progress("Creating EditorTab...") self.editorTab = EditorTab(self) self.toolChanged.connect(self.toolDidChange) self.editorTab.urlsDropped.connect(self.urlsWereDropped) self.editorTab.mapItemDropped.connect(self.mapItemWasDropped) self.undoStack.indexChanged.connect(self.undoIndexChanged) self.findReplaceDialog = FindReplaceDialog(self) for resultsWidget in self.findReplaceDialog.resultsWidgets: self.dockWidgets.append((Qt.BottomDockWidgetArea, resultsWidget)) self.inspectorWidget = InspectorWidget(self) self.inspectorDockWidget = QtGui.QDockWidget(self.tr("Inspector"), objectName="inspector") self.inspectorDockWidget.setWidget(self.inspectorWidget) self.inspectorDockWidget.hide() self.dockWidgets.append((Qt.RightDockWidgetArea, self.inspectorDockWidget)) if len(self.toolActions): # Must be called after toolChanged is connected to editorTab self.toolActions[0].trigger() if hasattr(progress, 'progressCount') and progress.progressCount != progressMax: log.info("Update progressMax to %d, please.", progress.progressCount) def minecraftVersionLabel(self): version = minecraftinstall.currentVersionOption.value() return "Minecraft Version: %s" % version def resourcePackLabel(self): resourcePack = minecraftinstall.currentResourcePackOption.value() return "Resource Pack: %s" % resourcePack def _updateVersionsAndResourcePacks(self): self.mcVersionMapper = QtCore.QSignalMapper() self.mcVersionMapper.mapped[str].connect(self.changeMCVersion) self.resourcePackMapper = QtCore.QSignalMapper() self.resourcePackMapper.mapped[str].connect(self.changeResourcePack) self.mcVersionMenu.clear() self.resourcePackMenu.clear() defaultAction = self.resourcePackMenu.addAction(self.tr("(No resource pack)")) self.resourcePackMapper.setMapping(defaultAction, "") install = minecraftinstall.GetInstalls().getCurrentInstall() for version in sorted(install.versions, reverse=True): versionAction = self.mcVersionMenu.addAction(version) self.mcVersionMapper.setMapping(versionAction, version) versionAction.triggered.connect(self.mcVersionMapper.map) for resourcePack in sorted(install.resourcePacks): resourcePackAction = self.resourcePackMenu.addAction(resourcePack) self.resourcePackMapper.setMapping(resourcePackAction, resourcePack) resourcePackAction.triggered.connect(self.resourcePackMapper.map) def changeResourcePack(self, packName): minecraftinstall.currentResourcePackOption.setValue(packName or "") self.resourceLoader = minecraftinstall.getResourceLoaderForFilename(self.filename) self.changeResourcePackButton.setText(self.resourcePackLabel()) self.reloadModels() def changeMCVersion(self, version): minecraftinstall.currentVersionOption.setValue(version) self.resourceLoader = minecraftinstall.getResourceLoaderForFilename(self.filename) self.changeMCVersionButton.setText(self.minecraftVersionLabel()) self.reloadModels() # Connecting these signals to the EditorTab creates a circular reference through # the Qt objects, preventing the EditorSession from being destroyed def focusWorldView(self): self.editorTab.currentView().setFocus() def updateView(self): self.editorTab.currentView().update() def toolDidChange(self, tool): self.editorTab.toolDidChange(tool) # --- Block config --- # Emitted when configuredBlocks is changed. TextureAtlas and BlockModels will also have changed. configuredBlocksChanged = QtCore.Signal() def setConfiguredBlocks(self, configuredBlocks): blocktypes = self.worldEditor.blocktypes if self.configuredBlocks is not None: # Remove all previously configured blocks deadJsons = [] for json in blocktypes.blockJsons: if '__configured__' in json: deadJsons.append(json) deadIDs = set((j['internalName'], j['meta']) for j in deadJsons) blocktypes.allBlocks[:] = [ bt for bt in blocktypes.allBlocks if (bt.internalName, bt.meta) not in deadIDs ] for json in deadJsons: internalName = json['internalName'] fakeState = json['blockState'] blocktypes.blockJsons.remove(json) ID = blocktypes.IDsByName[internalName] del blocktypes.IDsByState[internalName + fakeState] del blocktypes.statesByID[ID, json['meta']] for blockDef in configuredBlocks: internalName = blockDef.internalName if internalName not in blocktypes.IDsByName: # no ID mapped to this name, skip continue if blockDef.meta == 0: blockType = blocktypes[internalName] blockJson = blockType.json else: # not automatically created by FML mapping loader ID = blocktypes.IDsByName[internalName] fakeState = '[%d]' % blockDef.meta nameAndState = internalName + fakeState blocktypes.blockJsons[nameAndState] = { 'displayName': internalName, 'internalName': internalName, 'blockState': fakeState, 'unknown': False, 'meta': blockDef.meta, } blockType = BlockType(ID, blockDef.meta, blocktypes) blocktypes.allBlocks.append(blockType) blocktypes.IDsByState[nameAndState] = ID, blockDef.meta blocktypes.statesByID[ID, blockDef.meta] = nameAndState blockJson = blockType.json blockJson['forcedModel'] = blockDef.modelPath blockJson['forcedModelTextures'] = blockDef.modelTextures blockJson['forcedModelRotation'] = blockDef.modelRotations blockJson['forcedRotationFlags'] = blockDef.rotationFlags blockJson['__configured__'] = True self.configuredBlocks = configuredBlocks self.reloadModels() self.configuredBlocksChanged.emit() def reloadModels(self): self.blockModels = BlockModels(self.worldEditor.blocktypes, self.resourceLoader) self.textureAtlas = TextureAtlas(self.worldEditor, self.resourceLoader, self.blockModels) # May be called before editorTab is created if self.editorTab: for view in self.editorTab.views: view.setTextureAtlas(self.textureAtlas) # --- Selection --- selectionChanged = QtCore.Signal(BoundingBox) _currentSelection = None @property def currentSelection(self): return self._currentSelection @currentSelection.setter def currentSelection(self, box): self._currentSelection = box self.enableSelectionCommands(box is not None and box.volume != 0) self.enableChunkSelectionCommands(box is not None) self.selectionChanged.emit(box) def enableSelectionCommands(self, enable): self.actionCut.setEnabled(enable) self.actionCopy.setEnabled(enable) self.actionPaste.setEnabled(enable) self.actionPaste_Blocks.setEnabled(enable) self.actionPaste_Entities.setEnabled(enable) self.actionClear.setEnabled(enable) self.actionDeleteBlocks.setEnabled(enable) self.actionDeleteEntities.setEnabled(enable) self.actionFill.setEnabled(enable) self.actionExport.setEnabled(enable) def enableChunkSelectionCommands(self, enable): self.actionDeleteChunks.setEnabled(enable) self.actionCreateChunks.setEnabled(enable) self.actionRepopChunks.setEnabled(enable) # --- Menu commands --- # - World - def save(self): self.undoStack.clearUndoBlock() saveTask = self.worldEditor.saveChangesIter() showProgress("Saving...", saveTask) self.dirty = False # - Edit - def cut(self): command = SimpleRevisionCommand(self, "Cut") with command.begin(): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Cutting...", task) task = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Cutting...", task) self.undoStack.push(command) def copy(self): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Copying...", task) def paste(self): if self.copiedSchematic is None: return view = self.editorTab.currentView() imp = PendingImport(self.copiedSchematic, view.mouseBlockPos, self.tr("<Pasted Object>")) command = PasteImportCommand(self, imp, "Paste") self.undoStack.push(command) def pasteBlocks(self): NotImplementedYet() def pasteEntities(self): NotImplementedYet() def findReplace(self): self.findReplaceDialog.exec_() def analyze(self): if self.currentSelection is None: return task = self.currentDimension.analyzeIter(self.currentSelection) showProgress("Analyzing...", task) outputDialog = AnalyzeOutputDialog(self, task.blocks, task.entityCounts, task.tileEntityCounts, task.dimension.worldEditor.displayName) def deleteSelection(self): command = SimpleRevisionCommand(self, "Delete") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) task = ComposeOperations(fillTask, entitiesTask) showProgress("Deleting...", task) self.pushCommand(command) def deleteBlocks(self): command = SimpleRevisionCommand(self, "Delete Blocks") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Deleting...", fillTask) self.pushCommand(command) def deleteEntities(self): command = SimpleRevisionCommand(self, "Delete Entities") with command.begin(): entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) showProgress("Deleting...", entitiesTask) self.pushCommand(command) def fill(self): fillCommand(self) # - Select - def selectAll(self): command = SelectCommand(self, self.currentDimension.bounds, self.tr("Select All")) self.pushCommand(command) def deselect(self): command = SelectCommand(self, None) command.setText(self.tr("Deselect")) self.pushCommand(command) # - Chunk - def deleteChunks(self): if self.currentSelection is None: return command = SimpleRevisionCommand(self, self.tr("Delete Chunks")) with command.begin(): for cx in range(self.currentSelection.mincx, self.currentSelection.maxcx): for cz in range(self.currentSelection.mincz, self.currentSelection.maxcz): self.currentDimension.deleteChunk(cx, cz) self.pushCommand(command) def createChunks(self): QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Not implemented.", "Create chunks is not implemented yet!") def repopChunks(self): QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Not implemented.", "Repop chunks is not implemented yet!") # - Dimensions - dimensionChanged = QtCore.Signal(object) _dimDisplayNames = {"": "Overworld", "DIM-1": "Nether", "DIM1": "The End", } def dimensionDisplayName(self, dimName): return self._dimDisplayNames.get(dimName, dimName) def dimensionMenuLabel(self, dimName): return self.tr("Dimension: %s" % self.dimensionDisplayName(dimName)) def gotoDimension(self, dimName): dim = self.worldEditor.getDimension(dimName) if dim is self.currentDimension: return log.info("Going to dimension %s", dimName) self.changeDimensionButton.setText(self.dimensionMenuLabel(dimName)) self.currentDimension = dim self.loader = chunkloader.ChunkLoader(self.currentDimension) self.loader.chunkCompleted.connect(self.chunkDidComplete) self.loader.allChunksDone.connect(self.updateView) self.revisionChanged.connect(self.loader.revisionDidChange) self.dimensionChanged.emit(dim) # - Import/export - def import_(self): # prompt for a file to import startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.mainWindow, self.tr("Import"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: self.importSchematic(filename) def export(self): # prompt for filename and format. maybe use custom browser to save to export library?? startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.mainWindow, self.tr("Export Schematic"), startingDir, "Schematic files (*.schematic)") if result: filename = result[0] if filename: task = self.currentDimension.exportSchematicIter(self.currentSelection) schematic = showProgress("Copying...", task) schematic.saveToFile(filename) # --- Drag-and-drop --- def urlsWereDropped(self, mimeData, position, face): log.info("URLs dropped:\n%s", mimeData.urls()) def mapItemWasDropped(self, mimeData, position, face): log.info("Map item dropped.") assert mimeData.hasFormat(MimeFormats.MapItem) mapIDString = mimeData.data(MimeFormats.MapItem).data() mapIDs = mapIDString.split(", ") mapIDs = [int(m) for m in mapIDs] mapID = mapIDs[0] # xxx only one at a time for now position = position + face.vector x, y, z = position cx = x >> 4 cz = z >> 4 try: chunk = self.currentDimension.getChunk(cx, cz) except ChunkNotPresent: log.info("Refusing to import map into non-existent chunk %s", (cx, cz)) return ref = self.worldEditor.createEntity("ItemFrame") if ref is None: return facing = ref.facingForMCEditFace(face) if facing is None: # xxx by camera vector? facing = ref.SouthFacing ref.Item.Damage = mapID ref.Item.id = "minecraft:filled_map" ref.Position = position + (0.5, 0.5, 0.5) ref.TilePos = position # 1.7/1.8 issues should be handled by ref... ref.Facing = facing log.info("Created map ItemFrame with ID %s, importing...", mapID) command = SimpleRevisionCommand(self, self.tr("Import map %(mapID)s") % {"mapID": mapID}) with command.begin(): chunk.addEntity(ref) log.info(nbt.nested_string(ref.rootTag)) self.pushCommand(command) # --- Library support --- def importSchematic(self, filename): schematic = WorldEditor(filename, readonly=True) ray = self.editorTab.currentView().rayAtCenter() pos, face = rayCastInBounds(ray, self.currentDimension) if pos is None: pos = ray.point name = os.path.basename(filename) imp = PendingImport(schematic, pos, name) command = PasteImportCommand(self, imp, "Import %s" % name) self.undoStack.push(command) # --- Undo support --- revisionChanged = QtCore.Signal(RevisionChanges) def undoIndexChanged(self, index): self.editorTab.currentView().update() def pushCommand(self, command): log.info("Pushing command %s" % command.text()) self.undoStack.push(command) def setUndoBlock(self, callback): self.undoStack.setUndoBlock(callback) def removeUndoBlock(self, callback): self.undoStack.removeUndoBlock(callback) def beginUndo(self): self.undoStack.clearUndoBlock() self.dirty = True self.worldEditor.beginUndo() def commitUndo(self): exhaust(self.commitUndoIter()) def commitUndoIter(self): for status in self.worldEditor.commitUndoIter(): yield status changes = self.worldEditor.getRevisionChanges(self.currentRevision-1, self.currentRevision) self.revisionChanged.emit(changes) def undoForward(self): self.worldEditor.redo() changes = self.worldEditor.getRevisionChanges(self.currentRevision-1, self.currentRevision) self.revisionChanged.emit(changes) def undoBackward(self): self.worldEditor.undo() changes = self.worldEditor.getRevisionChanges(self.currentRevision, self.currentRevision+1) self.revisionChanged.emit(changes) def gotoRevision(self, index): if index != self.currentRevision: changes = self.worldEditor.getRevisionChanges(self.currentRevision, index) self.worldEditor.gotoRevision(index) self.revisionChanged.emit(changes) @property def currentRevision(self): return self.worldEditor.currentRevision # --- Misplaced startup code? --- def loadDone(self): # Called by MCEditApp after the view is on screen to make sure view.center() works correctly # xxx was needed because view.centerOnPoint used a depthbuffer read for that, now what? try: try: player = self.worldEditor.getPlayer() center = Vector(*player.Position) + (0, 1.8, 0) dimNo = player.Dimension dimName = self.worldEditor.dimNameFromNumber(dimNo) log.info("Setting view angle to single-player player's view in dimension %s.", dimName) rotation = player.Rotation if dimName: self.gotoDimension(dimName) try: self.editorTab.currentView().yawPitch = rotation except AttributeError: pass except PlayerNotFound: try: center = self.worldEditor.getWorldMetadata().Spawn log.info("Centering on spawn position.") except AttributeError: log.info("Centering on world center") center = self.currentDimension.bounds.origin + (self.currentDimension.bounds.size * 0.5) self.editorTab.miniMap.centerOnPoint(center) self.editorTab.currentView().centerOnPoint(center, distance=0) except Exception as e: log.exception("Error while centering on player for world editor: %s", e) # --- Tools --- def toolShortcut(self, name): toolShortcuts = { "Select": "S", "Create": "D", } return toolShortcuts.get(name, "") def getTool(self, name): for t in self.tools: if t.name == name: return t def chooseTool(self, name): oldTool = self.currentTool self.currentTool = self.getTool(name) if oldTool is not self.currentTool: if oldTool: oldTool.toolInactive() self.currentTool.toolActive() self.toolChanged.emit(self.currentTool) self.actionsByName[name].setChecked(True) toolChanged = QtCore.Signal(object) def chunkDidComplete(self): from mcedit2 import editorapp editorapp.MCEditApp.app.updateStatusLabel(None, None, None, self.loader.cps, self.editorTab.currentView().fps) def updateStatusFromEvent(self, event): from mcedit2 import editorapp if event.blockPosition: id = self.currentDimension.getBlockID(*event.blockPosition) data = self.currentDimension.getBlockData(*event.blockPosition) block = self.worldEditor.blocktypes[id, data] biomeID = self.currentDimension.getBiomeID(event.blockPosition[0], event.blockPosition[2]) biome = self.biomeTypes.types.get(biomeID) if biome is not None: biomeName = biome.name else: biomeName = "Unknown biome" biomeText = "%s (%d)" % (biomeName, biomeID) editorapp.MCEditApp.app.updateStatusLabel(event.blockPosition, block, biomeText, self.loader.cps, event.view.fps) else: editorapp.MCEditApp.app.updateStatusLabel('(N/A)', None, None, self.loader.cps, event.view.fps) def viewMousePress(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mousePress') and event.blockPosition is not None: self.currentTool.mousePress(event) self.editorTab.currentView().update() def viewMouseMove(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseMove'): self.currentTool.mouseMove(event) self.editorTab.currentView().update() def viewMouseDrag(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseDrag'): self.currentTool.mouseDrag(event) self.editorTab.currentView().update() def viewMouseRelease(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseRelease'): self.currentTool.mouseRelease(event) self.editorTab.currentView().update() # --- EditorTab handling --- def tabCaption(self): return util.displayName(self.filename) def closeTab(self): if self.worldEditor is None: return True if self.dirty: msgBox = QtGui.QMessageBox(self.editorTab.window()) msgBox.setText("The world has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons( QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Save) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Save: self.save() if ret == QtGui.QMessageBox.Cancel: return False for panel in self.panels: panel.close() self.editorTab.saveState() self.worldEditor.close() self.worldEditor = None return True # --- Inspector --- def inspectBlock(self, pos): self.inspectorDockWidget.show() self.inspectorWidget.inspectBlock(pos) def inspectEntity(self, entity): self.inspectorDockWidget.show() self.inspectorWidget.inspectEntity(entity) def inspectChunk(self, cx, cz): self.inspectorDockWidget.show() self.inspectorWidget.inspectChunk(cx, cz) # --- Zooming --- def zoomAndInspectBlock(self, pos): self.zoomToPoint(pos) self.inspectBlock(pos) def zoomAndInspectEntity(self, entity): self.zoomToPoint(entity.Position) self.inspectEntity(entity) def zoomToPoint(self, point): self.editorTab.currentView().centerOnPoint(point, 15) # --- Blocktype handling --- def unknownBlocks(self): for blocktype in self.worldEditor.blocktypes: if blocktype.unknown: yield blocktype.internalName
class EditorSession(QtCore.QObject): def __init__(self, filename, versionInfo, readonly=False, progressCallback=None): progressMax = 7 # fixme if progressCallback is None: def progress(status): pass else: def progress(status): progressCallback(progress.progressCount, progressMax, status) progress.progressCount += 1 progress.progressCount = 0 QtCore.QObject.__init__(self) self.undoStack = MCEUndoStack() self.filename = filename self.dockWidgets = [] self.undoBlock = None self.currentTool = None self.dirty = False self.copiedSchematic = None """:type : WorldEditor""" self.versionInfo = versionInfo # --- Open world editor --- try: progress("Creating WorldEditor...") self.worldEditor = WorldEditor(filename, readonly=readonly) except UndoFolderExists: msgBox = QtGui.QMessageBox() msgBox.setIcon(QtGui.QMessageBox.Warning) msgBox.setWindowTitle(self.tr("MCEdit tech demo")) msgBox.setText( self.tr("This world was not properly closed by MCEdit.")) msgBox.setInformativeText( self. tr("MCEdit may have crashed. An undo history was found for this world. You may try to resume editing " "with the saved undo history, or start over with the current state of the world." )) resumeBtn = msgBox.addButton("Resume Editing", QtGui.QMessageBox.ApplyRole) msgBox.addButton("Discard History", QtGui.QMessageBox.DestructiveRole) # msgBox.exec_() # clicked = msgBox.clickedButton() clicked = None # xxxxx resume = clicked is resumeBtn try: self.worldEditor = WorldEditor(filename, readonly=readonly, resume=resume) except NotImplementedError: NotImplementedYet() raise IOError("Uh-oh") self.worldEditor.requireRevisions() self.currentDimension = None progress("Creating menus...") # --- Menus --- self.menus = [] # - Edit - self.menuEdit = QtGui.QMenu(self.tr("Edit")) self.menuEdit.setObjectName("menuEdit") self.actionCut = QtGui.QAction(self.tr("Cut"), self, triggered=self.cut, enabled=False) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionCut.setObjectName("actionCut") self.actionCopy = QtGui.QAction(self.tr("Copy"), self, triggered=self.copy, enabled=False) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionCopy.setObjectName("actionCopy") self.actionPaste = QtGui.QAction(self.tr("Paste"), self, triggered=self.paste, enabled=False) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.actionPaste.setObjectName("actionPaste") self.actionPaste_Blocks = QtGui.QAction(self.tr("Paste Blocks"), self, triggered=self.pasteBlocks, enabled=False) self.actionPaste_Blocks.setShortcut(QtGui.QKeySequence("Ctrl+Shift+V")) self.actionPaste_Blocks.setObjectName("actionPaste_Blocks") self.actionPaste_Entities = QtGui.QAction(self.tr("Paste Entities"), self, triggered=self.pasteEntities, enabled=False) self.actionPaste_Entities.setShortcut(QtGui.QKeySequence("Ctrl+Alt+V")) self.actionPaste_Entities.setObjectName("actionPaste_Entities") self.actionClear = QtGui.QAction(self.tr("Delete"), self, triggered=self.deleteSelection, enabled=False) self.actionClear.setShortcut(QtGui.QKeySequence.Delete) self.actionClear.setObjectName("actionClear") self.actionDeleteBlocks = QtGui.QAction(self.tr("Delete Blocks"), self, triggered=self.deleteBlocks, enabled=False) self.actionDeleteBlocks.setShortcut(QtGui.QKeySequence("Shift+Del")) self.actionDeleteBlocks.setObjectName("actionDeleteBlocks") self.actionDeleteEntities = QtGui.QAction( self.tr("Delete Entities"), self, triggered=self.deleteEntities, enabled=False) self.actionDeleteEntities.setShortcut( QtGui.QKeySequence("Shift+Alt+Del")) self.actionDeleteEntities.setObjectName("actionDeleteEntities") self.actionFill = QtGui.QAction(self.tr("Fill"), self, triggered=self.fill, enabled=False) self.actionFill.setShortcut(QtGui.QKeySequence("Shift+Ctrl+F")) self.actionFill.setObjectName("actionFill") self.actionFindReplace = QtGui.QAction(self.tr("Find/Replace"), self, triggered=self.findReplace, enabled=True) self.actionFindReplace.setShortcut(QtGui.QKeySequence.Find) self.actionFindReplace.setObjectName("actionFindReplace") undoAction = self.undoStack.createUndoAction(self.menuEdit) undoAction.setShortcut(QtGui.QKeySequence.Undo) redoAction = self.undoStack.createRedoAction(self.menuEdit) redoAction.setShortcut(QtGui.QKeySequence.Redo) self.menuEdit.addAction(undoAction) self.menuEdit.addAction(redoAction) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionCut) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionPaste) self.menuEdit.addAction(self.actionPaste_Blocks) self.menuEdit.addAction(self.actionPaste_Entities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionClear) self.menuEdit.addAction(self.actionDeleteBlocks) self.menuEdit.addAction(self.actionDeleteEntities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFill) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFindReplace) self.menus.append(self.menuEdit) # - Select - self.menuSelect = QtGui.QMenu(self.tr("Select")) self.actionSelectAll = QtGui.QAction(self.tr("Select All"), self, triggered=self.selectAll) self.actionSelectAll.setShortcut(QtGui.QKeySequence.SelectAll) self.menuSelect.addAction(self.actionSelectAll) self.actionDeselect = QtGui.QAction(self.tr("Deselect"), self, triggered=self.deselect) self.actionDeselect.setShortcut(QtGui.QKeySequence("Ctrl+D")) self.menuSelect.addAction(self.actionDeselect) self.menus.append(self.menuSelect) # - Import/Export - self.menuImportExport = QtGui.QMenu(self.tr("Import/Export")) self.actionExport = QtGui.QAction(self.tr("Export"), self, triggered=self.export) self.actionExport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+E")) self.menuImportExport.addAction(self.actionExport) self.actionImport = QtGui.QAction(self.tr("Import"), self, triggered=self.import_) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+D")) self.menuImportExport.addAction(self.actionImport) self.actionImport = QtGui.QAction( self.tr("Show Exports Library"), self, triggered=QtGui.qApp.libraryDockWidget.toggleViewAction().trigger) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+L")) self.menuImportExport.addAction(self.actionImport) self.menus.append(self.menuImportExport) # --- Resources --- progress("Loading resources...") i, v, p = self.versionInfo self.resourceLoader = i.getResourceLoader(v, p) self.geometryCache = GeometryCache() self.blockModels = BlockModels(self.worldEditor.blocktypes, self.resourceLoader) self.textureAtlas = TextureAtlas(self.worldEditor, self.resourceLoader, self.blockModels) self.editorOverlay = scenegraph.Node() # --- Panels --- progress("Loading panels...") self.playerPanel = PlayerPanel(self) self.panels = [self.playerPanel] # --- Tools --- progress("Loading tools...") self.toolClasses = list(editortools.ToolClasses()) self.toolActionGroup = QtGui.QActionGroup(self) self.tools = [cls(self) for cls in self.toolClasses] self.toolActions = [tool.pickToolAction() for tool in self.tools] self.actionsByName = { action.toolName: action for action in self.toolActions } for tool in self.tools: tool.toolPicked.connect(self.chooseTool) for action in self.toolActions: self.toolActionGroup.addAction(action) self.selectionTool = self.getTool("Select") self.moveTool = self.getTool("Move") # --- Dimensions --- def _dimChanged(f): def _changed(): self.gotoDimension(f) return _changed dimButton = self.changeDimensionButton = QtGui.QToolButton() dimButton.setText(self.dimensionMenuLabel("")) dimAction = self.changeDimensionAction = QtGui.QWidgetAction(self) dimAction.setDefaultWidget(dimButton) dimMenu = self.dimensionsMenu = QtGui.QMenu() for dimName in self.worldEditor.listDimensions(): displayName = self.dimensionDisplayName(dimName) action = dimMenu.addAction(displayName) action._changed = _dimChanged(dimName) action.triggered.connect(action._changed) dimButton.setMenu(dimMenu) dimButton.setPopupMode(dimButton.InstantPopup) progress("Loading overworld dimension") self.gotoDimension("") # --- Editor stuff --- progress("Creating EditorTab...") self.editorTab = EditorTab(self) self.toolChanged.connect(self.toolDidChange) self.undoStack.indexChanged.connect(self.undoIndexChanged) self.findReplaceDialog = FindReplaceDialog(self) for resultsWidget in self.findReplaceDialog.resultsWidgets: self.dockWidgets.append((Qt.BottomDockWidgetArea, resultsWidget)) self.inspectorWidget = InspectorWidget(self) self.inspectorDockWidget = QtGui.QDockWidget(self.tr("Inspector"), objectName="inspector") self.inspectorDockWidget.setWidget(self.inspectorWidget) self.inspectorDockWidget.hide() self.dockWidgets.append( (Qt.RightDockWidgetArea, self.inspectorDockWidget)) if len(self.toolActions): self.toolActions[0].trigger( ) # Must be called after toolChanged is connected to editorTab if hasattr(progress, 'progressCount') and progress.progressCount != progressMax: log.info("Update progressMax to %d, please.", progress.progressCount) def dispose(self): if self.textureAtlas: self.textureAtlas.dispose() self.textureAtlas = None if self.editorTab: self.editorTab.destroy() self.editorTab = None if self.worldEditor: self.worldEditor.close() self.worldEditor = None # Connecting these signals to the EditorTab creates a circular reference through # the Qt objects, preventing the EditorSession from being destroyed def focusWorldView(self): self.editorTab.currentView().setFocus() def updateView(self): self.editorTab.currentView().update() def toolDidChange(self, tool): self.editorTab.toolDidChange(tool) # --- Selection --- selectionChanged = QtCore.Signal(BoundingBox) _currentSelection = None @property def currentSelection(self): return self._currentSelection @currentSelection.setter def currentSelection(self, box): self._currentSelection = box self.enableSelectionCommands(box is not None and box.volume != 0) self.selectionChanged.emit(box) def enableSelectionCommands(self, enable): self.actionCut.setEnabled(enable) self.actionCopy.setEnabled(enable) self.actionPaste.setEnabled(enable) self.actionPaste_Blocks.setEnabled(enable) self.actionPaste_Entities.setEnabled(enable) self.actionClear.setEnabled(enable) self.actionDeleteBlocks.setEnabled(enable) self.actionDeleteEntities.setEnabled(enable) self.actionFill.setEnabled(enable) self.actionExport.setEnabled(enable) # --- Menu commands --- # - World - def save(self): self.undoStack.clearUndoBlock() self.worldEditor.saveChanges() self.dirty = False # - Edit - def cut(self): command = SimpleRevisionCommand(self, "Cut") with command.begin(): task = self.currentDimension.exportSchematicIter( self.currentSelection) self.copiedSchematic = showProgress("Cutting...", task) task = self.currentDimension.fillBlocksIter( self.currentSelection, "air") showProgress("Cutting...", task) self.undoStack.push(command) def copy(self): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Copying...", task) def paste(self): if self.copiedSchematic is None: return view = self.editorTab.currentView() imp = PendingImport(self.copiedSchematic, view.mouseBlockPos, self.tr("<Pasted Object>")) command = PasteImportCommand(self, imp, "Paste") self.undoStack.push(command) def pasteBlocks(self): NotImplementedYet() def pasteEntities(self): NotImplementedYet() def findReplace(self): self.findReplaceDialog.exec_() def deleteSelection(self): command = SimpleRevisionCommand(self, "Delete") with command.begin(): fillTask = self.currentDimension.fillBlocksIter( self.currentSelection, "air") entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) task = ComposeOperations(fillTask, entitiesTask) showProgress("Deleting...", task) self.pushCommand(command) def deleteBlocks(self): command = SimpleRevisionCommand(self, "Delete Blocks") with command.begin(): fillTask = self.currentDimension.fillBlocksIter( self.currentSelection, "air") showProgress("Deleting...", fillTask) self.pushCommand(command) def deleteEntities(self): command = SimpleRevisionCommand(self, "Delete Entities") with command.begin(): entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) showProgress("Deleting...", entitiesTask) self.pushCommand(command) def fill(self): fillCommand(self) # - Select - def selectAll(self): command = SelectCommand(self, self.currentDimension.bounds, self.tr("Select All")) self.pushCommand(command) def deselect(self): command = SelectCommand(self, None) command.setText(self.tr("Deselect")) self.pushCommand(command) # - Dimensions - dimensionChanged = QtCore.Signal(object) _dimDisplayNames = { "": "Overworld", "DIM-1": "Nether", "DIM1": "The End", } def dimensionDisplayName(self, dimName): return self._dimDisplayNames.get(dimName, dimName) def dimensionMenuLabel(self, dimName): return self.tr("Dimension: %s" % self.dimensionDisplayName(dimName)) def gotoDimension(self, dimName): dim = self.worldEditor.getDimension(dimName) if dim is self.currentDimension: return log.info("Going to dimension %s", dimName) self.changeDimensionButton.setText(self.dimensionMenuLabel(dimName)) self.currentDimension = dim self.loader = chunkloader.ChunkLoader(self.currentDimension) self.loader.chunkCompleted.connect(self.chunkDidComplete) self.loader.allChunksDone.connect(self.updateView) self.dimensionChanged.emit(dim) # - Import/export - def import_(self): # prompt for a file to import startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getOpenFileName(self.mainWindow, self.tr("Import"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: self.importSchematic(filename) def export(self): # prompt for filename and format. maybe use custom browser to save to export library?? startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.mainWindow, self.tr("Export Schematic"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: task = self.currentDimension.exportSchematicIter( self.currentSelection) schematic = showProgress("Copying...", task) schematic.saveToFile(filename) # --- Library support --- def importSchematic(self, filename): schematic = WorldEditor(filename, readonly=True) ray = self.editorTab.currentView().rayAtCenter() pos, face = rayCastInBounds(ray, self.currentDimension) if pos is None: pos = ray.point name = os.path.basename(filename) imp = PendingImport(schematic, pos, name) command = PasteImportCommand(self, imp, "Import %s" % name) self.undoStack.push(command) # --- Undo support --- revisionChanged = QtCore.Signal(int) def undoIndexChanged(self, index): self.editorTab.currentView().update() def pushCommand(self, command): self.undoStack.push(command) def setUndoBlock(self, callback): self.undoStack.setUndoBlock(callback) def removeUndoBlock(self, callback): self.undoStack.removeUndoBlock(callback) def beginUndo(self): self.undoStack.clearUndoBlock() self.dirty = True self.worldEditor.beginUndo() def commitUndo(self): self.worldEditor.commitUndo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoForward(self): self.worldEditor.redo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoBackward(self): self.worldEditor.undo() self.revisionChanged.emit(self.worldEditor.currentRevision) def gotoRevision(self, index): if index != self.currentRevision: self.worldEditor.gotoRevision(index) self.revisionChanged.emit(self.worldEditor.currentRevision) @property def currentRevision(self): return self.worldEditor.currentRevision # --- Misplaced startup code? --- def loadDone(self): # Called by MCEditApp after the view is on screen to make sure view.center() works correctly xxx used depth # buffer read for that, now what? try: player = self.worldEditor.getPlayer() center = Vector(*player.Position) + (0, 1.8, 0) log.info("Setting view angle to single-player player's view.") rotation = player.Rotation try: self.editorTab.currentView().yawPitch = rotation except AttributeError: pass except PlayerNotFound: try: center = self.worldEditor.worldSpawnPosition() log.info("Centering on spawn position.") except AttributeError: log.info("Centering on world center") center = self.currentDimension.bounds.origin + ( self.currentDimension.bounds.size * 0.5) self.editorTab.miniMap.centerOnPoint(center) self.editorTab.currentView().centerOnPoint(center, distance=0) # --- Tools --- def toolShortcut(self, name): toolShortcuts = { "Select": "S", "Create": "D", } return toolShortcuts.get(name, "") def getTool(self, name): for t in self.tools: if t.name == name: return t def chooseTool(self, name): oldTool = self.currentTool self.currentTool = self.getTool(name) if oldTool is not self.currentTool: if oldTool: oldTool.toolInactive() self.currentTool.toolActive() self.toolChanged.emit(self.currentTool) self.actionsByName[name].setChecked(True) toolChanged = QtCore.Signal(object) def chunkDidComplete(self): from mcedit2 import editorapp editorapp.MCEditApp.app.updateStatusLabel( None, None, self.loader.cps, self.editorTab.currentView().fps) def updateStatusFromEvent(self, event): from mcedit2 import editorapp if event.blockPosition: id = self.currentDimension.getBlockID(*event.blockPosition) data = self.currentDimension.getBlockData(*event.blockPosition) block = self.worldEditor.blocktypes[id, data] editorapp.MCEditApp.app.updateStatusLabel(event.blockPosition, block, self.loader.cps, event.view.fps) else: editorapp.MCEditApp.app.updateStatusLabel('(N/A)', None, self.loader.cps, event.view.fps) def viewMousePress(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mousePress') and event.blockPosition is not None: self.currentTool.mousePress(event) self.editorTab.currentView().update() def viewMouseMove(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseMove'): self.currentTool.mouseMove(event) self.editorTab.currentView().update() def viewMouseDrag(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseDrag'): self.currentTool.mouseDrag(event) self.editorTab.currentView().update() def viewMouseRelease(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseRelease'): self.currentTool.mouseRelease(event) self.editorTab.currentView().update() # --- EditorTab handling --- def tabCaption(self): return util.displayName(self.filename) def closeTab(self): if self.worldEditor is None: return True if self.dirty: msgBox = QtGui.QMessageBox(self.editorTab.window()) msgBox.setText("The world has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Save) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Save: self.save() if ret == QtGui.QMessageBox.Cancel: return False self.editorTab.saveState() self.worldEditor.close() self.worldEditor = None return True # --- Inspector --- def inspectBlock(self, pos): self.inspectorDockWidget.show() self.inspectorWidget.inspectBlock(pos) def inspectEntity(self, entity): self.inspectorDockWidget.show() self.inspectorWidget.inspectEntity(entity) # --- Zooming --- def zoomAndInspectBlock(self, pos): self.zoomToPoint(pos) self.inspectBlock(pos) def zoomAndInspectEntity(self, entity): self.zoomToPoint(entity.Position) self.inspectEntity(entity) def zoomToPoint(self, point): self.editorTab.currentView().centerOnPoint(point, 15)
class EditorSession(QtCore.QObject): def __init__(self, filename, versionInfo, readonly=False, progressCallback=None): progressMax = 7 # fixme if progressCallback is None: def progress(status): pass else: def progress(status): progressCallback(progress.progressCount, progressMax, status) progress.progressCount += 1 progress.progressCount = 0 QtCore.QObject.__init__(self) self.undoStack = MCEUndoStack() self.filename = filename self.dockWidgets = [] self.undoBlock = None self.currentTool = None self.dirty = False self.copiedSchematic = None """:type : WorldEditor""" self.versionInfo = versionInfo # --- Open world editor --- try: progress("Creating WorldEditor...") self.worldEditor = WorldEditor(filename, readonly=readonly) except UndoFolderExists: msgBox = QtGui.QMessageBox() msgBox.setIcon(QtGui.QMessageBox.Warning) msgBox.setWindowTitle(self.tr("MCEdit tech demo")) msgBox.setText(self.tr("This world was not properly closed by MCEdit.")) msgBox.setInformativeText(self.tr( "MCEdit may have crashed. An undo history was found for this world. You may try to resume editing " "with the saved undo history, or start over with the current state of the world.")) resumeBtn = msgBox.addButton("Resume Editing", QtGui.QMessageBox.ApplyRole) msgBox.addButton("Discard History", QtGui.QMessageBox.DestructiveRole) # msgBox.exec_() # clicked = msgBox.clickedButton() clicked = None # xxxxx resume = clicked is resumeBtn try: self.worldEditor = WorldEditor(filename, readonly=readonly, resume=resume) except NotImplementedError: NotImplementedYet() raise IOError("Uh-oh") self.worldEditor.requireRevisions() self.currentDimension = None progress("Creating menus...") # --- Menus --- self.menus = [] # - Edit - self.menuEdit = QtGui.QMenu(self.tr("Edit")) self.menuEdit.setObjectName("menuEdit") self.actionCut = QtGui.QAction(self.tr("Cut"), self, triggered=self.cut, enabled=False) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionCut.setObjectName("actionCut") self.actionCopy = QtGui.QAction(self.tr("Copy"), self, triggered=self.copy, enabled=False) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionCopy.setObjectName("actionCopy") self.actionPaste = QtGui.QAction(self.tr("Paste"), self, triggered=self.paste, enabled=False) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.actionPaste.setObjectName("actionPaste") self.actionPaste_Blocks = QtGui.QAction(self.tr("Paste Blocks"), self, triggered=self.pasteBlocks, enabled=False) self.actionPaste_Blocks.setShortcut(QtGui.QKeySequence("Ctrl+Shift+V")) self.actionPaste_Blocks.setObjectName("actionPaste_Blocks") self.actionPaste_Entities = QtGui.QAction(self.tr("Paste Entities"), self, triggered=self.pasteEntities, enabled=False) self.actionPaste_Entities.setShortcut(QtGui.QKeySequence("Ctrl+Alt+V")) self.actionPaste_Entities.setObjectName("actionPaste_Entities") self.actionClear = QtGui.QAction(self.tr("Delete"), self, triggered=self.deleteSelection, enabled=False) self.actionClear.setShortcut(QtGui.QKeySequence.Delete) self.actionClear.setObjectName("actionClear") self.actionDeleteBlocks = QtGui.QAction(self.tr("Delete Blocks"), self, triggered=self.deleteBlocks, enabled=False) self.actionDeleteBlocks.setShortcut(QtGui.QKeySequence("Shift+Del")) self.actionDeleteBlocks.setObjectName("actionDeleteBlocks") self.actionDeleteEntities = QtGui.QAction(self.tr("Delete Entities"), self, triggered=self.deleteEntities, enabled=False) self.actionDeleteEntities.setShortcut(QtGui.QKeySequence("Shift+Alt+Del")) self.actionDeleteEntities.setObjectName("actionDeleteEntities") self.actionFill = QtGui.QAction(self.tr("Fill"), self, triggered=self.fill, enabled=False) self.actionFill.setShortcut(QtGui.QKeySequence("Shift+Ctrl+F")) self.actionFill.setObjectName("actionFill") self.actionFindReplace = QtGui.QAction(self.tr("Find/Replace"), self, triggered=self.findReplace, enabled=True) self.actionFindReplace.setShortcut(QtGui.QKeySequence.Find) self.actionFindReplace.setObjectName("actionFindReplace") undoAction = self.undoStack.createUndoAction(self.menuEdit) undoAction.setShortcut(QtGui.QKeySequence.Undo) redoAction = self.undoStack.createRedoAction(self.menuEdit) redoAction.setShortcut(QtGui.QKeySequence.Redo) self.menuEdit.addAction(undoAction) self.menuEdit.addAction(redoAction) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionCut) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionPaste) self.menuEdit.addAction(self.actionPaste_Blocks) self.menuEdit.addAction(self.actionPaste_Entities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionClear) self.menuEdit.addAction(self.actionDeleteBlocks) self.menuEdit.addAction(self.actionDeleteEntities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFill) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFindReplace) self.menus.append(self.menuEdit) # - Select - self.menuSelect = QtGui.QMenu(self.tr("Select")) self.actionSelectAll = QtGui.QAction(self.tr("Select All"), self, triggered=self.selectAll) self.actionSelectAll.setShortcut(QtGui.QKeySequence.SelectAll) self.menuSelect.addAction(self.actionSelectAll) self.actionDeselect = QtGui.QAction(self.tr("Deselect"), self, triggered=self.deselect) self.actionDeselect.setShortcut(QtGui.QKeySequence("Ctrl+D")) self.menuSelect.addAction(self.actionDeselect) self.menus.append(self.menuSelect) # - Import/Export - self.menuImportExport = QtGui.QMenu(self.tr("Import/Export")) self.actionExport = QtGui.QAction(self.tr("Export"), self, triggered=self.export) self.actionExport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+E")) self.menuImportExport.addAction(self.actionExport) self.actionImport = QtGui.QAction(self.tr("Import"), self, triggered=self.import_) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+D")) self.menuImportExport.addAction(self.actionImport) self.actionImport = QtGui.QAction(self.tr("Show Exports Library"), self, triggered=QtGui.qApp.libraryDockWidget.toggleViewAction().trigger) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+L")) self.menuImportExport.addAction(self.actionImport) self.menus.append(self.menuImportExport) # --- Resources --- progress("Loading resources...") i, v, p = self.versionInfo self.resourceLoader = i.getResourceLoader(v, p) self.geometryCache = GeometryCache() self.blockModels = BlockModels(self.worldEditor.blocktypes, self.resourceLoader) self.textureAtlas = TextureAtlas(self.worldEditor, self.resourceLoader, self.blockModels) self.editorOverlay = scenegraph.Node() # --- Panels --- progress("Loading panels...") self.playerPanel = PlayerPanel(self) self.panels = [self.playerPanel] # --- Tools --- progress("Loading tools...") self.toolClasses = list(editortools.ToolClasses()) self.toolActionGroup = QtGui.QActionGroup(self) self.tools = [cls(self) for cls in self.toolClasses] self.toolActions = [tool.pickToolAction() for tool in self.tools] self.actionsByName = {action.toolName: action for action in self.toolActions} for tool in self.tools: tool.toolPicked.connect(self.chooseTool) for action in self.toolActions: self.toolActionGroup.addAction(action) self.selectionTool = self.getTool("Select") self.moveTool = self.getTool("Move") # --- Dimensions --- def _dimChanged(f): def _changed(): self.gotoDimension(f) return _changed dimButton = self.changeDimensionButton = QtGui.QToolButton() dimButton.setText(self.dimensionMenuLabel("")) dimAction = self.changeDimensionAction = QtGui.QWidgetAction(self) dimAction.setDefaultWidget(dimButton) dimMenu = self.dimensionsMenu = QtGui.QMenu() for dimName in self.worldEditor.listDimensions(): displayName = self.dimensionDisplayName(dimName) action = dimMenu.addAction(displayName) action._changed = _dimChanged(dimName) action.triggered.connect(action._changed) dimButton.setMenu(dimMenu) dimButton.setPopupMode(dimButton.InstantPopup) progress("Loading overworld dimension") self.gotoDimension("") # --- Editor stuff --- progress("Creating EditorTab...") self.editorTab = EditorTab(self) self.toolChanged.connect(self.toolDidChange) self.undoStack.indexChanged.connect(self.undoIndexChanged) self.findReplaceDialog = FindReplaceDialog(self) for resultsWidget in self.findReplaceDialog.resultsWidgets: self.dockWidgets.append((Qt.BottomDockWidgetArea, resultsWidget)) self.inspectorWidget = InspectorWidget(self) self.inspectorDockWidget = QtGui.QDockWidget(self.tr("Inspector"), objectName="inspector") self.inspectorDockWidget.setWidget(self.inspectorWidget) self.inspectorDockWidget.hide() self.dockWidgets.append((Qt.RightDockWidgetArea, self.inspectorDockWidget)) if len(self.toolActions): self.toolActions[0].trigger() # Must be called after toolChanged is connected to editorTab if hasattr(progress, 'progressCount') and progress.progressCount != progressMax: log.info("Update progressMax to %d, please.", progress.progressCount) def dispose(self): if self.textureAtlas: self.textureAtlas.dispose() self.textureAtlas = None if self.editorTab: self.editorTab.destroy() self.editorTab = None if self.worldEditor: self.worldEditor.close() self.worldEditor = None # Connecting these signals to the EditorTab creates a circular reference through # the Qt objects, preventing the EditorSession from being destroyed def focusWorldView(self): self.editorTab.currentView().setFocus() def updateView(self): self.editorTab.currentView().update() def toolDidChange(self, tool): self.editorTab.toolDidChange(tool) # --- Selection --- selectionChanged = QtCore.Signal(BoundingBox) _currentSelection = None @property def currentSelection(self): return self._currentSelection @currentSelection.setter def currentSelection(self, box): self._currentSelection = box self.enableSelectionCommands(box is not None and box.volume != 0) self.selectionChanged.emit(box) def enableSelectionCommands(self, enable): self.actionCut.setEnabled(enable) self.actionCopy.setEnabled(enable) self.actionPaste.setEnabled(enable) self.actionPaste_Blocks.setEnabled(enable) self.actionPaste_Entities.setEnabled(enable) self.actionClear.setEnabled(enable) self.actionDeleteBlocks.setEnabled(enable) self.actionDeleteEntities.setEnabled(enable) self.actionFill.setEnabled(enable) self.actionExport.setEnabled(enable) # --- Menu commands --- # - World - def save(self): self.undoStack.clearUndoBlock() self.worldEditor.saveChanges() self.dirty = False # - Edit - def cut(self): command = SimpleRevisionCommand(self, "Cut") with command.begin(): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Cutting...", task) task = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Cutting...", task) self.undoStack.push(command) def copy(self): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Copying...", task) def paste(self): if self.copiedSchematic is None: return view = self.editorTab.currentView() imp = PendingImport(self.copiedSchematic, view.mouseBlockPos, self.tr("<Pasted Object>")) command = PasteImportCommand(self, imp, "Paste") self.undoStack.push(command) def pasteBlocks(self): NotImplementedYet() def pasteEntities(self): NotImplementedYet() def findReplace(self): self.findReplaceDialog.exec_() def deleteSelection(self): command = SimpleRevisionCommand(self, "Delete") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) task = ComposeOperations(fillTask, entitiesTask) showProgress("Deleting...", task) self.pushCommand(command) def deleteBlocks(self): command = SimpleRevisionCommand(self, "Delete Blocks") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Deleting...", fillTask) self.pushCommand(command) def deleteEntities(self): command = SimpleRevisionCommand(self, "Delete Entities") with command.begin(): entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) showProgress("Deleting...", entitiesTask) self.pushCommand(command) def fill(self): fillCommand(self) # - Select - def selectAll(self): command = SelectCommand(self, self.currentDimension.bounds, self.tr("Select All")) self.pushCommand(command) def deselect(self): command = SelectCommand(self, None) command.setText(self.tr("Deselect")) self.pushCommand(command) # - Dimensions - dimensionChanged = QtCore.Signal(object) _dimDisplayNames = {"": "Overworld", "DIM-1": "Nether", "DIM1": "The End", } def dimensionDisplayName(self, dimName): return self._dimDisplayNames.get(dimName, dimName) def dimensionMenuLabel(self, dimName): return self.tr("Dimension: %s" % self.dimensionDisplayName(dimName)) def gotoDimension(self, dimName): dim = self.worldEditor.getDimension(dimName) if dim is self.currentDimension: return log.info("Going to dimension %s", dimName) self.changeDimensionButton.setText(self.dimensionMenuLabel(dimName)) self.currentDimension = dim self.loader = chunkloader.ChunkLoader(self.currentDimension) self.loader.chunkCompleted.connect(self.chunkDidComplete) self.loader.allChunksDone.connect(self.updateView) self.dimensionChanged.emit(dim) # - Import/export - def import_(self): # prompt for a file to import startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getOpenFileName(self.mainWindow, self.tr("Import"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: self.importSchematic(filename) def export(self): # prompt for filename and format. maybe use custom browser to save to export library?? startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.mainWindow, self.tr("Export Schematic"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: task = self.currentDimension.exportSchematicIter(self.currentSelection) schematic = showProgress("Copying...", task) schematic.saveToFile(filename) # --- Library support --- def importSchematic(self, filename): schematic = WorldEditor(filename, readonly=True) ray = self.editorTab.currentView().rayAtCenter() pos, face = rayCastInBounds(ray, self.currentDimension) if pos is None: pos = ray.point name = os.path.basename(filename) imp = PendingImport(schematic, pos, name) command = PasteImportCommand(self, imp, "Import %s" % name) self.undoStack.push(command) # --- Undo support --- revisionChanged = QtCore.Signal(int) def undoIndexChanged(self, index): self.editorTab.currentView().update() def pushCommand(self, command): self.undoStack.push(command) def setUndoBlock(self, callback): self.undoStack.setUndoBlock(callback) def removeUndoBlock(self, callback): self.undoStack.removeUndoBlock(callback) def beginUndo(self): self.undoStack.clearUndoBlock() self.dirty = True self.worldEditor.beginUndo() def commitUndo(self): self.worldEditor.commitUndo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoForward(self): self.worldEditor.redo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoBackward(self): self.worldEditor.undo() self.revisionChanged.emit(self.worldEditor.currentRevision) def gotoRevision(self, index): if index != self.currentRevision: self.worldEditor.gotoRevision(index) self.revisionChanged.emit(self.worldEditor.currentRevision) @property def currentRevision(self): return self.worldEditor.currentRevision # --- Misplaced startup code? --- def loadDone(self): # Called by MCEditApp after the view is on screen to make sure view.center() works correctly xxx used depth # buffer read for that, now what? try: player = self.worldEditor.getPlayer() center = Vector(*player.Position) + (0, 1.8, 0) log.info("Setting view angle to single-player player's view.") rotation = player.Rotation try: self.editorTab.currentView().yawPitch = rotation except AttributeError: pass except PlayerNotFound: try: center = self.worldEditor.worldSpawnPosition() log.info("Centering on spawn position.") except AttributeError: log.info("Centering on world center") center = self.currentDimension.bounds.origin + (self.currentDimension.bounds.size * 0.5) self.editorTab.miniMap.centerOnPoint(center) self.editorTab.currentView().centerOnPoint(center, distance=0) # --- Tools --- def toolShortcut(self, name): toolShortcuts = { "Select": "S", "Create": "D", } return toolShortcuts.get(name, "") def getTool(self, name): for t in self.tools: if t.name == name: return t def chooseTool(self, name): oldTool = self.currentTool self.currentTool = self.getTool(name) if oldTool is not self.currentTool: if oldTool: oldTool.toolInactive() self.currentTool.toolActive() self.toolChanged.emit(self.currentTool) self.actionsByName[name].setChecked(True) toolChanged = QtCore.Signal(object) def chunkDidComplete(self): from mcedit2 import editorapp editorapp.MCEditApp.app.updateStatusLabel(None, None, self.loader.cps, self.editorTab.currentView().fps) def updateStatusFromEvent(self, event): from mcedit2 import editorapp if event.blockPosition: id = self.currentDimension.getBlockID(*event.blockPosition) data = self.currentDimension.getBlockData(*event.blockPosition) block = self.worldEditor.blocktypes[id, data] editorapp.MCEditApp.app.updateStatusLabel(event.blockPosition, block, self.loader.cps, event.view.fps) else: editorapp.MCEditApp.app.updateStatusLabel('(N/A)', None, self.loader.cps, event.view.fps) def viewMousePress(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mousePress') and event.blockPosition is not None: self.currentTool.mousePress(event) self.editorTab.currentView().update() def viewMouseMove(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseMove'): self.currentTool.mouseMove(event) self.editorTab.currentView().update() def viewMouseDrag(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseDrag'): self.currentTool.mouseDrag(event) self.editorTab.currentView().update() def viewMouseRelease(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseRelease'): self.currentTool.mouseRelease(event) self.editorTab.currentView().update() # --- EditorTab handling --- def tabCaption(self): return util.displayName(self.filename) def closeTab(self): if self.worldEditor is None: return True if self.dirty: msgBox = QtGui.QMessageBox(self.editorTab.window()) msgBox.setText("The world has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Save) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Save: self.save() if ret == QtGui.QMessageBox.Cancel: return False self.editorTab.saveState() self.worldEditor.close() self.worldEditor = None return True # --- Inspector --- def inspectBlock(self, pos): self.inspectorDockWidget.show() self.inspectorWidget.inspectBlock(pos) def inspectEntity(self, entity): self.inspectorDockWidget.show() self.inspectorWidget.inspectEntity(entity) # --- Zooming --- def zoomAndInspectBlock(self, pos): self.zoomToPoint(pos) self.inspectBlock(pos) def zoomAndInspectEntity(self, entity): self.zoomToPoint(entity.Position) self.inspectEntity(entity) def zoomToPoint(self, point): self.editorTab.currentView().centerOnPoint(point, 15)
class EditorSession(QtCore.QObject): def __init__(self, filename, resourceLoader, configuredBlocks, readonly=False, progressCallback=None): """ :param filename: :type filename: str :param resourceLoader: :type resourceLoader: mcedit2.resourceloader.ResourceLoader :param configuredBlocks: :type configuredBlocks: dict??? :param readonly: :type readonly: bool :param progressCallback: :type progressCallback: callable :return: :rtype: """ from mcedit2 import __version__ as v progressMax = 8 # fixme if progressCallback is None: def progress(status): pass else: def progress(status): progressCallback(progress.progressCount, progressMax, status) progress.progressCount += 1 progress.progressCount = 0 QtCore.QObject.__init__(self) self.undoStack = MCEUndoStack() self.loader = None self.blockModels = None self.textureAtlas = None self.filename = filename self.dockWidgets = [] self.undoBlock = None self.currentTool = None self.dirty = False self.configuredBlocks = None self.copiedSchematic = None # xxx should be app global!! """:type : WorldEditor""" # --- Open world editor --- try: progress("Creating WorldEditor...") self.worldEditor = WorldEditor(filename, readonly=readonly) except UndoFolderExists: msgBox = QtGui.QMessageBox() msgBox.setIcon(QtGui.QMessageBox.Warning) msgBox.setWindowTitle(self.tr("MCEdit %(version)s") % {"version": v}) msgBox.setText(self.tr("This world was not properly closed by MCEdit.")) msgBox.setInformativeText(self.tr( "MCEdit may have crashed. An undo history was found for this world. You may try " "to resume editing with the saved undo history, or start over with the current " "state of the world.")) resumeBtn = msgBox.addButton("Resume Editing", QtGui.QMessageBox.ApplyRole) msgBox.addButton("Discard History", QtGui.QMessageBox.DestructiveRole) # msgBox.exec_() # clicked = msgBox.clickedButton() # xxxxx resume editing not implemented in session - need to restore undo history! clicked = None resume = clicked is resumeBtn try: self.worldEditor = WorldEditor(filename, readonly=readonly, resume=resume) except NotImplementedError: NotImplementedYet() raise IOError("Uh-oh") self.worldEditor.requireRevisions() self.currentDimension = None progress("Creating menus...") # --- Menus --- self.menus = [] # - Edit - self.menuEdit = QtGui.QMenu(self.tr("Edit")) self.menuEdit.setObjectName("menuEdit") self.actionCut = QtGui.QAction(self.tr("Cut"), self, triggered=self.cut, enabled=False) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionCut.setObjectName("actionCut") self.actionCopy = QtGui.QAction(self.tr("Copy"), self, triggered=self.copy, enabled=False) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionCopy.setObjectName("actionCopy") self.actionPaste = QtGui.QAction(self.tr("Paste"), self, triggered=self.paste, enabled=False) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.actionPaste.setObjectName("actionPaste") self.actionPaste_Blocks = QtGui.QAction(self.tr("Paste Blocks"), self, triggered=self.pasteBlocks, enabled=False) self.actionPaste_Blocks.setShortcut(QtGui.QKeySequence("Ctrl+Shift+V")) self.actionPaste_Blocks.setObjectName("actionPaste_Blocks") self.actionPaste_Entities = QtGui.QAction(self.tr("Paste Entities"), self, triggered=self.pasteEntities, enabled=False) self.actionPaste_Entities.setShortcut(QtGui.QKeySequence("Ctrl+Alt+V")) self.actionPaste_Entities.setObjectName("actionPaste_Entities") self.actionClear = QtGui.QAction(self.tr("Delete"), self, triggered=self.deleteSelection, enabled=False) self.actionClear.setShortcut(QtGui.QKeySequence.Delete) self.actionClear.setObjectName("actionClear") self.actionDeleteBlocks = QtGui.QAction(self.tr("Delete Blocks"), self, triggered=self.deleteBlocks, enabled=False) self.actionDeleteBlocks.setShortcut(QtGui.QKeySequence("Shift+Del")) self.actionDeleteBlocks.setObjectName("actionDeleteBlocks") self.actionDeleteEntities = QtGui.QAction(self.tr("Delete Entities"), self, triggered=self.deleteEntities, enabled=False) self.actionDeleteEntities.setShortcut(QtGui.QKeySequence("Shift+Alt+Del")) self.actionDeleteEntities.setObjectName("actionDeleteEntities") self.actionFill = QtGui.QAction(self.tr("Fill"), self, triggered=self.fill, enabled=False) self.actionFill.setShortcut(QtGui.QKeySequence("Shift+Ctrl+F")) self.actionFill.setObjectName("actionFill") self.actionFindReplace = QtGui.QAction(self.tr("Find/Replace"), self, triggered=self.findReplace, enabled=True) self.actionFindReplace.setShortcut(QtGui.QKeySequence.Find) self.actionFindReplace.setObjectName("actionFindReplace") self.actionAnalyze = QtGui.QAction(self.tr("Analyze"), self, triggered=self.analyze, enabled=True) # self.actionAnalyze.setShortcut(QtGui.QKeySequence.Analyze) self.actionAnalyze.setObjectName("actionAnalyze") undoAction = self.undoStack.createUndoAction(self.menuEdit) undoAction.setShortcut(QtGui.QKeySequence.Undo) redoAction = self.undoStack.createRedoAction(self.menuEdit) redoAction.setShortcut(QtGui.QKeySequence.Redo) self.menuEdit.addAction(undoAction) self.menuEdit.addAction(redoAction) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionCut) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionPaste) self.menuEdit.addAction(self.actionPaste_Blocks) self.menuEdit.addAction(self.actionPaste_Entities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionClear) self.menuEdit.addAction(self.actionDeleteBlocks) self.menuEdit.addAction(self.actionDeleteEntities) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFill) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFindReplace) self.menuEdit.addAction(self.actionAnalyze) self.menus.append(self.menuEdit) # - Select - self.menuSelect = QtGui.QMenu(self.tr("Select")) self.actionSelectAll = QtGui.QAction(self.tr("Select All"), self, triggered=self.selectAll) self.actionSelectAll.setShortcut(QtGui.QKeySequence.SelectAll) self.menuSelect.addAction(self.actionSelectAll) self.actionDeselect = QtGui.QAction(self.tr("Deselect"), self, triggered=self.deselect) self.actionDeselect.setShortcut(QtGui.QKeySequence("Ctrl+D")) self.menuSelect.addAction(self.actionDeselect) self.menus.append(self.menuSelect) # - Import/Export - self.menuImportExport = QtGui.QMenu(self.tr("Import/Export")) self.actionExport = QtGui.QAction(self.tr("Export"), self, triggered=self.export) self.actionExport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+E")) self.menuImportExport.addAction(self.actionExport) self.actionImport = QtGui.QAction(self.tr("Import"), self, triggered=self.import_) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+D")) self.menuImportExport.addAction(self.actionImport) self.actionImport = QtGui.QAction(self.tr("Show Exports Library"), self, triggered=QtGui.qApp.libraryDockWidget.toggleViewAction().trigger) self.actionImport.setShortcut(QtGui.QKeySequence("Ctrl+Shift+L")) self.menuImportExport.addAction(self.actionImport) self.menus.append(self.menuImportExport) # - Chunk - self.menuChunk = QtGui.QMenu(self.tr("Chunk")) self.actionDeleteChunks = QtGui.QAction(self.tr("Delete Chunks"), self, triggered=self.deleteChunks) self.actionCreateChunks = QtGui.QAction(self.tr("Create Chunks"), self, triggered=self.createChunks) self.actionRepopChunks = QtGui.QAction(self.tr("Mark Chunks For Repopulation"), self, triggered=self.repopChunks) self.menuChunk.addAction(self.actionDeleteChunks) self.menuChunk.addAction(self.actionCreateChunks) self.menuChunk.addAction(self.actionRepopChunks) self.menus.append(self.menuChunk) # --- Resources --- self.resourceLoader = resourceLoader self.geometryCache = GeometryCache() progress("Loading textures and models...") self.setConfiguredBlocks(configuredBlocks) # Must be called after resourceLoader is in place self.editorOverlay = scenegraph.Node() self.biomeTypes = BiomeTypes() # --- Panels --- progress("Loading panels...") self.playerPanel = PlayerPanel(self) self.worldInfoPanel = WorldInfoPanel(self) self.panels = [self.playerPanel, self.worldInfoPanel] # --- Tools --- progress("Loading tools...") self.toolClasses = list(editortools.ToolClasses()) self.toolActionGroup = QtGui.QActionGroup(self) self.tools = [cls(self) for cls in self.toolClasses] self.toolActions = [tool.pickToolAction() for tool in self.tools] self.actionsByName = {action.toolName: action for action in self.toolActions} for tool in self.tools: tool.toolPicked.connect(self.chooseTool) for action in self.toolActions: self.toolActionGroup.addAction(action) self.selectionTool = self.getTool("Select") self.moveTool = self.getTool("Move") # --- Dimensions --- def _dimChanged(f): def _changed(): self.gotoDimension(f) return _changed dimButton = self.changeDimensionButton = QtGui.QToolButton() dimButton.setText(self.dimensionMenuLabel("")) dimAction = self.changeDimensionAction = QtGui.QWidgetAction(self) dimAction.setDefaultWidget(dimButton) dimMenu = self.dimensionsMenu = QtGui.QMenu() for dimName in self.worldEditor.listDimensions(): displayName = self.dimensionDisplayName(dimName) action = dimMenu.addAction(displayName) action._changed = _dimChanged(dimName) action.triggered.connect(action._changed) dimButton.setMenu(dimMenu) dimButton.setPopupMode(dimButton.InstantPopup) progress("Loading overworld dimension") self.gotoDimension("") # --- Editor stuff --- progress("Creating EditorTab...") self.editorTab = EditorTab(self) self.toolChanged.connect(self.toolDidChange) self.undoStack.indexChanged.connect(self.undoIndexChanged) self.findReplaceDialog = FindReplaceDialog(self) for resultsWidget in self.findReplaceDialog.resultsWidgets: self.dockWidgets.append((Qt.BottomDockWidgetArea, resultsWidget)) self.inspectorWidget = InspectorWidget(self) self.inspectorDockWidget = QtGui.QDockWidget(self.tr("Inspector"), objectName="inspector") self.inspectorDockWidget.setWidget(self.inspectorWidget) self.inspectorDockWidget.hide() self.dockWidgets.append((Qt.RightDockWidgetArea, self.inspectorDockWidget)) if len(self.toolActions): # Must be called after toolChanged is connected to editorTab self.toolActions[0].trigger() if hasattr(progress, 'progressCount') and progress.progressCount != progressMax: log.info("Update progressMax to %d, please.", progress.progressCount) # Connecting these signals to the EditorTab creates a circular reference through # the Qt objects, preventing the EditorSession from being destroyed def focusWorldView(self): self.editorTab.currentView().setFocus() def updateView(self): self.editorTab.currentView().update() def toolDidChange(self, tool): self.editorTab.toolDidChange(tool) # --- Block config --- # Emitted when configuredBlocks is changed. TextureAtlas and BlockModels will also have changed. configuredBlocksChanged = QtCore.Signal() def setConfiguredBlocks(self, configuredBlocks): blocktypes = self.worldEditor.blocktypes if self.configuredBlocks is not None: # Remove all previously configured blocks deadJsons = [] for json in blocktypes.blockJsons: if '__configured__' in json: deadJsons.append(json) deadIDs = set((j['internalName'], j['meta']) for j in deadJsons) blocktypes.allBlocks[:] = [ bt for bt in blocktypes.allBlocks if (bt.internalName, bt.meta) not in deadIDs ] for json in deadJsons: internalName = json['internalName'] fakeState = json['blockState'] blocktypes.blockJsons.remove(json) ID = blocktypes.IDsByName[internalName] del blocktypes.IDsByState[internalName + fakeState] del blocktypes.statesByID[ID, json['meta']] for blockDef in configuredBlocks: internalName = blockDef.internalName if internalName not in blocktypes.IDsByName: # no ID mapped to this name, skip continue if blockDef.meta == 0: blockType = blocktypes[internalName] blockJson = blockType.json else: # not automatically created by FML mapping loader ID = blocktypes.IDsByName[internalName] fakeState = '[%d]' % blockDef.meta nameAndState = internalName + fakeState blocktypes.blockJsons[nameAndState] = { 'displayName': internalName, 'internalName': internalName, 'blockState': fakeState, 'unknown': False, 'meta': blockDef.meta, } blockType = BlockType(ID, blockDef.meta, blocktypes) blocktypes.allBlocks.append(blockType) blocktypes.IDsByState[nameAndState] = ID, blockDef.meta blocktypes.statesByID[ID, blockDef.meta] = nameAndState blockJson = blockType.json blockJson['forcedModel'] = blockDef.modelPath blockJson['forcedModelTextures'] = blockDef.modelTextures blockJson['forcedModelRotation'] = blockDef.modelRotations blockJson['forcedRotationFlags'] = blockDef.rotationFlags blockJson['__configured__'] = True self.configuredBlocks = configuredBlocks self.blockModels = BlockModels(self.worldEditor.blocktypes, self.resourceLoader) self.textureAtlas = TextureAtlas(self.worldEditor, self.resourceLoader, self.blockModels) self.configuredBlocksChanged.emit() # --- Selection --- selectionChanged = QtCore.Signal(BoundingBox) _currentSelection = None @property def currentSelection(self): return self._currentSelection @currentSelection.setter def currentSelection(self, box): self._currentSelection = box self.enableSelectionCommands(box is not None and box.volume != 0) self.enableChunkSelectionCommands(box is not None) self.selectionChanged.emit(box) def enableSelectionCommands(self, enable): self.actionCut.setEnabled(enable) self.actionCopy.setEnabled(enable) self.actionPaste.setEnabled(enable) self.actionPaste_Blocks.setEnabled(enable) self.actionPaste_Entities.setEnabled(enable) self.actionClear.setEnabled(enable) self.actionDeleteBlocks.setEnabled(enable) self.actionDeleteEntities.setEnabled(enable) self.actionFill.setEnabled(enable) self.actionExport.setEnabled(enable) def enableChunkSelectionCommands(self, enable): self.actionDeleteChunks.setEnabled(enable) self.actionCreateChunks.setEnabled(enable) self.actionRepopChunks.setEnabled(enable) # --- Menu commands --- # - World - def save(self): self.undoStack.clearUndoBlock() saveTask = self.worldEditor.saveChangesIter() showProgress("Saving...", saveTask) self.dirty = False # - Edit - def cut(self): command = SimpleRevisionCommand(self, "Cut") with command.begin(): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Cutting...", task) task = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Cutting...", task) self.undoStack.push(command) def copy(self): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Copying...", task) def paste(self): if self.copiedSchematic is None: return view = self.editorTab.currentView() imp = PendingImport(self.copiedSchematic, view.mouseBlockPos, self.tr("<Pasted Object>")) command = PasteImportCommand(self, imp, "Paste") self.undoStack.push(command) def pasteBlocks(self): NotImplementedYet() def pasteEntities(self): NotImplementedYet() def findReplace(self): self.findReplaceDialog.exec_() def analyze(self): if self.currentSelection is None: return task = self.currentDimension.analyzeIter(self.currentSelection) showProgress("Analyzing...", task) outputDialog = AnalyzeOutputDialog(self, task.blocks, task.entityCounts, task.tileEntityCounts, task.dimension.worldEditor.displayName) def deleteSelection(self): command = SimpleRevisionCommand(self, "Delete") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) task = ComposeOperations(fillTask, entitiesTask) showProgress("Deleting...", task) self.pushCommand(command) def deleteBlocks(self): command = SimpleRevisionCommand(self, "Delete Blocks") with command.begin(): fillTask = self.currentDimension.fillBlocksIter(self.currentSelection, "air") showProgress("Deleting...", fillTask) self.pushCommand(command) def deleteEntities(self): command = SimpleRevisionCommand(self, "Delete Entities") with command.begin(): entitiesTask = RemoveEntitiesOperation(self.currentDimension, self.currentSelection) showProgress("Deleting...", entitiesTask) self.pushCommand(command) def fill(self): fillCommand(self) # - Select - def selectAll(self): command = SelectCommand(self, self.currentDimension.bounds, self.tr("Select All")) self.pushCommand(command) def deselect(self): command = SelectCommand(self, None) command.setText(self.tr("Deselect")) self.pushCommand(command) # - Chunk - def deleteChunks(self): if self.currentSelection is None: return command = SimpleRevisionCommand(self, self.tr("Delete Chunks")) with command.begin(): for cx in range(self.currentSelection.mincx, self.currentSelection.maxcx): for cz in range(self.currentSelection.mincz, self.currentSelection.maxcz): self.currentDimension.deleteChunk(cx, cz) self.pushCommand(command) def createChunks(self): QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Not implemented.", "Create chunks is not implemented yet!") def repopChunks(self): QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Not implemented.", "Repop chunks is not implemented yet!") # - Dimensions - dimensionChanged = QtCore.Signal(object) _dimDisplayNames = {"": "Overworld", "DIM-1": "Nether", "DIM1": "The End", } def dimensionDisplayName(self, dimName): return self._dimDisplayNames.get(dimName, dimName) def dimensionMenuLabel(self, dimName): return self.tr("Dimension: %s" % self.dimensionDisplayName(dimName)) def gotoDimension(self, dimName): dim = self.worldEditor.getDimension(dimName) if dim is self.currentDimension: return log.info("Going to dimension %s", dimName) self.changeDimensionButton.setText(self.dimensionMenuLabel(dimName)) self.currentDimension = dim self.loader = chunkloader.ChunkLoader(self.currentDimension) self.loader.chunkCompleted.connect(self.chunkDidComplete) self.loader.allChunksDone.connect(self.updateView) self.revisionChanged.connect(self.loader.revisionDidChange) self.dimensionChanged.emit(dim) # - Import/export - def import_(self): # prompt for a file to import startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.mainWindow, self.tr("Import"), startingDir, "All files (*.*)") if result: filename = result[0] if filename: self.importSchematic(filename) def export(self): # prompt for filename and format. maybe use custom browser to save to export library?? startingDir = Settings().value("import_dialog/starting_dir", getUserSchematicsDirectory()) result = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.mainWindow, self.tr("Export Schematic"), startingDir, "Schematic files (*.schematic)") if result: filename = result[0] if filename: task = self.currentDimension.exportSchematicIter(self.currentSelection) schematic = showProgress("Copying...", task) schematic.saveToFile(filename) # --- Library support --- def importSchematic(self, filename): schematic = WorldEditor(filename, readonly=True) ray = self.editorTab.currentView().rayAtCenter() pos, face = rayCastInBounds(ray, self.currentDimension) if pos is None: pos = ray.point name = os.path.basename(filename) imp = PendingImport(schematic, pos, name) command = PasteImportCommand(self, imp, "Import %s" % name) self.undoStack.push(command) # --- Undo support --- revisionChanged = QtCore.Signal(RevisionChanges) def undoIndexChanged(self, index): self.editorTab.currentView().update() def pushCommand(self, command): log.info("Pushing command %s" % command.text()) self.undoStack.push(command) def setUndoBlock(self, callback): self.undoStack.setUndoBlock(callback) def removeUndoBlock(self, callback): self.undoStack.removeUndoBlock(callback) def beginUndo(self): self.undoStack.clearUndoBlock() self.dirty = True self.worldEditor.beginUndo() def commitUndo(self): exhaust(self.commitUndoIter()) def commitUndoIter(self): for status in self.worldEditor.commitUndoIter(): yield status changes = self.worldEditor.getRevisionChanges(self.currentRevision-1, self.currentRevision) self.revisionChanged.emit(changes) def undoForward(self): self.worldEditor.redo() changes = self.worldEditor.getRevisionChanges(self.currentRevision-1, self.currentRevision) self.revisionChanged.emit(changes) def undoBackward(self): self.worldEditor.undo() changes = self.worldEditor.getRevisionChanges(self.currentRevision, self.currentRevision+1) self.revisionChanged.emit(changes) def gotoRevision(self, index): if index != self.currentRevision: changes = self.worldEditor.getRevisionChanges(self.currentRevision, index) self.worldEditor.gotoRevision(index) self.revisionChanged.emit(changes) @property def currentRevision(self): return self.worldEditor.currentRevision # --- Misplaced startup code? --- def loadDone(self): # Called by MCEditApp after the view is on screen to make sure view.center() works correctly # xxx was needed because view.centerOnPoint used a depthbuffer read for that, now what? try: try: player = self.worldEditor.getPlayer() center = Vector(*player.Position) + (0, 1.8, 0) dimNo = player.Dimension dimName = self.worldEditor.dimNameFromNumber(dimNo) log.info("Setting view angle to single-player player's view in dimension %s.", dimName) rotation = player.Rotation if dimName: self.gotoDimension(dimName) try: self.editorTab.currentView().yawPitch = rotation except AttributeError: pass except PlayerNotFound: try: center = self.worldEditor.getWorldMetadata().Spawn log.info("Centering on spawn position.") except AttributeError: log.info("Centering on world center") center = self.currentDimension.bounds.origin + (self.currentDimension.bounds.size * 0.5) self.editorTab.miniMap.centerOnPoint(center) self.editorTab.currentView().centerOnPoint(center, distance=0) except Exception as e: log.exception("Error while centering on player for world editor: %s", e) # --- Tools --- def toolShortcut(self, name): toolShortcuts = { "Select": "S", "Create": "D", } return toolShortcuts.get(name, "") def getTool(self, name): for t in self.tools: if t.name == name: return t def chooseTool(self, name): oldTool = self.currentTool self.currentTool = self.getTool(name) if oldTool is not self.currentTool: if oldTool: oldTool.toolInactive() self.currentTool.toolActive() self.toolChanged.emit(self.currentTool) self.actionsByName[name].setChecked(True) toolChanged = QtCore.Signal(object) def chunkDidComplete(self): from mcedit2 import editorapp editorapp.MCEditApp.app.updateStatusLabel(None, None, None, self.loader.cps, self.editorTab.currentView().fps) def updateStatusFromEvent(self, event): from mcedit2 import editorapp if event.blockPosition: id = self.currentDimension.getBlockID(*event.blockPosition) data = self.currentDimension.getBlockData(*event.blockPosition) block = self.worldEditor.blocktypes[id, data] biomeID = self.currentDimension.getBiomeID(event.blockPosition[0], event.blockPosition[2]) biome = self.biomeTypes.types.get(biomeID) if biome is not None: biomeName = biome.name else: biomeName = "Unknown biome" biomeText = "%s (%d)" % (biomeName, biomeID) editorapp.MCEditApp.app.updateStatusLabel(event.blockPosition, block, biomeText, self.loader.cps, event.view.fps) else: editorapp.MCEditApp.app.updateStatusLabel('(N/A)', None, None, self.loader.cps, event.view.fps) def viewMousePress(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mousePress') and event.blockPosition is not None: self.currentTool.mousePress(event) self.editorTab.currentView().update() def viewMouseMove(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseMove'): self.currentTool.mouseMove(event) self.editorTab.currentView().update() def viewMouseDrag(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseDrag'): self.currentTool.mouseDrag(event) self.editorTab.currentView().update() def viewMouseRelease(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseRelease'): self.currentTool.mouseRelease(event) self.editorTab.currentView().update() # --- EditorTab handling --- def tabCaption(self): return util.displayName(self.filename) def closeTab(self): if self.worldEditor is None: return True if self.dirty: msgBox = QtGui.QMessageBox(self.editorTab.window()) msgBox.setText("The world has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons( QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Save) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Save: self.save() if ret == QtGui.QMessageBox.Cancel: return False for panel in self.panels: panel.close() self.editorTab.saveState() self.worldEditor.close() self.worldEditor = None return True # --- Inspector --- def inspectBlock(self, pos): self.inspectorDockWidget.show() self.inspectorWidget.inspectBlock(pos) def inspectEntity(self, entity): self.inspectorDockWidget.show() self.inspectorWidget.inspectEntity(entity) # --- Zooming --- def zoomAndInspectBlock(self, pos): self.zoomToPoint(pos) self.inspectBlock(pos) def zoomAndInspectEntity(self, entity): self.zoomToPoint(entity.Position) self.inspectEntity(entity) def zoomToPoint(self, point): self.editorTab.currentView().centerOnPoint(point, 15) # --- Blocktype handling --- def unknownBlocks(self): for blocktype in self.worldEditor.blocktypes: if blocktype.unknown: yield blocktype.internalName
class EditorSession(QtCore.QObject): def __init__(self, filename, versionInfo, readonly=False): QtCore.QObject.__init__(self) self.undoStack = MCEUndoStack() self.filename = filename self.dockWidgets = [] self.undoBlock = None self.currentTool = None self.dirty = False self.copiedSchematic = None """:type : WorldEditor""" self.versionInfo = versionInfo # --- Open world editor --- try: self.worldEditor = WorldEditor(filename, readonly=readonly) except UndoFolderExists: msgBox = QtGui.QMessageBox() msgBox.setIcon(QtGui.QMessageBox.Warning) msgBox.setWindowTitle(self.tr("MCEdit tech demo")) msgBox.setText( self.tr("This world was not properly closed by MCEdit.")) msgBox.setInformativeText( self. tr("MCEdit may have crashed. An undo history was found for this world. You may try to resume editing " "with the saved undo history, or start over with the current state of the world." )) resumeBtn = msgBox.addButton("Resume Editing", QtGui.QMessageBox.ApplyRole) msgBox.addButton("Discard History", QtGui.QMessageBox.DestructiveRole) # msgBox.exec_() # clicked = msgBox.clickedButton() clicked = None # xxxxx resume = clicked is resumeBtn try: self.worldEditor = WorldEditor(filename, readonly=readonly, resume=resume) except NotImplementedError: NotImplementedYet() raise IOError("Uh-oh") self.worldEditor.requireRevisions() self.currentDimension = self.worldEditor.getDimension() self.loader = chunkloader.ChunkLoader(self.currentDimension) self.loader.chunkCompleted.connect(self.chunkDidComplete) self.loader.allChunksDone.connect( lambda: self.editorTab.currentView().update()) # --- Menus --- self.menus = [] self.menuEdit = QtGui.QMenu(self.tr("Edit")) self.menuEdit.setObjectName("menuEdit") self.actionCut = QtGui.QAction(self.tr("Cut"), self, triggered=self.cut, enabled=False) self.actionCut.setObjectName("actionCut") self.actionCopy = QtGui.QAction(self.tr("Copy"), self, triggered=self.copy, enabled=False) self.actionCopy.setObjectName("actionCopy") self.actionPaste = QtGui.QAction(self.tr("Paste"), self, triggered=self.paste, enabled=False) self.actionPaste.setObjectName("actionPaste") self.actionPaste_Blocks = QtGui.QAction(self.tr("Paste Blocks"), self, triggered=self.pasteBlocks, enabled=False) self.actionPaste_Blocks.setObjectName("actionPaste_Blocks") self.actionPaste_Entities = QtGui.QAction(self.tr("Paste Entities"), self, triggered=self.pasteEntities, enabled=False) self.actionPaste_Entities.setObjectName("actionPaste_Entities") self.actionClear = QtGui.QAction(self.tr("Clear"), self, triggered=self.clear, enabled=False) self.actionClear.setObjectName("actionClear") undoAction = self.undoStack.createUndoAction(self.menuEdit) undoAction.setShortcut(QtGui.QKeySequence.Undo) redoAction = self.undoStack.createRedoAction(self.menuEdit) redoAction.setShortcut(QtGui.QKeySequence.Redo) self.menuEdit.addAction(undoAction) self.menuEdit.addAction(redoAction) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionCut) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionPaste) self.menuEdit.addAction(self.actionPaste_Blocks) self.menuEdit.addAction(self.actionPaste_Entities) self.menuEdit.addAction(self.actionClear) self.actionCut.setShortcut(QtGui.QKeySequence.Cut) self.actionCopy.setShortcut(QtGui.QKeySequence.Copy) self.actionPaste.setShortcut(QtGui.QKeySequence.Paste) self.actionPaste_Blocks.setShortcut(QtGui.QKeySequence("Ctrl+Shift+V")) self.actionPaste_Entities.setShortcut(QtGui.QKeySequence("Ctrl+Alt+V")) self.actionClear.setShortcut(QtGui.QKeySequence.Quit) self.menus.append(self.menuEdit) # --- Resources --- i, v, p = self.versionInfo self.resourceLoader = i.getResourceLoader(v, p) self.geometryCache = GeometryCache() self.blockModels = BlockModels(self.worldEditor.blocktypes, self.resourceLoader) self.textureAtlas = TextureAtlas(self.worldEditor, self.resourceLoader, self.blockModels) self.editorOverlay = scenegraph.Node() # --- Panels --- self.playerPanel = PlayerPanel(self) self.panels = [self.playerPanel] # --- Tools --- def PickToolAction(tool): name = tool.name iconName = tool.iconName if iconName: iconPath = resourcePath( "mcedit2/assets/mcedit2/toolicons/%s.png" % iconName) if not os.path.exists(iconPath): log.error("Tool icon %s not found", iconPath) icon = None else: icon = QtGui.QIcon(iconPath) else: icon = None def _triggered(): self.chooseTool(name) action = QtGui.QAction( self.tr(name), self, #shortcut=self.toolShortcut(name), # xxxx coordinate with view movement keys triggered=_triggered, checkable=True, icon=icon, ) action.toolName = name action._triggered = _triggered # Needed because connecting _triggered doesn't increase its refcount self.toolActionGroup.addAction(action) return action self.toolClasses = list(editortools.ToolClasses()) self.toolActionGroup = QtGui.QActionGroup(self) self.toolActions = [PickToolAction(cls) for cls in self.toolClasses] self.actionsByName = { action.toolName: action for action in self.toolActions } self.tools = {cls.name: cls(self) for cls in self.toolClasses} self.selectionTool = self.tools["Select"] self.moveTool = self.tools["Move"] # --- Editor stuff --- self.editorTab = EditorTab(self) self.toolChanged.connect(self.editorTab.toolDidChange) self.undoStack.indexChanged.connect(self.undoIndexChanged) if len(self.toolActions): self.toolActions[0].trigger( ) # Must be called after toolChanged is connected to editorTab def dispose(self): if self.textureAtlas: self.textureAtlas.dispose() self.textureAtlas = None if self.editorTab: self.editorTab.destroy() self.editorTab = None if self.worldEditor: self.worldEditor.close() self.worldEditor = None def focusWorldView(self): self.editorTab.currentView().setFocus() # --- Selection --- selectionChanged = QtCore.Signal(BoundingBox) _currentSelection = None @property def currentSelection(self): return self._currentSelection @currentSelection.setter def currentSelection(self, box): self._currentSelection = box self.enableSelectionCommands(box is not None and box.volume != 0) self.selectionChanged.emit(box) def enableSelectionCommands(self, enable): self.actionCut.setEnabled(enable) self.actionCopy.setEnabled(enable) self.actionPaste.setEnabled(enable) self.actionPaste_Blocks.setEnabled(enable) self.actionPaste_Entities.setEnabled(enable) self.actionClear.setEnabled(enable) # --- Menu commands --- def save(self): self.undoStack.clearUndoBlock() self.worldEditor.saveChanges() self.dirty = False def cut(self): command = SimpleRevisionCommand(self, "Cut") with command.begin(): task = self.currentDimension.exportSchematicIter( self.currentSelection) self.copiedSchematic = showProgress("Cutting...", task) task = self.currentDimension.fillBlocksIter( self.currentSelection, "air") showProgress("Cutting...", task) self.undoStack.push(command) def copy(self): task = self.currentDimension.exportSchematicIter(self.currentSelection) self.copiedSchematic = showProgress("Copying...", task) def paste(self): if self.copiedSchematic is None: return imp = PendingImport(self.copiedSchematic, self.currentSelection.origin, self.tr("<Pasted Object>")) command = PasteImportCommand(self, imp, "Paste") self.undoStack.push(command) def pasteBlocks(self): NotImplementedYet() def pasteEntities(self): NotImplementedYet() def clear(self): self.selectionTool.deleteSelection() # --- Library support --- def importSchematic(self, filename): schematic = WorldEditor(filename, readonly=True) ray = self.editorTab.currentView().rayAtCenter() pos, face = rayCastInBounds(ray, self.currentDimension) if pos is None: pos = ray.point name = os.path.basename(filename) imp = PendingImport(schematic, pos, name) command = PasteImportCommand(self, imp, "Import %s" % name) self.undoStack.push(command) # --- Undo support --- revisionChanged = QtCore.Signal(int) def undoIndexChanged(self, index): self.editorTab.currentView().update() def pushCommand(self, command): self.undoStack.push(command) def setUndoBlock(self, callback): self.undoStack.setUndoBlock(callback) def removeUndoBlock(self, callback): self.undoStack.removeUndoBlock(callback) def beginUndo(self): self.undoStack.clearUndoBlock() self.dirty = True self.worldEditor.beginUndo() def commitUndo(self): self.worldEditor.commitUndo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoForward(self): self.worldEditor.redo() self.revisionChanged.emit(self.worldEditor.currentRevision) def undoBackward(self): self.worldEditor.undo() self.revisionChanged.emit(self.worldEditor.currentRevision) def gotoRevision(self, index): if index != self.currentRevision: self.worldEditor.gotoRevision(index) self.revisionChanged.emit(self.worldEditor.currentRevision) @property def currentRevision(self): return self.worldEditor.currentRevision # --- Misplaced startup code? --- def loadDone(self): # Called by MCEditApp after the view is on screen to make sure view.center() works correctly xxx used depth # buffer read for that, now what? try: player = self.worldEditor.getPlayer() center = Vector(*player.Position) log.info("Centering on single-player player.") rotation = player.Rotation try: self.editorTab.currentView().yawPitch = rotation except AttributeError: pass except PlayerNotFound: try: center = self.worldEditor.worldSpawnPosition() log.info("Centering on spawn position.") except AttributeError: log.info("Centering on world center") center = self.currentDimension.bounds.origin + ( self.currentDimension.bounds.size * 0.5) self.editorTab.miniMap.centerOnPoint(center) self.editorTab.currentView().centerOnPoint(center) # --- Tools --- def toolShortcut(self, name): toolShortcuts = { "Select": "S", "Create": "D", } return toolShortcuts.get(name, "") def chooseTool(self, name): oldTool = self.currentTool self.currentTool = self.tools[name] if oldTool is not self.currentTool: if oldTool: oldTool.toolInactive() self.currentTool.toolActive() self.toolChanged.emit(self.currentTool) self.actionsByName[name].setChecked(True) toolChanged = QtCore.Signal(object) def chunkDidComplete(self): from mcedit2 import editorapp editorapp.MCEditApp.app.updateStatusLabel( None, None, self.loader.cps, self.editorTab.currentView().fps) def updateStatusFromEvent(self, event): from mcedit2 import editorapp if event.blockPosition: id = self.currentDimension.getBlockID(*event.blockPosition) data = self.currentDimension.getBlockData(*event.blockPosition) block = self.worldEditor.blocktypes[id, data] editorapp.MCEditApp.app.updateStatusLabel(event.blockPosition, block, self.loader.cps, event.view.fps) else: editorapp.MCEditApp.app.updateStatusLabel('(N/A)', None, self.loader.cps, event.view.fps) def viewMousePress(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mousePress') and event.blockPosition is not None: self.currentTool.mousePress(event) self.editorTab.currentView().update() def viewMouseMove(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseMove'): self.currentTool.mouseMove(event) self.editorTab.currentView().update() def viewMouseDrag(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseDrag'): self.currentTool.mouseDrag(event) self.editorTab.currentView().update() def viewMouseRelease(self, event): self.updateStatusFromEvent(event) if hasattr(self.currentTool, 'mouseRelease'): self.currentTool.mouseRelease(event) self.editorTab.currentView().update() # --- EditorTab handling --- def tabCaption(self): return util.displayName(self.filename) def closeTab(self): if self.worldEditor is None: return True if self.dirty: msgBox = QtGui.QMessageBox(self.editorTab.window()) msgBox.setText("The world has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel) msgBox.setDefaultButton(QtGui.QMessageBox.Save) ret = msgBox.exec_() if ret == QtGui.QMessageBox.Save: self.save() if ret == QtGui.QMessageBox.Cancel: return False self.editorTab.saveState() self.worldEditor.close() self.worldEditor = None return True