I recently got started with Python Qt and I'm trying to make a click-and-drag map editor that reads from and writes to a .json file. So far my progress is getting the QGraphicsView to accurately display the entire room layout with rectangles dynamically. But now I have no idea how to access the x and y offsets of each rectangle given that they can all be moved individually with the setFlag(QGraphicsItem.ItemIsMovable) property.
Here is a short version of my code:
import json
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
TILEWIDTH = 25
TILEHEIGHT = 15
OUTLINE = 3
class Room:
def __init__(self, name, width, height, offset_x, offset_z):
self.name = name
self.width = width
self.height = height
self.offset_x = offset_x
self.offset_z = offset_z
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#Setting up viewport
self.scene = QGraphicsScene(self)
self.view = QGraphicsView(self.scene, self)
self.view.scale(1, -1)
self.view.setStyleSheet("background:transparent; border: 0px")
self.setCentralWidget(self.view)
self.setGeometry(360, 190, 1200, 700)
self.showMaximized()
#Reading json and converting entries to rooms
self.room_list = []
with open("Data\Content\PB_DT_RoomMaster.json", "r") as file_reader:
self.content = json.load(file_reader)
for i in self.content:
self.room_list.append(self.convert_json_to_room(i))
self.draw_map()
def convert_json_to_room(self, json):
name = json["Key"]
width = json["Value"]["AreaWidthSize"] * TILEWIDTH
height = json["Value"]["AreaHeightSize"] * TILEHEIGHT
offset_x = round(json["Value"]["OffsetX"]/12.6) * TILEWIDTH
offset_z = round(json["Value"]["OffsetZ"]/7.2) * TILEHEIGHT
room = Room(name, width, height, offset_x, offset_z)
return room
def draw_map(self):
for i in self.room_list:
fill = QColor("#000000")
outline = QPen("#ffffff")
outline.setWidth(OUTLINE)
outline.setJoinStyle(Qt.MiterJoin)
#Drawing rooms
rect = self.scene.addRect(i.offset_x, i.offset_z, i.width, i.height, outline, fill)
rect.setFlag(QGraphicsItem.ItemIsMovable)
def main():
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is the json:
[
{
"Key": "m01SIP_000",
"Value": {
"LevelName": "m01SIP_000",
"EnemyPatternSuffix": "",
"AreaID": "EAreaID::m01SIP",
"SameRoom": "None",
"AdjacentRoomName": [
"m01SIP_001",
"m01SIP_024",
"m01SIP_023"
],
"OutOfMap": false,
"EventFlagNameForShowEventIfNotSeen": "None",
"EventFlagNameForMarkEventAsSeen": "None",
"WarpPositionX": 0.0,
"WarpPositionY": 0.0,
"WarpPositionZ": 0.0,
"RoomType": "ERoomType::Normal",
"RoomPath": "ERoomPath::Both",
"ConsiderLeft": true,
"ConsiderRight": true,
"ConsiderTop": true,
"ConsiderBottom": true,
"AreaWidthSize": 2,
"AreaHeightSize": 1,
"OffsetX": 25.2,
"OffsetZ": 0.0,
"DoorFlag": [
2,
32
],
"HiddenFlag": [],
"RoomCollisionFromSplineOnly": false,
"RoomCollisionFromGimmick": false,
"NoRoomOutBlinder": false,
"Collision2DProjectionDistance": -1.0,
"FlyMaterialDistance": 10.0,
"NoTraverse": [],
"MagCameraFovScale": 0.0,
"MagCameraVolumeScale": 1.5,
"DemagCameraFovScale": 0.77,
"DemagCameraVolumeScale": 0.0,
"BgmID": "BGM_m01SIP",
"BgmType": "ERoomBgmType::PlayNormal",
"Amb1": "AMB_01SIP_Ship_Roll01",
"AmbVol1": 70,
"Amb2": "",
"AmbVol2": 0,
"Amb3": "",
"AmbVol3": 0,
"Amb4": "",
"AmbVol4": 0,
"Decay_Near": 1260.0,
"Decay_Far": 2520.0,
"Decay_Far_Volume": 0.5,
"UseLava": false,
"FrameType": "EFramePlateType::FPT_Full",
"PerfLevel": 1
}
},
{
"Key": "m01SIP_001",
"Value": {
"LevelName": "m01SIP_001",
"EnemyPatternSuffix": "",
"AreaID": "EAreaID::m01SIP",
"SameRoom": "None",
"AdjacentRoomName": [
"m01SIP_000",
"m01SIP_023",
"m01SIP_002"
],
"OutOfMap": false,
"EventFlagNameForShowEventIfNotSeen": "None",
"EventFlagNameForMarkEventAsSeen": "None",
"WarpPositionX": 0.0,
"WarpPositionY": 0.0,
"WarpPositionZ": 0.0,
"RoomType": "ERoomType::Normal",
"RoomPath": "ERoomPath::Both",
"ConsiderLeft": true,
"ConsiderRight": true,
"ConsiderTop": true,
"ConsiderBottom": true,
"AreaWidthSize": 3,
"AreaHeightSize": 1,
"OffsetX": 50.4,
"OffsetZ": 0.0,
"DoorFlag": [
1,
24,
3,
8
],
"HiddenFlag": [],
"RoomCollisionFromSplineOnly": false,
"RoomCollisionFromGimmick": false,
"NoRoomOutBlinder": false,
"Collision2DProjectionDistance": -1.0,
"FlyMaterialDistance": 10.0,
"NoTraverse": [],
"MagCameraFovScale": 0.0,
"MagCameraVolumeScale": 1.5,
"DemagCameraFovScale": 0.77,
"DemagCameraVolumeScale": 0.0,
"BgmID": "BGM_m01SIP",
"BgmType": "ERoomBgmType::PlayNormal",
"Amb1": "AMB_01SIP_Ship_Roll01",
"AmbVol1": 70,
"Amb2": "AMB_01SIP_Wind02_LP",
"AmbVol2": 70,
"Amb3": "",
"AmbVol3": 0,
"Amb4": "",
"AmbVol4": 0,
"Decay_Near": 1260.0,
"Decay_Far": 2520.0,
"Decay_Far_Volume": 0.5,
"UseLava": false,
"FrameType": "EFramePlateType::FPT_Full",
"PerfLevel": 1
}
},
...
Basically I need to update the offsets of each room in the json after it's been moved on the map editor. What is the best way to approach this ?
You did not set the offset as a parameter of the rectangle since later you will have to do a conversion since it is in local coordinates of the item, instead use the method pos() that is in coordinates of the scene:
rect = self.scene.addRect(0, 0, i.width, i.height, outline, fill)
rect.setPos(i.offset_x, i.offset_z)
rect.setFlag(QGraphicsItem.ItemIsMovable)
Then you can get the position after doing:
i.offset_x = rect.pos().x()
i.offset_z = rect.pos().y()
I don't see the need to separate the information as you can create an item that stores the information so it can be retrieved later.
import json
import sys
from functools import cached_property
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPen
from PySide6.QtWidgets import (
QApplication,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
QMainWindow,
QGraphicsItem,
)
TILEWIDTH = 25
TILEHEIGHT = 15
OUTLINE = 3
KEY_METADATA = 1
class RoomItem(QGraphicsRectItem):
def __init__(self, x, y, width, height, metadata=None, parent=None):
super().__init__(0, 0, width, height, parent)
self.setPos(x, y)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setData(KEY_METADATA, metadata)
fill = QColor("#000000")
outline = QPen("#ffffff")
outline.setWidth(OUTLINE)
outline.setJoinStyle(Qt.MiterJoin)
self.setPen(outline)
self.setBrush(fill)
@classmethod
def from_json(cls, d):
x = d["Value"]["OffsetX"] / 12.6 * TILEWIDTH
y = d["Value"]["OffsetZ"] / 7.2 * TILEHEIGHT
width = d["Value"]["AreaWidthSize"] * TILEWIDTH
height = d["Value"]["AreaHeightSize"] * TILEHEIGHT
return cls(x, y, width, height, d)
def to_json(self):
d = self.data(KEY_METADATA) or dict()
d["Value"] = dict(
OffsetX=self.pos().x() * 12.6 / TILEWIDTH,
OffsetZ=self.pos().y() * 7.2 / TILEHEIGHT,
AreaWidthSize=self.rect().width() / TILEWIDTH,
AreaHeightSize=self.rect().height() / TILEHEIGHT,
)
return d
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
@cached_property
def items(self):
return list()
def initUI(self):
# Setting up viewport
self.scene = QGraphicsScene(self)
self.view = QGraphicsView(self.scene, self)
self.view.scale(1, -1)
self.view.setStyleSheet("background:transparent; border: 0px")
self.setCentralWidget(self.view)
self.setGeometry(360, 190, 1200, 700)
self.showMaximized()
def load_from_json(self, filename):
with open(filename, "r") as f:
for e in json.load(f):
item = RoomItem.from_json(e)
self.scene.addItem(item)
self.items.append(item)
def save_to_json(self, filename):
with open(filename, "w") as f:
l = []
for item in self.items:
l.append(item.to_json())
json.dump(l, f)
def main():
app = QApplication(sys.argv)
main = Main()
main.show()
main.load_from_json("data.json")
ret = app.exec_()
main.save_to_json("data.json")
sys.exit(ret)
if __name__ == "__main__":
main()