Tools to view, edit and create 7dtd world-maps https://chrani.net/map-tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
10 KiB

import os
import math
import pathlib
import numpy as np
from PyQt5.QtCore import (
Qt, QPoint, QSize
)
from PyQt5.QtGui import (
QCursor, QKeySequence, QPixmap
)
from PyQt5.QtWidgets import (
QWidget, QAction,
QScrollArea, QFrame, QApplication,
QFileDialog
)
from einops import rearrange
from .region_label_collection import RegionLabelCollection
class WorldRegionsWidget(QScrollArea):
root_dir = str
min_zoom_factor = float
max_zoom_factor = float
region_size = int
regions_per_row = int
world_loaded = bool
world_label_zoom_factor = float
world_label_region_size = float
region_label_collection = RegionLabelCollection
action_open_world_file = QAction
action_save_world_file = QAction
world_regions_image_raw_data = np.array
cursor_add_region = QCursor
cursor_remove_region = QCursor
cursor_hover_region = QCursor
cursor_region_context = QCursor
cursor_blank = QCursor
def __init__(self, zoom_factor=1.0, min_zoom_factor=0.5, max_zoom_factor=24.0, region_size=48):
super().__init__()
self.world_loaded = False
self.min_zoom_factor = min_zoom_factor
self.max_zoom_factor = max_zoom_factor
self.world_label_zoom_factor = zoom_factor
self.world_label_region_size = region_size
self.drag_in_progress = False
self.drag_start = QPoint(0, 0)
self.region_label_collection = RegionLabelCollection(self)
self.region_label_collection.setSpacing(0)
self.region_label_collection.setContentsMargins(0, 0, 0, 0)
region_label_collection_wrapper_widget = QWidget()
region_label_collection_wrapper_widget.setLayout(self.region_label_collection)
self.setAlignment(Qt.AlignCenter)
self.setFrameShape(QFrame.NoFrame)
self.setWidget(region_label_collection_wrapper_widget)
self.init_actions()
self.init_cursors()
@property
def zoom_adjusted_label_size(self):
return self.world_label_zoom_factor * self.world_label_region_size
def init_actions(self):
open_world_file = QAction()
open_world_file.setShortcut(
QKeySequence(Qt.ControlModifier | Qt.Key_O)
)
open_world_file.triggered.connect(self.open_world_file_dialogue)
self.action_open_world_file = open_world_file
self.addAction(self.action_open_world_file)
save_world_file = QAction()
save_world_file.setShortcut(
QKeySequence(Qt.ControlModifier | Qt.Key_S)
)
save_world_file.triggered.connect(self.save_world_file_dialogue)
self.action_save_world_file = save_world_file
self.addAction(self.action_save_world_file)
def init_cursors(self):
self.root_dir = os.path.dirname(os.path.abspath(__file__))
scale_options = (QSize(86, 86), Qt.KeepAspectRatio)
self.cursor_hover_region = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/hover_region.png')).scaled(*scale_options), -1, -1
)
self.cursor_add_region = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/add_region.png')).scaled(*scale_options), -1, -1
)
self.cursor_remove_region = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/remove_region.png')).scaled(*scale_options), -1, -1
)
self.cursor_region_context = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/region_context.png')).scaled(*scale_options), -1, -1
)
self.cursor_blank = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/blank_cursor.png')).scaled(*scale_options), -1, -1
)
self.setCursor(self.cursor_hover_region)
def action_zoom_in(self):
self.world_label_zoom_factor += self.min_zoom_factor
if self.world_label_zoom_factor >= self.max_zoom_factor:
self.world_label_zoom_factor = self.max_zoom_factor
return
self.region_label_collection.change_label_size(
self.zoom_adjusted_label_size
)
self.adjust_scrollbar_positions()
self.region_label_collection.center_mouse_on_hovered_label()
def action_zoom_out(self):
self.world_label_zoom_factor -= self.min_zoom_factor
if self.world_label_zoom_factor < self.min_zoom_factor:
self.world_label_zoom_factor = self.min_zoom_factor
return
self.region_label_collection.change_label_size(
self.zoom_adjusted_label_size
)
self.adjust_scrollbar_positions()
self.region_label_collection.center_mouse_on_hovered_label()
def adjust_scrollbar_positions(self):
size = (self.region_label_collection.currently_hovered_label.width())
widget_index_x, widget_index_y = self.region_label_collection.create_grid_index_from_region_string(
self.region_label_collection.currently_hovered_label.region_string
)
widget_position_x = int((widget_index_x + 1) * size)
widget_position_y = int((widget_index_y + 1) * size)
self.horizontalScrollBar().setValue(
int(widget_position_x - size / 2 - self.width() / 2)
)
self.verticalScrollBar().setValue(
int(widget_position_y - size / 2 - self.height() / 2)
)
@staticmethod
def control_is_pressed(key_event=None) -> bool:
modifiers = QApplication.keyboardModifiers()
return any([
modifiers == Qt.ControlModifier,
key_event is not None and key_event.key() in (16777249, 16777250)
])
@staticmethod
def shift_is_pressed(key_event=None) -> bool:
modifiers = QApplication.keyboardModifiers()
return any([
modifiers == Qt.ShiftModifier,
key_event is not None and key_event.key() == 16777248
])
def trigger_cursor_change(self, key_event=None):
if self.control_is_pressed(key_event=key_event):
try:
if self.region_label_collection.currently_hovered_label.is_region:
if not self.region_label_collection.currently_hovered_label.is_marked:
self.setCursor(self.cursor_add_region)
else:
self.setCursor(self.cursor_remove_region)
else:
self.setCursor(self.cursor_blank)
# all possible eventualities covered!
return
except AttributeError:
pass
elif self.shift_is_pressed(key_event=key_event):
try:
if self.region_label_collection.currently_hovered_label.is_region:
self.setCursor(self.cursor_region_context)
else:
self.setCursor(self.cursor_blank)
return
except AttributeError:
pass
# sets the default, if none of the above applies
self.setCursor(self.cursor_hover_region)
""" altering class functions """
def mousePressEvent(self, mouse_event):
if mouse_event.button() == Qt.LeftButton:
self.trigger_cursor_change()
if mouse_event.button() == Qt.RightButton and all([
not self.shift_is_pressed(),
not self.control_is_pressed()
]):
self.drag_in_progress = True
self.drag_start.setX(mouse_event.x())
self.drag_start.setY(mouse_event.y())
else:
self.drag_in_progress = False
super().mousePressEvent(mouse_event)
def mouseReleaseEvent(self, mouse_event):
if mouse_event.button() == Qt.LeftButton:
self.drag_in_progress = False
if mouse_event.button() == Qt.RightButton:
self.trigger_cursor_change()
super().mouseReleaseEvent(mouse_event)
def mouseMoveEvent(self, mouse_event):
if self.drag_in_progress:
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - (mouse_event.y() - self.drag_start.y())
)
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - (mouse_event.x() - self.drag_start.x())
)
self.drag_start = QPoint(
mouse_event.x(), mouse_event.y()
)
super().mouseMoveEvent(mouse_event)
def wheelEvent(self, mouse_event):
if mouse_event.angleDelta().y() > 0:
self.action_zoom_in()
else:
self.action_zoom_out()
""" We do NOT weant to call the super() here,
we do not want to scroll with the wheel, this is not a list """
# super().wheelEvent(event)
def keyPressEvent(self, key_event):
if any([
self.shift_is_pressed(key_event=key_event),
self.control_is_pressed(key_event=key_event)
]):
self.trigger_cursor_change(key_event)
super().keyPressEvent(key_event)
def keyReleaseEvent(self, key_event):
self.setCursor(self.cursor_hover_region)
super().keyReleaseEvent(key_event)
# file operations
def open_world_file_dialogue(self):
dialog = QFileDialog()
file_name = QFileDialog.getOpenFileName(
caption='load a world-file', options=dialog.options(), filter="RAW files (*.raw)"
)[0]
if file_name:
length = int(math.sqrt(pathlib.Path(file_name).stat().st_size / 2))
self.regions_per_row = int(length / 512)
offset = int(32 / 2) - int(self.regions_per_row / 2)
with open(file_name, 'r') as raw_file:
raw_data_array = np.fromfile(raw_file, dtype=np.uint16)
self.world_regions_image_raw_data = raw_data_array
self.region_label_collection.update_label_collection_with_raw_map_data(
raw_data_array, length, offset=offset
)
self.world_loaded = True
def save_world_file_dialogue(self):
map_data = rearrange(self.region_label_collection.blockshaped_map_data, 'x y dx dy -> (x dx) (y dy)')
options = QFileDialog.Options()
file_name, _ = QFileDialog.getSaveFileName(
self, caption='save the world', filter='RAW files (*.raw)', options=options
)
if file_name:
with open(file_name, 'w+b') as raw_file:
raw_file.write(
bytearray(np.flipud(map_data))
)