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