Import Image as Map now implemented.
Removed mapCache from WorldEditor (questionable) createMap ignores idcounts.dat and updates it. Catch LevelFormatErrors while getting map datas.
This commit is contained in:
parent
d60ff4c552
commit
f46ce88d9a
@ -6,9 +6,11 @@ import logging
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide.QtCore import Qt
|
||||
import numpy
|
||||
from mcedit2.command import SimpleRevisionCommand
|
||||
from mcedit2.util.load_ui import load_ui
|
||||
from mcedit2.util.screen import centerWidgetInScreen
|
||||
from mceditlib.anvil.adapter import AnvilMapData
|
||||
from mceditlib.exceptions import LevelFormatError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -41,12 +43,22 @@ class MapListModel(QtCore.QAbstractListModel):
|
||||
return self.editorSession.worldEditor.getMap(mapID)
|
||||
|
||||
def imageForMapID(self, mapID):
|
||||
map = self.getMap(mapID)
|
||||
colorsRGBA = map.getColorsAsRGBA()
|
||||
colorsBGRA = numpy.ascontiguousarray(numpy.roll(colorsRGBA, 1, -1)[..., ::-1])
|
||||
image = QtGui.QImage(colorsBGRA, map.width, map.height, QtGui.QImage.Format_ARGB32)
|
||||
try:
|
||||
mapData = self.getMap(mapID)
|
||||
except LevelFormatError as e:
|
||||
log.exception("Invalid map for ID %s (while getting map image)", mapID)
|
||||
return None
|
||||
|
||||
colorsRGBA = mapData.getColorsAsRGBA()
|
||||
colorsBGRA = rgbaToBgra(colorsRGBA)
|
||||
image = QtGui.QImage(colorsBGRA, mapData.width, mapData.height, QtGui.QImage.Format_ARGB32)
|
||||
return image
|
||||
|
||||
def rgbaToBgra(colors):
|
||||
return numpy.ascontiguousarray(numpy.roll(colors, 1, -1)[..., ::-1])
|
||||
|
||||
bgraToRgba = rgbaToBgra
|
||||
|
||||
class MapPanel(QtGui.QWidget):
|
||||
def __init__(self, editorSession):
|
||||
"""
|
||||
@ -58,6 +70,7 @@ class MapPanel(QtGui.QWidget):
|
||||
|
||||
self.editorSession = editorSession
|
||||
self.pixmapItem = None
|
||||
self.mapListModel = None
|
||||
|
||||
load_ui("panels/map.ui", baseinstance=self)
|
||||
|
||||
@ -67,9 +80,8 @@ class MapPanel(QtGui.QWidget):
|
||||
action.triggered.connect(self.toggleView)
|
||||
self._toggleViewAction = action
|
||||
|
||||
self.mapListModel = MapListModel(self.editorSession)
|
||||
self.reloadModel()
|
||||
|
||||
self.mapListView.setModel(self.mapListModel)
|
||||
self.mapListView.clicked.connect(self.mapListClicked)
|
||||
|
||||
self.splitter.splitterMoved.connect(self.updatePixmapSize)
|
||||
@ -106,17 +118,25 @@ class MapPanel(QtGui.QWidget):
|
||||
|
||||
def displayMapID(self, mapID):
|
||||
if mapID is None:
|
||||
mapData = None
|
||||
else:
|
||||
try:
|
||||
mapData = self.mapListModel.getMap(mapID)
|
||||
except LevelFormatError as e:
|
||||
log.exception("Invalid data for map ID %s (while getting map info)", mapID)
|
||||
mapData = None
|
||||
|
||||
if mapData is None:
|
||||
self.widthLabel.setText("(N/A)")
|
||||
self.heightLabel.setText("(N/A)")
|
||||
self.dimensionLabel.setText("(N/A)")
|
||||
self.scaleLabel.setText("(N/A)")
|
||||
self.mapGraphicsView.setScene(None)
|
||||
else:
|
||||
map = self.mapListModel.getMap(mapID)
|
||||
self.widthLabel.setText(str(map.width))
|
||||
self.heightLabel.setText(str(map.height))
|
||||
self.dimensionLabel.setText(str(map.dimension))
|
||||
self.scaleLabel.setText(str(map.scale))
|
||||
self.widthLabel.setText(str(mapData.width))
|
||||
self.heightLabel.setText(str(mapData.height))
|
||||
self.dimensionLabel.setText(str(mapData.dimension))
|
||||
self.scaleLabel.setText(str(mapData.scale))
|
||||
self.updateScene(mapID)
|
||||
|
||||
def updateScene(self, mapID):
|
||||
@ -149,15 +169,29 @@ class MapPanel(QtGui.QWidget):
|
||||
if filename:
|
||||
colorTable = AnvilMapData.colorTable # xxxx dispatch through WorldEditor
|
||||
dialog = ImportMapDialog(filename, colorTable)
|
||||
dialog.importDone.connect(self.importImageDone)
|
||||
dialog.exec_()
|
||||
if dialog.result():
|
||||
convertedImages = dialog.getConvertedImages()
|
||||
command = MapImportCommand(self.editorSession, self.tr("Import Image as Map"))
|
||||
with command.begin():
|
||||
for x, y, image in convertedImages:
|
||||
colors = numpy.fromstring(image.bits(), dtype=numpy.uint8)
|
||||
colors.shape = 128, 128
|
||||
newMap = self.editorSession.worldEditor.createMap()
|
||||
newMap.colors[:] = colors
|
||||
newMap.save()
|
||||
|
||||
def importImageDone(self, importInfo):
|
||||
"""ImportMapCommand"""
|
||||
self.reloadModel()
|
||||
|
||||
def reloadModel(self):
|
||||
self.mapListModel = MapListModel(self.editorSession)
|
||||
self.mapListView.setModel(self.mapListModel)
|
||||
|
||||
|
||||
class MapImportCommand(SimpleRevisionCommand):
|
||||
pass
|
||||
|
||||
class ImportMapDialog(QtGui.QDialog):
|
||||
importDone = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, imageFilename, colorTable):
|
||||
super(ImportMapDialog, self).__init__()
|
||||
@ -172,6 +206,7 @@ class ImportMapDialog(QtGui.QDialog):
|
||||
|
||||
self.lines = []
|
||||
self.previewGroupItems = []
|
||||
self.convertedImages = []
|
||||
|
||||
self.colorTable = [(255)] * 256
|
||||
colorTable = numpy.array(colorTable)
|
||||
@ -206,6 +241,8 @@ class ImportMapDialog(QtGui.QDialog):
|
||||
self.previewGroup.removeFromGroup(item)
|
||||
self.previewGroupItems[:] = []
|
||||
|
||||
self.convertedImages[:] = []
|
||||
|
||||
tilesWide = self.tilesWideSpinbox.value()
|
||||
tilesHigh = self.tilesHighSpinbox.value()
|
||||
|
||||
@ -252,6 +289,8 @@ class ImportMapDialog(QtGui.QDialog):
|
||||
Qt.KeepAspectRatio if not expandImage else Qt.IgnoreAspectRatio)
|
||||
convertedImage = scaledImage.convertToFormat(QtGui.QImage.Format_Indexed8, self.colorTable)
|
||||
convertedPixmap = QtGui.QPixmap.fromImage(convertedImage)
|
||||
self.convertedImages.append((x, y, convertedImage))
|
||||
|
||||
convertedPixmapItem = QtGui.QGraphicsPixmapItem(convertedPixmap)
|
||||
convertedPixmapItem.setPos(x * tileOffset, y * tileOffset)
|
||||
self.previewGroup.addToGroup(convertedPixmapItem)
|
||||
@ -285,3 +324,5 @@ class ImportMapDialog(QtGui.QDialog):
|
||||
def showEvent(self, event):
|
||||
self.updateImageSize()
|
||||
|
||||
def getConvertedImages(self):
|
||||
return list(self.convertedImages)
|
||||
|
@ -132,5 +132,38 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>pushButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>742</x>
|
||||
<y>722</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>527</x>
|
||||
<y>-15</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>pushButton_2</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>651</x>
|
||||
<y>716</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>373</x>
|
||||
<y>-15</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
@ -979,27 +979,78 @@ class AnvilWorldAdapter(object):
|
||||
yield int(mapID)
|
||||
|
||||
def getMap(self, mapID):
|
||||
return AnvilMapData(self.getMapTag(mapID), mapID, self)
|
||||
|
||||
def getMapTag(self, mapID):
|
||||
mapPath = "data/map_%s.dat" % mapID
|
||||
if not self.selectedRevision.containsFile(mapPath):
|
||||
raise KeyError("Map %s not found" % mapID)
|
||||
|
||||
mapData = self.selectedRevision.readFile(mapPath)
|
||||
mapNBT = nbt.load(buf=mapData)
|
||||
return AnvilMapData(mapNBT)
|
||||
return mapNBT
|
||||
|
||||
def saveMapTag(self, mapID, mapTag):
|
||||
mapPath = "data/map_%s.dat" % mapID
|
||||
self.selectedRevision.writeFile(mapPath, mapTag.save())
|
||||
|
||||
def createMap(self):
|
||||
# idcounts.dat should hold the ID number of the last created map
|
||||
# but we can't trust it because of bugs in the old map import filters
|
||||
mapIDs = list(self.listMaps())
|
||||
if len(mapIDs):
|
||||
maximumID = max()
|
||||
mapID = maximumID + 1
|
||||
else:
|
||||
mapID = 0
|
||||
|
||||
idcountsTag = nbt.TAG_Compound()
|
||||
idcountsTag["map"] = nbt.TAG_Short(mapID)
|
||||
|
||||
# idcounts.dat is not compressed.
|
||||
self.selectedRevision.writeFile("data/idcounts.dat", idcountsTag.save(compressed=False))
|
||||
|
||||
mapData = AnvilMapData.create(mapID, self)
|
||||
|
||||
mapData.save()
|
||||
return mapData
|
||||
|
||||
|
||||
|
||||
class AnvilMapData(object):
|
||||
def __init__(self, rootTag):
|
||||
if "data" not in rootTag:
|
||||
def __init__(self, mapTag, mapID, adapter):
|
||||
if "data" not in mapTag:
|
||||
raise LevelFormatError("Map NBT is missing required tag 'data'")
|
||||
self.rootTag = rootTag["data"]
|
||||
self.mapTag = mapTag
|
||||
self.rootTag = mapTag["data"]
|
||||
if self._colors.shape[0] != self.width * self.height:
|
||||
raise LevelFormatError("Map colors array does not match map size. (%dx%d != %d)"
|
||||
% (self.width, self.height, self.colors.shape[0]))
|
||||
|
||||
self.mapID = mapID
|
||||
self.adapter = adapter
|
||||
|
||||
@classmethod
|
||||
def create(cls, mapID, adapter, width=128, height=128):
|
||||
mapTag = nbt.TAG_Compound()
|
||||
mapDataTag = nbt.TAG_Compound()
|
||||
mapTag["data"] = mapDataTag
|
||||
mapDataTag["colors"] = nbt.TAG_Byte_Array(numpy.zeros((width * height,), dtype=numpy.uint8))
|
||||
mapData = cls(mapTag, mapID, adapter)
|
||||
mapData.dimension = 0
|
||||
mapData.width = width
|
||||
mapData.height = height
|
||||
mapData.scale = 1
|
||||
mapData.xCenter = -1 << 30
|
||||
mapData.zCenter = -1 << 30
|
||||
return mapData
|
||||
|
||||
def save(self):
|
||||
self.adapter.saveMapTag(self.mapID, self.mapTag)
|
||||
|
||||
dimension = nbtattr.NBTAttr('dimension', nbt.TAG_Byte)
|
||||
height = nbtattr.NBTAttr('height', nbt.TAG_Short)
|
||||
width = nbtattr.NBTAttr('width', nbt.TAG_Short)
|
||||
height = nbtattr.NBTAttr('height', nbt.TAG_Short, 128)
|
||||
width = nbtattr.NBTAttr('width', nbt.TAG_Short, 128)
|
||||
scale = nbtattr.NBTAttr('scale', nbt.TAG_Byte)
|
||||
xCenter = nbtattr.NBTAttr('xCenter', nbt.TAG_Int, 0)
|
||||
zCenter = nbtattr.NBTAttr('zCenter', nbt.TAG_Int, 0)
|
||||
|
@ -215,7 +215,6 @@ class WorldEditor(object):
|
||||
:rtype: WorldEditor
|
||||
"""
|
||||
self.playerCache = {}
|
||||
self.mapCache = {}
|
||||
assert not (create and readonly)
|
||||
assert not create or adapterClass, "create=True requires an adapterClass"
|
||||
|
||||
@ -565,11 +564,10 @@ class WorldEditor(object):
|
||||
:param mapID: Map ID returned by listMaps
|
||||
:return:
|
||||
"""
|
||||
map = self.mapCache.get(mapID)
|
||||
if map is None:
|
||||
map = self.adapter.getMap(mapID)
|
||||
self.mapCache[mapID] = map
|
||||
return map
|
||||
return self.adapter.getMap(mapID)
|
||||
|
||||
def createMap(self):
|
||||
return self.adapter.createMap()
|
||||
|
||||
# --- Players ---
|
||||
|
||||
|
Reference in New Issue
Block a user