Basic Analyze Tool

Just some first things.
This commit is contained in:
Rubisk 2015-05-11 23:58:54 +02:00
parent fde3d1624b
commit fa65763c10
5 changed files with 209 additions and 3 deletions

View File

@ -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)

View File

@ -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():

28
src/mcedit2/ui/analyze.ui Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>409</width>
<height>193</height>
</rect>
</property>
<property name="windowTitle">
<string>Analyze Output</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,2">
<item>
<widget class="QTableWidget" name="blockOutputTable">
</widget>
</item>
<item>
<widget class="QTableWidget" name="entityOutputTable">
</widget>
</item>
</layout>
</widget>
<resources/>
</ui>

View File

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

View File

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