diff --git a/.gitignore b/.gitignore index b6e4761..8959a19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,4 @@ -# 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/ -pip-wheel-metadata/ -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/ - -# 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 -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.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 - -# 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/ +.vscode +.pyc +__pycache__ +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5d2fb92..771efe7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 VFXIM +Copyright (c) 2022 Netherlands Film Academy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 574cca0..5853642 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ -# tk-desktop-deliveries -App to get latest publish from shot and move it to the deliveries folder +[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +# ShotGrid deliveries app +This app will get all shots with the "Ready for Delivery" status, and copy the publishes to the delivery folder with the correct naming convention. Currently used at the Netherlands Film Academy. + +## User interface +![tk-desktop-deliveries](./resources/tk-desktop-deliveries.png) + +## Requirements +* This app will need a sg_projectcode field on the project in ShotGrid + +## Settings +* `delivery_sequence`: template to move the file sequence to +* `delivery_folder`: template to the delivery folder +* `default_root`: string for the root project, defaults to `primary`. +* `delivery_status`: string with the shot shortcode status for shots to be delivered. Defaults to `rfd` for `Ready for Delivery`. +* `delivered_status`: string with the shot shortcode status for shots that are delivered. Defaults to `fin` for `Final`. diff --git a/app.py b/app.py new file mode 100644 index 0000000..4b4be56 --- /dev/null +++ b/app.py @@ -0,0 +1,46 @@ +# MIT License + +# Copyright (c) 2022 Netherlands Film Academy + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from sgtk.platform import Application + + +class tkShotGridDeliveries(Application): + """ + App to deliver Shots with the correct naming convention + """ + + def init_app(self): + """ + Called as the application is being initialized + """ + + # Register command + self.engine.register_command("Deliveries", self.deliveries) + + def deliveries(self): + """This function will run the application""" + # Import application + app_payload = self.import_module("app") + + # Run application + app_payload.dialog.show_dialog(self) diff --git a/icon_256.png b/icon_256.png new file mode 100644 index 0000000..82642a2 Binary files /dev/null and b/icon_256.png differ diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000..3856ec2 --- /dev/null +++ b/python/__init__.py @@ -0,0 +1,24 @@ +# MIT License + +# Copyright (c) 2022 Netherlands Film Academy + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from . import app diff --git a/python/app/dialog.py b/python/app/dialog.py new file mode 100755 index 0000000..af208db --- /dev/null +++ b/python/app/dialog.py @@ -0,0 +1,413 @@ +# MIT License + +# Copyright (c) 2022 Netherlands Film Academy + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import sgtk +import os +import sys + +from sgtk.platform.qt import QtCore, QtGui +from .ui.dialog import Ui_Dialog + +# standard toolkit logger +logger = sgtk.platform.get_logger(__name__) + + +def show_dialog(app_instance): + """ + Shows the main dialog window. + """ + + app_instance.engine.show_dialog("Deliveries", app_instance, AppDialog) + + +class AppDialog(QtGui.QWidget): + """ + Main application dialog window + """ + + def __init__(self): + """ + Constructor + """ + # first, call the base class and let it do its thing. + QtGui.QWidget.__init__(self) + + # now load in the UI that was created in the UI designer + self.ui = Ui_Dialog() + self.ui.setupUi(self) + + self.operating_system = sys.platform + + # Getting current app instance and context + self._app = sgtk.platform.current_bundle() + self.sg = self._app.shotgun + + self.context = self._app.context + + # logging happens via a standard toolkit logger + logger.info("Launching Delivery Application...") + + self.setup_interface() + + self.ui.reload.clicked.connect(self.setup_interface) + self.ui.deliver.clicked.connect(self.set_ready_for_delivery) + self.ui.open_folder.clicked.connect(self.open_delivery_folder) + + def setup_interface(self): + """This function will initially setup the list widget + to show shots. + """ + # Clear widget + self.ui.deliveryItems.clear() + + # Get shots + shots = self.__get_delivery_shots() + + # Convert to readable list + readable_shots = self.__create_readable_list(shots) + + if len(readable_shots) > 0: + # Edit list widget + self.ui.deliveryItems.addItems(readable_shots) + + def open_delivery_folder(self): + """Function to open the delivery folder in the matching + operating system. + """ + # Get template + template = self._app.get_template("delivery_folder") + + # Get project location + roots = self.context.sgtk.roots + root_name = self._app.get_setting("default_root") + project_location = roots.get(root_name) + project_location = project_location.replace(os.sep, "/") + + # Generate delivery location + delivery_location = template.apply_fields(project_location) + delivery_location = delivery_location.replace(os.sep, "/") + + # Detect operating system to create correct command + operating_system = self.operating_system + + # Open folder in file system + if operating_system == "darwin": + os.system("open %s" % delivery_location) + + elif operating_system == "win32": + delivery_location = os.path.normpath(delivery_location) + os.system("explorer %s" % delivery_location) + + elif operating_system == "linux": + os.system('xdg-open "%s"' % delivery_location) + + def set_ready_for_delivery(self): + """This function will collect the shots to deliver, validate it, + and move it to the delivery folder. + """ + + # Get shots + shots = self.__get_delivery_shots() + + # Iterate trough every shot + for shot in shots: + + # Get shot information + sequence_name = shot.get("sg_sequence") + sequence_name = sequence_name.get("name") + + shot_name = shot.get("code") + shot_id = shot.get("id") + + # Find latest version + filters = [ + ["entity", "is", {"type": "Shot", "id": shot_id}], + ] + + # TODO add support for mattes + columns = [ + "published_files", + "mattes", + "sg_first_frame", + "sg_last_frame", + ] + + sorting = [ + { + "column": "created_at", + "direction": "desc", + } + ] + + # Get verision + latest_version = self.sg.find_one( + "Version", + filters, + columns, + sorting, + ) + + # Get version information + first_frame = latest_version.get("sg_first_frame") + last_frame = latest_version.get("sg_last_frame") + + # Get publishes + publishes = latest_version.get("published_files") + for publish in publishes: + + # Get publish information, via this way + # we can add support for extra mattes + + publish_id = publish.get("id") + + filters = [ + ["id", "is", publish_id], + ] + + columns = ["path", "published_file_type", "version_number"] + + # Get publish + publish = self.sg.find_one( + "PublishedFile", + filters, + columns, + ) + + # Get path to sequence path + sequence_path = publish.get("path") + + # Every os has a different path, so get the correct path + operating_system = self.operating_system + + # Probably this will match the studio's operating system path + if operating_system == "darwin": + sequence_path = sequence_path.get("local_path_mac") + + elif operating_system == "win32": + sequence_path = sequence_path.get("local_path_windows") + sequence_path = sequence_path.replace(os.sep, "/") + + elif operating_system == "linux": + sequence_path = sequence_path.get("local_path_linux") + + # Validate if every file is existing to deliver + validated = self.__validate_sequence( + sequence_path=sequence_path, + first_frame=first_frame, + last_frame=last_frame, + ) + + if validated: + # Hard link to the delivery folder to save + # time and disk space + self.__deliver_sequence( + publish=publish, + sequence_name=sequence_name, + shot_name=shot_name, + sequence_path=sequence_path, + first_frame=first_frame, + last_frame=last_frame, + shot_id=shot_id, + ) + + # Update interface + self.setup_interface() + + def __deliver_sequence( + self, + publish, + sequence_name, + shot_name, + sequence_path, + first_frame, + last_frame, + shot_id, + ): + """This function will hard link the provided sequence path to the + delivery folder. + + Args: + publish (dict): containing all publish information + sequence_name (str): sequence code name + shot_name (str): shot code name + sequence_path (str): publish file path to the sequence + first_frame (float): first frame to deliver + last_frame (float): last frame to deliver + shot_id (int): id of the shot in ShotGrid + + Returns: + bool: True if succeeded false if failed + """ + + try: + delivery_template = self._app.get_template("delivery_sequence") + + # Get project code + project_id = self.context.project["id"] + filters = [ + [ + "id", + "is", + project_id, + ] + ] + + columns = ["sg_projectcode"] + project = self.sg.find_one("Project", filters, columns) + + project_code = project.get("sg_projectcode") + version_number = publish.get("version_number") + + fields = {} + + fields["Projectcode"] = project_code + fields["Sequence"] = sequence_name + fields["Shot"] = shot_name + fields["version"] = version_number + + delivery_path = delivery_template.apply_fields(fields) + delivery_path = delivery_path.replace(os.sep, "/") + + delivery_folder = os.path.dirname(delivery_path) + + if not os.path.isdir(delivery_folder): + logger.info( + "Creating folder for delivery %s." % delivery_folder + ) + os.makedirs(delivery_folder) + + for frame in range(first_frame, last_frame): + publish_file = sequence_path % frame + delivery_file = delivery_path % frame + + logger.info( + "Hard linking %s to %s." % (publish_file, delivery_file) + ) + + os.link(publish_file, delivery_file) + + # Update status + + delivered_status = self._app.get_setting("delivered_status") + data = { + "sg_status_list": delivered_status, + } + self.sg.update("Shot", shot_id, data) + + except Exception as error: + logger.info("Something went wrong... %s" % str(error)) + return False + + return True + + @staticmethod + def __validate_sequence(sequence_path, first_frame, last_frame): + # Lets check if every frame is existing in the frame sequence + is_valid = True + + logger.info("Got %s" % sequence_path) + + for frame in range(first_frame, last_frame): + file_path = os.path.isfile(sequence_path % frame) + logger.info("Checking %s with frame %f" % (sequence_path, frame)) + if not file_path: + is_valid = False + logger.info( + "File sequence is not complete, please re-render it " + "again in the full frame range." + ) + + return is_valid + + return is_valid + + def __get_delivery_shots(self): + """Function to get shots from ShotGrid + with the status code "rfd" (ready for delivery). + + If the status is active on the specific shot, it will be added + to the list. + + Returns: + list: containing shots with correct status + example: [ + { + "type": "Shot", + "id": 7563, + "sg_sequence": {"id": 4518, "name": "010", "type": "Sequence"}, + "code": "0010", + } + ] + """ + # Get current context + project_id = self.context.project["id"] + + # Get the shortcode for the delivery status + delivery_status = self._app.get_setting("delivery_status") + + filters = [ + [ + "project", + "is", + {"type": "Project", "id": project_id}, + ], + ["sg_status_list", "is", delivery_status], + ] + + columns = [ + "sg_sequence", + "code", + ] + + # Find shots using provided parameters + shots = self.sg.find("Shot", filters, columns) + + return shots + + def __create_readable_list(self, shots): + """This function will convert the list from ShotGrid + to a somewhat more readable one to create the list in the + user interface. + + Args: + shots (list): containing all data regarding shots + + Returns: + list: with readable information on shots to process + """ + readable_list = [] + + for shot in shots: + sequence_name = shot.get("sg_sequence") + sequence_name = sequence_name.get("name") + + shot_name = shot.get("code") + + readable_shot = "Sequence: %s, shot: %s" % ( + sequence_name, + shot_name, + ) + readable_list.append(readable_shot) + + logger.info("Got shots %s." % readable_list) + return readable_list diff --git a/python/app/ui/__init__.py b/python/app/ui/__init__.py new file mode 100644 index 0000000..d0ea743 --- /dev/null +++ b/python/app/ui/__init__.py @@ -0,0 +1,21 @@ +# MIT License + +# Copyright (c) 2022 Netherlands Film Academy + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/python/app/ui/dialog.py b/python/app/ui/dialog.py new file mode 100644 index 0000000..f225c59 --- /dev/null +++ b/python/app/ui/dialog.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'dialog.ui' +# +# by: pyside-uic 0.2.15 running on PySide 1.2.2 +# +# WARNING! All changes made in this file will be lost! + +from tank.platform.qt import QtCore, QtGui + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(521, 514) + self.horizontalLayout = QtGui.QHBoxLayout(Dialog) + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.label1 = QtGui.QLabel(Dialog) + self.label1.setObjectName("label1") + + self.verticalLayout.addWidget(self.label1) + + self.label2 = QtGui.QLabel(Dialog) + self.label2.setObjectName("label2") + + self.verticalLayout.addWidget(self.label2) + + self.label3 = QtGui.QLabel(Dialog) + self.label3.setObjectName("label3") + + self.verticalLayout.addWidget(self.label3) + + self.label4 = QtGui.QLabel(Dialog) + self.label4.setObjectName("label4") + + self.verticalLayout.addWidget(self.label4) + + self.horizontalSpacer = QtGui.QSpacerItem( + 40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum + ) + + self.verticalLayout.addItem(self.horizontalSpacer) + + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.deliveryItemsLabel = QtGui.QLabel(Dialog) + self.deliveryItemsLabel.setObjectName("deliveryItemsLabel") + + self.horizontalLayout_2.addWidget(self.deliveryItemsLabel) + + self.deliveryItems = QtGui.QListWidget(Dialog) + self.deliveryItems.setObjectName("deliveryItems") + + self.horizontalLayout_2.addWidget(self.deliveryItems) + + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.horizontalSpacer_2 = QtGui.QSpacerItem( + 40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum + ) + + self.verticalLayout.addItem(self.horizontalSpacer_2) + + self.reload = QtGui.QPushButton(Dialog) + self.reload.setObjectName("reload") + + self.verticalLayout.addWidget(self.reload) + + self.deliver = QtGui.QPushButton(Dialog) + self.deliver.setObjectName("deliver") + + self.verticalLayout.addWidget(self.deliver) + + self.open_folder = QtGui.QPushButton(Dialog) + self.open_folder.setObjectName("open_folder") + + self.verticalLayout.addWidget(self.open_folder) + + self.horizontalLayout.addLayout(self.verticalLayout) + + self.retranslateUi(Dialog) + + QtCore.QMetaObject.connectSlotsByName(Dialog) + + # def retranslateUi(self, Dialog): + # Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "The Current Sgtk Environment", None, QtGui.QApplication.UnicodeUTF8)) + # self.context.setText(QtGui.QApplication.translate("Dialog", "Your Current Context: ", None, QtGui.QApplication.UnicodeUTF8)) + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle( + QtCore.QCoreApplication.translate( + "Dialog", "The Current Sgtk Environment", None + ) + ) + self.label1.setText( + QtCore.QCoreApplication.translate( + "Dialog", + '1. Set shots on ShotGrid to status "Ready for Delivery" ', + None, + ) + ) + self.label2.setText( + QtCore.QCoreApplication.translate( + "Dialog", "2. Optional: reload to see new items in list", None + ) + ) + self.label3.setText( + QtCore.QCoreApplication.translate( + "Dialog", + '3. Press the buton "Set ready for delivery" to move items to delivery folder', + None, + ) + ) + self.label4.setText( + QtCore.QCoreApplication.translate( + "Dialog", + "4. Use open deliveries folder to open the delivery folder", + None, + ) + ) + self.deliveryItemsLabel.setText( + QtCore.QCoreApplication.translate("Dialog", "Delivery items", None) + ) + self.reload.setText( + QtCore.QCoreApplication.translate("Dialog", "Reload", None) + ) + self.deliver.setText( + QtCore.QCoreApplication.translate( + "Dialog", "Set ready for deliveries", None + ) + ) + self.open_folder.setText( + QtCore.QCoreApplication.translate( + "Dialog", "Open deliveries folder", None + ) + ) + + # retranslateUi diff --git a/resources/build_resources.sh b/resources/build_resources.sh new file mode 100755 index 0000000..db80511 --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +# The path to output all built .py files to: +UI_PYTHON_PATH=../python/app/ui +# The path to where the PySide binaries are installed +PYTHON_BASE="/Applications/Shotgun.app/Contents/Resources/Python" + +# Remove any problematic profiles from pngs. +for f in *.png; do mogrify $f; done + +# Helper functions to build UI files +function build_qt { + echo " > Building " $2 + + # compile ui to python + $1 $2 > $UI_PYTHON_PATH/$3.py + + # replace PySide imports with tank.platform.qt and remove line containing Created by date + sed -i $UI_PYTHON_PATH/$3.py -e "s/from PySide import/from tank.platform.qt import/g" -e "/# Created:/d" +} + +function build_ui { + build_qt "${PYTHON_BASE}/bin/python ${PYTHON_BASE}/bin/pyside-uic --from-imports" "$1.ui" "$1" +} + +function build_res { + build_qt "${PYTHON_BASE}/bin/pyside-rcc -py3" "$1.qrc" "$1_rc" +} + + +# build UI's: +echo "building user interfaces..." +build_ui dialog +# add any additional .ui files you want converted here! + +# build resources +echo "building resources..." +build_res resources diff --git a/resources/dialog.ui b/resources/dialog.ui new file mode 100644 index 0000000..976b2f3 --- /dev/null +++ b/resources/dialog.ui @@ -0,0 +1,116 @@ + + + Dialog + + + + 0 + 0 + 521 + 514 + + + + The Current Sgtk Environment + + + + + + + + 1. Set shots on ShotGrid to status "Ready for Delivery" + + + + + + + 2. Optional: reload to see new items in list + + + + + + + 3. Press the buton "Set ready for delivery" to move items to delivery folder + + + + + + + 4. Use open deliveries folder to open the delivery folder + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Delivery items + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload + + + + + + + Set ready for deliveries + + + + + + + Open deliveries folder + + + + + + + + + + + + diff --git a/resources/tk-desktop-deliveries.png b/resources/tk-desktop-deliveries.png new file mode 100644 index 0000000..dad03a2 Binary files /dev/null and b/resources/tk-desktop-deliveries.png differ diff --git a/resources/ui_dialog.py b/resources/ui_dialog.py new file mode 100644 index 0000000..5f0ab2f --- /dev/null +++ b/resources/ui_dialog.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'dialog.ui' +## +## Created by: Qt User Interface Compiler version 6.3.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QDialog, QHBoxLayout, QLabel, + QListView, QPushButton, QSizePolicy, QSpacerItem, + QVBoxLayout, QWidget) +import resources_rc + +class Ui_Dialog(object): + def setupUi(self, Dialog): + if not Dialog.objectName(): + Dialog.setObjectName(u"Dialog") + Dialog.resize(521, 514) + self.horizontalLayout = QHBoxLayout(Dialog) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.verticalLayout = QVBoxLayout() + self.verticalLayout.setObjectName(u"verticalLayout") + self.label1 = QLabel(Dialog) + self.label1.setObjectName(u"label1") + + self.verticalLayout.addWidget(self.label1) + + self.label2 = QLabel(Dialog) + self.label2.setObjectName(u"label2") + + self.verticalLayout.addWidget(self.label2) + + self.label3 = QLabel(Dialog) + self.label3.setObjectName(u"label3") + + self.verticalLayout.addWidget(self.label3) + + self.label4 = QLabel(Dialog) + self.label4.setObjectName(u"label4") + + self.verticalLayout.addWidget(self.label4) + + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + + self.verticalLayout.addItem(self.horizontalSpacer) + + self.horizontalLayout_2 = QHBoxLayout() + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.deliveryItemsLabel = QLabel(Dialog) + self.deliveryItemsLabel.setObjectName(u"deliveryItemsLabel") + + self.horizontalLayout_2.addWidget(self.deliveryItemsLabel) + + self.deliveryItems = QListView(Dialog) + self.deliveryItems.setObjectName(u"deliveryItems") + + self.horizontalLayout_2.addWidget(self.deliveryItems) + + + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + + self.verticalLayout.addItem(self.horizontalSpacer_2) + + self.pushButton_3 = QPushButton(Dialog) + self.pushButton_3.setObjectName(u"pushButton_3") + + self.verticalLayout.addWidget(self.pushButton_3) + + self.pushButton = QPushButton(Dialog) + self.pushButton.setObjectName(u"pushButton") + + self.verticalLayout.addWidget(self.pushButton) + + self.pushButton_2 = QPushButton(Dialog) + self.pushButton_2.setObjectName(u"pushButton_2") + + self.verticalLayout.addWidget(self.pushButton_2) + + + self.horizontalLayout.addLayout(self.verticalLayout) + + + self.retranslateUi(Dialog) + + QMetaObject.connectSlotsByName(Dialog) + # setupUi + + def retranslateUi(self, Dialog): + Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"The Current Sgtk Environment", None)) + self.label1.setText(QCoreApplication.translate("Dialog", u"1. Set shots on ShotGrid to status \"Ready for Delivery\" ", None)) + self.label2.setText(QCoreApplication.translate("Dialog", u"2. Optional: reload to see new items in list", None)) + self.label3.setText(QCoreApplication.translate("Dialog", u"3. Press the buton \"Set ready for delivery\" to move items to delivery folder", None)) + self.label4.setText(QCoreApplication.translate("Dialog", u"4. Use open deliveries folder to open the delivery folder", None)) + self.deliveryItemsLabel.setText(QCoreApplication.translate("Dialog", u"Delivery items", None)) + self.pushButton_3.setText(QCoreApplication.translate("Dialog", u"Reload", None)) + self.pushButton.setText(QCoreApplication.translate("Dialog", u"Set ready for deliveries", None)) + self.pushButton_2.setText(QCoreApplication.translate("Dialog", u"Open deliveries folder", None)) + # retranslateUi + diff --git a/style.qss b/style.qss new file mode 100644 index 0000000..6f10e00 --- /dev/null +++ b/style.qss @@ -0,0 +1,20 @@ +/******************************************************************************************** + +QT Stylesheet for the app. This file will be read every time an app dialog is created via +the show_dialog, show_modal or show_panel methods. + +Certain keywords will be resolved, for example {{SG_HIGHLIGHT_COLOR}}. +For a full list of keywords, call the app.style_constants property at runtime. + +For more info about QT stylesheets, please see http://doc.qt.io/qt-4.8/stylesheet.html + +********************************************************************************************/ + + +/* Example: + +QListView, QTableView, QScrollArea { + border: 2px solid {{SG_HIGHLIGHT_COLOR}}; +} + +*/