How to access the position offsets of movable QGraphicsItems?

351 views Asked by At

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 ?

1

There are 1 answers

3
eyllanesc On BEST ANSWER

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