Browse Source

hacked together a bunch of stuff, probably requires a lot of fixing :)

You can now fully edit any map. Load an existing map, save regions, load regions, save the map back. It's not all that comfortable yet
using a new library for this, it's a bit slower, but much more versatile than skimages view_as_blocks
updated requirements
master
Christian Voigt 1 year ago
parent
commit
c38b965196
  1. 9
      README.md
  2. 3
      requirements.txt
  3. 136
      world_regions_widget/__init__.py
  4. 86
      world_regions_widget/region_label.py
  5. 36
      world_regions_widget/region_label_collection.py

9
README.md

@ -6,14 +6,15 @@ A complete set of tools to edit, modify or create a new map from @@ -6,14 +6,15 @@ A complete set of tools to edit, modify or create a new map from
scratch!
### Current state
Well, we have a barebone framework for the World-Regions-Widget
the whole project needs to be defined
That's about it for now
Well, we have a barebone framework for the World-Regions-Widget
You can do very basic editing on existing world maps, like swapping regions with stored ones.
#### Core Functions
Loading of 7dtd RAW map files (dtm.raw, any RAW 16bit grayscale that's stored in order) (CTRL+O)
~~Saving of any loaded map. It's of no use due to the fact that we can't change anything yet :) (CTRL+S)~~
Saving of any loaded map (CTRL+S)
Swap/Load any existing region with a region file on disk (shift + right-click for context menu)
Saving of any individual region in RAW format (shift + right-click for context menu)
*proper* Zooming in and out and navigating in a loaded map (Wheel, rightMouseDrag)
Visually marking and unmarking regions of a loaded 7dtd map file (CTRL+leftMouse)
Identifying region strings (Statusbar on Hover)

3
requirements.txt

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
pyqt5
numpy
scikit-image
einops

136
world_regions_widget/__init__.py

@ -15,6 +15,7 @@ from PyQt5.QtWidgets import ( @@ -15,6 +15,7 @@ from PyQt5.QtWidgets import (
QFileDialog
)
from einops import rearrange
from .region_label_collection import RegionLabelCollection
@ -38,12 +39,11 @@ class WorldRegionsWidget(QScrollArea): @@ -38,12 +39,11 @@ class WorldRegionsWidget(QScrollArea):
world_regions_image_raw_data = np.array
currently_hovered_label = object
cursor_add_region = QCursor
cursor_remove_region = QCursor
cursor_hover_region = QCursor
cursor_plot_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__()
@ -106,15 +106,15 @@ class WorldRegionsWidget(QScrollArea): @@ -106,15 +106,15 @@ class WorldRegionsWidget(QScrollArea):
self.cursor_remove_region = QCursor(
QPixmap(os.path.join(self.root_dir, 'cursors/remove_region.png')).scaled(*scale_options), -1, -1
)
self.cursor_plot_region = QCursor(
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 set_currently_hovered_label(self, label):
self.currently_hovered_label = label
def action_zoom_in(self):
self.world_label_zoom_factor += self.min_zoom_factor
if self.world_label_zoom_factor >= self.max_zoom_factor:
@ -125,7 +125,7 @@ class WorldRegionsWidget(QScrollArea): @@ -125,7 +125,7 @@ class WorldRegionsWidget(QScrollArea):
self.zoom_adjusted_label_size
)
self.adjust_scrollbar_positions()
self.center_mouse_on_hovered_label()
self.region_label_collection.center_mouse_on_hovered_label()
def action_zoom_out(self):
self.world_label_zoom_factor -= self.min_zoom_factor
@ -137,16 +137,16 @@ class WorldRegionsWidget(QScrollArea): @@ -137,16 +137,16 @@ class WorldRegionsWidget(QScrollArea):
self.zoom_adjusted_label_size
)
self.adjust_scrollbar_positions()
self.center_mouse_on_hovered_label()
self.region_label_collection.center_mouse_on_hovered_label()
def adjust_scrollbar_positions(self):
size = (self.currently_hovered_label.width())
widget_index_x = int(self.currently_hovered_label.region_string.split(".")[1]) + 17
widget_index_y = int((int(self.currently_hovered_label.region_string.split(".")[2]) * -1)) + 16
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 * size)
widget_position_y = int(widget_index_y * size)
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)
@ -155,41 +155,48 @@ class WorldRegionsWidget(QScrollArea): @@ -155,41 +155,48 @@ class WorldRegionsWidget(QScrollArea):
int(widget_position_y - size / 2 - self.height() / 2)
)
def center_mouse_on_hovered_label(self):
QCursor.setPos(self.currently_hovered_label.mapToGlobal(QPoint(
int(self.currently_hovered_label.width() / 2),
int(self.currently_hovered_label.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)
])
def trigger_cursor_change(self, event=None):
@staticmethod
def shift_is_pressed(key_event=None) -> bool:
modifiers = QApplication.keyboardModifiers()
control_is_pressed = (
modifiers == Qt.ControlModifier or (event is not None and event.key() in (16777249, 16777250))
)
shift_is_pressed = (
modifiers == Qt.ShiftModifier or (event is not None and event.key() == 16777248)
)
return any([
modifiers == Qt.ShiftModifier,
key_event is not None and key_event.key() == 16777248
])
if control_is_pressed:
def trigger_cursor_change(self, key_event=None):
if self.control_is_pressed(key_event=key_event):
try:
if self.currently_hovered_label.is_region:
if not self.currently_hovered_label.is_marked:
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)
# all states are covered here, we can return
return
else:
self.setCursor(self.cursor_blank)
# all possible eventualities covered!
return
except AttributeError:
pass
elif shift_is_pressed:
elif self.shift_is_pressed(key_event=key_event):
try:
if self.currently_hovered_label.is_region:
self.setCursor(self.cursor_plot_region)
# it's either on or not
return
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
@ -198,44 +205,47 @@ class WorldRegionsWidget(QScrollArea): @@ -198,44 +205,47 @@ class WorldRegionsWidget(QScrollArea):
self.setCursor(self.cursor_hover_region)
""" altering class functions """
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
def mousePressEvent(self, mouse_event):
if mouse_event.button() == Qt.LeftButton:
self.trigger_cursor_change()
if event.button() == Qt.RightButton:
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(event.x())
self.drag_start.setY(event.y())
self.drag_start.setX(mouse_event.x())
self.drag_start.setY(mouse_event.y())
else:
self.drag_in_progress = False
super().mousePressEvent(event)
super().mousePressEvent(mouse_event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
def mouseReleaseEvent(self, mouse_event):
if mouse_event.button() == Qt.LeftButton:
self.drag_in_progress = False
if event.button() == Qt.RightButton:
if mouse_event.button() == Qt.RightButton:
self.trigger_cursor_change()
super().mouseReleaseEvent(event)
super().mouseReleaseEvent(mouse_event)
def mouseMoveEvent(self, event):
def mouseMoveEvent(self, mouse_event):
if self.drag_in_progress:
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - (event.y() - self.drag_start.y())
self.verticalScrollBar().value() - (mouse_event.y() - self.drag_start.y())
)
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - (event.x() - self.drag_start.x())
self.horizontalScrollBar().value() - (mouse_event.x() - self.drag_start.x())
)
self.drag_start = QPoint(
event.x(), event.y()
mouse_event.x(), mouse_event.y()
)
super().mouseMoveEvent(event)
super().mouseMoveEvent(mouse_event)
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
def wheelEvent(self, mouse_event):
if mouse_event.angleDelta().y() > 0:
self.action_zoom_in()
else:
self.action_zoom_out()
@ -244,16 +254,19 @@ class WorldRegionsWidget(QScrollArea): @@ -244,16 +254,19 @@ class WorldRegionsWidget(QScrollArea):
we do not want to scroll with the wheel, this is not a list """
# super().wheelEvent(event)
def keyPressEvent(self, event):
if event.key() in (16777249, 16777250, 16777248):
self.trigger_cursor_change(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(event)
super().keyPressEvent(key_event)
def keyReleaseEvent(self, event):
def keyReleaseEvent(self, key_event):
self.setCursor(self.cursor_hover_region)
super().keyReleaseEvent(event)
super().keyReleaseEvent(key_event)
# file operations
def open_world_file_dialogue(self):
@ -279,6 +292,7 @@ class WorldRegionsWidget(QScrollArea): @@ -279,6 +292,7 @@ class WorldRegionsWidget(QScrollArea):
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
@ -286,5 +300,5 @@ class WorldRegionsWidget(QScrollArea): @@ -286,5 +300,5 @@ class WorldRegionsWidget(QScrollArea):
if file_name:
with open(file_name, 'w+b') as raw_file:
raw_file.write(
bytearray(np.flipud(self.region_label_collection.raw_map_data))
bytearray(np.flipud(map_data))
)

86
world_regions_widget/region_label.py

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
import math
import pathlib
from typing import TYPE_CHECKING
import numpy as np
@ -5,7 +7,7 @@ from PyQt5.QtCore import ( @@ -5,7 +7,7 @@ from PyQt5.QtCore import (
Qt, QSize
)
from PyQt5.QtGui import (
QPixmap, QColor, QImage
QPixmap, QColor, QImage, QPainter
)
from PyQt5.QtWidgets import (
QLabel, QGraphicsColorizeEffect, QApplication, QMenu, QAction, QWidgetAction, QFileDialog
@ -17,17 +19,15 @@ if TYPE_CHECKING: @@ -17,17 +19,15 @@ if TYPE_CHECKING:
class RegionLabel(QLabel):
dimensions = QSize
image = QImage
raw_data_array = np.array
parent_collection = 'RegionLabelCollection'
parent_collection_index = str
region_string = str
is_marked = bool
is_region = bool
is_selected = bool
context_menu = QMenu
context_menu_is_shown = bool
adjacent_marked_region_labels = dict
@ -44,18 +44,38 @@ class RegionLabel(QLabel): @@ -44,18 +44,38 @@ class RegionLabel(QLabel):
self.is_selected = False
self.parent_collection = collection
self.parent_collection_index = collection.create_region_string_from_grid_index(x=x, y=y)
self.region_string = self.parent_collection.create_region_string_from_grid_index(x=x, y=y)
# 7dtd seems to be peculiar about their coordinate system. I arrived at this by trial and error,
# I haven't really grasped the logic of it :)
self.region_string = self.parent_collection.create_region_string_from_grid_index(x, y)
self.setStatusTip(self.region_string)
pixmap.fill(self.get_color_by_coordinates(x, y))
self.setPixmap(pixmap)
self.setScaledContents(True)
self.setMouseTracking(True)
self.init_context_menu()
def set_label_pixmap(self, chunk):
size = (512, 512)
pixmap = QPixmap(*size)
pixmap.fill(QColor("transparent"))
painter = QPainter(pixmap)
painter.drawPixmap(0, 0, *size, QPixmap.fromImage(chunk))
painter.end()
self.setPixmap(pixmap)
""" getter + setter"""
def set_raw_data_array(self, raw_data_array: np.array = None) -> None:
self.raw_data_array = raw_data_array
def mark_as_region(self) -> None:
self.is_region = True
def set_adjacent_marked_region_labels(self, adjacent_marked_region_labels) -> None:
self.adjacent_marked_region_labels = adjacent_marked_region_labels
""" context menu """
def init_context_menu(self):
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.on_context_menu)
@ -66,33 +86,18 @@ class RegionLabel(QLabel): @@ -66,33 +86,18 @@ class RegionLabel(QLabel):
widget_action.setDefaultWidget(label)
menu.addAction(widget_action)
menu.addSeparator()
action_save_region = QAction("save region as...", self)
action_save_region.triggered.connect(self.save_region_as)
menu.addAction(action_save_region)
action_replace_region = QAction("replace region with file...", self)
action_replace_region.triggered.connect(self.load_region_from_file)
menu.addAction(action_replace_region)
menu.closeEvent = self.on_context_menu_close
self.context_menu = menu
def has_image(self) -> bool:
if isinstance(self.image, QImage):
return True
return False
""" getter + setter"""
def set_image(self, image: QImage = None) -> None:
self.image = image
def set_raw_data_array(self, raw_data_array: np.array = None) -> None:
self.raw_data_array = raw_data_array
def mark_as_region(self) -> None:
self.is_region = True
def set_adjacent_marked_region_labels(self, adjacent_marked_region_labels) -> None:
self.adjacent_marked_region_labels = adjacent_marked_region_labels
""" context menu """
def on_context_menu(self, point):
modifiers = QApplication.keyboardModifiers()
if modifiers == Qt.ShiftModifier and self.is_region:
@ -103,10 +108,9 @@ class RegionLabel(QLabel): @@ -103,10 +108,9 @@ class RegionLabel(QLabel):
self.parent_collection.toggle_open_in_context_menu(self)
""" visual stuff"""
# if used in a grid, this is basically creating a gradient
@staticmethod
def get_color_by_coordinates(x=0, y=0) -> QColor:
# if used in a grid, this is basically creating a gradient
return QColor(
int((((x + 1) * 4) + ((y + 1) * 4)) / 2 - 1),
int((((x + 1) * 4) + ((y + 1) * 4)) / 2 - 1),
@ -149,6 +153,28 @@ class RegionLabel(QLabel): @@ -149,6 +153,28 @@ class RegionLabel(QLabel):
bytearray(np.flipud(self.raw_data_array))
)
def load_region_from_file(self):
dialog = QFileDialog()
file_name = QFileDialog.getOpenFileName(
caption='load a raw region', options=dialog.options(), filter="RAW files (*.raw)"
)[0]
if file_name:
length = int(math.sqrt(pathlib.Path(file_name).stat().st_size / 2))
if int(length / 512) > 1:
return False
with open(file_name, 'r') as raw_file:
raw_data_array = np.fromfile(raw_file, dtype=np.uint16)
self.set_raw_data_array(np.flipud(np.reshape(raw_data_array, (length, length))))
index_x, index_y = self.parent_collection.create_grid_index_from_region_string(self.region_string)
offset = int(32 / 2) - int(self.parent_collection.world_regions.regions_per_row / 2)
self.parent_collection.blockshaped_map_data[index_y - offset][index_x - offset] = self.raw_data_array
chunk = QImage(self.raw_data_array.copy(), 512, 512, QImage.Format_Grayscale16)
self.set_label_pixmap(chunk)
""" altering class functions """
def mouse_any_click_event(self, event):
modifiers = QApplication.keyboardModifiers()

36
world_regions_widget/region_label_collection.py

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
import numpy as np
from skimage.util import view_as_blocks
from PyQt5.QtCore import QPoint
from PyQt5.QtGui import QPixmap, QColor, QPainter, QImage
from PyQt5.QtGui import QPixmap, QColor, QPainter, QImage, QCursor
from PyQt5.QtWidgets import (
QGridLayout
)
from einops import rearrange
from .region_label import RegionLabel
@ -13,7 +14,7 @@ class RegionLabelCollection(QGridLayout): @@ -13,7 +14,7 @@ class RegionLabelCollection(QGridLayout):
label_collection = dict
currently_hovered_label = RegionLabel
raw_map_data = np.array
blockshaped_map_data = np.array
def __init__(self, world_regions):
super().__init__()
@ -22,7 +23,7 @@ class RegionLabelCollection(QGridLayout): @@ -22,7 +23,7 @@ class RegionLabelCollection(QGridLayout):
self.create_label_collection(self.world_regions.zoom_adjusted_label_size)
def set_currently_hovered_label(self, region_label):
self.world_regions.set_currently_hovered_label(region_label)
self.currently_hovered_label = region_label
for index, label in self.label_collection.items():
if region_label is not label:
label.is_selected = False
@ -31,8 +32,16 @@ class RegionLabelCollection(QGridLayout): @@ -31,8 +32,16 @@ class RegionLabelCollection(QGridLayout):
if region_label is not None:
region_label.update_graphics_effect()
def center_mouse_on_hovered_label(self):
QCursor.setPos(self.currently_hovered_label.mapToGlobal(QPoint(
int(self.currently_hovered_label.width() / 2),
int(self.currently_hovered_label.height() / 2)
)))
@staticmethod
def create_region_string_from_grid_index(x=0, y=0) -> str:
# 7dtd seems to be peculiar about their coordinate system. I arrived at this by trial and error,
# I haven't really grasped the logic of it :)
return "r.{x}.{y}.7rg".format(
x=int(x - 16),
y=int((y - 16) + 1) * -1
@ -124,26 +133,17 @@ class RegionLabelCollection(QGridLayout): @@ -124,26 +133,17 @@ class RegionLabelCollection(QGridLayout):
self.clear_viewer_grid().create_label_collection(self.world_regions.zoom_adjusted_label_size)
regions_per_row = int(length / 512)
self.raw_map_data = np.flipud(np.reshape(raw_map_data, (length, length)))
blockshaped_map_data = view_as_blocks(self.raw_map_data, (512, 512))
raw_map_data = np.flipud(np.reshape(raw_map_data, (length, length)))
self.blockshaped_map_data = rearrange(raw_map_data, '(x dx) (y dy) -> x y dx dy', dx=512, dy=512)
for y in range(0, regions_per_row):
for x in range(0, regions_per_row):
chunk_raw_data = blockshaped_map_data[y][x]
chunk_raw_data = self.blockshaped_map_data[y][x]
chunk = QImage(chunk_raw_data.copy(), 512, 512, QImage.Format_Grayscale16)
grid_slot = self.create_region_string_from_grid_index(x=x + offset, y=y + offset)
region_label = self.label_collection.get(grid_slot)
region_label.set_image(chunk)
region_label.set_raw_data_array(chunk_raw_data)
region_label.mark_as_region()
size = (512, 512)
pixmap = QPixmap(*size)
pixmap.fill(QColor("transparent"))
painter = QPainter(pixmap)
painter.drawPixmap(0, 0, *size, QPixmap.fromImage(chunk))
painter.end()
region_label.setPixmap(pixmap)
region_label.set_label_pixmap(chunk)

Loading…
Cancel
Save