diff --git a/src/mcedit2/editorcommands/analyze.py b/src/mcedit2/editorcommands/analyze.py new file mode 100644 index 0000000..86989f8 --- /dev/null +++ b/src/mcedit2/editorcommands/analyze.py @@ -0,0 +1,67 @@ +""" + analyze +""" +from __future__ import absolute_import +from PySide import QtGui, QtCore +import logging + +from mcedit2.util.load_ui import load_ui + +log = logging.getLogger(__name__) + +class AnalyzeOutputDialog(QtGui.QDialog): + def __init__(self, editorSession, blockCount, entityCount, tileEntityCount, *args, **kwargs): + super(AnalyzeOutputDialog, self).__init__(*args, **kwargs) + self.editorSession = editorSession + self.blocktypes = editorSession.worldEditor.blocktypes + + load_ui("analyze.ui", baseinstance=self) + blockTable = self.blockOutputTable + self.setupBlockTable(blockCount, blockTable) + + entityTable = self.entityOutputTable + self.setupEntityTable(entityCount, tileEntityCount, entityTable) + + self.sizeHint() + self.exec_() + + + + def setupBlockTable(self, blockCount, table): + blockCounts = sorted([(self.editorSession.worldEditor.blocktypes[ i & 0xfff, i >> 12], blockCount[i]) + for i in blockCount.nonzero()[0]]) + table.setRowCount(len(blockCounts)) + table.setColumnCount(4) + table.setHorizontalHeaderLabels(['Name', 'ID', 'Data', 'Count']) + + for n, output in enumerate(blockCounts): + nameItem = QtGui.QTableWidgetItem(output[0].displayName) + idItem = QtGui.QTableWidgetItem(str(output[0].ID)) + dataItem = QtGui.QTableWidgetItem(str(output[0].meta)) + countItem = QtGui.QTableWidgetItem(str(output[1])) + table.setItem(n, 0, nameItem) + table.setItem(n, 1, idItem) + table.setItem(n, 2, dataItem) + table.setItem(n, 3, countItem) + + table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + table.resizeColumnsToContents() + table.resizeRowsToContents() + table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + def setupEntityTable(self, entityCount, tileEntityCount, table): + table.setRowCount(len(entityCount.items())+len(tileEntityCount.items())) + table.setColumnCount(2) + table.setHorizontalHeaderLabels(['Name', 'Count']) + + for c in (entityCount, tileEntityCount): + for n, (id, count) in enumerate(sorted(c.iteritems())): + idItem = QtGui.QTableWidgetItem(str(id)) + countItem = QtGui.QTableWidgetItem(str(count)) + table.setItem(n, 0, idItem) + table.setItem(n, 1, countItem) + + table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + table.resizeColumnsToContents() + table.resizeRowsToContents() + table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) \ No newline at end of file diff --git a/src/mcedit2/editorsession.py b/src/mcedit2/editorsession.py index 14cba73..421c1f9 100644 --- a/src/mcedit2/editorsession.py +++ b/src/mcedit2/editorsession.py @@ -10,6 +10,7 @@ from mcedit2 import editortools from mcedit2.command import SimpleRevisionCommand from mcedit2.editorcommands.fill import fillCommand from mcedit2.editorcommands.find_replace import FindReplaceDialog +from mcedit2.editorcommands.analyze import AnalyzeOutputDialog from mcedit2.editortools.select import SelectCommand from mcedit2.panels.player import PlayerPanel from mcedit2.util.dialogs import NotImplementedYet @@ -197,6 +198,10 @@ class EditorSession(QtCore.QObject): 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) @@ -218,6 +223,8 @@ class EditorSession(QtCore.QObject): self.menuEdit.addAction(self.actionFill) self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionFindReplace) + self.menuEdit.addAction(self.actionAnalyze) + self.menus.append(self.menuEdit) @@ -500,7 +507,14 @@ class EditorSession(QtCore.QObject): def findReplace(self): self.findReplaceDialog.exec_() - + + def analyze(self): + task = self.currentDimension.analyzeIter(self.currentSelection) + showProgress("Analyzing...", task) + outputDialog = AnalyzeOutputDialog(self, self.worldEditor.analyzeBlockOutput, + self.worldEditor.analyzeEntityOutput, + self.worldEditor.analyzeTileEntityOutput) + def deleteSelection(self): command = SimpleRevisionCommand(self, "Delete") with command.begin(): diff --git a/src/mcedit2/ui/analyze.ui b/src/mcedit2/ui/analyze.ui new file mode 100644 index 0000000..644d96c --- /dev/null +++ b/src/mcedit2/ui/analyze.ui @@ -0,0 +1,28 @@ + + + Dialog + + + + 0 + 0 + 409 + 193 + + + + Analyze Output + + + + + + + + + + + + + + diff --git a/src/mceditlib/operations/analyze.py b/src/mceditlib/operations/analyze.py new file mode 100644 index 0000000..e133ce1 --- /dev/null +++ b/src/mceditlib/operations/analyze.py @@ -0,0 +1,87 @@ +""" + block_fill.py + + Optimized functions for mass-replacing blocks in a world. +""" +from __future__ import absolute_import +import logging + +import numpy +from collections import defaultdict +from mceditlib.operations import Operation + +log = logging.getLogger(__name__) + + + +class AnalyzeOperation(Operation): + def __init__(self, dimension, selection): + """ + Analyze all blocks in a selection. + + If blocksToReplace is given, it may be a list or tuple of blocktypes to replace with the given blocktype. + + Additionally, blockType may be given as a list of (oldBlockType, newBlockType) pairs + to perform multiple replacements. + + If updateLights is True, also checks to see if block changes require lighting updates and performs them. + + :type dimension: WorldEditorDimension + :type selection: `~.BoundingBox` + """ + super(AnalyzeOperation, self).__init__(dimension, selection) + + self.createSections = False + self.blocks = numpy.zeros(65536, dtype='uint32') + self.selection = selection + self.entityCounts = defaultdict(int) + self.tileEntityCounts = defaultdict(int) + + self.chunkCount = 0 + self.skipped = 0 + self.sections = 0 + log.info("Analyzing %s blocks", selection.volume) + + def done(self): + log.info(u"Analyze: Skipped {0}/{1} sections".format(self.skipped, self.sections)) + self.dimension.worldEditor.analyzeBlockOutput = self.blocks + self.dimension.worldEditor.analyzeEntityOutput = self.entityCounts + self.dimension.worldEditor.analyzeTileEntityOutput = self.tileEntityCounts + + def operateOnChunk(self, chunk): + self.chunkCount += 1 + + cx, cz = chunk.cx, chunk.cz + for cy in chunk.bounds.sectionPositions(cx, cz): + section = chunk.getSection(cy, create=False) + + if section is None: + continue + self.sections += 1 + + sectionMask = self.selection.section_mask(cx, cy, cz) + if sectionMask is None: + self.skipped += 1 + continue + + maskSize = sectionMask.sum() + if maskSize == 0: + self.skipped += 1 + continue + + for ref in chunk.Entities: + if ref.Position in self.selection: + self.entityCounts[ref.rootTag["id"].value] += 1 + + for ref in chunk.TileEntities: + if ref.Position in self.selection: + self.tileEntityCounts[ref.rootTag["id"].value] += 1 + + + blocks = numpy.array(section.Blocks[sectionMask], dtype='uint16') + blocks |= (numpy.array(section.Data[sectionMask], dtype='uint16') << 12) + b = numpy.bincount(blocks.ravel()) + self.blocks[:b.shape[0]] += b + + + diff --git a/src/mceditlib/worldeditor.py b/src/mceditlib/worldeditor.py index b5c4089..cbddfbb 100644 --- a/src/mceditlib/worldeditor.py +++ b/src/mceditlib/worldeditor.py @@ -12,13 +12,14 @@ from mceditlib import cachefunc from mceditlib.block_copy import copyBlocksIter from mceditlib.nbtattr import NBTListProxy from mceditlib.operations.block_fill import FillBlocksOperation +from mceditlib.operations.analyze import AnalyzeOperation from mceditlib.selection import BoundingBox from mceditlib.findadapter import findAdapter from mceditlib.multi_block import getBlocks, setBlocks from mceditlib.schematic import createSchematic from mceditlib.util import displayName, chunk_pos, exhaust, matchEntityTags from mceditlib.util.lazyprop import weakrefprop - +from mceditlib.blocktypes import BlockType log = logging.getLogger(__name__) @@ -598,6 +599,8 @@ class WorldEditor(object): if not len(matches): raise ValueError("Could not parse a dimension number from %s", dimName) return int(matches[-1]) + + analyzeOutput = None class WorldEditorDimension(object): def __init__(self, worldEditor, dimName): @@ -752,7 +755,6 @@ class WorldEditorDimension(object): def exportSchematic(self, selection): """ - :type selection: mceditlib.box.BoundingBox :return: :rtype: WorldEditor @@ -773,6 +775,14 @@ class WorldEditorDimension(object): def fillBlocks(self, box, block, blocksToReplace=(), updateLights=True): return exhaust(self.fillBlocksIter(box, block, blocksToReplace, updateLights)) + + # --- Analyze --- + + def analyzeIter(self, selection): + return AnalyzeOperation(self, selection) + + def analyze(self, selection): + return exhaust(self.analyzeIter(selection)) # --- Blocks by single coordinate ---