from PyQt5.QtWidgets import ( QApplication, QDialog, QLabel, QListWidget, QLineEdit, QPushButton, QHBoxLayout, QVBoxLayout, QSizePolicy ) from PyQt5.QtCore import Qt import os import re import sys import json import shutil import ayon_api import requests import subprocess import webbrowser if len(sys.argv) > 3: file_format = sys.argv[1] file_executable = sys.argv[2] file_template = sys.argv[3] application_type = sys.argv[4] print("Format: ", file_format) print("Executable: ", file_executable) print("Template: ", file_template) print("Application Type: ", application_type) else: print("Missing arguments...") input("\nPress Enter to exit...") exit(1) # === CONFIG === SERVER_URL = os.environ.get("AYON_SERVER_URL") API_KEY = os.environ.get("AYON_API_KEY") # === AYON API Connection === con = ayon_api.get_server_api_connection() # === File Path Config === project = os.environ.get("AYON_PROJECT_NAME") folder = os.environ.get("AYON_FOLDER_PATH") task_name = os.environ.get("AYON_TASK_NAME") user = ayon_api.get(f"users/me") user_name = user["name"] print("") print("Project:", project) print("Folder Path:", folder) print("Task Name:", task_name) print("User Name:", user_name) print("") # Get the project root project_entity = ayon_api.get(f"projects/{project}") project_root = project_entity["config"]["roots"]["work"]["windows"] print("Project Root:", project_root) print("") # URL input class class UrlInputDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) # --- Window setup --- self.setWindowTitle("Web URL") self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # remove "?" button self.setFixedSize(500, 50) # --- Widgets --- self.label = QLabel("Enter URL:") self.line_edit = QLineEdit() self.line_edit.setPlaceholderText("https://www.example.com") # placeholder text for guidance self.ok_button = QPushButton("OK") # Pressing Enter in the line edit should trigger OK self.line_edit.returnPressed.connect(self.accept) # Clicking OK also closes dialog self.ok_button.clicked.connect(self.accept) # --- Layout (everything in one line) --- input_layout = QHBoxLayout() input_layout.addWidget(self.label) input_layout.addWidget(self.line_edit, stretch=1) # stretch makes input expand input_layout.addWidget(self.ok_button) main_layout = QVBoxLayout() main_layout.addLayout(input_layout) self.setLayout(main_layout) # --- Apply style --- self.setStyleSheet(""" QDialog { background-color: #21252b; } QLabel { color: #ecf0f1; font-size: 14px; font-family: Arial; } QLineEdit { background-color: #2c313a; color: #ecf0f1; padding: 6px; border-radius: 4px; border: 1px solid #555; } QPushButton { background-color: #395b70; color: white; border-radius: 4px; padding: 6px 12px; } QPushButton:hover { background-color: #2980b9; } """) # Template File selection UI class FileSelectDialog(QDialog): def __init__(self, files, parent=None): super().__init__(parent) self.setWindowTitle("Select a File") self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # remove "?" button self.selected_file = None # --- Label --- label = QLabel("Template Files:") label.setStyleSheet("color: #ecf0f1; font-size: 14px;") # --- List of files --- self.list_widget = QListWidget() #files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] self.list_widget.addItems(files) # --- Dynamic sizing --- if files: row_height = self.list_widget.sizeHintForRow(0) # row height visible_items = min(len(files) + 5, 30) # max 30 items before scroll self.list_widget.setMinimumHeight(row_height * visible_items + 8) # Width based on longest filename longest_width = max(self.list_widget.fontMetrics().width(f) for f in files) self.list_widget.setMinimumWidth(longest_width + 150) # Double click = accept self.list_widget.itemDoubleClicked.connect(self.accept) # --- OK Button --- self.ok_button = QPushButton("OK") self.ok_button.clicked.connect(self.accept) # --- Layout --- button_layout = QHBoxLayout() button_layout.addStretch() button_layout.addWidget(self.ok_button) layout = QVBoxLayout() layout.addWidget(label) layout.addWidget(self.list_widget) layout.addLayout(button_layout) self.setLayout(layout) # Auto-size the dialog to fit contents self.adjustSize() # --- Style --- self.setStyleSheet(""" QDialog { background-color: #2c313a; } QListWidget { background-color: #21252b; color: #ecf0f1; border: 1px solid #555; font-size: 14px; } QPushButton { background-color: #3498db; color: white; border-radius: 4px; padding: 6px 12px; } QPushButton:hover { background-color: #2980b9; } """) # Get the folder entity def find_folder_entity(project_name, folder_path): folders = con.get_folders(project_name, folder_paths=[folder_path]) for f in folders: if f["path"].lower() == folder_path.lower(): return f return None # Get the task id and task type def find_task_id(project_name, folder_id, task_name): task = con.get_task_by_name(project_name, folder_id, task_name) #print(task) if task: return task["id"], task["taskType"] return None # Register the workfile in Ayon after creating it def register_workfile(project_name, file_id, folder_id, file_path, task_id=None): url = f"{SERVER_URL}/api/projects/{project_name}/workfiles" _, ext = os.path.splitext(file_path) headers = { "x-api-key": API_KEY, "x-ayon-user": user_name, "x-sender": "workfile_uploader", "x-sender-type": "api" } payload = { "fileId": file_id, "folderId": folder_id, "path": file_path, "taskId": task_id, "createdBy": user_name, "updatedBy": user_name, "status": "In progress", "tags": [], #tags if needed "attrib": { "extension": ext, "description": "-" }, "data": {}, "active": True } try: resp = requests.post(url, json=payload, headers=headers) if resp.status_code == 201: workfile_id = resp.json().get("id") print(f"✅ Registered workfile (ID: {workfile_id})") return workfile_id else: print(f"❌ Failed to register workfile: {resp.status_code} {resp.text}") return None except Exception as e: print(f"❌ Exception while registering workfile: {e}") return None # === Main logic === def main(): folder_entity = find_folder_entity(project, folder) if folder_entity: folder_id = folder_entity["id"] folder_name = folder_entity["name"] print(f"✅ Folder '{folder_name}' found.") print("Folder ID: ", folder_id) print("") task_id, task_type = find_task_id(project, folder_id, task_name) if task_id: print(f"✅ Task '{task_name}' found in folder '{folder}'.") print(f"Task ID: {task_id}") print(f"Task Type: {task_type}") print("") workdir_path = project_root + "\\" + project + folder.replace("/", "\\") + "\work" + "\\" + task_type workdir = workdir_path.replace("\\\\", "\\") print("Dir:", workdir) # file path to register in ayon ayon_workdir = project_root + "/" + project + folder + "/work" + "/" + task_type if not all([project, folder, task_type, task_name, workdir]): print("❌ Missing required environment variables from AYON Launcher.") input("\nPress Enter to exit...") exit(1) file_anatomy = rf"{project}_{folder_name}_{task_name}_v\d+\.({file_format})$" compiled_anatomy = re.compile(file_anatomy, re.IGNORECASE) latest = None latest_version = -1 if not os.path.exists(workdir): print("❌ Task folder not found...") os.makedirs(workdir) print("✅ Task folder created:", workdir) else: print("✅ Task folder already exists:", workdir) # Search for latest version of matching file for f in os.listdir(workdir): match = compiled_anatomy.match(f) if match: version = int(re.search(r"_v(\d+)", f).group(1)) if version > latest_version: latest = f latest_version = version # Check if we need to use template if application_type.lower() == "template": if not latest: print("❌ No matching workfile found.") latest = rf"{project}_{folder_name}_{task_name}_v001.{file_format}" file_path = os.path.join(workdir, latest) else: new_version = latest_version + 1 new_workfile = re.sub(r"_v(\d+)", f"_v{new_version:03d}", latest) latest = new_workfile file_path = os.path.join(workdir, latest) with open(r"L:\resources\templates\templates.json", "r") as json_file: lists = json.load(json_file) if task_type in lists: print("✅ Task type found in templates.") if lists[task_type]: print(f"All templates in \"{task_type}\" task: ", lists[task_type]) filter_files = [f for f in lists[task_type] if f.lower().endswith("." + file_format)] if filter_files: print(f"Templates for selected application in \"{task_type}\" task: ", filter_files) app = QApplication(sys.argv) dialog = FileSelectDialog(filter_files) if dialog.exec_() == QDialog.Accepted: selected_file = dialog.list_widget.currentItem().text() if selected_file: print("Selected File: ", selected_file) selected_file_path = os.path.join(file_template, selected_file) print("File Path:", selected_file_path) print("✅ Creating new file from template...") shutil.copy2(selected_file_path, file_path) file_id = "" ayon_file_path = ayon_workdir + "\\" + latest register_workfile(project, file_id, folder_id, ayon_file_path, task_id) else: print("Error! No template file was selected.") input("\nPress Enter to exit...") exit(1) else: print("Error! No template found for the selected application in current task type.") input("\nPress Enter to exit...") exit(1) else: print("Error! No templates found for the current task type.") input("\nPress Enter to exit...") exit(1) else: print("Error! Selected task type is not registered in custom templates.") input("\nPress Enter to exit...") exit(1) elif not latest: print("❌ No matching workfile found.") latest = rf"{project}_{folder_name}_{task_name}_v001.{file_format}" file_path = os.path.join(workdir, latest) # Creating new file if it does not exist already if latest.endswith((".txt", ".pptx", ".docx")): print("✅ Creating new file...") new_file = open(file_path, "x").close() elif file_format.lower() == "html": app = QApplication(sys.argv) dialog = UrlInputDialog() if dialog.exec_() == QDialog.Accepted: url = dialog.line_edit.text() print("URL entered: ", url) html_content = f"""\n\n
\n \n \n\n""" with open(file_path, "w", encoding="utf-8") as file: file.write(html_content) else: print("Error! URL input closed.") input("\nPress Enter to exit...") exit(1) elif file_format.lower() == "storyboarder": print("✅ Creating new file from base template for Storyboard...") root_folder = os.path.dirname(file_template) images_folder = root_folder + "\\" + "images" dst_folder = workdir + "\\" + "images" shutil.copytree(images_folder, dst_folder, dirs_exist_ok=True) shutil.copy2(file_template, file_path) elif file_template: print("✅ Creating new file from base template...") shutil.copy2(file_template, file_path) file_id = "" ayon_file_path = ayon_workdir + "/" + latest register_workfile(project, file_id, folder_id, ayon_file_path, task_id) elif application_type.lower() == "version": new_version = latest_version + 1 new_workfile = re.sub(r"_v(\d+)", f"_v{new_version:03d}", latest) print("Current Version: ", latest) print("New Version: ", new_workfile) src_path = os.path.join(workdir, latest) dst_path = os.path.join(workdir, new_workfile) # Copy and rename to new workfile shutil.copy2(src_path, dst_path) latest = new_workfile file_id = "" ayon_file_path = ayon_workdir + "/" + latest register_workfile(project, file_id, folder_id, ayon_file_path, task_id) print(f"✅ New version created: {new_workfile}") full_path = os.path.join(workdir, latest) if os.path.exists(full_path): # Launch app print(f"✅ Launching {full_path}") if file_format.lower() == "html": webbrowser.open(f"file:///{os.path.abspath(full_path)}") elif file_format.lower() == "storyboarder": try: subprocess.Popen([file_executable, full_path], cwd=os.path.dirname(file_executable)) except FileNotFoundError: print("❌ Application is not able to open due to some errors...") else: try: subprocess.Popen([file_executable, full_path]) except FileNotFoundError: print("❌ Application is not able to open due to some errors...") #input("\nPress Enter to exit...") if __name__ == "__main__": main()