#!/usr/bin/env python import archive import os.path import struct import sys import cPickle from ctypes import create_string_buffer from PyQt4 import QtCore, QtGui try: import nsmblib HaveNSMBLib = True except ImportError: HaveNSMBLib = False ######################################################## # To Do: # # - Object Editor # - Moving objects around # # - Make UI simpler for Pop # - C speed saving # ######################################################## Tileset = None ############################################################################################# ########################## Tileset Class and Tile/Object Subclasses ######################### class TilesetClass(): '''Contains Tileset data. Inits itself to a blank tileset. Methods: addTile, removeTile, addObject, removeObject, clear''' class Tile(): def __init__(self, image, noalpha): '''Tile Constructor''' self.image = image self.noalpha = noalpha class Object(): def __init__(self, height, width, uslope, lslope, tilelist): '''Tile Constructor''' self.height = height self.width = width self.upperslope = uslope self.lowerslope = lslope self.tiles = tilelist def __init__(self): '''Constructor''' self.tiles = [] self.objects = [] self.slot = 0 def addTile(self, image, noalpha): '''Adds an tile class to the tile list with the passed image or parameters''' self.tiles.append(self.Tile(image, noalpha)) def addObject(self, height = 1, width = 1, uslope = [0, 0], lslope = [0, 0], tilelist = [[(0, 0xFFFF, 0)]]): '''Adds a new object''' global Tileset # Initialize trusim power! This is required to work, due to python's single default parameter initialization if tilelist == [[(0, 0xFFFF, 0)]]: tilelist = [[(0, 0xFFFF, 0)]] self.objects.append(self.Object(height, width, uslope, lslope, tilelist)) def removeObject(self, index): '''Removes an Object by Index number. Don't use this much, because we want objects to preserve their ID.''' self.objects.pop(index) def clear(self): '''Clears the tileset for a new file''' self.tiles = [] self.objects = [] def clearObjects(self): '''Clears the object data''' self.objects = [] ############################################################################################# ##################### Object List Widget and Model Setup with Painter ####################### class objectList(QtGui.QListView): def __init__(self, parent=None): super(objectList, self).__init__(parent) self.setFlow(QtGui.QListView.TopToBottom) # self.setViewMode(QtGui.QListView.ListMode) self.setIconSize(QtCore.QSize(96,96)) self.setGridSize(QtCore.QSize(100,100)) self.setMovement(QtGui.QListView.Static) self.setBackgroundRole(QtGui.QPalette.BrightText) self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.setWrapping(False) def SetupObjectModel(self, objects, tiles): global Tileset self.clear() count = 0 for object in objects: tex = QtGui.QPixmap(object.width * 24, object.height * 24) tex.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(tex) Xoffset = 0 Yoffset = 0 for i in range(len(object.tiles)): for tile in object.tiles[i]: if (Tileset.slot == 0): painter.drawPixmap(Xoffset, Yoffset, tiles[tile[1]].image) Xoffset += 24 Xoffset = 0 Yoffset += 24 painter.end() self.appendRow(QtGui.QStandardItem(QtGui.QIcon(tex), 'Object {0}'.format(count))) count += 1 @QtCore.pyqtSlot(QtGui.QTreeWidgetItem, int) def connectToTileWidget(tree, column): row = tree.text(0) if row[:7] == "Object ": newrow = int(row[7:]) index = window.objmodel.index(newrow, 0) window.objectList.setCurrentIndex(index) window.tileWidget.setObject(index) ############################################################################################# ######################## List Widget with custom painter/MouseEvent ######################### class displayWidget(QtGui.QListView): def __init__(self, parent=None): super(displayWidget, self).__init__(parent) self.setMinimumWidth(818) self.setMaximumWidth(818) self.setMinimumHeight(404) self.setMaximumHeight(404) self.setDragEnabled(True) self.setViewMode(QtGui.QListView.IconMode) self.setIconSize(QtCore.QSize(24,24)) self.setGridSize(QtCore.QSize(25,25)) self.setMovement(QtGui.QListView.Static) self.setAcceptDrops(False) self.setDropIndicatorShown(True) self.setResizeMode(QtGui.QListView.Adjust) self.setUniformItemSizes(True) self.setBackgroundRole(QtGui.QPalette.BrightText) self.setMouseTracking(True) self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.setItemDelegate(self.TileItemDelegate()) class TileItemDelegate(QtGui.QAbstractItemDelegate): """Handles tiles and their rendering""" def __init__(self): """Initialises the delegate""" QtGui.QAbstractItemDelegate.__init__(self) def paint(self, painter, option, index): """Paints an object""" global Tileset p = index.model().data(index, QtCore.Qt.DecorationRole) painter.drawPixmap(option.rect.x(), option.rect.y(), p.pixmap(24,24)) x = option.rect.x() y = option.rect.y() # Collision Overlays curTile = Tileset.tiles[index.row()] # Highlight stuff. colour = QtGui.QColor(option.palette.highlight()) colour.setAlpha(80) if option.state & QtGui.QStyle.State_Selected: painter.fillRect(option.rect, colour) def sizeHint(self, option, index): """Returns the size for the object""" return QtCore.QSize(24,24) ############################################################################################# ############################ Tile widget for drag n'drop Objects ############################ class tileOverlord(QtGui.QWidget): def __init__(self): super(tileOverlord, self).__init__() # Setup Widgets self.tiles = tileWidget() self.addObject = QtGui.QPushButton('Add') self.removeObject = QtGui.QPushButton('Remove') self.addRow = QtGui.QPushButton('+') self.removeRow = QtGui.QPushButton('-') self.addColumn = QtGui.QPushButton('+') self.removeColumn = QtGui.QPushButton('-') self.tilingMethod = QtGui.QComboBox() self.tilingMethod.addItems(['Repeat', 'Stretch Center', 'Stretch X', 'Stretch Y', 'Repeat Bottom', 'Repeat Top', 'Repeat Left', 'Repeat Right', 'Upward slope', 'Downward slope', 'Downward reverse slope', 'Upward reverse slope']) # Connections self.addObject.released.connect(self.addObj) self.removeObject.released.connect(self.removeObj) self.addRow.released.connect(self.tiles.addRow) self.removeRow.released.connect(self.tiles.removeRow) self.addColumn.released.connect(self.tiles.addColumn) self.removeColumn.released.connect(self.tiles.removeColumn) self.tilingMethod.activated.connect(self.setTiling) # Layout layout = QtGui.QGridLayout() layout.addWidget(self.tilingMethod, 0, 0, 1, 3) layout.addWidget(self.addObject, 0, 6, 1, 1) layout.addWidget(self.removeObject, 0, 7, 1, 1) layout.setRowMinimumHeight(1, 40) layout.setRowStretch(1, 1) layout.setRowStretch(2, 5) layout.setRowStretch(5, 5) layout.addWidget(self.tiles, 2, 1, 4, 6) layout.addWidget(self.addColumn, 3, 7, 1, 1) layout.addWidget(self.removeColumn, 4, 7, 1, 1) layout.addWidget(self.addRow, 6, 3, 1, 1) layout.addWidget(self.removeRow, 6, 4, 1, 1) self.setLayout(layout) def addObj(self): global Tileset Tileset.addObject() pix = QtGui.QPixmap(24, 24) pix.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(pix) painter.drawPixmap(0, 0, pix) painter.end() count = len(Tileset.objects) window.objmodel.appendRow(QtGui.QStandardItem(QtGui.QIcon(pix), 'Object {0}'.format(count-1))) a = QtGui.QTreeWidgetItem(window.treeki) a.setText(0, 'Object {0}'.format(count-1)) a.setFlags(QtCore.Qt.ItemFlags(0x25)) a.setIcon(1, QtGui.QIcon(pix)) index = window.objectList.currentIndex() window.objectList.setCurrentIndex(index) self.setObject(index) window.objectList.update() self.update() def removeObj(self): global Tileset index = window.objectList.currentIndex() Tileset.removeObject(index.row()) window.objmodel.removeRow(index.row()) self.tiles.clear() matchList = window.treeki.findItems("Object {0}".format(index.row()), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive | QtCore.Qt.MatchWrap) for x in matchList: index = window.treeki.indexFromItem(x, 0) realx = index.row() if x.parent(): y = x.parent().takeChild(realx) del y else: y =window.treeki.takeTopLevelItem(realx) del y window.objectList.update() self.update() def setObject(self, index): global Tileset object = Tileset.objects[index.row()] width = len(object.tiles[0])-1 height = len(object.tiles)-1 Xuniform = True Yuniform = True Xstretch = False Ystretch = False for tile in object.tiles[0]: if tile[0] != object.tiles[0][0][1]: Xuniform = False for tile in object.tiles: if tile[0][0] != object.tiles[0][0][1]: Yuniform = False if object.tiles[0][0][1] == object.tiles[0][width][0] and Xuniform == False: Xstretch = True if object.tiles[0][0][1] == object.tiles[height][0][0] and Xuniform == False: Ystretch = True if object.upperslope[0] != 0: if object.upperslope[0] == 0x90: self.tilingMethod.setCurrentIndex(8) elif object.upperslope[0] == 0x91: self.tilingMethod.setCurrentIndex(9) elif object.upperslope[0] == 0x92: self.tilingMethod.setCurrentIndex(10) elif object.upperslope[0] == 0x93: self.tilingMethod.setCurrentIndex(11) else: if Xuniform and Yuniform: self.tilingMethod.setCurrentIndex(0) elif Xstretch and Ystretch: self.tilingMethod.setCurrentIndex(1) elif Xstretch: self.tilingMethod.setCurrentIndex(2) elif Ystretch: self.tilingMethod.setCurrentIndex(3) elif Xuniform and Yuniform == False and object.tiles[0][0][0] == 0: self.tilingMethod.setCurrentIndex(4) elif Xuniform and Yuniform == False and object.tiles[height][0][0] == 0: self.tilingMethod.setCurrentIndex(5) elif Xuniform == False and Yuniform and object.tiles[0][0][0] == 0: self.tilingMethod.setCurrentIndex(6) elif Xuniform == False and Yuniform and object.tiles[0][width][0] == 0: self.tilingMethod.setCurrentIndex(7) self.tiles.setObject(object) # print 'Object {0}, Width: {1} / Height: {2}, Slope {3}/{4}'.format(index.row(), object.width, object.height, object.upperslope, object.lowerslope) # for row in object.tiles: # print 'Row: {0}'.format(row) # print '' @QtCore.pyqtSlot(int) def setTiling(self, listindex): global Tileset index = window.objectList.currentIndex() object = Tileset.objects[index.row()] if listindex == 0: # Repeat ctile = 0 crow = 0 for row in object.tiles: for tile in row: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 if listindex == 1: # Stretch Center if object.width < 3 and object.height < 3: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\nwide and 3 tiles tall to apply stretch center.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if crow == 0 and ctile == 0: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif crow == 0 and ctile == object.width-1: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif crow == object.height-1 and ctile == object.width-1: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif crow == object.height-1 and ctile == 0: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif crow == 0 or crow == object.height-1: object.tiles[crow][ctile] = (1, tile[1], tile[2]) elif ctile == 0 or ctile == object.width-1: object.tiles[crow][ctile] = (2, tile[1], tile[2]) else: object.tiles[crow][ctile] = (3, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 2: # Stretch X if object.width < 3: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\nwide to apply stretch X.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if ctile == 0: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif ctile == object.width-1: object.tiles[crow][ctile] = (0, tile[1], tile[2]) else: object.tiles[crow][ctile] = (1, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 3: # Stretch Y if object.height < 3: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\ntall to apply stretch Y.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if crow == 0: object.tiles[crow][ctile] = (0, tile[1], tile[2]) elif crow == object.height-1: object.tiles[crow][ctile] = (0, tile[1], tile[2]) else: object.tiles[crow][ctile] = (2, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 4: # Repeat Bottom if object.height < 2: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\ntall to apply repeat bottom.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if crow == object.height-1: object.tiles[crow][ctile] = (2, tile[1], tile[2]) else: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 5: # Repeat Top if object.height < 2: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\ntall to apply repeat top.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if crow == 0: object.tiles[crow][ctile] = (2, tile[1], tile[2]) else: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 6: # Repeat Left if object.width < 2: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\nwide to apply repeat left.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if ctile == 0: object.tiles[crow][ctile] = (1, tile[1], tile[2]) else: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 7: # Repeat Right if object.width < 2: reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\nwide to apply repeat right.") self.setObject(index) return ctile = 0 crow = 0 for row in object.tiles: for tile in row: if ctile == object.width-1: object.tiles[crow][ctile] = (1, tile[1], tile[2]) else: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0, 0] object.lowerslope = [0, 0] if listindex == 8: # Upward Slope ctile = 0 crow = 0 for row in object.tiles: for tile in row: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0x90, 1] object.lowerslope = [0x84, object.height - 1] self.tiles.slope = 1 self.tiles.update() if listindex == 9: # Downward Slope ctile = 0 crow = 0 for row in object.tiles: for tile in row: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0x91, 1] object.lowerslope = [0x84, object.height - 1] self.tiles.slope = 1 self.tiles.update() if listindex == 10: # Upward Reverse Slope ctile = 0 crow = 0 for row in object.tiles: for tile in row: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0x92, object.height - 1] object.lowerslope = [0x84, 1] self.tiles.slope = 0-(object.height-1) self.tiles.update() if listindex == 11: # Downward Reverse Slope ctile = 0 crow = 0 for row in object.tiles: for tile in row: object.tiles[crow][ctile] = (0, tile[1], tile[2]) ctile += 1 crow += 1 ctile = 0 object.upperslope = [0x93, object.height - 1] object.lowerslope = [0x84, 1] self.tiles.slope = 0-(object.height-1) self.tiles.update() class tileWidget(QtGui.QWidget): def __init__(self): super(tileWidget, self).__init__() self.tiles = [] self.size = [1, 1] self.setMinimumSize(120, 120) self.slope = 0 self.highlightedRect = QtCore.QRect() self.setAcceptDrops(True) self.object = 0 def clear(self): self.tiles = [] self.size = [1, 1] # [width, height] self.slope = 0 self.highlightedRect = QtCore.QRect() self.update() return def addColumn(self): global Tileset if self.size[0] >= 24: return if len(Tileset.objects) == 0: window.tileWidget.addObj() self.size[0] += 1 self.setMinimumSize(self.size[0]*24, self.size[1]*24) pix = QtGui.QPixmap(24,24) pix.fill(QtGui.QColor(205, 205, 255)) for y in xrange(self.size[1]): self.tiles.insert(((y+1) * self.size[0]) -1, [self.size[0]-1, y, pix]) curObj = Tileset.objects[self.object] curObj.width += 1 for row in curObj.tiles: row.append((0, 0xFFFF, 0)) self.update() self.updateList() def removeColumn(self): global Tileset if self.size[0] == 1: return if len(Tileset.objects) == 0: window.tileWidget.addObj() for y in xrange(self.size[1]): self.tiles.pop(((y+1) * self.size[0])-(y+1)) self.size[0] = self.size[0] - 1 self.setMinimumSize(self.size[0]*24, self.size[1]*24) curObj = Tileset.objects[self.object] curObj.width -= 1 for row in curObj.tiles: row.pop() self.update() self.updateList() def addRow(self): global Tileset if len(Tileset.objects) == 0: window.tileWidget.addObj() if self.size[1] >= 24: return self.size[1] += 1 self.setMinimumSize(self.size[0]*24, self.size[1]*24) pix = QtGui.QPixmap(24,24) pix.fill(QtGui.QColor(205, 205, 255)) for x in xrange(self.size[0]): self.tiles.append([x, self.size[1]-1, pix]) curObj = Tileset.objects[self.object] curObj.height += 1 curObj.tiles.append([]) for i in xrange(0, curObj.width): curObj.tiles[len(curObj.tiles)-1].append((0, 0xFFFF, 0)) self.update() self.updateList() def removeRow(self): global Tileset if self.size[1] == 1: return if len(Tileset.objects) == 0: window.tileWidget.addObj() for x in xrange(self.size[0]): self.tiles.pop() self.size[1] -= 1 self.setMinimumSize(self.size[0]*24, self.size[1]*24) curObj = Tileset.objects[self.object] curObj.height -= 1 curObj.tiles.pop() self.update() self.updateList() def setObject(self, object): self.clear() global Tileset self.size = [object.width, object.height] if not object.upperslope[1] == 0: if object.upperslope[0] & 2: self.slope = 0 - object.lowerslope[1] else: self.slope = object.upperslope[1] x = 0 y = 0 for row in object.tiles: for tile in row: if (Tileset.slot == 0) or ((tile[2] & 3) != 0): if (tile[1] == 0xFFFF): pix = QtGui.QPixmap(24,24) pix.fill(QtGui.QColor(205, 205, 255)) self.tiles.append([x, y, pix]) else: self.tiles.append([x, y, Tileset.tiles[tile[1]].image]) else: pix = QtGui.QPixmap(24,24) pix.fill(QtGui.QColor(205, 205, 255)) self.tiles.append([x, y, pix]) x += 1 y += 1 x = 0 self.object = window.objectList.currentIndex().row() self.update() self.updateList() def contextMenuEvent(self, event): TileMenu = QtGui.QMenu(self) self.contX = event.x() self.contY = event.y() TileMenu.addAction('Set tile...', self.setTile) TileMenu.exec_(event.globalPos()) def mousePressEvent(self, event): global Tileset if event.button() == 2: return if window.tileDisplay.selectedIndexes() == []: return currentSelected = window.tileDisplay.selectedIndexes() ix = 0 iy = 0 for modelItem in currentSelected: # Update yourself! centerPoint = self.contentsRect().center() tile = modelItem.row() upperLeftX = centerPoint.x() - self.size[0]*12 upperLeftY = centerPoint.y() - self.size[1]*12 lowerRightX = centerPoint.x() + self.size[0]*12 lowerRightY = centerPoint.y() + self.size[1]*12 x = (event.x() - upperLeftX)/24 + ix y = (event.y() - upperLeftY)/24 + iy if event.x() < upperLeftX or event.y() < upperLeftY or event.x() > lowerRightX or event.y() > lowerRightY: return self.tiles[(y * self.size[0]) + x][2] = Tileset.tiles[tile].image Tileset.objects[self.object].tiles[y][x] = (Tileset.objects[self.object].tiles[y][x][0], tile, Tileset.slot) ix += 1 if self.size[0]-1 < ix: ix = 0 iy += 1 if iy > self.size[1]-1: break self.update() self.updateList() def updateList(self): # Update the list >.> object = window.objmodel.itemFromIndex(window.objectList.currentIndex()) matchList = window.treeki.findItems("Object {0}".format(window.objectList.currentIndex().row()), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive | QtCore.Qt.MatchWrap) tex = QtGui.QPixmap(self.size[0] * 24, self.size[1] * 24) tex.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(tex) Xoffset = 0 Yoffset = 0 for tile in self.tiles: painter.drawPixmap(tile[0]*24, tile[1]*24, tile[2]) painter.end() try: object.setIcon(QtGui.QIcon(tex)) matchList[0].setIcon(1, QtGui.QIcon(tex)) except: pass window.objectList.update() def setTile(self): global Tileset dlg = self.setTileDialog() if dlg.exec_() == QtGui.QDialog.Accepted: # Do stuff centerPoint = self.contentsRect().center() upperLeftX = centerPoint.x() - self.size[0]*12 upperLeftY = centerPoint.y() - self.size[1]*12 tile = dlg.tile.value() tileset = dlg.tileset.currentIndex() x = (self.contX - upperLeftX)/24 y = (self.contY - upperLeftY)/24 if tileset != Tileset.slot: tex = QtGui.QPixmap(self.size[0] * 24, self.size[1] * 24) tex.fill(QtCore.Qt.transparent) self.tiles[(y * self.size[0]) + x][2] = tex Tileset.objects[self.object].tiles[y][x] = (Tileset.objects[self.object].tiles[y][x][0], tile, tileset) self.update() self.updateList() class setTileDialog(QtGui.QDialog): def __init__(self): QtGui.QDialog.__init__(self) self.setWindowTitle('Set tiles') self.tile = QtGui.QSpinBox() self.tile.setRange(0, 512) self.buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.layout = QtGui.QGridLayout() self.layout.addWidget(QtGui.QLabel('Tile:'), 0,3,1,1, QtCore.Qt.AlignLeft) self.layout.addWidget(self.tile, 1, 3, 1, 3) self.layout.addWidget(self.buttons, 2, 3) self.setLayout(self.layout) def paintEvent(self, event): painter = QtGui.QPainter() painter.begin(self) centerPoint = self.contentsRect().center() upperLeftX = centerPoint.x() - self.size[0]*12 lowerRightX = centerPoint.x() + self.size[0]*12 upperLeftY = centerPoint.y() - self.size[1]*12 lowerRightY = centerPoint.y() + self.size[1]*12 painter.fillRect(upperLeftX, upperLeftY, self.size[0] * 24, self.size[1]*24, QtGui.QColor(205, 205, 255)) for x, y, pix in self.tiles: painter.drawPixmap(upperLeftX + (x * 24), upperLeftY + (y * 24), pix) if not self.slope == 0: pen = QtGui.QPen() # pen.setStyle(QtCore.Qt.QDashLine) pen.setWidth(1) pen.setColor(QtCore.Qt.blue) painter.setPen(QtGui.QPen(pen)) painter.drawLine(upperLeftX, upperLeftY + (abs(self.slope) * 24), lowerRightX, upperLeftY + (abs(self.slope) * 24)) if self.slope > 0: main = 'Main' sub = 'Sub' elif self.slope < 0: main = 'Sub' sub = 'Main' font = painter.font() font.setPixelSize(8) font.setFamily('Monaco') painter.setFont(font) painter.drawText(upperLeftX+1, upperLeftY+10, main) painter.drawText(upperLeftX+1, upperLeftY + (abs(self.slope) * 24) + 9, sub) painter.end() ############################################################################################# ############################ Subclassed one dimension Item Model ############################ class PiecesModel(QtCore.QAbstractListModel): def __init__(self, parent=None): super(PiecesModel, self).__init__(parent) self.pixmaps = [] self.setSupportedDragActions(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction | QtCore.Qt.LinkAction) def data(self, index, role=QtCore.Qt.DisplayRole): if not index.isValid(): return None if role == QtCore.Qt.DecorationRole: return QtGui.QIcon(self.pixmaps[index.row()]) if role == QtCore.Qt.UserRole: return self.pixmaps[index.row()] return None def addPieces(self, pixmap): row = len(self.pixmaps) self.beginInsertRows(QtCore.QModelIndex(), row, row) self.pixmaps.insert(row, pixmap) self.endInsertRows() def flags(self,index): if index.isValid(): return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled) def clear(self): row = len(self.pixmaps) del self.pixmaps[:] def mimeTypes(self): return ['image/x-tile-piece'] def mimeData(self, indexes): mimeData = QtCore.QMimeData() encodedData = QtCore.QByteArray() stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.WriteOnly) for index in indexes: if index.isValid(): pixmap = QtGui.QPixmap(self.data(index, QtCore.Qt.UserRole)) stream << pixmap mimeData.setData('image/x-tile-piece', encodedData) return mimeData def rowCount(self, parent): if parent.isValid(): return 0 else: return len(self.pixmaps) def supportedDragActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction ############################################################################################# ################## Python-based RGB5a3 Decoding code from my BRFNT program ################## def RGB4A3Decode(tex): dest = QtGui.QImage(1024,512,QtGui.QImage.Format_ARGB32) dest.fill(QtCore.Qt.transparent) i = 0 for ytile in xrange(0, 512, 4): for xtile in xrange(0, 1024, 4): for ypixel in xrange(ytile, ytile + 4): for xpixel in xrange(xtile, xtile + 4): if(xpixel >= 1024 or ypixel >= 512): continue newpixel = (tex[i] << 8) | tex[i+1] if(newpixel >= 0x8000): # Check if it's RGB555 red = ((newpixel >> 10) & 0x1F) * 255 / 0x1F green = ((newpixel >> 5) & 0x1F) * 255 / 0x1F blue = (newpixel & 0x1F) * 255 / 0x1F alpha = 0xFF else: # If not, it's RGB4A3 alpha = ((newpixel & 0x7000) >> 12) * 255 / 0x7 blue = ((newpixel & 0xF00) >> 8) * 255 / 0xF green = ((newpixel & 0xF0) >> 4) * 255 / 0xF red = (newpixel & 0xF) * 255 / 0xF argb = (blue) | (green << 8) | (red << 16) | (alpha << 24) dest.setPixel(xpixel, ypixel, argb) i += 2 return dest def RGB4A3Encode(tex): destBuffer = create_string_buffer(1024*512*2) shortstruct = struct.Struct('>H') offset = 0 for ytile in xrange(0, 512, 4): for xtile in xrange(0, 1024, 4): for ypixel in xrange(ytile, ytile + 4): for xpixel in xrange(xtile, xtile + 4): if(xpixel >= 1024 or ypixel >= 512): continue pixel = tex.pixel(xpixel, ypixel) a = pixel >> 24 r = (pixel >> 16) & 0xFF g = (pixel >> 8) & 0xFF b = pixel & 0xFF if a < 245: #RGB4A3 alpha = a/32 red = r/16 green = g/16 blue = b/16 rgbDAT = (blue) | (green << 4) | (red << 8) | (alpha << 12) else: # RGB555 red = r/8 green = g/8 blue = b/8 rgbDAT = (blue) | (green << 5) | (red << 10) | (0x8000) # 0rrrrrgggggbbbbb shortstruct.pack_into(destBuffer, offset, rgbDAT) offset += 2 return destBuffer.raw ############################################################################################# ############ Main Window Class. Takes care of menu functions and widget creation ############ class MainWindow(QtGui.QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.tileImage = QtGui.QPixmap() self.alpha = True global Tileset Tileset = TilesetClass() self.name = '' self.setupMenus() self.setupWidgets() self.setuptile() self.newTileset() self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)) self.setWindowTitle("New Tileset") def setuptile(self): self.tileWidget.tiles.clear() self.model.clear() if self.alpha == True: for tile in Tileset.tiles: self.model.addPieces(tile.image) else: for tile in Tileset.tiles: self.model.addPieces(tile.noalpha) def newTileset(self): '''Creates a new, blank tileset''' global Tileset Tileset.clear() Tileset = TilesetClass() self.treeki.clear() # self.objectList.clear() self.objmodel.clear() EmptyPix = QtGui.QPixmap(24, 24) EmptyPix.fill(QtCore.Qt.black) for i in range(512): Tileset.addTile(EmptyPix, EmptyPix) self.setuptile() self.setWindowTitle('New Tileset') def openTileset(self): '''Opens a Koopatlas tileset arc and parses the heck out of it.''' path = str(QtGui.QFileDialog.getOpenFileName(self, "Open Koopatlas Tileset", '', "Image Files (*.arc)")) if path: self.setWindowTitle(os.path.basename(path)) Tileset.clear() name = path[str(path).rfind('/')+1:-4] file = open(path,'rb') data = file.read() file.close() arc = archive.U8() arc._load(data) Image = None behaviourdata = None objstrings = None metadata = None for key, value in arc.files: if value == None: pass if key.startswith('BG_tex/') and key.endswith('_tex.bin'): Image = arc[key] if key.startswith('BG_grp/') and key.endswith('_grp.bin'): Image = arc[key] if key.startswith('BG_unt/'): if key.endswith('_hd.bin'): metadata = arc[key] elif key.endswith('.bin'): objstrings = arc[key] if (Image == None) or (group == None) or (objstrings == None) or (metadata == None): QtGui.QMessageBox.warning(None, 'Error', 'Error - the necessary files were not found.\n\nNot a valid Koopatlas tileset, sadly.') return # Stolen from Reggie! Loads the Image Data. if HaveNSMBLib: argbdata = nsmblib.decodeTileset(Image) rgbdata = nsmblib.decodeTilesetNoAlpha(Image) dest = QtGui.QImage(argbdata, 1024, 512, 4096, QtGui.QImage.Format_ARGB32_Premultiplied) noalphadest = QtGui.QImage(rgbdata, 1024, 512, 4096, QtGui.QImage.Format_ARGB32_Premultiplied) else: dest = RGB4A3Decode(Image) self.tileImage = QtGui.QPixmap.fromImage(dest) noalpha = QtGui.QPixmap.fromImage(noalphadest) # Makes us some nice Tile Classes! Xoffset = 4 Yoffset = 4 for i in range(512): Tileset.addTile(self.tileImage.copy(Xoffset,Yoffset,24,24), noalpha.copy(Xoffset,Yoffset,24,24)) Xoffset += 32 if Xoffset >= 1024: Xoffset = 4 Yoffset += 32 # Load Objects meta = [] for i in xrange(len(metadata)/4): meta.append(struct.unpack_from('>H2B', metadata, i * 4)) tilelist = [[]] upperslope = [0, 0] lowerslope = [0, 0] byte = 0 for entry in meta: offset = entry[0] byte = struct.unpack_from('>B', objstrings, offset)[0] row = 0 while byte != 0xFF: if byte == 0xFE: tilelist.append([]) if (upperslope[0] != 0) and (lowerslope[0] == 0): upperslope[1] = upperslope[1] + 1 if lowerslope[0] != 0: lowerslope[1] = lowerslope[1] + 1 offset += 1 byte = struct.unpack_from('>B', objstrings, offset)[0] elif (byte & 0x80): if upperslope[0] == 0: upperslope[0] = byte else: lowerslope[0] = byte offset += 1 byte = struct.unpack_from('>B', objstrings, offset)[0] else: tilelist[len(tilelist)-1].append(struct.unpack_from('>BH', objstrings, offset).extend([0])) offset += 3 byte = struct.unpack_from('>B', objstrings, offset)[0] tilelist.pop() if (upperslope[0] & 0x80) and (upperslope[0] & 0x2): for i in range(lowerslope[1]): pop = tilelist.pop() tilelist.insert(0, pop) Tileset.addObject(entry[2], entry[1], upperslope, lowerslope, tilelist) tilelist = [[]] upperslope = [0, 0] lowerslope = [0, 0] Tileset.slot = Tileset.objects[0].tiles[0][0][2] & 3 self.tileWidget.tilesetType.setText('Pa{0}'.format(Tileset.slot)) self.setuptile() SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles) self.name = path def openImage(self): '''Opens an Image from png, and creates a new tileset from it.''' path = QtGui.QFileDialog.getOpenFileName(self, "Open Image", '', "Image Files (*.png)") if path: newImage = QtGui.QPixmap() self.tileImage = newImage if not newImage.load(path): QtGui.QMessageBox.warning(self, "Open Image", "The image file could not be loaded.", QtGui.QMessageBox.Cancel) return if ((newImage.width() == 768) & (newImage.height() == 384)): x = 0 y = 0 for i in range(512): Tileset.tiles[i].image = self.tileImage.copy(x*24,y*24,24,24) x += 1 if (x * 24) >= 768: y += 1 x = 0 else: QtGui.QMessageBox.warning(self, "Open Image", "The image was not the proper dimensions." "Please resize the image to 768x384 pixels.", QtGui.QMessageBox.Cancel) return self.setuptile() def saveImage(self): fn = QtGui.QFileDialog.getSaveFileName(self, 'Choose a new filename', '', '.png (*.png)') if fn == '': return tex = QtGui.QPixmap(768, 384) tex.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(tex) Xoffset = 0 Yoffset = 0 for tile in Tileset.tiles: painter.drawPixmap(Xoffset, Yoffset, tile.image) Xoffset += 24 if Xoffset >= 768: Xoffset = 0 Yoffset += 24 painter.end() tex.save(fn) def saveTileset(self): if self.name == '': self.saveTilesetAs() return outdata = self.saving(os.path.basename(self.name)[:-4]) fn = self.name f = open(fn, 'wb') f.write(outdata) f.close() def saveTilesetAs(self): fn = QtGui.QFileDialog.getSaveFileName(self, 'Choose a new filename', '', '.arc (*.arc)') if fn == '': return self.name = fn self.setWindowTitle(os.path.basename(unicode(fn))) outdata = self.saving(os.path.basename(unicode(fn))[:-4]) f = open(fn, 'wb') f.write(outdata) f.close() def saving(self, name): # Prepare tiles, objects, object metadata, and textures and stuff into buffers. textureBuffer = self.PackTexture() objectBuffers = self.PackObjects() objectBuffer = objectBuffers[0] objectMetaBuffer = objectBuffers[1] groupBuffer = self.PackGroups() # Make an arc and pack up the files! arc = archive.U8() arc['BG_tex'] = None arc['BG_tex/{0}_tex.bin'.format(name)] = textureBuffer arc['BG_unt'] = None arc['BG_unt/{0}.bin'.format(name)] = objectBuffer arc['BG_unt/{0}_hd.bin'.format(name)] = objectMetaBuffer arc['BG_grp'] = None arc['BG_grp/{0}_grp.bin'.format(name)] = groupBuffer return arc._dump() def PackTexture(self): tex = QtGui.QImage(1024, 512, QtGui.QImage.Format_ARGB32) tex.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(tex) Xoffset = 0 Yoffset = 0 for tile in Tileset.tiles: minitex = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32) minitex.fill(QtCore.Qt.transparent) minipainter = QtGui.QPainter(minitex) minipainter.drawPixmap(4, 4, tile.image) minipainter.end() # Read colours and DESTROY THEM (or copy them to the edges, w/e) for i in xrange(4,28): # Top Clamp colour = minitex.pixel(i, 4) for p in xrange(0,4): minitex.setPixel(i, p, colour) # Left Clamp colour = minitex.pixel(4, i) for p in xrange(0,4): minitex.setPixel(p, i, colour) # Right Clamp colour = minitex.pixel(i, 27) for p in xrange(27,31): minitex.setPixel(i, p, colour) # Bottom Clamp colour = minitex.pixel(27, i) for p in xrange(27,31): minitex.setPixel(p, i, colour) # UpperLeft Corner Clamp colour = minitex.pixel(4, 4) for x in xrange(0,4): for y in xrange(0,4): minitex.setPixel(x, y, colour) # UpperRight Corner Clamp colour = minitex.pixel(27, 4) for x in xrange(27,31): for y in xrange(0,4): minitex.setPixel(x, y, colour) # LowerLeft Corner Clamp colour = minitex.pixel(4, 27) for x in xrange(0,4): for y in xrange(27,31): minitex.setPixel(x, y, colour) # LowerRight Corner Clamp colour = minitex.pixel(27, 27) for x in xrange(27,31): for y in xrange(27,31): minitex.setPixel(x, y, colour) painter.drawImage(Xoffset, Yoffset, minitex) Xoffset += 32 if Xoffset >= 1024: Xoffset = 0 Yoffset += 32 painter.end() dest = RGB4A3Encode(tex) return dest def PackObjects(self): objectStrings = [] o = 0 for object in Tileset.objects: # Slopes if object.upperslope[0] != 0: # Reverse Slopes if object.upperslope[0] & 0x2: a = struct.pack('>B', object.upperslope[0]) if object.height == 1: iterationsA = 0 iterationsB = 1 else: iterationsA = object.upperslope[1] iterationsB = object.lowerslope[1] + object.upperslope[1] for row in xrange(iterationsA, iterationsB): for tile in object.tiles[row]: a = a + struct.pack('>BH', tile[0], tile[1]) a = a + '\xfe' if object.height > 1: a = a + struct.pack('>B', object.lowerslope[0]) for row in xrange(0, object.upperslope[1]): for tile in object.tiles[row]: a = a + struct.pack('>BH', tile[0], tile[1]) a = a + '\xfe' a = a + '\xff' objectStrings.append(a) # Regular Slopes else: a = struct.pack('>B', object.upperslope[0]) for row in xrange(0, object.upperslope[1]): for tile in object.tiles[row]: a = a + struct.pack('>BH', tile[0], tile[1]) a = a + '\xfe' if object.height > 1: a = a + struct.pack('>B', object.lowerslope[0]) for row in xrange(object.upperslope[1], object.height): for tile in object.tiles[row]: a = a + struct.pack('>BH', tile[0], tile[1]) a = a + '\xfe' a = a + '\xff' objectStrings.append(a) # Not slopes! else: a = '' for tilerow in object.tiles: for tile in tilerow: a = a + struct.pack('>BH', tile[0], tile[1]) a = a + '\xfe' a = a + '\xff' objectStrings.append(a) o += 1 Objbuffer = '' Metabuffer = '' i = 0 for a in objectStrings: Metabuffer = Metabuffer + struct.pack('>H2B', len(Objbuffer), Tileset.objects[i].width, Tileset.objects[i].height) Objbuffer = Objbuffer + a i += 1 return (Objbuffer, Metabuffer) def PackGroups(self): groupList = [] for kiddy in xrange(self.treeki.topLevelItemCount()): self.walkTree(groupList, self.treeki.topLevelItem(kiddy)); return cPickle.dumps(groupList) def walkTree(self, stringsList, treeItem): for kiddy in xrange(treeItem.childCount()): newList = [] self.walkTree(newList, treeItem.child(kiddy)) if treeItem.childCount() > 0: stringsList.append(str(treeItem.text(0)), newList) else: stringsList.append(str(treeItem.text(0))) def setupMenus(self): fileMenu = self.menuBar().addMenu("&File") pixmap = QtGui.QPixmap(60,60) pixmap.fill(QtCore.Qt.black) icon = QtGui.QIcon(pixmap) self.action = fileMenu.addAction(icon, "New", self.newTileset, QtGui.QKeySequence.New) fileMenu.addAction("Open...", self.openTileset, QtGui.QKeySequence.Open) fileMenu.addAction("Import Image...", self.openImage, QtGui.QKeySequence('Ctrl+I')) fileMenu.addAction("Export Image...", self.saveImage, QtGui.QKeySequence('Ctrl+E')) fileMenu.addAction("Save", self.saveTileset, QtGui.QKeySequence.Save) fileMenu.addAction("Save as...", self.saveTilesetAs, QtGui.QKeySequence.SaveAs) fileMenu.addAction("Quit", self.close, QtGui.QKeySequence('Ctrl-Q')) taskMenu = self.menuBar().addMenu("&Tasks") taskMenu.addAction("Toggle Alpha", self.toggleAlpha, QtGui.QKeySequence('Ctrl+Shift+A')) taskMenu.addAction("Clear Object Data", Tileset.clearObjects, QtGui.QKeySequence('Ctrl+Alt+Backspace')) def toggleAlpha(self): # Replace Alpha Image with non-Alpha images in model if self.alpha == True: self.alpha = False else: self.alpha = True self.setuptile() def TriggerNewGroup(self): a = QtGui.QTreeWidgetItem(self.treeki) a.setText(0, 'Double Click to Rename') a.setFlags(QtCore.Qt.ItemFlags(0x2F)) a.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ShowIndicator) a.setExpanded(True) def TriggerDelGroup(self): treecko = self.treeki.currentItem() if str(treecko.text(0)).find("Object"): eggs = treecko.takeChildren() self.treeki.addTopLevelItems(eggs) index = window.treeki.indexFromItem(treecko, 0) realx = index.row() if treecko.parent(): y = treecko.parent().takeChild(realx) else: y = self.treeki.takeTopLevelItem(realx) del y def setupWidgets(self): frame = QtGui.QFrame() frameLayout = QtGui.QHBoxLayout(frame) # Displays the tiles self.tileDisplay = displayWidget() # Sets up the model for the tile pieces self.model = PiecesModel(self) self.tileDisplay.setModel(self.model) # Object List self.objectList = objectList() self.objmodel = QtGui.QStandardItemModel() SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles) self.objectList.setModel(self.objmodel) self.tileWidget = tileOverlord() # Vertical Container A self.container = QtGui.QWidget() layout = QtGui.QVBoxLayout() layout.addWidget(self.tileDisplay) layout.addWidget(self.tileWidget) self.container.setLayout(layout) # Create the Group Tree self.treeki = QtGui.QTreeWidget() self.treeki.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.treeki.setDragEnabled(True) self.treeki.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.treeki.setAcceptDrops(True) self.treeki.setDropIndicatorShown(True) self.treeki.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked) self.treeki.setColumnCount(2) self.treeki.setColumnWidth(0, 200) self.treeki.setColumnWidth(1, 40) # Vertical Container B self.treeBario = QtGui.QWidget() tlayout = QtGui.QVBoxLayout() tlayout.addWidget(self.treeki) self.groupbar = QtGui.QToolBar() self.newGroup = QtGui.QAction('New Group', self.groupbar) self.newGroup.triggered.connect(self.TriggerNewGroup) self.newGroup.setShortcut(QtGui.QKeySequence('Ctrl+Shift+N')) self.delGroup = QtGui.QAction('Delete Group', self.groupbar) self.delGroup.triggered.connect(self.TriggerDelGroup) self.delGroup.setShortcut(QtGui.QKeySequence('Ctrl+Shift+Del')) self.groupbar.addAction(self.newGroup) self.groupbar.addAction(self.delGroup) self.groupbar.setFloatable(False) tlayout.addWidget(self.groupbar) self.treeBario.setLayout(tlayout) # Creates the Tab Widget for behaviours and objects self.tabWidget = QtGui.QTabWidget() # Sets the Tabs self.tabWidget.addTab(self.objectList, 'Object List') self.tabWidget.addTab(self.treeBario, 'Object Groups') # Connections do things! self.objectList.clicked.connect(self.tileWidget.setObject) self.treeki.itemClicked.connect(connectToTileWidget) frameLayout.addWidget(self.container) frameLayout.addWidget(self.tabWidget) self.setCentralWidget(frame) ############################################################################################# ####################################### Main Function ####################################### if __name__ == '__main__': import sys app = QtGui.QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) app.deleteLater()