From a904dfc451ad3ff034d61666d2a16cecfcf6de4a Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Mon, 21 Mar 2022 13:18:56 -0400 Subject: [PATCH] Add the new anim_stitcher tool --- .gitignore | 154 ++++++++++++++++- .vscode/settings.json | 8 + .../ewpratten}/chr_testFox/preview-01.jpg | Bin .../ewpratten}/chr_testFox/preview-02.jpg | Bin .../ewpratten}/chr_testFox/preview-03.jpg | Bin .../ewpratten}/chr_testFox/preview-04.jpg | Bin .../ewpratten}/chr_testFox/preview-05.jpg | Bin .../ewpratten}/chr_testFox/preview-06.jpg | Bin .../ewpratten}/chr_testFox/preview-07.jpg | Bin .../ewpratten}/chr_testFox/preview-08.jpg | Bin .../ewpratten}/chr_testFox/preview-09.jpg | Bin .../ewpratten}/chr_testFox/preview-10.jpg | Bin .../ewpratten}/chr_testFox/preview-11.jpg | Bin .../ewpratten}/chr_testFox/preview-12.jpg | Bin .../ewpratten}/chr_testFox/preview-13.jpg | Bin .../ewpratten}/chr_testFox/preview-14.jpg | Bin .../ewpratten}/chr_testFox/preview-15.jpg | Bin .../ewpratten}/chr_testFox/preview-16.jpg | Bin .../ewpratten}/chr_testFox/preview-17.jpg | Bin .../ewpratten}/chr_testFox/preview-18.jpg | Bin automation/anim_stitcher/__main__.py | 27 +++ automation/anim_stitcher/stitcher.py | 85 +++++++++ automation/anim_stitcher/ui.py | 163 ++++++++++++++++++ automation/project_root.py | 11 ++ automation/qt_common/__init__.py | 2 + automation/qt_common/dialog.css | 25 +++ automation/qt_common/qt_app_wrapper.py | 37 ++++ automation/qt_common/qt_dialog_style.py | 5 + automation/qt_common/qt_lines.py | 17 ++ automation/qt_common/qt_window_center.py | 8 + automation/sprite_types.py | 30 ++++ docs/artist-information.md | 1 + game/auto_stitch/README.md | 9 - game/dist/.gitignore | 1 - launch_anim_stitcher.sh | 6 + plugins/.gitkeep | 0 requirements.txt | 2 + 37 files changed, 580 insertions(+), 11 deletions(-) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-01.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-02.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-03.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-04.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-05.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-06.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-07.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-08.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-09.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-10.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-11.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-12.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-13.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-14.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-15.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-16.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-17.jpg (100%) rename {game/auto_stitch => assets/ewpratten}/chr_testFox/preview-18.jpg (100%) create mode 100644 automation/anim_stitcher/__main__.py create mode 100644 automation/anim_stitcher/stitcher.py create mode 100644 automation/anim_stitcher/ui.py create mode 100644 automation/project_root.py create mode 100644 automation/qt_common/__init__.py create mode 100644 automation/qt_common/dialog.css create mode 100644 automation/qt_common/qt_app_wrapper.py create mode 100644 automation/qt_common/qt_dialog_style.py create mode 100644 automation/qt_common/qt_lines.py create mode 100644 automation/qt_common/qt_window_center.py create mode 100644 automation/sprite_types.py delete mode 100644 game/auto_stitch/README.md delete mode 100644 game/dist/.gitignore create mode 100644 launch_anim_stitcher.sh delete mode 100644 plugins/.gitkeep create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 32db6399..eeae8a53 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,156 @@ Cargo.lock *.pdb # MdBook generated files -/book \ No newline at end of file +/book +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 26d1811d..6194f3c3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,13 @@ "**/node_modules/*/**": true, "**/.hg/store/**": true, "**/target/**": true, + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/__pycache__": true, } } \ No newline at end of file diff --git a/game/auto_stitch/chr_testFox/preview-01.jpg b/assets/ewpratten/chr_testFox/preview-01.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-01.jpg rename to assets/ewpratten/chr_testFox/preview-01.jpg diff --git a/game/auto_stitch/chr_testFox/preview-02.jpg b/assets/ewpratten/chr_testFox/preview-02.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-02.jpg rename to assets/ewpratten/chr_testFox/preview-02.jpg diff --git a/game/auto_stitch/chr_testFox/preview-03.jpg b/assets/ewpratten/chr_testFox/preview-03.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-03.jpg rename to assets/ewpratten/chr_testFox/preview-03.jpg diff --git a/game/auto_stitch/chr_testFox/preview-04.jpg b/assets/ewpratten/chr_testFox/preview-04.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-04.jpg rename to assets/ewpratten/chr_testFox/preview-04.jpg diff --git a/game/auto_stitch/chr_testFox/preview-05.jpg b/assets/ewpratten/chr_testFox/preview-05.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-05.jpg rename to assets/ewpratten/chr_testFox/preview-05.jpg diff --git a/game/auto_stitch/chr_testFox/preview-06.jpg b/assets/ewpratten/chr_testFox/preview-06.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-06.jpg rename to assets/ewpratten/chr_testFox/preview-06.jpg diff --git a/game/auto_stitch/chr_testFox/preview-07.jpg b/assets/ewpratten/chr_testFox/preview-07.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-07.jpg rename to assets/ewpratten/chr_testFox/preview-07.jpg diff --git a/game/auto_stitch/chr_testFox/preview-08.jpg b/assets/ewpratten/chr_testFox/preview-08.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-08.jpg rename to assets/ewpratten/chr_testFox/preview-08.jpg diff --git a/game/auto_stitch/chr_testFox/preview-09.jpg b/assets/ewpratten/chr_testFox/preview-09.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-09.jpg rename to assets/ewpratten/chr_testFox/preview-09.jpg diff --git a/game/auto_stitch/chr_testFox/preview-10.jpg b/assets/ewpratten/chr_testFox/preview-10.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-10.jpg rename to assets/ewpratten/chr_testFox/preview-10.jpg diff --git a/game/auto_stitch/chr_testFox/preview-11.jpg b/assets/ewpratten/chr_testFox/preview-11.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-11.jpg rename to assets/ewpratten/chr_testFox/preview-11.jpg diff --git a/game/auto_stitch/chr_testFox/preview-12.jpg b/assets/ewpratten/chr_testFox/preview-12.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-12.jpg rename to assets/ewpratten/chr_testFox/preview-12.jpg diff --git a/game/auto_stitch/chr_testFox/preview-13.jpg b/assets/ewpratten/chr_testFox/preview-13.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-13.jpg rename to assets/ewpratten/chr_testFox/preview-13.jpg diff --git a/game/auto_stitch/chr_testFox/preview-14.jpg b/assets/ewpratten/chr_testFox/preview-14.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-14.jpg rename to assets/ewpratten/chr_testFox/preview-14.jpg diff --git a/game/auto_stitch/chr_testFox/preview-15.jpg b/assets/ewpratten/chr_testFox/preview-15.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-15.jpg rename to assets/ewpratten/chr_testFox/preview-15.jpg diff --git a/game/auto_stitch/chr_testFox/preview-16.jpg b/assets/ewpratten/chr_testFox/preview-16.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-16.jpg rename to assets/ewpratten/chr_testFox/preview-16.jpg diff --git a/game/auto_stitch/chr_testFox/preview-17.jpg b/assets/ewpratten/chr_testFox/preview-17.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-17.jpg rename to assets/ewpratten/chr_testFox/preview-17.jpg diff --git a/game/auto_stitch/chr_testFox/preview-18.jpg b/assets/ewpratten/chr_testFox/preview-18.jpg similarity index 100% rename from game/auto_stitch/chr_testFox/preview-18.jpg rename to assets/ewpratten/chr_testFox/preview-18.jpg diff --git a/automation/anim_stitcher/__main__.py b/automation/anim_stitcher/__main__.py new file mode 100644 index 00000000..e06e4e8d --- /dev/null +++ b/automation/anim_stitcher/__main__.py @@ -0,0 +1,27 @@ +import argparse +import sys +import logging +from qt_common.qt_app_wrapper import QtAppWrapper +from . import ui + +def main() -> int: + # Handle program arguments + ap = argparse.ArgumentParser( + prog='anim_stitcher', description='A tool for stitching PNG sequences into sprite sheets') + + args = ap.parse_args() + + # Setup logging + logging.basicConfig(level=logging.DEBUG) + + # Run the application + with QtAppWrapper(): + # Create and show the window + w = ui.AnimStitcherWindow() + w.show() + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/automation/anim_stitcher/stitcher.py b/automation/anim_stitcher/stitcher.py new file mode 100644 index 00000000..5bdaab39 --- /dev/null +++ b/automation/anim_stitcher/stitcher.py @@ -0,0 +1,85 @@ +"""This file contains the actual stitcher logic.""" + +import os +from PIL import Image +import json +import logging +from typing import List +from project_root import get_project_root +logger = logging.getLogger(__name__) + + +def check_sprite_exists(sprite_type: str, sprite_name: str) -> bool: + """Checks if a sprite directory exists for the given sprite type and name. + + Args: + sprite_type (str): Sprite type (short name) + sprite_name (str): Sprite name + + Returns: + bool: Does it exist? + """ + + # Get the project root + project_root = get_project_root() + logger.debug(f"Project root: {project_root}") + + # Build the path the sprite should exist in + sprite_path = os.path.join( + project_root, "game", "dist", "anm", sprite_type, f"{sprite_type}_{sprite_name}") + + return os.path.isdir(sprite_path) + + +def stitch_images_and_write_to_disk(sprite_type: str, sprite_name: str, images: List[str]) -> None: + + # Load all the images + images_to_stitch = [] + for image_path in images: + images_to_stitch.append(Image.open(image_path)) + + # Collect the total width and maximum height of the images while building a list of the sizes + total_width = 0 + max_height = 0 + for image in images_to_stitch: + total_width += image.size[0] + max_height = max(max_height, image.size[1]) + + # Create a new image with the total width and maximum height + new_image = Image.new("RGBA", (total_width, max_height)) + + # Paste each image into the new image + x_offset = 0 + for image in images_to_stitch: + new_image.paste(image, (x_offset, 0)) + x_offset += image.size[0] + + # Save the new image + project_root = get_project_root() + logger.debug(f"Project root: {project_root}") + new_image = new_image.quantize(method=2) + new_image.save(os.path.join(project_root, "game", "dist", "anm", sprite_type, + f"{sprite_type}_{sprite_name}", f"{sprite_type}_{sprite_name}.png")) + + # Build some JSON metadata + metadata = { + "sheet_height": max_height, + "sheet_width": total_width, + "frames": [] + } + + # Add the metadata for each image + x_offset = 0 + for image in images_to_stitch: + metadata["frames"].append({ + "x": x_offset, + "y": 0, + "width": image.size[0], + "height": image.size[1] + }) + x_offset += image.size[0] + + # Write the metadata to disk + with open(os.path.join(project_root, "game", "dist", "anm", sprite_type, + f"{sprite_type}_{sprite_name}", f"{sprite_type}_{sprite_name}.anim_meta.json"), "w") as f: + json.dump(metadata, f, indent=4) diff --git a/automation/anim_stitcher/ui.py b/automation/anim_stitcher/ui.py new file mode 100644 index 00000000..42b8dba5 --- /dev/null +++ b/automation/anim_stitcher/ui.py @@ -0,0 +1,163 @@ +"""anim_stitcher GUI""" + +from PySide2 import QtWidgets +from PySide2.QtCore import Qt +import pkgutil +from qt_common import qt_window_center, qt_lines, qt_dialog_style +import sprite_types +import re +import os +from . import stitcher +from project_root import get_project_root + +import logging +logger = logging.getLogger("anim_stitcher.ui") + +SPRITE_NAMING_VALIDATION = r"^[a-z][a-zA-Z\d]+$" + + +class AnimStitcherWindow(QtWidgets.QWidget): + + selected_files = None + + def __init__(self): + super(AnimStitcherWindow, self).__init__() + + # Configure the window + self.setWindowFlags( + self.windowFlags() ^ Qt.Window) + self.setWindowTitle("Anim Stitcher") + self.resize(280, 200) + qt_window_center.center_window(self) + + # Set the root of the application to be a vertical list + self.setLayout(QtWidgets.QVBoxLayout()) + + # Load the stylesheet for this app + self.setStyleSheet(qt_dialog_style.STYLESHEET) + + # Configure the title at the top of the window + self.label = QtWidgets.QLabel("Anim Stitcher") + self.label.setProperty('labelClass', 'label-title') + self.layout().addWidget(self.label) + self.description = QtWidgets.QLabel( + "Stitch PNG sequences into a sprite sheet") + self.description.setProperty('labelClass', 'label-description') + self.layout().addWidget(self.description) + self.layout().addWidget(qt_lines.QHLine()) + + # Add an import button + self.import_button = QtWidgets.QPushButton("Select PNGs") + self.import_button.clicked.connect(self.load_png_dialog) + self.layout().addWidget(self.import_button) + self.layout().addWidget(qt_lines.QHLine()) + + # Add a selection option for the sprite type + known_sprite_types = sprite_types.get_known_sprite_types().values() + self.sprite_type_layout = QtWidgets.QHBoxLayout() + self.sprite_type_label = QtWidgets.QLabel("Sprite Type") + self.sprite_type_layout.addWidget(self.sprite_type_label) + self.sprite_type_dropdown = QtWidgets.QComboBox() + for ty in known_sprite_types: + self.sprite_type_dropdown.addItem(ty) + self.sprite_type_dropdown.setEnabled(False) + self.sprite_type_layout.addWidget(self.sprite_type_dropdown) + self.layout().addLayout(self.sprite_type_layout) + + # Add a box to accept a sprite name + self.sprite_name_layout = QtWidgets.QHBoxLayout() + self.sprite_name_label = QtWidgets.QLabel("Sprite Name") + self.sprite_name_layout.addWidget(self.sprite_name_label) + self.sprite_name_input = QtWidgets.QLineEdit() + self.sprite_name_input.setText("unnamedSprite") + self.sprite_name_input.setEnabled(False) + self.sprite_name_layout.addWidget(self.sprite_name_input) + self.layout().addLayout(self.sprite_name_layout) + + # Add a seperator + self.layout().addWidget(qt_lines.QHLine()) + + # Add a button to start the stitching process + self.finishing_layout = QtWidgets.QHBoxLayout() + self.close_button = QtWidgets.QPushButton("Cancel") + self.close_button.clicked.connect(self.close) + self.finishing_layout.addWidget(self.close_button) + self.stitch_button = QtWidgets.QPushButton("Stitch") + self.stitch_button.clicked.connect(self.stitch_images) + self.stitch_button.setEnabled(False) + self.finishing_layout.addWidget(self.stitch_button) + self.layout().addLayout(self.finishing_layout) + + # Add space at the bottom in case window size is wrong + self.layout().addStretch() + + def load_png_dialog(self): + + # Open a file picker to search for the desired image + file_dialog = QtWidgets.QFileDialog() + file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles) + file_dialog.setNameFilter("Image Files (*.png *.jpg *.jpeg)") + file_dialog.setViewMode(QtWidgets.QFileDialog.Detail) + file_dialog.setLabelText(QtWidgets.QFileDialog.Accept, "Import") + file_dialog.setLabelText(QtWidgets.QFileDialog.Reject, "Cancel") + file_dialog.setWindowTitle("Import PNG Sequence") + file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) + file_dialog.setDirectory(os.path.join(get_project_root(), "assets")) + + # If the user selected an image, import it + if file_dialog.exec_(): + # Enable all the disabled fields + self.sprite_type_dropdown.setEnabled(True) + self.sprite_name_input.setEnabled(True) + self.stitch_button.setEnabled(True) + + # Save the selected files + self.selected_files = file_dialog.selectedFiles() + + else: + logger.warning("No image selected") + return + + def stitch_images(self): + + # Check the naming convention + if not re.match(SPRITE_NAMING_VALIDATION, self.sprite_name_input.text()): + + # Pop up a warning + warning_dialog = QtWidgets.QMessageBox() + warning_dialog.setIcon(QtWidgets.QMessageBox.Warning) + warning_dialog.setText("Invalid Sprite Name") + warning_dialog.setInformativeText( + "The sprite name must be lower camel case\nExample: myShinySprite") + warning_dialog.setWindowTitle("Invalid Sprite Name") + warning_dialog.exec_() + + return + + # Check if we are about to overwrite an existing sprite + known_sprite_types = sprite_types.get_known_sprite_types() + ty_long_to_short_map = dict(map(reversed, known_sprite_types.items())) + sprite_type = ty_long_to_short_map[self.sprite_type_dropdown.currentText( + )] + sprite_name = self.sprite_name_input.text() + if stitcher.check_sprite_exists(sprite_type, sprite_name): + + # Pop up confirmation box + warning_dialog = QtWidgets.QMessageBox() + warning_dialog.setIcon(QtWidgets.QMessageBox.Warning) + warning_dialog.setText("Overwrite Sprite?") + warning_dialog.setInformativeText( + "A sprite with the name {}_{} already exists.\nDo you want to overwrite it?".format(sprite_type, sprite_name)) + warning_dialog.setWindowTitle("Overwrite Sprite?") + warning_dialog.setStandardButtons( + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + warning_dialog.setDefaultButton(QtWidgets.QMessageBox.No) + if warning_dialog.exec_() == QtWidgets.QMessageBox.No: + return + + # Perform the actual stitching action + stitcher.stitch_images_and_write_to_disk( + sprite_type, sprite_name, self.selected_files) + + # Close the window + self.close() diff --git a/automation/project_root.py b/automation/project_root.py new file mode 100644 index 00000000..ee9cd8dd --- /dev/null +++ b/automation/project_root.py @@ -0,0 +1,11 @@ +"""Module to fetch the project root directory.""" + +import os + +def get_project_root() -> str: + """Gets the project root directory. + + Returns: + str: The project root directory. + """ + return os.environ.get("LD50_PROJECT_ROOT") \ No newline at end of file diff --git a/automation/qt_common/__init__.py b/automation/qt_common/__init__.py new file mode 100644 index 00000000..93145f6c --- /dev/null +++ b/automation/qt_common/__init__.py @@ -0,0 +1,2 @@ +"""This module is used to provide common functionality for Qt applications.""" + diff --git a/automation/qt_common/dialog.css b/automation/qt_common/dialog.css new file mode 100644 index 00000000..e7aa910e --- /dev/null +++ b/automation/qt_common/dialog.css @@ -0,0 +1,25 @@ +QWidget { + background-color: #444444; + color: white; +} + +QLabel[labelClass="label-title"] { + color: #58a5cc; + font-weight: bold; + font-size:25px; +} + +QComboBox:disabled { + background-color:#272727; + color: #444444; +} + +QCheckBox:disabled { + background-color:#272727; + color: #444444; +} + +QPushButton:disabled { + background-color:#272727; + color: #444444; +} \ No newline at end of file diff --git a/automation/qt_common/qt_app_wrapper.py b/automation/qt_common/qt_app_wrapper.py new file mode 100644 index 00000000..2939677c --- /dev/null +++ b/automation/qt_common/qt_app_wrapper.py @@ -0,0 +1,37 @@ +"""A wrapper that handles bootstrapping QT applications in environments that do not have QT support.""" + +# Load the logging system +import logging +logger = logging.getLogger("qt_common.utils") + +# We need to have a global to keep track of the QApplication +_qt_app = None + +class QtAppWrapper: + + def __init__(self, parent=None): + self.parent = parent + + def __enter__(self): + global _qt_app + + # If there is no parent, we must make a QApplication + if self.parent is None: + logger.info("No parent specified. Creating QApplication") + from PySide2 import QtWidgets + try: + if not _qt_app: + _qt_app = QtWidgets.QApplication([]) + except RuntimeError: + logger.error( + "Could not create QApplication. Is it already running?") + raise + + def __exit__(self, type, value, traceback): + global _qt_app + + # If there is no parent, we must run the QApplication ourselves + if self.parent is None: + logger.info("Running QApplication") + if _qt_app: + _qt_app.exec_() \ No newline at end of file diff --git a/automation/qt_common/qt_dialog_style.py b/automation/qt_common/qt_dialog_style.py new file mode 100644 index 00000000..e29bf2e2 --- /dev/null +++ b/automation/qt_common/qt_dialog_style.py @@ -0,0 +1,5 @@ +"""This module has an embedded CSS file defining the style of the dialogs.""" + +import pkgutil + +STYLESHEET = pkgutil.get_data(__name__, "./dialog.css").decode("utf-8") \ No newline at end of file diff --git a/automation/qt_common/qt_lines.py b/automation/qt_common/qt_lines.py new file mode 100644 index 00000000..e58ca606 --- /dev/null +++ b/automation/qt_common/qt_lines.py @@ -0,0 +1,17 @@ +"""Some utility classes for making lines in QT panels""" + +from PySide2 import QtWidgets + + +class QHLine(QtWidgets.QFrame): + def __init__(self): + super(QHLine, self).__init__() + self.setFrameShape(QtWidgets.QFrame.HLine) + self.setFrameShadow(QtWidgets.QFrame.Sunken) + + +class QVLine(QtWidgets.QFrame): + def __init__(self): + super(QVLine, self).__init__() + self.setFrameShape(QtWidgets.QFrame.VLine) + self.setFrameShadow(QtWidgets.QFrame.Sunken) \ No newline at end of file diff --git a/automation/qt_common/qt_window_center.py b/automation/qt_common/qt_window_center.py new file mode 100644 index 00000000..1659c594 --- /dev/null +++ b/automation/qt_common/qt_window_center.py @@ -0,0 +1,8 @@ +from PySide2 import QtWidgets + + +def center_window(w): + qr = w.frameGeometry() + cp = QtWidgets.QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + w.move(qr.topLeft()) \ No newline at end of file diff --git a/automation/sprite_types.py b/automation/sprite_types.py new file mode 100644 index 00000000..6fbe21db --- /dev/null +++ b/automation/sprite_types.py @@ -0,0 +1,30 @@ +"""This file contains functions for reading known sprite types.""" + +from typing import Dict +import json +import os +from project_root import get_project_root + +import logging +logger = logging.getLogger(__name__) + +def get_known_sprite_types() -> Dict[str, str]: + """Gets a dictionary of known sprite types as a mapping from short name to friendly name + + Returns: + Dict[str, str]: Short name -> Friendly name + """ + + # Load our JSON file containing known sprite types + project_root = get_project_root() + logger.debug(f"Project root: {project_root}") + with open(os.path.join(project_root, "game", "dist", "known-sprite-types.json"), "r") as f: + known_sprite_types = json.load(f) + + # We need to re-shape the data + sprite_types = {} + for item in known_sprite_types: + sprite_types[item["short"]] = item["friendly"] + + + return sprite_types diff --git a/docs/artist-information.md b/docs/artist-information.md index 0c302f08..dc01fcc8 100644 --- a/docs/artist-information.md +++ b/docs/artist-information.md @@ -1 +1,2 @@ # Artist Information + diff --git a/game/auto_stitch/README.md b/game/auto_stitch/README.md deleted file mode 100644 index 81903a25..00000000 --- a/game/auto_stitch/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# The Auto-Stitch directory - -This is a bit of a *magic* directory. Anything put in here will automatically be turned into a spritesheet at compile time. - -## File organization - -In this directory, framesets are expected to be stored in subdirectories. The names of these are important as they will translate into the names of the spritesheets. - -For example, if you put a set of frames in `auto_stitch/chr_testFox`, you will get a spritesheet generated to `dist/gen/anm/chr/chr_testFox.png`. diff --git a/game/dist/.gitignore b/game/dist/.gitignore deleted file mode 100644 index 549fe731..00000000 --- a/game/dist/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/gen/* \ No newline at end of file diff --git a/launch_anim_stitcher.sh b/launch_anim_stitcher.sh new file mode 100644 index 00000000..8d4785a9 --- /dev/null +++ b/launch_anim_stitcher.sh @@ -0,0 +1,6 @@ +#! /bin/bash +set -ex + +export PYTHONPATH=$(pwd)/automation:$PYTHONPATH +export LD50_PROJECT_ROOT=$(pwd) +python3 -m anim_stitcher \ No newline at end of file diff --git a/plugins/.gitkeep b/plugins/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..04a9292e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pillow +PySide2 \ No newline at end of file